From a778c4c0fa2665f970eaafa74151aaee71c35df4 Mon Sep 17 00:00:00 2001 From: Genyus Date: Fri, 31 Jul 2026 04:04:40 +0000 Subject: [PATCH 1/8] chore(release): 0.1.51 --- CHANGELOG.md | 1 + package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) 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/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": "[![Packagist](https://img.shields.io/packagist/v/roots/bedrock.svg?style=flat-square)](https://packagist.org/packages/roots/bedrock) [![Build Status](https://img.shields.io/travis/roots/bedrock.svg?style=flat-square)](https://travis-ci.org/roots/bedrock)", "devDependencies": { "@commitlint/cli": "^19.0", From f7008fc942cef1a47cd0259761d963f57a091b04 Mon Sep 17 00:00:00 2001 From: Genyus Date: Wed, 19 Aug 2026 21:04:46 -0400 Subject: [PATCH 2/8] chore(deps): update Composer dependencies - Upgrading aws/aws-sdk-php (3.390.0 => 3.393.2) - Upgrading guzzlehttp/guzzle (8.0.1 => 8.0.2) - Upgrading guzzlehttp/promises (3.0.0 => 3.0.1) - Upgrading johnpbloch/wordpress (6.9.5 => 6.9.7) - Upgrading johnpbloch/wordpress-core (6.9.5 => 6.9.7) - Upgrading koodimonni-language/en_gb (7.0.2 => 7.1) - Upgrading koodimonni-plugin-language/ai-en_gb (1.2.0 => 1.3.0) - Upgrading koodimonni-plugin-language/ai-provider-for-openai-en_gb (1.0.3 => 1.1.0) - Upgrading koodimonni-plugin-language/akismet-en_gb (5.7 => 5.7.2) - Upgrading koodimonni-plugin-language/optimization-detective-en_gb (1.0.0-beta5 => 1.0.0-beta6) - Locking koodimonni-plugin-language/secure-custom-fields-en_gb (6.9.5) - Upgrading koodimonni-plugin-language/sqlite-database-integration-en_gb (2.2.23 => 3.0.0) - Upgrading koodimonni-plugin-language/woocommerce-en_gb (10.9.4 => 11.0.1) - Upgrading mck89/peast (v1.17.7 => v1.18.0) - Upgrading roave/security-advisories (dev-latest 65b8998 => dev-latest 4a8b109) - Upgrading squizlabs/php_codesniffer (3.13.5 => 3.13.6) - Upgrading symfony/console (v8.1.2 => v8.1.4) - Upgrading wp-cli/ability-command (v1.0.1 => v1.0.2) - Upgrading wp-cli/ai-command (v1.0.1 => v1.0.3) - Upgrading wp-cli/block-command (v1.0.1 => v1.0.3) - Upgrading wp-cli/cache-command (v2.2.2 => v3.0.0) - Upgrading wp-cli/checksum-command (v2.3.7 => v3.0.0) - Upgrading wp-cli/config-command (v2.5.2 => v3.0.0) - Upgrading wp-cli/core-command (v2.1.32 => v3.0.1) - Upgrading wp-cli/cron-command (v2.3.10 => v3.0.0) - Upgrading wp-cli/db-command (v2.1.6 => v3.0.1) - Upgrading wp-cli/embed-command (v2.1.2 => v3.0.0) - Upgrading wp-cli/entity-command (v2.8.12 => v3.0.2) - Upgrading wp-cli/eval-command (v2.2.10 => v3.0.0) - Upgrading wp-cli/export-command (v2.1.18 => v3.0.0) - Upgrading wp-cli/extension-command (v2.3.6 => v3.0.0) - Upgrading wp-cli/i18n-command (v2.7.3 => v3.0.0) - Upgrading wp-cli/import-command (v2.0.18 => v3.0.0) - Upgrading wp-cli/language-command (v2.0.27 => v3.0.0) - Upgrading wp-cli/maintenance-mode-command (v2.1.4 => v3.0.0) - Upgrading wp-cli/media-command (v2.2.8 => v3.0.0) - Upgrading wp-cli/rewrite-command (v2.0.18 => v3.0.0) - Upgrading wp-cli/role-command (v2.0.17 => v3.0.0) - Upgrading wp-cli/scaffold-command (v2.5.7 => v3.0.1) - Upgrading wp-cli/search-replace-command (v2.1.14 => v3.0.1) - Upgrading wp-cli/server-command (v2.0.19 => v3.0.0) - Upgrading wp-cli/shell-command (v2.0.18 => v3.0.0) - Upgrading wp-cli/site-health-command (v1.0.0 => v1.0.1) - Upgrading wp-cli/super-admin-command (v2.0.18 => v3.0.0) - Upgrading wp-cli/widget-command (v2.2.3 => v3.0.0) - Upgrading wp-cli/wp-cli (dev-main aa9adcc => dev-main 6f7facf) - Upgrading wp-cli/wp-cli-bundle (dev-main 63e7d5c => dev-main 8796efe) - Upgrading wp-coding-standards/wpcs (dev-develop ec2ff94 => dev-develop a31c319) - Upgrading wp-plugin/akismet (5.7 => 5.7.2) - Upgrading wp-plugin/easy-table-of-contents (2.0.85 => 2.0.86) - Upgrading wp-plugin/import-meetup-events (1.7.4 => 1.7.5) - Upgrading wp-plugin/jetpack (16.0.1 => 16.1.1) - Upgrading wp-plugin/login-as-user (1.7.2 => 1.7.3) - Upgrading wp-plugin/mailchimp-for-woocommerce (6.1.1 => 6.2) - Upgrading wp-plugin/otter-blocks (3.2.1 => 3.2.2) - Upgrading wp-plugin/pdf-embedder (5.0.1 => 5.0.2) - Upgrading wp-plugin/pods (3.3.9 => 3.3.9.1) - Upgrading wp-plugin/webberzone-code-block-highlighting (1.1.0 => 1.2.0) - Upgrading wp-plugin/woocommerce (10.9.4 => 11.0.1) - Upgrading wp-plugin/woocommerce-gateway-stripe (10.8.4 => 10.9.0) - Upgrading wp-plugin/woocommerce-paypal-payments (4.1.1 => 4.1.2) - Upgrading wp-plugin/woocommerce-services (3.6.10 => 3.6.12) - Upgrading wp-plugin/wp-graphql (2.18.0 => 2.20.0) - Upgrading wp-plugin/wp-job-manager (2.4.5 => 2.4.6) - Upgrading wp-plugin/wp-rest-api-authentication (4.4.0 => 4.6.0) - Upgrading wp-theme/twentytwentythree (1.6 => 1.7) - Upgrading wpackagist-plugin/social-networks-auto-poster-facebook-twitter-g (4.4.7 => 4.4.8) --- composer.json | 2 +- composer.lock | 953 ++++++++++++++++++++++++++------------------------ 2 files changed, 492 insertions(+), 463 deletions(-) diff --git a/composer.json b/composer.json index f58158be..8d116a25 100644 --- a/composer.json +++ b/composer.json @@ -94,7 +94,7 @@ "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", diff --git a/composer.lock b/composer.lock index be32d57b..442f926f 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": "1029529897213525bf75edef76bd110d", "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", @@ -7202,15 +7227,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 +7254,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 +7314,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 +7336,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 +7358,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 +7366,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 +8120,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 +8166,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 +8224,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 +8255,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 +8280,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 +8363,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 +8448,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 +8457,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 +8477,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 +8589,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 +8632,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 +8654,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 +8742,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 +8765,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 +8793,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 +8842,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 +8899,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 +8916,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 +8933,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 +8956,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 +9028,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 +9055,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 +9125,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 +9135,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 +9241,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 +9310,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 +9385,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 +9465,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 +9485,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 +10004,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 +10020,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 +10063,7 @@ "type": "custom" } ], - "time": "2026-07-27T11:53:23+00:00" + "time": "2026-08-10T10:25:32+00:00" } ], "aliases": [], From 1553a9d2a79965789f42c5acd755a349f44e099d Mon Sep 17 00:00:00 2001 From: Genyus Date: Wed, 19 Aug 2026 21:07:35 -0400 Subject: [PATCH 3/8] chore(deps): install WP Mail SMTP Pro --- .gitignore | 1 + composer.json | 1 - composer.lock | 33 +- web/app/plugins/wp-mail-smtp-pro/CHANGELOG.md | 1019 +++ .../assets/css/admin-bar.min.css | 1 + .../assets/css/admin-notifications.min.css | 1 + .../assets/css/admin-site-health.min.css | 1 + .../assets/css/dashboard-widget.min.css | 1 + .../css/emails/summary-report-email.css | 297 + .../assets/css/smtp-about.min.css | 1 + .../assets/css/smtp-activelayer-wc.min.css | 1 + .../assets/css/smtp-admin.min.css | 3 + .../assets/css/smtp-smart-routing.min.css | 1 + .../assets/images/about/affiliatewp.png | Bin 0 -> 1300 bytes .../images/about/duplicator-icon-large.png | Bin 0 -> 4980 bytes .../assets/images/about/edd.png | Bin 0 -> 3594 bytes .../assets/images/about/icon-activelayer.svg | 22 + .../assets/images/about/icon-duplicator.svg | 7 + .../assets/images/about/icon-full.svg | 1 + .../assets/images/about/icon-none.svg | 1 + .../assets/images/about/icon-partial.svg | 1 + .../assets/images/about/icon-universally.svg | 10 + .../assets/images/about/icon-wpconsent.svg | 13 + .../assets/images/about/plugin-aioseo.png | Bin 0 -> 2595 bytes .../assets/images/about/plugin-charitable.png | Bin 0 -> 2846 bytes .../assets/images/about/plugin-mi.png | Bin 0 -> 2665 bytes .../assets/images/about/plugin-om.png | Bin 0 -> 10229 bytes .../assets/images/about/plugin-pushengage.png | Bin 0 -> 1420 bytes .../assets/images/about/plugin-rp.png | Bin 0 -> 2039 bytes .../assets/images/about/plugin-seedprod.png | Bin 0 -> 7472 bytes .../plugin-smash-balloon-facebook-feeds.png | Bin 0 -> 2989 bytes .../plugin-smash-balloon-instagram-feeds.png | Bin 0 -> 3114 bytes .../plugin-smash-balloon-twitter-feeds.png | Bin 0 -> 2309 bytes .../plugin-smash-balloon-youtube-feeds.png | Bin 0 -> 1371 bytes .../assets/images/about/plugin-trustpulse.png | Bin 0 -> 1281 bytes .../assets/images/about/plugin-vibe-ai.png | Bin 0 -> 7999 bytes .../assets/images/about/plugin-wpcode.png | Bin 0 -> 17815 bytes .../assets/images/about/plugin-wpf.png | Bin 0 -> 17306 bytes .../assets/images/about/searchwp.png | Bin 0 -> 3033 bytes .../assets/images/about/sugar-calendar.png | Bin 0 -> 1696 bytes .../assets/images/about/team.jpg | Bin 0 -> 122709 bytes .../assets/images/about/wp-simple-pay.png | Bin 0 -> 2851 bytes .../additional-connections/screenshot-01.png | Bin 0 -> 116402 bytes .../additional-connections/screenshot-02.png | Bin 0 -> 62252 bytes .../additional-connections/thumbnail-01.png | Bin 0 -> 16947 bytes .../additional-connections/thumbnail-02.png | Bin 0 -> 11029 bytes .../images/ai-mcp/hero-illustration.svg | 156 + .../assets/images/dash-widget/error-icon.svg | 1 + .../images/dash-widget/smtp/delivered.svg | 1 + .../assets/images/dash-widget/smtp/sent.svg | 1 + .../assets/images/dash-widget/smtp/total.svg | 1 + .../assets/images/dash-widget/smtp/unsent.svg | 1 + .../images/dash-widget/wp/delivered.svg | 1 + .../assets/images/dash-widget/wp/sent.svg | 1 + .../assets/images/dash-widget/wp/total.svg | 1 + .../assets/images/dash-widget/wp/unsent.svg | 1 + .../assets/images/dash-widget/yes-green.svg | 1 + .../assets/images/education/activelayer.svg | 20 + .../get-pro-tab/card-advanced-mailers.svg | 406 + .../get-pro-tab/card-backup-connections.svg | 311 + .../get-pro-tab/card-email-failure-alerts.svg | 350 + .../education/get-pro-tab/card-email-logs.svg | 373 + .../card-one-click-mailer-setups.svg | 161 + .../get-pro-tab/card-smart-routing.svg | 329 + .../images/education/get-pro-tab/hero.svg | 83 + .../education/get-pro-tab/status-full.svg | 4 + .../education/get-pro-tab/status-none.svg | 4 + .../education/get-pro-tab/status-partial.svg | 3 + .../images/education/sendlayer-banner.svg | 1 + .../images/email-reports/screenshot-01.png | Bin 0 -> 70348 bytes .../images/email-reports/screenshot-02.png | Bin 0 -> 128516 bytes .../images/email-reports/screenshot-03.png | Bin 0 -> 51644 bytes .../images/email-reports/thumbnail-01.png | Bin 0 -> 14897 bytes .../images/email-reports/thumbnail-02.png | Bin 0 -> 18849 bytes .../images/email-reports/thumbnail-03.png | Bin 0 -> 11636 bytes .../assets/images/email/icon-check.png | Bin 0 -> 1092 bytes .../images/email/illustration-success.svg | 1 + .../assets/images/email/signature.png | Bin 0 -> 2131 bytes .../images/email/wp-mail-smtp-whitelabel.png | Bin 0 -> 8872 bytes .../assets/images/email/wp-mail-smtp.png | Bin 0 -> 10848 bytes .../assets/images/flyout-menu/facebook.svg | 1 + .../assets/images/flyout-menu/life-ring.svg | 1 + .../assets/images/flyout-menu/lightbulb.svg | 1 + .../assets/images/flyout-menu/mascot.svg | 1 + .../assets/images/flyout-menu/star.svg | 1 + .../arrow-rotate-right-purple.svg | 1 + .../assets/images/font-awesome/cancel-red.svg | 1 + .../font-awesome/check-circle-dark-green.svg | 1 + .../font-awesome/check-circle-solid-green.svg | 1 + .../images/font-awesome/clock-orange.svg | 1 + .../exclamation-circle-regular-red.svg | 1 + .../exclamation-circle-solid-orange.svg | 1 + .../exclamation-circle-solid-red.svg | 1 + .../images/font-awesome/info-circle-blue.svg | 1 + .../images/font-awesome/info-circle.svg | 1 + .../assets/images/icons/arrow-up.svg | 1 + .../assets/images/icons/badge-percent.svg | 1 + .../assets/images/icons/check-solid.svg | 1 + .../assets/images/icons/check.svg | 1 + .../assets/images/icons/close.svg | 1 + .../images/icons/custom/badge-percent.svg | 1 + .../icons/dashicons/dashicons-pdf-grey.svg | 1 + .../dashicons/dashicons-yes-alt-green.svg | 1 + .../assets/images/icons/error.svg | 1 + .../images/icons/exclamation-circle.svg | 1 + .../assets/images/icons/lightbulb.svg | 1 + .../assets/images/icons/success.svg | 1 + .../assets/images/icons/warning.svg | 1 + .../assets/images/icons/zoom.svg | 1 + .../assets/images/loaders/loading-blue.svg | 1 + .../assets/images/loaders/loading-white.svg | 1 + .../assets/images/loaders/loading.svg | 1 + .../assets/images/logo-whitelabel.svg | 1 + .../wp-mail-smtp-pro/assets/images/logo.svg | 1 + .../assets/images/logs/archive-thumbnail.png | Bin 0 -> 16537 bytes .../assets/images/logs/archive.png | Bin 0 -> 111197 bytes .../assets/images/logs/single-thumbnail.png | Bin 0 -> 30417 bytes .../assets/images/logs/single.png | Bin 0 -> 71468 bytes .../assets/images/menu-icon.svg | 1 + .../wp-mail-smtp-pro/assets/images/pattie.svg | 1 + .../assets/images/pro-badge-small.svg | 1 + .../assets/images/pro-badge.svg | 1 + .../assets/images/providers/aws.svg | 1 + .../assets/images/providers/brevo.svg | 1 + .../assets/images/providers/elasticemail.svg | 1 + .../assets/images/providers/google.svg | 1 + .../assets/images/providers/mailersend.svg | 1 + .../assets/images/providers/mailgun.svg | 1 + .../assets/images/providers/mailjet.svg | 1 + .../assets/images/providers/mandrill.svg | 1 + .../assets/images/providers/microsoft.svg | 1 + .../assets/images/providers/pepipost-smtp.png | Bin 0 -> 1368 bytes .../assets/images/providers/pepipost.png | Bin 0 -> 12339 bytes .../assets/images/providers/php.svg | 1 + .../assets/images/providers/postmark.svg | 1 + .../assets/images/providers/resend.svg | 1 + .../assets/images/providers/sendgrid.svg | 1 + .../assets/images/providers/sendlayer.svg | 1 + .../assets/images/providers/smtp-com.svg | 1 + .../assets/images/providers/smtp.svg | 1 + .../assets/images/providers/smtp2go.svg | 1 + .../assets/images/providers/sparkpost.svg | 1 + .../assets/images/providers/zoho.svg | 1 + .../recommendations/plugin-page/complete.svg | 1 + .../recommendations/plugin-page/step-1.svg | 1 + .../recommendations/plugin-page/step-2.svg | 1 + .../recommendations/plugin-page/step-3.svg | 1 + .../plugins/activelayer/logo.svg | 22 + .../activelayer/screenshot-full@2x.png | Bin 0 -> 96949 bytes .../plugins/activelayer/screenshot-tnail.png | Bin 0 -> 7575 bytes .../activelayer/screenshot-tnail@2x.png | Bin 0 -> 18833 bytes .../plugins/duplicator/logo.svg | 23 + .../plugins/duplicator/screenshot-full@2x.png | Bin 0 -> 60130 bytes .../plugins/duplicator/screenshot-tnail.png | Bin 0 -> 4642 bytes .../duplicator/screenshot-tnail@2x.png | Bin 0 -> 11369 bytes .../plugins/universally/logo.svg | 45 + .../universally/screenshot-full@2x.png | Bin 0 -> 194685 bytes .../plugins/universally/screenshot-tnail.png | Bin 0 -> 14260 bytes .../universally/screenshot-tnail@2x.png | Bin 0 -> 34834 bytes .../recommendations/plugins/wpcode/logo.svg | 23 + .../plugins/wpcode/screenshot-full@2x.png | Bin 0 -> 135198 bytes .../plugins/wpcode/screenshot-tnail.png | Bin 0 -> 6330 bytes .../plugins/wpcode/screenshot-tnail@2x.png | Bin 0 -> 22485 bytes .../plugins/wpconsent/logo.svg | 26 + .../plugins/wpconsent/screenshot-full@2x.png | Bin 0 -> 32511 bytes .../plugins/wpconsent/screenshot-tnail.png | Bin 0 -> 5028 bytes .../plugins/wpconsent/screenshot-tnail@2x.png | Bin 0 -> 10433 bytes .../recommendations/plugins/wpvibe/logo.svg | 22 + .../plugins/wpvibe/screenshot-full@2x.png | Bin 0 -> 101377 bytes .../plugins/wpvibe/screenshot-tnail.png | Bin 0 -> 9122 bytes .../plugins/wpvibe/screenshot-tnail@2x.png | Bin 0 -> 24336 bytes .../assets/images/recommended.svg | 1 + .../images/reports/email/icon-arrow-down.png | Bin 0 -> 271 bytes .../images/reports/email/icon-arrow-up.png | Bin 0 -> 276 bytes .../images/reports/email/icon-check-gray.png | Bin 0 -> 886 bytes .../images/reports/email/icon-check.png | Bin 0 -> 876 bytes .../images/reports/email/icon-click.png | Bin 0 -> 421 bytes .../images/reports/email/icon-email.png | Bin 0 -> 585 bytes .../images/reports/email/icon-error.png | Bin 0 -> 815 bytes .../assets/images/reports/email/icon-open.png | Bin 0 -> 427 bytes .../wp-mail-smtp-logo-dark-whitelabel.png | Bin 0 -> 5254 bytes .../reports/email/wp-mail-smtp-logo-dark.png | Bin 0 -> 7527 bytes .../email/wp-mail-smtp-logo-whitelabel.png | Bin 0 -> 5791 bytes .../reports/email/wp-mail-smtp-logo.png | Bin 0 -> 7831 bytes .../assets/images/reports/icon-note.png | Bin 0 -> 1233 bytes .../images/test-success/illustration-lite.svg | 89 + .../assets/images/wp-spinner.gif | Bin 0 -> 3656 bytes .../wp-mail-smtp-pro/assets/js/connect.js | 178 + .../wp-mail-smtp-pro/assets/js/connect.min.js | 1 + .../wp-mail-smtp-pro/assets/js/smtp-about.js | 189 + .../assets/js/smtp-about.min.js | 1 + .../assets/js/smtp-activelayer-wc.js | 174 + .../assets/js/smtp-activelayer-wc.min.js | 1 + .../assets/js/smtp-admin-notices.js | 118 + .../assets/js/smtp-admin-notices.min.js | 1 + .../wp-mail-smtp-pro/assets/js/smtp-admin.js | 1396 ++++ .../assets/js/smtp-admin.min.js | 1 + .../wp-mail-smtp-pro/assets/js/smtp-ai-mcp.js | 197 + .../assets/js/smtp-ai-mcp.min.js | 1 + .../assets/js/smtp-code-snippets.js | 692 ++ .../assets/js/smtp-code-snippets.min.js | 1 + .../assets/js/smtp-dashboard-widget.js | 305 + .../assets/js/smtp-dashboard-widget.min.js | 1 + .../assets/js/smtp-notifications.js | 185 + .../assets/js/smtp-notifications.min.js | 1 + .../assets/js/smtp-recommendations.js | 282 + .../assets/js/smtp-recommendations.min.js | 1 + .../assets/js/smtp-tools-debug-events.js | 360 + .../assets/js/smtp-tools-debug-events.min.js | 1 + .../assets/languages/wp-mail-smtp-vue.php | 1129 +++ .../assets/languages/wp-mail-smtp.pot | 7271 +++++++++++++++++ .../pro/css/smtp-pro-email-reports.min.css | 1 + .../pro/css/smtp-pro-logs-print.min.css | 1 + .../assets/pro/css/smtp-pro-logs.min.css | 1 + .../assets/pro/css/smtp-pro-settings.min.css | 1 + .../css/smtp-pro-tools-logs-export.min.css | 1 + .../pro/images/email/sendlayer-icon.png | Bin 0 -> 1250 bytes .../pro/images/logs/icon-circle-green.svg | 1 + .../pro/images/logs/icon-circle-red.svg | 1 + .../pro/images/logs/icon-envelope-open.svg | 1 + .../assets/pro/images/logs/icon-envelope.svg | 1 + .../assets/pro/images/logs/icon-file-alt.svg | 1 + .../pro/images/logs/icon-hand-pointer.svg | 1 + .../assets/pro/images/logs/icon-note.png | Bin 0 -> 727 bytes .../assets/pro/images/logs/icon-paperclip.svg | 1 + .../pro/images/test-success/icon-alerts.svg | 20 + .../test-success/icon-backup-connection.svg | 7 + .../images/test-success/icon-email-log.svg | 26 + .../icon-feature-backup-connection.svg | 15 + .../js/smtp-pro-alerts-push-notifications.js | 541 ++ .../smtp-pro-alerts-push-notifications.min.js | 1 + .../assets/pro/js/smtp-pro-alerts.js | 248 + .../assets/pro/js/smtp-pro-alerts.min.js | 1 + .../pro/js/smtp-pro-conditional-logic.js | 253 + .../pro/js/smtp-pro-conditional-logic.min.js | 1 + .../pro/js/smtp-pro-dashboard-widget.js | 911 +++ .../pro/js/smtp-pro-dashboard-widget.min.js | 1 + .../assets/pro/js/smtp-pro-email-reports.js | 623 ++ .../pro/js/smtp-pro-email-reports.min.js | 1 + .../assets/pro/js/smtp-pro-logs.js | 693 ++ .../assets/pro/js/smtp-pro-logs.min.js | 1 + .../assets/pro/js/smtp-pro-network-admin.js | 121 + .../pro/js/smtp-pro-network-admin.min.js | 1 + ...p-pro-push-notifications-service-worker.js | 48 + ...o-push-notifications-service-worker.min.js | 1 + .../pro/js/smtp-pro-push-notifications.js | 78 + .../pro/js/smtp-pro-push-notifications.min.js | 1 + .../assets/pro/js/smtp-pro-select2.js | 77 + .../assets/pro/js/smtp-pro-select2.min.js | 1 + .../assets/pro/js/smtp-pro-settings.js | 1444 ++++ .../assets/pro/js/smtp-pro-settings.min.js | 1 + .../assets/pro/js/smtp-pro-smart-routing.js | 167 + .../pro/js/smtp-pro-smart-routing.min.js | 1 + .../pro/js/smtp-pro-tools-logs-export.js | 373 + .../pro/js/smtp-pro-tools-logs-export.min.js | 1 + .../pro/js/smtp-pro-tools-logs-importer.js | 315 + .../js/smtp-pro-tools-logs-importer.min.js | 1 + .../assets/pro/languages/wp-mail-smtp-pro.pot | 3920 +++++++++ .../assets/vue/css/wizard.min.css | 1 + .../assets/vue/css/wizard.rtl.min.css | 1 + .../assets/vue/img/amazonses.svg | 1 + .../wp-mail-smtp-pro/assets/vue/img/arrow.svg | 1 + .../wp-mail-smtp-pro/assets/vue/img/brevo.svg | 4 + .../vue/img/check-circle-solid-white.svg | 1 + .../assets/vue/img/check-circle-solid.svg | 1 + .../assets/vue/img/check-solid.svg | 1 + .../vue/img/chevron-down-solid-grey.svg | 1 + .../assets/vue/img/copy-solid.svg | 1 + .../assets/vue/img/elasticemail.svg | 1 + .../vue/img/exclamation-circle-solid.svg | 1 + .../assets/vue/img/gmail-sign-in-btn.svg | 1 + .../wp-mail-smtp-pro/assets/vue/img/gmail.svg | 1 + .../assets/vue/img/info-circle-solid.svg | 1 + .../assets/vue/img/loading-blue.svg | 1 + .../assets/vue/img/loading-pattie.svg | 28 + .../assets/vue/img/loading-white.svg | 1 + .../assets/vue/img/loading.svg | 1 + .../assets/vue/img/lock-solid.svg | 1 + .../wp-mail-smtp-pro/assets/vue/img/logo.svg | 1 + .../img/long-arrow-alt-left-regular-grey.svg | 1 + .../vue/img/long-arrow-alt-left-regular.svg | 1 + .../long-arrow-alt-right-regular-white.svg | 1 + .../vue/img/long-arrow-alt-right-regular.svg | 1 + .../assets/vue/img/mailersend.svg | 10 + .../assets/vue/img/mailgun.svg | 1 + .../assets/vue/img/mailjet.svg | 1 + .../assets/vue/img/mandrill.svg | 15 + .../assets/vue/img/outlook-sign-in-btn.svg | 1 + .../assets/vue/img/outlook.svg | 1 + .../assets/vue/img/postmark.svg | 1 + .../assets/vue/img/pro-badge.svg | 1 + .../assets/vue/img/question-circle-solid.svg | 1 + .../assets/vue/img/resend.svg | 1 + .../assets/vue/img/sendgrid.svg | 1 + .../assets/vue/img/sendlayer-logo.svg | 18 + .../assets/vue/img/sendlayer.svg | 5 + .../wp-mail-smtp-pro/assets/vue/img/smtp.svg | 1 + .../assets/vue/img/smtp2go.svg | 1 + .../assets/vue/img/smtpcom.svg | 1 + .../assets/vue/img/sparkpost.svg | 1 + .../assets/vue/img/star-solid.svg | 1 + .../assets/vue/img/thumbs-down-hover.svg | 1 + .../assets/vue/img/thumbs-down.svg | 1 + .../assets/vue/img/thumbs-up-hover.svg | 1 + .../assets/vue/img/thumbs-up.svg | 1 + .../assets/vue/img/times-solid-grey.svg | 1 + .../assets/vue/img/times-solid.svg | 1 + .../assets/vue/img/working.svg | 67 + .../wp-mail-smtp-pro/assets/vue/img/zoho.svg | 1 + .../assets/vue/js/chunk-vendors.min.js | 43 + .../assets/vue/js/wizard.min.js | 1 + web/app/plugins/wp-mail-smtp-pro/loco.xml | 29 + .../plugins/wp-mail-smtp-pro/polyfills.php | 29 + .../src/Abilities/AbilityInterface.php | 108 + .../src/Abilities/AbilityRegistrar.php | 184 + .../src/Abilities/AbstractAbility.php | 232 + .../DebugEvents/GetDebugEventsAbility.php | 188 + .../src/AbstractConnection.php | 67 + .../src/Admin/AdminBarMenu.php | 196 + .../wp-mail-smtp-pro/src/Admin/Area.php | 1669 ++++ .../src/Admin/ConnectionSettings.php | 463 ++ .../src/Admin/DashboardWidget.php | 779 ++ .../src/Admin/DebugEvents/DebugEvents.php | 473 ++ .../src/Admin/DebugEvents/Event.php | 597 ++ .../Admin/DebugEvents/EventsCollection.php | 421 + .../src/Admin/DebugEvents/Migration.php | 69 + .../src/Admin/DebugEvents/Table.php | 582 ++ .../src/Admin/DomainChecker.php | 203 + .../wp-mail-smtp-pro/src/Admin/Education.php | 108 + .../EmailSendingErrors/EmailSendingErrors.php | 1661 ++++ .../src/Admin/EmailSendingErrors/Registry.php | 241 + .../wp-mail-smtp-pro/src/Admin/FlyoutMenu.php | 146 + .../src/Admin/Notifications.php | 544 ++ .../src/Admin/PageAbstract.php | 208 + .../src/Admin/PageInterface.php | 45 + .../src/Admin/Pages/About.php | 92 + .../src/Admin/Pages/AboutTab.php | 698 ++ .../src/Admin/Pages/ActionSchedulerTab.php | 157 + .../Admin/Pages/AdditionalConnectionsTab.php | 193 + .../src/Admin/Pages/AiMcpTab.php | 362 + .../src/Admin/Pages/AlertsTab.php | 407 + .../src/Admin/Pages/AuthTab.php | 78 + .../src/Admin/Pages/CodeSnippetsTab.php | 479 ++ .../src/Admin/Pages/ControlTab.php | 327 + .../src/Admin/Pages/DebugEventsTab.php | 604 ++ .../src/Admin/Pages/EmailReports.php | 55 + .../src/Admin/Pages/EmailReportsTab.php | 180 + .../src/Admin/Pages/ExportTab.php | 151 + .../src/Admin/Pages/GetProTab.php | 535 ++ .../wp-mail-smtp-pro/src/Admin/Pages/Logs.php | 68 + .../src/Admin/Pages/LogsTab.php | 204 + .../src/Admin/Pages/MiscTab.php | 550 ++ .../src/Admin/Pages/SettingsTab.php | 439 + .../src/Admin/Pages/SmartRoutingTab.php | 313 + .../src/Admin/Pages/TestTab.php | 731 ++ .../src/Admin/Pages/Tools.php | 55 + .../src/Admin/ParentPageAbstract.php | 392 + .../src/Admin/PluginsInstallSkin.php | 58 + .../src/Admin/Recommendations/Migration.php | 175 + .../Admin/Recommendations/PageAbstract.php | 746 ++ .../Recommendations/Pages/ActiveLayer.php | 375 + .../Recommendations/Pages/Duplicator.php | 481 ++ .../Recommendations/Pages/Universally.php | 373 + .../Admin/Recommendations/Pages/WPCode.php | 397 + .../Admin/Recommendations/Pages/WPConsent.php | 462 ++ .../Admin/Recommendations/Pages/WPVibe.php | 373 + .../Recommendations/RecommendedPlugins.php | 257 + .../wp-mail-smtp-pro/src/Admin/Review.php | 226 + .../src/Admin/SetupWizard.php | 1520 ++++ .../Admin/WooCommerceActiveLayerEducation.php | 402 + .../src/Compatibility/Compatibility.php | 80 + .../src/Compatibility/Plugin/Admin2020.php | 70 + .../Compatibility/Plugin/PluginAbstract.php | 77 + .../Compatibility/Plugin/PluginInterface.php | 68 + .../src/Compatibility/Plugin/Polylang.php | 65 + .../src/Compatibility/Plugin/PolylangPro.php | 36 + .../src/Compatibility/Plugin/WPForms.php | 35 + .../src/Compatibility/Plugin/WPFormsLite.php | 47 + .../src/Compatibility/Plugin/WPML.php | 69 + .../src/Compatibility/Plugin/WooCommerce.php | 47 + .../wp-mail-smtp-pro/src/Conflicts.php | 685 ++ .../plugins/wp-mail-smtp-pro/src/Connect.php | 308 + .../wp-mail-smtp-pro/src/Connection.php | 78 + .../src/ConnectionInterface.php | 76 + .../src/ConnectionsManager.php | 48 + web/app/plugins/wp-mail-smtp-pro/src/Core.php | 1418 ++++ .../plugins/wp-mail-smtp-pro/src/DBRepair.php | 240 + .../plugins/wp-mail-smtp-pro/src/Debug.php | 207 + .../src/EmailSendingDebug.php | 228 + web/app/plugins/wp-mail-smtp-pro/src/Geo.php | 225 + .../wp-mail-smtp-pro/src/Helpers/Crypto.php | 163 + .../wp-mail-smtp-pro/src/Helpers/DB.php | 41 + .../wp-mail-smtp-pro/src/Helpers/Data.php | 172 + .../wp-mail-smtp-pro/src/Helpers/Helpers.php | 218 + .../src/Helpers/PluginImportDataRetriever.php | 323 + .../wp-mail-smtp-pro/src/Helpers/UI.php | 120 + .../Integrations/WPCode/RegisterLibrary.php | 48 + .../Integrations/WPCode/SnippetsProvider.php | 298 + .../wp-mail-smtp-pro/src/MailCatcher.php | 61 + .../src/MailCatcherInterface.php | 69 + .../wp-mail-smtp-pro/src/MailCatcherTrait.php | 902 ++ .../wp-mail-smtp-pro/src/MailCatcherV6.php | 54 + .../wp-mail-smtp-pro/src/Migration.php | 517 ++ .../src/MigrationAbstract.php | 161 + .../wp-mail-smtp-pro/src/Migrations.php | 185 + .../src/OptimizedEmailSending.php | 59 + .../plugins/wp-mail-smtp-pro/src/Options.php | 1824 +++++ .../Pro/Abilities/AbstractEmailLogAbility.php | 377 + .../Abilities/EmailLogResponseFormatter.php | 198 + .../EmailLogs/GetEmailLogAbility.php | 149 + .../EmailLogs/ListEmailLogsAbility.php | 180 + .../Abilities/Stats/GetEmailStatsAbility.php | 279 + .../AdditionalConnections.php | 266 + .../Admin/SettingsTab.php | 669 ++ .../AdditionalConnections/Admin/TestTab.php | 133 + .../Pro/AdditionalConnections/Connection.php | 86 + .../ConnectionOptions.php | 140 + .../wp-mail-smtp-pro/src/Pro/Admin/Area.php | 90 + .../src/Pro/Admin/DashboardWidget.php | 1317 +++ .../src/Pro/Admin/Pages/MiscTab.php | 162 + .../src/Pro/Admin/Pages/TestTab.php | 350 + .../src/Pro/Admin/PluginsList.php | 483 ++ .../src/Pro/Alerts/AbstractOptions.php | 171 + .../src/Pro/Alerts/Admin/SettingsTab.php | 519 ++ .../wp-mail-smtp-pro/src/Pro/Alerts/Alert.php | 67 + .../src/Pro/Alerts/Alerts.php | 394 + .../Handlers/CanValidateWebhookUrlTrait.php | 138 + .../Pro/Alerts/Handlers/HandlerInterface.php | 35 + .../src/Pro/Alerts/Loader.php | 178 + .../src/Pro/Alerts/Notifier.php | 52 + .../src/Pro/Alerts/OptionsInterface.php | 57 + .../Providers/CustomWebhook/Handler.php | 191 + .../Providers/CustomWebhook/Options.php | 139 + .../Providers/DiscordWebhook/Handler.php | 272 + .../Providers/DiscordWebhook/Options.php | 139 + .../Pro/Alerts/Providers/Email/Handler.php | 166 + .../Pro/Alerts/Providers/Email/Options.php | 165 + .../src/Pro/Alerts/Providers/Push/Handler.php | 197 + .../src/Pro/Alerts/Providers/Push/Options.php | 182 + .../Pro/Alerts/Providers/Push/Provider.php | 695 ++ .../Alerts/Providers/SlackWebhook/Handler.php | 331 + .../Alerts/Providers/SlackWebhook/Options.php | 139 + .../Alerts/Providers/TeamsWebhook/Handler.php | 302 + .../Alerts/Providers/TeamsWebhook/Options.php | 139 + .../Alerts/Providers/TwilioSMS/Handler.php | 222 + .../Alerts/Providers/TwilioSMS/Options.php | 235 + .../Pro/Alerts/Providers/WhatsApp/Handler.php | 784 ++ .../Pro/Alerts/Providers/WhatsApp/Options.php | 407 + .../Alerts/Providers/WhatsApp/Provider.php | 167 + .../BackupConnections/Admin/SettingsTab.php | 135 + .../BackupConnections/BackupConnections.php | 219 + .../CanProcessConditionalLogicTrait.php | 121 + .../ConditionalLogicSettings.php | 265 + .../src/Pro/ConnectionsManager.php | 169 + .../wp-mail-smtp-pro/src/Pro/DBRepair.php | 83 + .../Pro/Emails/Control/Admin/SettingsTab.php | 139 + .../src/Pro/Emails/Control/Control.php | 105 + .../src/Pro/Emails/Control/Reload.php | 185 + .../src/Pro/Emails/Control/Switcher.php | 158 + .../src/Pro/Emails/Control/functions.php | 56 + .../src/Pro/Emails/Logs/Admin/ArchivePage.php | 305 + .../Pro/Emails/Logs/Admin/PageAbstract.php | 147 + .../Pro/Emails/Logs/Admin/PrintPreview.php | 333 + .../src/Pro/Emails/Logs/Admin/SettingsTab.php | 508 ++ .../src/Pro/Emails/Logs/Admin/SinglePage.php | 779 ++ .../src/Pro/Emails/Logs/Admin/Table.php | 1023 +++ .../Emails/Logs/Attachments/Attachment.php | 461 ++ .../Emails/Logs/Attachments/Attachments.php | 334 + .../Pro/Emails/Logs/Attachments/Cleanup.php | 151 + .../Pro/Emails/Logs/Attachments/Migration.php | 118 + .../Pro/Emails/Logs/CanResendEmailTrait.php | 166 + .../AbstractDeliveryVerifier.php | 281 + .../DeliveryVerification/DeliveryStatus.php | 165 + .../DeliveryVerification.php | 172 + .../ElasticEmail/DeliveryVerifier.php | 112 + .../MailerSend/DeliveryVerifier.php | 104 + .../Mailgun/DeliveryVerifier.php | 117 + .../Mailjet/DeliveryVerifier.php | 103 + .../Mandrill/DeliveryVerifier.php | 104 + .../Postmark/DeliveryVerifier.php | 163 + .../Resend/DeliveryVerifier.php | 93 + .../SMTP2GO/DeliveryVerifier.php | 114 + .../SMTPcom/DeliveryVerifier.php | 112 + .../Sendinblue/DeliveryVerifier.php | 99 + .../Sendlayer/DeliveryVerifier.php | 99 + .../SparkPost/DeliveryVerifier.php | 117 + .../src/Pro/Emails/Logs/Email.php | 1312 +++ .../src/Pro/Emails/Logs/EmailsCollection.php | 614 ++ .../Pro/Emails/Logs/Export/AbstractData.php | 200 + .../src/Pro/Emails/Logs/Export/Admin.php | 289 + .../Logs/Export/CanRemoveExportFileTrait.php | 35 + .../src/Pro/Emails/Logs/Export/EMLData.php | 277 + .../src/Pro/Emails/Logs/Export/Export.php | 227 + .../src/Pro/Emails/Logs/Export/File.php | 403 + .../src/Pro/Emails/Logs/Export/Handler.php | 256 + .../src/Pro/Emails/Logs/Export/Request.php | 337 + .../src/Pro/Emails/Logs/Export/TableData.php | 103 + .../Logs/Importers/ImporterAbstract.php | 226 + .../Logs/Importers/ImporterInterface.php | 105 + .../Logs/Importers/ImporterTabAbstract.php | 227 + .../ImporterTabAbstractInterface.php | 24 + .../Pro/Emails/Logs/Importers/Importers.php | 134 + .../Logs/Importers/WPMailLogging/Importer.php | 934 +++ .../Logs/Importers/WPMailLogging/Tab.php | 212 + .../src/Pro/Emails/Logs/Logs.php | 1227 +++ .../src/Pro/Emails/Logs/Migration.php | 471 ++ .../src/Pro/Emails/Logs/Providers/Common.php | 302 + .../src/Pro/Emails/Logs/Providers/SMTP.php | 235 + .../Pro/Emails/Logs/RecheckDeliveryStatus.php | 184 + .../src/Pro/Emails/Logs/Reports/Admin.php | 482 ++ .../Emails/Logs/Reports/Emails/Summary.php | 454 + .../src/Pro/Emails/Logs/Reports/Report.php | 780 ++ .../src/Pro/Emails/Logs/Reports/Reports.php | 48 + .../src/Pro/Emails/Logs/Reports/Table.php | 406 + .../src/Pro/Emails/Logs/Resend.php | 197 + .../src/Pro/Emails/Logs/Tracking/Cleanup.php | 96 + .../Logs/Tracking/Events/AbstractEvent.php | 274 + .../Logs/Tracking/Events/EventFactory.php | 108 + .../Logs/Tracking/Events/EventInterface.php | 38 + .../Emails/Logs/Tracking/Events/Events.php | 109 + .../Injectable/AbstractInjectableEvent.php | 153 + .../Events/Injectable/ClickLinkEvent.php | 293 + .../Events/Injectable/OpenEmailEvent.php | 73 + .../Pro/Emails/Logs/Tracking/Migration.php | 122 + .../src/Pro/Emails/Logs/Tracking/Tracking.php | 234 + .../Logs/Webhooks/AbstractProcessor.php | 47 + .../Emails/Logs/Webhooks/AbstractProvider.php | 285 + .../Logs/Webhooks/AbstractSubscriber.php | 32 + .../Emails/Logs/Webhooks/Events/Delivered.php | 31 + .../Logs/Webhooks/Events/EventInterface.php | 25 + .../Emails/Logs/Webhooks/Events/Failed.php | 87 + .../Logs/Webhooks/ProcessorInterface.php | 33 + .../Logs/Webhooks/ProviderInterface.php | 29 + .../Providers/MailerSend/Events/Failed.php | 53 + .../Providers/MailerSend/Processor.php | 65 + .../Providers/MailerSend/Provider.php | 47 + .../Providers/MailerSend/Subscriber.php | 325 + .../Providers/Mailgun/Events/Failed.php | 52 + .../Webhooks/Providers/Mailgun/Processor.php | 97 + .../Webhooks/Providers/Mailgun/Provider.php | 47 + .../Webhooks/Providers/Mailgun/Subscriber.php | 263 + .../Providers/Mailjet/Events/Failed.php | 50 + .../Webhooks/Providers/Mailjet/Processor.php | 62 + .../Webhooks/Providers/Mailjet/Provider.php | 47 + .../Webhooks/Providers/Mailjet/Subscriber.php | 258 + .../Providers/Mandrill/Events/Failed.php | 59 + .../Webhooks/Providers/Mandrill/Processor.php | 90 + .../Webhooks/Providers/Mandrill/Provider.php | 47 + .../Providers/Mandrill/Subscriber.php | 218 + .../Providers/Postmark/Events/Failed.php | 43 + .../Webhooks/Providers/Postmark/Processor.php | 56 + .../Webhooks/Providers/Postmark/Provider.php | 217 + .../Providers/Postmark/Subscriber.php | 246 + .../Providers/Resend/Events/Failed.php | 57 + .../Webhooks/Providers/Resend/Processor.php | 73 + .../Webhooks/Providers/Resend/Provider.php | 47 + .../Webhooks/Providers/Resend/Subscriber.php | 250 + .../Providers/SMTP2GO/Events/Failed.php | 43 + .../Webhooks/Providers/SMTP2GO/Processor.php | 72 + .../Webhooks/Providers/SMTP2GO/Provider.php | 47 + .../Webhooks/Providers/SMTP2GO/Subscriber.php | 232 + .../Providers/SMTPcom/Events/Failed.php | 47 + .../Webhooks/Providers/SMTPcom/Processor.php | 62 + .../Webhooks/Providers/SMTPcom/Provider.php | 47 + .../Webhooks/Providers/SMTPcom/Subscriber.php | 208 + .../Providers/Sendinblue/Events/Failed.php | 42 + .../Providers/Sendinblue/Processor.php | 57 + .../Providers/Sendinblue/Provider.php | 47 + .../Providers/Sendinblue/Subscriber.php | 235 + .../Providers/Sendlayer/Events/Failed.php | 43 + .../Providers/Sendlayer/Processor.php | 72 + .../Webhooks/Providers/Sendlayer/Provider.php | 47 + .../Providers/Sendlayer/Subscriber.php | 225 + .../Providers/SparkPost/Events/Failed.php | 43 + .../Providers/SparkPost/Processor.php | 76 + .../Webhooks/Providers/SparkPost/Provider.php | 47 + .../Providers/SparkPost/Subscriber.php | 227 + .../Logs/Webhooks/SubscriberInterface.php | 38 + .../src/Pro/Emails/Logs/Webhooks/Webhooks.php | 523 ++ .../Pro/Emails/RateLimiting/RateLimiting.php | 290 + .../src/Pro/Emails/TestEmail.php | 204 + .../src/Pro/License/BundledLicense.php | 197 + .../src/Pro/License/License.php | 1317 +++ .../src/Pro/License/Updater.php | 474 ++ .../wp-mail-smtp-pro/src/Pro/MailCatcher.php | 20 + .../src/Pro/MailCatcherTrait.php | 129 + .../src/Pro/MailCatcherV6.php | 20 + .../wp-mail-smtp-pro/src/Pro/Migration.php | 54 + .../wp-mail-smtp-pro/src/Pro/Multisite.php | 1045 +++ .../plugins/wp-mail-smtp-pro/src/Pro/Pro.php | 1344 +++ .../src/Pro/ProductApi/Client.php | 203 + .../src/Pro/ProductApi/Credentials.php | 136 + .../ProductApi/CredentialsGenerationNonce.php | 81 + .../Pro/ProductApi/CredentialsGenerator.php | 194 + .../Pro/ProductApi/CredentialsRepository.php | 49 + .../src/Pro/ProductApi/ProductApi.php | 126 + .../src/Pro/ProductApi/Response.php | 134 + .../src/Pro/Providers/AmazonSES/Auth.php | 796 ++ .../Providers/AmazonSES/IdentitiesTable.php | 326 + .../src/Pro/Providers/AmazonSES/Identity.php | 258 + .../src/Pro/Providers/AmazonSES/Mailer.php | 192 + .../src/Pro/Providers/AmazonSES/Options.php | 549 ++ .../src/Pro/Providers/Gmail/Api/Client.php | 309 + .../Pro/Providers/Gmail/Api/OneTimeToken.php | 75 + .../src/Pro/Providers/Gmail/Api/Response.php | 117 + .../src/Pro/Providers/Gmail/Api/SiteId.php | 69 + .../src/Pro/Providers/Gmail/Auth.php | 353 + .../src/Pro/Providers/Gmail/Mailer.php | 259 + .../src/Pro/Providers/Gmail/Options.php | 340 + .../src/Pro/Providers/Gmail/Provider.php | 361 + .../Providers/Outlook/AttachmentsUploader.php | 360 + .../src/Pro/Providers/Outlook/Auth.php | 404 + .../src/Pro/Providers/Outlook/Mailer.php | 893 ++ .../Pro/Providers/Outlook/OneClick/Auth.php | 405 + .../Outlook/OneClick/Auth/Client.php | 206 + .../Outlook/OneClick/Auth/Response.php | 117 + .../Providers/Outlook/OneClick/Options.php | 305 + .../src/Pro/Providers/Outlook/Options.php | 262 + .../src/Pro/Providers/Outlook/Provider.php | 298 + .../src/Pro/Providers/Providers.php | 479 ++ .../src/Pro/Providers/Zoho/Auth.php | 355 + .../src/Pro/Providers/Zoho/Auth/Zoho.php | 314 + .../src/Pro/Providers/Zoho/Auth/ZohoUser.php | 171 + .../src/Pro/Providers/Zoho/Mailer.php | 467 ++ .../src/Pro/Providers/Zoho/Options.php | 319 + .../wp-mail-smtp-pro/src/Pro/SiteHealth.php | 212 + .../Pro/SmartRouting/Admin/SettingsTab.php | 459 ++ .../src/Pro/SmartRouting/ConditionalLogic.php | 183 + .../src/Pro/SmartRouting/SmartRouting.php | 167 + .../src/Pro/Tasks/EmailLogCleanupTask.php | 149 + .../src/Pro/Tasks/LicenseCheckTask.php | 98 + .../Tasks/Logs/BulkVerifySentStatusTask.php | 104 + .../ElasticEmail/VerifySentStatusTask.php | 20 + .../src/Pro/Tasks/Logs/ExportCleanupTask.php | 97 + .../Logs/MailerSend/VerifySentStatusTask.php | 20 + .../Logs/Mailgun/VerifySentStatusTask.php | 20 + .../Logs/Mailjet/VerifySentStatusTask.php | 20 + .../Logs/Mandrill/VerifySentStatusTask.php | 20 + .../Logs/Postmark/VerifySentStatusTask.php | 20 + .../Logs/Resend/VerifySentStatusTask.php | 20 + .../src/Pro/Tasks/Logs/ResendTask.php | 112 + .../Logs/SMTP2GO/VerifySentStatusTask.php | 20 + .../Logs/SMTPcom/VerifySentStatusTask.php | 27 + .../Logs/Sendinblue/VerifySentStatusTask.php | 20 + .../Logs/Sendlayer/VerifySentStatusTask.php | 20 + .../Logs/SparkPost/VerifySentStatusTask.php | 20 + .../Logs/VerifySentStatusTaskAbstract.php | 194 + .../Tasks/Migrations/EmailLogMigration11.php | 268 + .../Tasks/Migrations/EmailLogMigration4.php | 86 + .../Tasks/Migrations/EmailLogMigration5.php | 63 + .../src/Pro/Tasks/NotifierTask.php | 86 + .../wp-mail-smtp-pro/src/Pro/Translations.php | 471 ++ .../wp-mail-smtp-pro/src/Pro/Upgrade.php | 62 + .../src/Pro/WPCLI/Options/Registry.php | 645 ++ .../wp-mail-smtp-pro/src/Pro/activation.php | 91 + .../wp-mail-smtp-pro/src/Processor.php | 528 ++ .../src/Providers/AmazonSES/Options.php | 44 + .../src/Providers/AuthAbstract.php | 245 + .../src/Providers/AuthInterface.php | 30 + .../src/Providers/ElasticEmail/Mailer.php | 479 ++ .../src/Providers/ElasticEmail/Options.php | 132 + .../src/Providers/Gmail/Auth.php | 579 ++ .../src/Providers/Gmail/Logger.php | 175 + .../src/Providers/Gmail/Mailer.php | 303 + .../src/Providers/Gmail/Options.php | 299 + .../wp-mail-smtp-pro/src/Providers/Loader.php | 244 + .../src/Providers/Mail/Mailer.php | 49 + .../src/Providers/Mail/Options.php | 59 + .../src/Providers/MailerAbstract.php | 757 ++ .../src/Providers/MailerInterface.php | 86 + .../src/Providers/MailerSend/Mailer.php | 528 ++ .../src/Providers/MailerSend/Options.php | 160 + .../src/Providers/Mailgun/Mailer.php | 468 ++ .../src/Providers/Mailgun/Options.php | 178 + .../src/Providers/Mailjet/Mailer.php | 571 ++ .../src/Providers/Mailjet/Options.php | 173 + .../src/Providers/Mandrill/Mailer.php | 541 ++ .../src/Providers/Mandrill/Options.php | 128 + .../src/Providers/OptionsAbstract.php | 563 ++ .../src/Providers/OptionsInterface.php | 71 + .../src/Providers/Outlook/Options.php | 44 + .../src/Providers/Outlook/Provider.php | 114 + .../src/Providers/Pepipost/Mailer.php | 32 + .../src/Providers/Pepipost/Options.php | 29 + .../src/Providers/PepipostAPI/Mailer.php | 477 ++ .../src/Providers/PepipostAPI/Options.php | 127 + .../src/Providers/Postmark/Mailer.php | 488 ++ .../src/Providers/Postmark/Options.php | 164 + .../src/Providers/Resend/Mailer.php | 474 ++ .../src/Providers/Resend/Options.php | 133 + .../src/Providers/SMTP/Mailer.php | 32 + .../src/Providers/SMTP/Options.php | 48 + .../src/Providers/SMTP2GO/Mailer.php | 529 ++ .../src/Providers/SMTP2GO/Options.php | 133 + .../src/Providers/SMTPcom/Mailer.php | 503 ++ .../src/Providers/SMTPcom/Options.php | 174 + .../src/Providers/Sendgrid/Mailer.php | 418 + .../src/Providers/Sendgrid/Options.php | 148 + .../src/Providers/Sendinblue/Api.php | 115 + .../src/Providers/Sendinblue/Mailer.php | 474 ++ .../src/Providers/Sendinblue/Options.php | 184 + .../src/Providers/Sendlayer/Mailer.php | 474 ++ .../src/Providers/Sendlayer/Options.php | 226 + .../src/Providers/Sendlayer/QuickConnect.php | 975 +++ .../Providers/Sendlayer/QuickConnectUsage.php | 706 ++ .../src/Providers/SparkPost/Mailer.php | 559 ++ .../src/Providers/SparkPost/Options.php | 182 + .../src/Providers/Zoho/Options.php | 46 + .../src/Queue/Attachments.php | 242 + .../wp-mail-smtp-pro/src/Queue/Email.php | 608 ++ .../wp-mail-smtp-pro/src/Queue/Migration.php | 87 + .../wp-mail-smtp-pro/src/Queue/Queue.php | 763 ++ .../src/Reports/Emails/Summary.php | 433 + .../wp-mail-smtp-pro/src/Reports/Reports.php | 269 + .../wp-mail-smtp-pro/src/SiteHealth.php | 428 + .../src/Tasks/DebugEventsCleanupTask.php | 129 + .../wp-mail-smtp-pro/src/Tasks/Meta.php | 533 ++ .../src/Tasks/NotificationsUpdateTask.php | 75 + .../src/Tasks/Queue/CleanupQueueTask.php | 90 + .../src/Tasks/Queue/ProcessQueueTask.php | 84 + .../src/Tasks/Queue/SendEnqueuedEmailTask.php | 97 + .../src/Tasks/Reports/SummaryEmailTask.php | 104 + .../wp-mail-smtp-pro/src/Tasks/Task.php | 449 + .../wp-mail-smtp-pro/src/Tasks/Tasks.php | 321 + .../src/TestEmail/TestEmail.php | 521 ++ .../plugins/wp-mail-smtp-pro/src/Upgrade.php | 91 + .../plugins/wp-mail-smtp-pro/src/Uploads.php | 237 + .../src/UsageTracking/ErrorStats.php | 329 + .../src/UsageTracking/SendUsageTask.php | 122 + .../src/UsageTracking/UsageTracking.php | 420 + web/app/plugins/wp-mail-smtp-pro/src/WP.php | 974 +++ .../wp-mail-smtp-pro/src/WPCLI/Bootstrap.php | 61 + .../src/WPCLI/Commands/Manage.php | 27 + .../src/WPCLI/Commands/Option.php | 386 + .../src/WPCLI/Commands/Setup.php | 124 + .../src/WPCLI/Commands/Test.php | 91 + .../src/WPCLI/Options/Help.php | 230 + .../src/WPCLI/Options/Registry.php | 631 ++ .../src/WPCLI/Options/Writer.php | 506 ++ .../wp-mail-smtp-pro/src/WPMailArgs.php | 252 + .../wp-mail-smtp-pro/src/WPMailInitiator.php | 271 + .../plugins/wp-mail-smtp-pro/uninstall.php | 263 + .../vendor_prefixed/aws/aws-crt-php/LICENSE | 175 + .../vendor_prefixed/aws/aws-crt-php/NOTICE | 1 + .../aws/aws-crt-php/format-check.py | 46 + .../src/AWS/CRT/Auth/AwsCredentials.php | 61 + .../src/AWS/CRT/Auth/CredentialsProvider.php | 24 + .../aws-crt-php/src/AWS/CRT/Auth/Signable.php | 44 + .../src/AWS/CRT/Auth/SignatureType.php | 17 + .../src/AWS/CRT/Auth/SignedBodyHeaderType.php | 13 + .../aws-crt-php/src/AWS/CRT/Auth/Signing.php | 23 + .../src/AWS/CRT/Auth/SigningAlgorithm.php | 13 + .../src/AWS/CRT/Auth/SigningConfigAWS.php | 50 + .../src/AWS/CRT/Auth/SigningResult.php | 34 + .../CRT/Auth/StaticCredentialsProvider.php | 35 + .../aws/aws-crt-php/src/AWS/CRT/CRT.php | 356 + .../aws-crt-php/src/AWS/CRT/HTTP/Headers.php | 51 + .../aws-crt-php/src/AWS/CRT/HTTP/Message.php | 94 + .../aws-crt-php/src/AWS/CRT/HTTP/Request.php | 33 + .../aws-crt-php/src/AWS/CRT/HTTP/Response.php | 29 + .../src/AWS/CRT/IO/EventLoopGroup.php | 38 + .../src/AWS/CRT/IO/InputStream.php | 50 + .../src/AWS/CRT/Internal/Encoding.php | 40 + .../src/AWS/CRT/Internal/Extension.php | 31 + .../aws/aws-crt-php/src/AWS/CRT/Log.php | 49 + .../src/AWS/CRT/NativeResource.php | 42 + .../aws/aws-crt-php/src/AWS/CRT/Options.php | 80 + .../vendor_prefixed/aws/aws-sdk-php/LICENSE | 141 + .../vendor_prefixed/aws/aws-sdk-php/NOTICE | 17 + .../aws/aws-sdk-php/THIRD-PARTY-LICENSES | 84 + .../src/AbstractConfigurationProvider.php | 133 + .../aws/aws-sdk-php/src/Api/AbstractModel.php | 75 + .../aws/aws-sdk-php/src/Api/ApiProvider.php | 212 + .../aws-sdk-php/src/Api/DateTimeResult.php | 115 + .../aws/aws-sdk-php/src/Api/DocModel.php | 107 + .../Api/ErrorParser/AbstractErrorParser.php | 70 + .../src/Api/ErrorParser/JsonParserTrait.php | 37 + .../Api/ErrorParser/JsonRpcErrorParser.php | 38 + .../Api/ErrorParser/RestJsonErrorParser.php | 43 + .../src/Api/ErrorParser/XmlErrorParser.php | 82 + .../aws/aws-sdk-php/src/Api/ListShape.php | 30 + .../aws/aws-sdk-php/src/Api/MapShape.php | 43 + .../aws/aws-sdk-php/src/Api/Operation.php | 138 + .../src/Api/Parser/AbstractParser.php | 35 + .../src/Api/Parser/AbstractRestParser.php | 140 + .../src/Api/Parser/Crc32ValidatingParser.php | 38 + .../Parser/DecodingEventStreamIterator.php | 257 + .../src/Api/Parser/EventParsingIterator.php | 158 + .../Api/Parser/Exception/ParserException.php | 49 + .../aws-sdk-php/src/Api/Parser/JsonParser.php | 57 + .../src/Api/Parser/JsonRpcParser.php | 59 + .../src/Api/Parser/MetadataParserTrait.php | 71 + ...kableStreamDecodingEventStreamIterator.php | 81 + .../src/Api/Parser/PayloadParserTrait.php | 47 + .../src/Api/Parser/QueryParser.php | 46 + .../src/Api/Parser/RestJsonParser.php | 39 + .../src/Api/Parser/RestXmlParser.php | 33 + .../aws-sdk-php/src/Api/Parser/XmlParser.php | 125 + .../src/Api/Serializer/Ec2ParamBuilder.php | 30 + .../src/Api/Serializer/JsonBody.php | 94 + .../src/Api/Serializer/JsonRpcSerializer.php | 59 + .../src/Api/Serializer/QueryParamBuilder.php | 112 + .../src/Api/Serializer/QuerySerializer.php | 54 + .../src/Api/Serializer/RestJsonSerializer.php | 34 + .../src/Api/Serializer/RestSerializer.php | 254 + .../src/Api/Serializer/RestXmlSerializer.php | 42 + .../src/Api/Serializer/XmlBody.php | 148 + .../aws/aws-sdk-php/src/Api/Service.php | 416 + .../aws/aws-sdk-php/src/Api/Shape.php | 56 + .../aws/aws-sdk-php/src/Api/ShapeMap.php | 59 + .../aws-sdk-php/src/Api/StructureShape.php | 68 + .../aws-sdk-php/src/Api/TimestampShape.php | 45 + .../aws/aws-sdk-php/src/Api/Validator.php | 246 + .../aws-sdk-php/src/Arn/AccessPointArn.php | 55 + .../src/Arn/AccessPointArnInterface.php | 11 + .../aws/aws-sdk-php/src/Arn/Arn.php | 140 + .../aws/aws-sdk-php/src/Arn/ArnInterface.php | 30 + .../aws/aws-sdk-php/src/Arn/ArnParser.php | 66 + .../src/Arn/Exception/InvalidArnException.php | 10 + .../src/Arn/ObjectLambdaAccessPointArn.php | 35 + .../src/Arn/ResourceTypeAndIdTrait.php | 25 + .../aws-sdk-php/src/Arn/S3/AccessPointArn.php | 26 + .../src/Arn/S3/BucketArnInterface.php | 12 + .../src/Arn/S3/MultiRegionAccessPointArn.php | 35 + .../src/Arn/S3/OutpostsAccessPointArn.php | 77 + .../src/Arn/S3/OutpostsArnInterface.php | 12 + .../src/Arn/S3/OutpostsBucketArn.php | 71 + .../src/Auth/AuthSchemeResolver.php | 134 + .../src/Auth/AuthSchemeResolverInterface.php | 20 + .../src/Auth/AuthSelectionMiddleware.php | 78 + .../UnresolvedAuthSchemeException.php | 13 + .../aws/aws-sdk-php/src/AwsClient.php | 619 ++ .../aws-sdk-php/src/AwsClientInterface.php | 156 + .../aws/aws-sdk-php/src/AwsClientTrait.php | 76 + .../aws/aws-sdk-php/src/CacheInterface.php | 33 + .../aws/aws-sdk-php/src/ClientResolver.php | 748 ++ .../AbstractMonitoringMiddleware.php | 244 + .../ApiCallAttemptMonitoringMiddleware.php | 181 + .../ApiCallMonitoringMiddleware.php | 126 + .../ClientSideMonitoring/Configuration.php | 65 + .../ConfigurationInterface.php | 41 + .../ConfigurationProvider.php | 185 + .../Exception/ConfigurationException.php | 13 + .../MonitoringMiddlewareInterface.php | 30 + .../aws/aws-sdk-php/src/Command.php | 106 + .../aws/aws-sdk-php/src/CommandInterface.php | 40 + .../aws/aws-sdk-php/src/CommandPool.php | 131 + .../Configuration/ConfigurationResolver.php | 176 + .../src/ConfigurationProviderInterface.php | 14 + .../AssumeRoleCredentialProvider.php | 53 + ...eRoleWithWebIdentityCredentialProvider.php | 125 + .../src/Credentials/CredentialProvider.php | 704 ++ .../src/Credentials/CredentialSources.php | 22 + .../src/Credentials/Credentials.php | 112 + .../src/Credentials/CredentialsInterface.php | 48 + .../src/Credentials/CredentialsUtils.php | 31 + .../src/Credentials/EcsCredentialProvider.php | 206 + .../Credentials/InstanceProfileProvider.php | 324 + .../src/Crypto/AbstractCryptoClient.php | 102 + .../src/Crypto/AbstractCryptoClientV2.php | 98 + .../src/Crypto/AesDecryptingStream.php | 108 + .../src/Crypto/AesEncryptingStream.php | 113 + .../src/Crypto/AesGcmDecryptingStream.php | 78 + .../src/Crypto/AesGcmEncryptingStream.php | 94 + .../src/Crypto/AesStreamInterface.php | 28 + .../src/Crypto/AesStreamInterfaceV2.php | 29 + .../aws/aws-sdk-php/src/Crypto/Cipher/Cbc.php | 76 + .../src/Crypto/Cipher/CipherBuilderTrait.php | 66 + .../src/Crypto/Cipher/CipherMethod.php | 55 + .../src/Crypto/DecryptionTrait.php | 109 + .../src/Crypto/DecryptionTraitV2.php | 138 + .../src/Crypto/EncryptionTrait.php | 114 + .../src/Crypto/EncryptionTraitV2.php | 122 + .../src/Crypto/KmsMaterialsProvider.php | 90 + .../src/Crypto/KmsMaterialsProviderV2.php | 69 + .../src/Crypto/MaterialsProvider.php | 92 + .../src/Crypto/MaterialsProviderInterface.php | 57 + .../Crypto/MaterialsProviderInterfaceV2.php | 50 + .../src/Crypto/MaterialsProviderV2.php | 57 + .../src/Crypto/MetadataEnvelope.php | 53 + .../src/Crypto/MetadataStrategyInterface.php | 30 + .../src/Crypto/Polyfill/AesGcm.php | 161 + .../src/Crypto/Polyfill/ByteArray.php | 234 + .../aws-sdk-php/src/Crypto/Polyfill/Gmac.php | 173 + .../aws-sdk-php/src/Crypto/Polyfill/Key.php | 71 + .../src/Crypto/Polyfill/NeedsTrait.php | 37 + .../src/DefaultsMode/Configuration.php | 104 + .../DefaultsMode/ConfigurationInterface.php | 47 + .../DefaultsMode/ConfigurationProvider.php | 166 + .../Exception/ConfigurationException.php | 13 + .../aws-sdk-php/src/DoctrineCacheAdapter.php | 61 + .../src/Endpoint/EndpointProvider.php | 89 + .../aws-sdk-php/src/Endpoint/Partition.php | 210 + .../Endpoint/PartitionEndpointProvider.php | 113 + .../src/Endpoint/PartitionInterface.php | 51 + .../src/Endpoint/PatternEndpointProvider.php | 37 + .../UseDualstackEndpoint/Configuration.php | 34 + .../ConfigurationInterface.php | 19 + .../ConfigurationProvider.php | 145 + .../Exception/ConfigurationException.php | 13 + .../UseFipsEndpoint/Configuration.php | 31 + .../ConfigurationInterface.php | 19 + .../UseFipsEndpoint/ConfigurationProvider.php | 150 + .../Exception/ConfigurationException.php | 13 + .../src/EndpointDiscovery/Configuration.php | 39 + .../ConfigurationInterface.php | 29 + .../ConfigurationProvider.php | 190 + .../EndpointDiscoveryMiddleware.php | 246 + .../src/EndpointDiscovery/EndpointList.php | 80 + .../Exception/ConfigurationException.php | 13 + .../src/EndpointParameterMiddleware.php | 69 + .../EndpointV2/EndpointDefinitionProvider.php | 57 + .../src/EndpointV2/EndpointProviderV2.php | 56 + .../src/EndpointV2/EndpointV2Middleware.php | 287 + .../EndpointV2/EndpointV2SerializerTrait.php | 59 + .../src/EndpointV2/Rule/AbstractRule.php | 49 + .../src/EndpointV2/Rule/EndpointRule.php | 90 + .../src/EndpointV2/Rule/ErrorRule.php | 38 + .../src/EndpointV2/Rule/RuleCreator.php | 21 + .../src/EndpointV2/Rule/TreeRule.php | 52 + .../src/EndpointV2/Ruleset/Ruleset.php | 99 + .../EndpointV2/Ruleset/RulesetEndpoint.php | 55 + .../EndpointV2/Ruleset/RulesetParameter.php | 143 + .../Ruleset/RulesetStandardLibrary.php | 342 + .../src/Exception/AwsException.php | 226 + .../src/Exception/CommonRuntimeException.php | 7 + .../CouldNotCreateChecksumException.php | 15 + .../src/Exception/CredentialsException.php | 10 + .../src/Exception/CryptoException.php | 11 + .../src/Exception/CryptoPolyfillException.php | 10 + .../Exception/EventStreamDataException.php | 36 + .../IncalculablePayloadException.php | 10 + .../src/Exception/InvalidJsonException.php | 10 + .../src/Exception/InvalidRegionException.php | 10 + .../Exception/MultipartUploadException.php | 57 + .../src/Exception/TokenException.php | 10 + .../src/Exception/UnresolvedApiException.php | 10 + .../Exception/UnresolvedEndpointException.php | 10 + .../UnresolvedSignatureException.php | 10 + .../src/Handler/GuzzleV5/GuzzleHandler.php | 140 + .../src/Handler/GuzzleV5/GuzzleStream.php | 22 + .../src/Handler/GuzzleV5/PsrStream.php | 30 + .../src/Handler/GuzzleV6/GuzzleHandler.php | 61 + .../aws/aws-sdk-php/src/HandlerList.php | 391 + .../aws/aws-sdk-php/src/HasDataTrait.php | 74 + .../src/HasMonitoringEventsTrait.php | 36 + .../aws/aws-sdk-php/src/HashInterface.php | 26 + .../aws/aws-sdk-php/src/HashingStream.php | 52 + .../aws/aws-sdk-php/src/History.php | 134 + .../src/IdempotencyTokenMiddleware.php | 102 + .../src/Identity/AwsCredentialIdentity.php | 19 + .../src/Identity/BearerTokenIdentity.php | 19 + .../src/Identity/IdentityInterface.php | 21 + .../src/Identity/S3/S3ExpressIdentity.php | 8 + .../Identity/S3/S3ExpressIdentityProvider.php | 41 + .../src/InputValidationMiddleware.php | 62 + .../aws/aws-sdk-php/src/JsonCompiler.php | 25 + .../src/Kms/Exception/KmsException.php | 11 + .../aws/aws-sdk-php/src/Kms/KmsClient.php | 118 + .../aws/aws-sdk-php/src/LruArrayCache.php | 73 + .../aws/aws-sdk-php/src/MetricsBuilder.php | 245 + .../aws/aws-sdk-php/src/Middleware.php | 327 + .../aws/aws-sdk-php/src/MockHandler.php | 123 + .../src/MonitoringEventsInterface.php | 29 + .../aws/aws-sdk-php/src/MultiRegionClient.php | 194 + .../src/Multipart/AbstractUploadManager.php | 252 + .../src/Multipart/AbstractUploader.php | 118 + .../aws-sdk-php/src/Multipart/UploadState.php | 130 + .../aws/aws-sdk-php/src/PhpHash.php | 65 + .../aws-sdk-php/src/PresignUrlMiddleware.php | 91 + .../aws/aws-sdk-php/src/Psr16CacheAdapter.php | 26 + .../aws/aws-sdk-php/src/PsrCacheAdapter.php | 32 + .../src/QueryCompatibleInputMiddleware.php | 204 + .../src/RequestCompressionMiddleware.php | 118 + .../src/ResponseContainerInterface.php | 14 + .../aws/aws-sdk-php/src/Result.php | 51 + .../aws/aws-sdk-php/src/ResultInterface.php | 51 + .../aws/aws-sdk-php/src/ResultPaginator.php | 166 + .../aws-sdk-php/src/Retry/Configuration.php | 44 + .../src/Retry/ConfigurationInterface.php | 29 + .../src/Retry/ConfigurationProvider.php | 177 + .../Exception/ConfigurationException.php | 13 + .../aws-sdk-php/src/Retry/QuotaManager.php | 65 + .../aws/aws-sdk-php/src/Retry/RateLimiter.php | 143 + .../src/Retry/RetryHelperTrait.php | 46 + .../aws/aws-sdk-php/src/RetryMiddleware.php | 191 + .../aws/aws-sdk-php/src/RetryMiddlewareV2.php | 235 + .../src/SSO/Exception/SSOException.php | 11 + .../aws/aws-sdk-php/src/SSO/SSOClient.php | 19 + .../SSOOIDC/Exception/SSOOIDCException.php | 11 + .../aws-sdk-php/src/SSOOIDC/SSOOIDCClient.php | 19 + .../src/Script/Composer/Composer.php | 90 + .../aws/aws-sdk-php/src/Sdk.php | 911 +++ .../src/Ses/Exception/SesException.php | 11 + .../aws/aws-sdk-php/src/Ses/SesClient.php | 223 + .../src/SesV2/Exception/SesV2Exception.php | 11 + .../aws/aws-sdk-php/src/SesV2/SesV2Client.php | 203 + .../src/Signature/AnonymousSignature.php | 26 + .../src/Signature/S3ExpressSignature.php | 32 + .../src/Signature/S3SignatureV4.php | 88 + .../src/Signature/SignatureInterface.php | 36 + .../src/Signature/SignatureProvider.php | 125 + .../src/Signature/SignatureTrait.php | 34 + .../aws-sdk-php/src/Signature/SignatureV4.php | 383 + .../src/StreamRequestPayloadMiddleware.php | 67 + .../src/Sts/Exception/StsException.php | 11 + .../Sts/RegionalEndpoints/Configuration.php | 35 + .../ConfigurationInterface.php | 22 + .../ConfigurationProvider.php | 169 + .../Exception/ConfigurationException.php | 13 + .../aws/aws-sdk-php/src/Sts/StsClient.php | 115 + .../src/Token/BearerTokenAuthorization.php | 29 + .../aws-sdk-php/src/Token/ParsesIniTrait.php | 41 + .../RefreshableTokenProviderInterface.php | 23 + .../aws/aws-sdk-php/src/Token/SsoToken.php | 102 + .../src/Token/SsoTokenProvider.php | 225 + .../aws/aws-sdk-php/src/Token/Token.php | 94 + .../src/Token/TokenAuthorization.php | 21 + .../aws-sdk-php/src/Token/TokenInterface.php | 34 + .../aws-sdk-php/src/Token/TokenProvider.php | 211 + .../aws/aws-sdk-php/src/TraceMiddleware.php | 243 + .../aws-sdk-php/src/UserAgentMiddleware.php | 263 + .../aws/aws-sdk-php/src/Waiter.php | 222 + .../aws-sdk-php/src/WrappedHttpHandler.php | 142 + .../aws/aws-sdk-php/src/functions.php | 539 ++ .../google/apiclient-services/src/Gmail.php | 115 + .../src/Gmail/AutoForwarding.php | 78 + .../src/Gmail/BatchDeleteMessagesRequest.php | 43 + .../src/Gmail/BatchModifyMessagesRequest.php | 79 + .../src/Gmail/CseIdentity.php | 76 + .../src/Gmail/CseKeyPair.php | 149 + .../src/Gmail/CsePrivateKeyMetadata.php | 74 + .../apiclient-services/src/Gmail/Delegate.php | 60 + .../src/Gmail/DisableCseKeyPairRequest.php | 24 + .../apiclient-services/src/Gmail/Draft.php | 58 + .../src/Gmail/EnableCseKeyPairRequest.php | 24 + .../apiclient-services/src/Gmail/Filter.php | 74 + .../src/Gmail/FilterAction.php | 79 + .../src/Gmail/FilterCriteria.php | 186 + .../src/Gmail/ForwardingAddress.php | 60 + .../src/Gmail/HardwareKeyMetadata.php | 42 + .../apiclient-services/src/Gmail/History.php | 123 + .../src/Gmail/HistoryLabelAdded.php | 59 + .../src/Gmail/HistoryLabelRemoved.php | 59 + .../src/Gmail/HistoryMessageAdded.php | 40 + .../src/Gmail/HistoryMessageDeleted.php | 40 + .../src/Gmail/ImapSettings.php | 96 + .../src/Gmail/KaclsKeyMetadata.php | 60 + .../apiclient-services/src/Gmail/Label.php | 202 + .../src/Gmail/LabelColor.php | 60 + .../src/Gmail/LanguageSettings.php | 42 + .../src/Gmail/ListCseIdentitiesResponse.php | 59 + .../src/Gmail/ListCseKeyPairsResponse.php | 59 + .../src/Gmail/ListDelegatesResponse.php | 41 + .../src/Gmail/ListDraftsResponse.php | 77 + .../src/Gmail/ListFiltersResponse.php | 41 + .../Gmail/ListForwardingAddressesResponse.php | 41 + .../src/Gmail/ListHistoryResponse.php | 77 + .../src/Gmail/ListLabelsResponse.php | 41 + .../src/Gmail/ListMessagesResponse.php | 77 + .../src/Gmail/ListSendAsResponse.php | 41 + .../src/Gmail/ListSmimeInfoResponse.php | 41 + .../src/Gmail/ListThreadsResponse.php | 77 + .../apiclient-services/src/Gmail/Message.php | 185 + .../src/Gmail/MessagePart.php | 127 + .../src/Gmail/MessagePartBody.php | 78 + .../src/Gmail/MessagePartHeader.php | 60 + .../src/Gmail/ModifyMessageRequest.php | 61 + .../src/Gmail/ModifyThreadRequest.php | 61 + .../src/Gmail/ObliterateCseKeyPairRequest.php | 24 + .../src/Gmail/PivKeyMetadata.php | 42 + .../src/Gmail/PopSettings.php | 60 + .../apiclient-services/src/Gmail/Profile.php | 96 + .../src/Gmail/Resource/Users.php | 83 + .../src/Gmail/Resource/UsersDrafts.php | 144 + .../src/Gmail/Resource/UsersHistory.php | 68 + .../src/Gmail/Resource/UsersLabels.php | 131 + .../src/Gmail/Resource/UsersMessages.php | 261 + .../Resource/UsersMessagesAttachments.php | 52 + .../src/Gmail/Resource/UsersSettings.php | 201 + .../src/Gmail/Resource/UsersSettingsCse.php | 32 + .../Resource/UsersSettingsCseIdentities.php | 132 + .../Resource/UsersSettingsCseKeypairs.php | 153 + .../Gmail/Resource/UsersSettingsDelegates.php | 117 + .../Gmail/Resource/UsersSettingsFilters.php | 97 + .../UsersSettingsForwardingAddresses.php | 105 + .../Gmail/Resource/UsersSettingsSendAs.php | 164 + .../Resource/UsersSettingsSendAsSmimeInfo.php | 126 + .../src/Gmail/Resource/UsersThreads.php | 154 + .../apiclient-services/src/Gmail/SendAs.php | 184 + .../src/Gmail/SignAndEncryptKeyPairs.php | 60 + .../src/Gmail/SmimeInfo.php | 150 + .../apiclient-services/src/Gmail/SmtpMsa.php | 114 + .../apiclient-services/src/Gmail/Thread.php | 95 + .../src/Gmail/VacationSettings.php | 168 + .../src/Gmail/WatchRequest.php | 97 + .../src/Gmail/WatchResponse.php | 60 + .../vendor_prefixed/google/apiclient/LICENSE | 203 + .../apiclient/src/AccessToken/Revoke.php | 65 + .../apiclient/src/AccessToken/Verify.php | 217 + .../src/AuthHandler/AuthHandlerFactory.php | 47 + .../src/AuthHandler/Guzzle6AuthHandler.php | 76 + .../src/AuthHandler/Guzzle7AuthHandler.php | 25 + .../google/apiclient/src/Client.php | 1063 +++ .../google/apiclient/src/Collection.php | 104 + .../google/apiclient/src/Exception.php | 23 + .../google/apiclient/src/Http/Batch.php | 191 + .../apiclient/src/Http/MediaFileUpload.php | 273 + .../google/apiclient/src/Http/REST.php | 153 + .../google/apiclient/src/Model.php | 301 + .../google/apiclient/src/Service.php | 67 + .../apiclient/src/Service/Exception.php | 65 + .../google/apiclient/src/Service/Resource.php | 216 + .../google/apiclient/src/Task/Composer.php | 77 + .../google/apiclient/src/Task/Exception.php | 23 + .../google/apiclient/src/Task/Retryable.php | 26 + .../google/apiclient/src/Task/Runner.php | 235 + .../apiclient/src/Utils/UriTemplate.php | 264 + .../google/apiclient/src/aliases.php | 80 + .../vendor_prefixed/google/auth/LICENSE | 203 + .../vendor_prefixed/google/auth/autoload.php | 35 + .../google/auth/src/AccessToken.php | 390 + .../src/ApplicationDefaultCredentials.php | 301 + .../src/Cache/InvalidArgumentException.php | 23 + .../google/auth/src/Cache/Item.php | 146 + .../auth/src/Cache/MemoryCacheItemPool.php | 161 + .../auth/src/Cache/SysVCacheItemPool.php | 207 + .../google/auth/src/Cache/TypedItem.php | 142 + .../google/auth/src/CacheTrait.php | 96 + .../src/CredentialSource/AwsNativeSource.php | 270 + .../auth/src/CredentialSource/FileSource.php | 60 + .../auth/src/CredentialSource/UrlSource.php | 74 + .../Credentials/AppIdentityCredentials.php | 209 + .../ExternalAccountCredentials.php | 229 + .../auth/src/Credentials/GCECredentials.php | 493 ++ .../auth/src/Credentials/IAMCredentials.php | 77 + .../ImpersonatedServiceAccountCredentials.php | 120 + .../src/Credentials/InsecureCredentials.php | 62 + .../Credentials/ServiceAccountCredentials.php | 312 + .../ServiceAccountJwtAccessCredentials.php | 171 + .../Credentials/UserRefreshCredentials.php | 130 + .../google/auth/src/CredentialsLoader.php | 242 + ...ternalAccountCredentialSourceInterface.php | 23 + .../google/auth/src/FetchAuthTokenCache.php | 261 + .../auth/src/FetchAuthTokenInterface.php | 52 + .../google/auth/src/GCECache.php | 70 + .../auth/src/GetQuotaProjectInterface.php | 32 + .../auth/src/GetUniverseDomainInterface.php | 34 + .../src/HttpHandler/Guzzle6HttpHandler.php | 59 + .../src/HttpHandler/Guzzle7HttpHandler.php | 22 + .../auth/src/HttpHandler/HttpClientCache.php | 51 + .../src/HttpHandler/HttpHandlerFactory.php | 62 + .../vendor_prefixed/google/auth/src/Iam.php | 86 + .../google/auth/src/IamSignerTrait.php | 61 + .../src/Middleware/AuthTokenMiddleware.php | 138 + .../Middleware/ProxyAuthTokenMiddleware.php | 130 + .../ScopedAccessTokenMiddleware.php | 140 + .../auth/src/Middleware/SimpleMiddleware.php | 86 + .../google/auth/src/OAuth2.php | 1520 ++++ .../auth/src/ProjectIdProviderInterface.php | 32 + .../auth/src/ServiceAccountSignerTrait.php | 52 + .../google/auth/src/SignBlobInterface.php | 43 + .../auth/src/UpdateMetadataInterface.php | 36 + .../google/auth/src/UpdateMetadataTrait.php | 62 + .../vendor_prefixed/guzzlehttp/guzzle/LICENSE | 27 + .../guzzlehttp/guzzle/src/BodySummarizer.php | 23 + .../guzzle/src/BodySummarizerInterface.php | 12 + .../guzzlehttp/guzzle/src/Client.php | 880 ++ .../guzzlehttp/guzzle/src/ClientInterface.php | 78 + .../guzzlehttp/guzzle/src/ClientTrait.php | 227 + .../guzzle/src/Cookie/CookieJar.php | 240 + .../guzzle/src/Cookie/CookieJarInterface.php | 74 + .../guzzle/src/Cookie/FileCookieJar.php | 92 + .../guzzle/src/Cookie/SessionCookieJar.php | 82 + .../guzzle/src/Cookie/SetCookie.php | 421 + .../src/Exception/BadResponseException.php | 31 + .../guzzle/src/Exception/ClientException.php | 10 + .../guzzle/src/Exception/ConnectException.php | 45 + .../guzzle/src/Exception/GuzzleException.php | 8 + .../Exception/InvalidArgumentException.php | 7 + .../guzzle/src/Exception/RequestException.php | 114 + .../guzzle/src/Exception/ServerException.php | 10 + .../Exception/TooManyRedirectsException.php | 7 + .../src/Exception/TransferException.php | 7 + .../guzzle/src/Handler/CurlFactory.php | 997 +++ .../src/Handler/CurlFactoryInterface.php | 23 + .../guzzle/src/Handler/CurlHandler.php | 58 + .../guzzle/src/Handler/CurlMultiHandler.php | 313 + .../src/Handler/CurlShareHandleState.php | 140 + .../guzzle/src/Handler/CurlVersion.php | 102 + .../guzzle/src/Handler/EasyHandle.php | 96 + .../guzzle/src/Handler/HeaderProcessor.php | 64 + .../guzzle/src/Handler/MockHandler.php | 173 + .../guzzlehttp/guzzle/src/Handler/Proxy.php | 49 + .../guzzle/src/Handler/ProxyEnvironment.php | 101 + .../guzzle/src/Handler/StreamHandler.php | 711 ++ .../guzzlehttp/guzzle/src/HandlerStack.php | 246 + .../guzzle/src/MessageFormatter.php | 168 + .../guzzle/src/MessageFormatterInterface.php | 17 + .../guzzlehttp/guzzle/src/Middleware.php | 227 + .../guzzlehttp/guzzle/src/Pool.php | 120 + .../guzzle/src/PrepareBodyMiddleware.php | 86 + .../guzzle/src/RedirectMiddleware.php | 162 + .../guzzlehttp/guzzle/src/RequestOptions.php | 286 + .../guzzlehttp/guzzle/src/RetryMiddleware.php | 96 + .../guzzlehttp/guzzle/src/TransferStats.php | 114 + .../guzzle/src/TransportSharing.php | 13 + .../guzzlehttp/guzzle/src/Utils.php | 673 ++ .../guzzlehttp/guzzle/src/functions.php | 158 + .../guzzle/src/functions_include.php | 8 + .../guzzlehttp/promises/LICENSE | 24 + .../promises/src/AggregateException.php | 15 + .../promises/src/CancellationException.php | 11 + .../guzzlehttp/promises/src/Coroutine.php | 145 + .../guzzlehttp/promises/src/Create.php | 71 + .../guzzlehttp/promises/src/Each.php | 67 + .../guzzlehttp/promises/src/EachPromise.php | 212 + .../promises/src/FulfilledPromise.php | 73 + .../guzzlehttp/promises/src/Is.php | 36 + .../guzzlehttp/promises/src/Promise.php | 236 + .../promises/src/PromiseInterface.php | 80 + .../promises/src/PromisorInterface.php | 15 + .../promises/src/RejectedPromise.php | 78 + .../promises/src/RejectionException.php | 41 + .../guzzlehttp/promises/src/TaskQueue.php | 65 + .../promises/src/TaskQueueInterface.php | 21 + .../guzzlehttp/promises/src/Utils.php | 244 + .../vendor_prefixed/guzzlehttp/psr7/LICENSE | 26 + .../guzzlehttp/psr7/src/AppendStream.php | 221 + .../guzzlehttp/psr7/src/BufferStream.php | 136 + .../guzzlehttp/psr7/src/CachingStream.php | 164 + .../guzzlehttp/psr7/src/DroppingStream.php | 43 + .../src/Exception/MalformedUriException.php | 12 + .../guzzlehttp/psr7/src/FnStream.php | 164 + .../guzzlehttp/psr7/src/Header.php | 150 + .../guzzlehttp/psr7/src/HttpFactory.php | 76 + .../guzzlehttp/psr7/src/InflateStream.php | 33 + .../guzzlehttp/psr7/src/LazyOpenStream.php | 41 + .../guzzlehttp/psr7/src/LimitStream.php | 138 + .../guzzlehttp/psr7/src/Message.php | 252 + .../guzzlehttp/psr7/src/MessageTrait.php | 272 + .../guzzlehttp/psr7/src/MimeType.php | 27 + .../guzzlehttp/psr7/src/MultipartStream.php | 225 + .../guzzlehttp/psr7/src/NoSeekStream.php | 29 + .../guzzlehttp/psr7/src/PumpStream.php | 170 + .../guzzlehttp/psr7/src/Query.php | 128 + .../guzzlehttp/psr7/src/Request.php | 139 + .../guzzlehttp/psr7/src/Response.php | 89 + .../guzzlehttp/psr7/src/Rfc3986.php | 23 + .../guzzlehttp/psr7/src/Rfc7230.php | 88 + .../guzzlehttp/psr7/src/ServerRequest.php | 343 + .../guzzlehttp/psr7/src/Stream.php | 250 + .../psr7/src/StreamDecoratorTrait.php | 146 + .../guzzlehttp/psr7/src/StreamWrapper.php | 149 + .../guzzlehttp/psr7/src/UploadedFile.php | 152 + .../guzzlehttp/psr7/src/Uri.php | 633 ++ .../guzzlehttp/psr7/src/UriComparator.php | 49 + .../guzzlehttp/psr7/src/UriNormalizer.php | 186 + .../guzzlehttp/psr7/src/UriResolver.php | 176 + .../guzzlehttp/psr7/src/Utils.php | 544 ++ .../league/oauth2-client/LICENSE | 21 + .../oauth2-client/src/Grant/AbstractGrant.php | 72 + .../src/Grant/AuthorizationCode.php | 38 + .../src/Grant/ClientCredentials.php | 38 + .../Grant/Exception/InvalidGrantException.php | 25 + .../oauth2-client/src/Grant/GrantFactory.php | 91 + .../oauth2-client/src/Grant/Password.php | 38 + .../oauth2-client/src/Grant/RefreshToken.php | 38 + .../HttpBasicAuthOptionProvider.php | 38 + .../OptionProviderInterface.php | 30 + .../OptionProvider/PostAuthOptionProvider.php | 46 + .../src/Provider/AbstractProvider.php | 802 ++ .../Exception/IdentityProviderException.php | 45 + .../src/Provider/GenericProvider.php | 201 + .../src/Provider/GenericResourceOwner.php | 57 + .../src/Provider/ResourceOwnerInterface.php | 35 + .../oauth2-client/src/Token/AccessToken.php | 211 + .../src/Token/AccessTokenInterface.php | 67 + .../ResourceOwnerAccessTokenInterface.php | 25 + .../Token/SettableRefreshTokenInterface.php | 26 + .../src/Tool/ArrayAccessorTrait.php | 47 + .../src/Tool/BearerAuthorizationTrait.php | 35 + .../src/Tool/GuardedPropertyTrait.php | 66 + .../src/Tool/MacAuthorizationTrait.php | 76 + .../src/Tool/ProviderRedirectTrait.php | 105 + .../src/Tool/QueryBuilderTrait.php | 33 + .../oauth2-client/src/Tool/RequestFactory.php | 67 + .../src/Tool/RequiredParameterTrait.php | 51 + .../mk-j/php_xlsxwriter/LICENSE | 20 + .../mk-j/php_xlsxwriter/xlsxwriter.class.php | 1028 +++ .../mtdowling/jmespath.php/LICENSE | 19 + .../mtdowling/jmespath.php/src/AstRuntime.php | 41 + .../jmespath.php/src/CompilerRuntime.php | 92 + .../jmespath.php/src/DebugRuntime.php | 83 + .../mtdowling/jmespath.php/src/Env.php | 87 + .../jmespath.php/src/FnDispatcher.php | 406 + .../mtdowling/jmespath.php/src/JmesPath.php | 18 + .../mtdowling/jmespath.php/src/Lexer.php | 254 + .../mtdowling/jmespath.php/src/Parser.php | 363 + .../jmespath.php/src/SyntaxErrorException.php | 26 + .../jmespath.php/src/TreeCompiler.php | 225 + .../jmespath.php/src/TreeInterpreter.php | 183 + .../mtdowling/jmespath.php/src/Utils.php | 242 + .../constant_time_encoding/LICENSE.txt | 48 + .../constant_time_encoding/src/Base32.php | 440 + .../constant_time_encoding/src/Base32Hex.php | 99 + .../constant_time_encoding/src/Base64.php | 338 + .../src/Base64DotSlash.php | 79 + .../src/Base64DotSlashOrdered.php | 75 + .../src/Base64UrlSafe.php | 83 + .../constant_time_encoding/src/Binary.php | 98 + .../src/EncoderInterface.php | 51 + .../constant_time_encoding/src/Encoding.php | 299 + .../constant_time_encoding/src/Hex.php | 155 + .../constant_time_encoding/src/RFC4648.php | 206 + .../psr/cache/src/CacheException.php | 10 + .../psr/cache/src/CacheItemInterface.php | 100 + .../psr/cache/src/CacheItemPoolInterface.php | 129 + .../cache/src/InvalidArgumentException.php | 13 + .../vendor_prefixed/psr/http-client/LICENSE | 19 + .../src/ClientExceptionInterface.php | 10 + .../psr/http-client/src/ClientInterface.php | 19 + .../src/NetworkExceptionInterface.php | 23 + .../src/RequestExceptionInterface.php | 23 + .../vendor_prefixed/psr/http-factory/LICENSE | 21 + .../src/RequestFactoryInterface.php | 18 + .../src/ResponseFactoryInterface.php | 18 + .../src/ServerRequestFactoryInterface.php | 24 + .../src/StreamFactoryInterface.php | 43 + .../src/UploadedFileFactoryInterface.php | 28 + .../http-factory/src/UriFactoryInterface.php | 17 + .../vendor_prefixed/psr/http-message/LICENSE | 19 + .../psr/http-message/src/MessageInterface.php | 177 + .../psr/http-message/src/RequestInterface.php | 124 + .../http-message/src/ResponseInterface.php | 66 + .../src/ServerRequestInterface.php | 249 + .../psr/http-message/src/StreamInterface.php | 144 + .../src/UploadedFileInterface.php | 118 + .../psr/http-message/src/UriInterface.php | 309 + .../vendor_prefixed/psr/log/LICENSE | 19 + .../psr/log/Psr/Log/AbstractLogger.php | 121 + .../log/Psr/Log/InvalidArgumentException.php | 7 + .../psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + .../psr/log/Psr/Log/LoggerAwareTrait.php | 25 + .../psr/log/Psr/Log/LoggerInterface.php | 117 + .../psr/log/Psr/Log/LoggerTrait.php | 134 + .../psr/log/Psr/Log/NullLogger.php | 30 + .../symfony/deprecation-contracts/LICENSE | 19 + .../deprecation-contracts/function.php | 27 + .../symfony/polyfill-intl-idn/Idn.php | 736 ++ .../symfony/polyfill-intl-idn/Info.php | 22 + .../symfony/polyfill-intl-idn/LICENSE | 19 + .../Resources/unidata/DisallowedRanges.php | 294 + .../Resources/unidata/Regex.php | 29 + .../Resources/unidata/deviation.php | 5 + .../Resources/unidata/disallowed.php | 5 + .../unidata/disallowed_STD3_mapped.php | 5 + .../unidata/disallowed_STD3_valid.php | 5 + .../Resources/unidata/ignored.php | 5 + .../Resources/unidata/mapped.php | 5 + .../Resources/unidata/virama.php | 5 + .../symfony/polyfill-intl-idn/bootstrap.php | 145 + .../symfony/polyfill-intl-idn/bootstrap80.php | 125 + .../symfony/polyfill-mbstring/LICENSE | 19 + .../symfony/polyfill-mbstring/Mbstring.php | 910 +++ .../Resources/unidata/caseFolding.php | 5 + .../Resources/unidata/lowerCase.php | 5 + .../Resources/unidata/titleCaseRegexp.php | 6 + .../Resources/unidata/upperCase.php | 5 + .../symfony/polyfill-mbstring/bootstrap.php | 16 + .../symfony/polyfill-mbstring/bootstrap72.php | 296 + .../symfony/polyfill-mbstring/bootstrap80.php | 167 + .../plugins/wp-mail-smtp-pro/wp-mail-smtp.php | 66 + .../plugins/wp-mail-smtp-pro/wp_mail_smtp.php | 349 + 1375 files changed, 211810 insertions(+), 33 deletions(-) create mode 100644 web/app/plugins/wp-mail-smtp-pro/CHANGELOG.md create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/admin-bar.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/admin-notifications.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/admin-site-health.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/dashboard-widget.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/emails/summary-report-email.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-about.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-activelayer-wc.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-admin.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-smart-routing.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/affiliatewp.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/duplicator-icon-large.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/edd.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-activelayer.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-duplicator.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-full.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-none.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-partial.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-universally.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-wpconsent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-aioseo.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-charitable.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-mi.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-om.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-pushengage.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-rp.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-seedprod.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-smash-balloon-facebook-feeds.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-smash-balloon-instagram-feeds.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-smash-balloon-twitter-feeds.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-smash-balloon-youtube-feeds.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-trustpulse.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-vibe-ai.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-wpcode.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-wpf.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/searchwp.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/sugar-calendar.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/team.jpg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/about/wp-simple-pay.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/additional-connections/screenshot-01.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/additional-connections/screenshot-02.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/additional-connections/thumbnail-01.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/additional-connections/thumbnail-02.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/ai-mcp/hero-illustration.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/error-icon.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/delivered.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/sent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/total.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/unsent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/delivered.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/sent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/total.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/unsent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/yes-green.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/activelayer.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-advanced-mailers.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-backup-connections.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-failure-alerts.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-logs.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-one-click-mailer-setups.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-smart-routing.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/hero.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-full.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-none.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-partial.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/education/sendlayer-banner.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/screenshot-01.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/screenshot-02.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/screenshot-03.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/thumbnail-01.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/thumbnail-02.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/thumbnail-03.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email/icon-check.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email/illustration-success.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email/signature.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email/wp-mail-smtp-whitelabel.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/email/wp-mail-smtp.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/facebook.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/life-ring.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/lightbulb.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/mascot.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/star.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/arrow-rotate-right-purple.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/cancel-red.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-dark-green.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-solid-green.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/clock-orange.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-regular-red.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-orange.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-red.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle-blue.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/arrow-up.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/badge-percent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/close.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/custom/badge-percent.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-pdf-grey.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-yes-alt-green.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/error.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/exclamation-circle.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/lightbulb.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/success.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/warning.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/icons/zoom.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-blue.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-white.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/logo-whitelabel.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/logs/archive-thumbnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/logs/archive.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/logs/single-thumbnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/logs/single.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/menu-icon.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/pattie.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge-small.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/aws.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/brevo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/elasticemail.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/google.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailersend.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailgun.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailjet.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mandrill.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/microsoft.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/pepipost-smtp.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/pepipost.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/php.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/postmark.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/resend.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendgrid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendlayer.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp-com.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp2go.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sparkpost.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/providers/zoho.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/complete.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-1.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-2.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-3.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-full@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-tnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-tnail@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/duplicator/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/duplicator/screenshot-full@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/duplicator/screenshot-tnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/duplicator/screenshot-tnail@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/universally/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/universally/screenshot-full@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/universally/screenshot-tnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/universally/screenshot-tnail@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-full@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-tnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-tnail@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-full@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-tnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-tnail@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-full@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-tnail.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-tnail@2x.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/recommended.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-arrow-down.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-arrow-up.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-check-gray.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-check.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-click.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-email.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-error.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-open.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/wp-mail-smtp-logo-dark-whitelabel.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/wp-mail-smtp-logo-dark.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/wp-mail-smtp-logo-whitelabel.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/wp-mail-smtp-logo.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/reports/icon-note.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/test-success/illustration-lite.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/images/wp-spinner.gif create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/connect.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/connect.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-about.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-about.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-ai-mcp.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-ai-mcp.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-code-snippets.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-code-snippets.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-tools-debug-events.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-tools-debug-events.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/languages/wp-mail-smtp-vue.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/languages/wp-mail-smtp.pot create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/css/smtp-pro-email-reports.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/css/smtp-pro-logs-print.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/css/smtp-pro-logs.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/css/smtp-pro-settings.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/css/smtp-pro-tools-logs-export.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/email/sendlayer-icon.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-circle-green.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-circle-red.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-envelope-open.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-envelope.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-file-alt.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-hand-pointer.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-note.png create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/logs/icon-paperclip.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/test-success/icon-alerts.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/test-success/icon-backup-connection.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/test-success/icon-email-log.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/images/test-success/icon-feature-backup-connection.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-alerts-push-notifications.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-alerts-push-notifications.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-alerts.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-alerts.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-conditional-logic.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-conditional-logic.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-dashboard-widget.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-dashboard-widget.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-email-reports.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-email-reports.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-logs.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-logs.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-network-admin.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-network-admin.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-push-notifications-service-worker.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-push-notifications-service-worker.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-push-notifications.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-push-notifications.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-select2.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-select2.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-settings.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-settings.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-smart-routing.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-smart-routing.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-tools-logs-export.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-tools-logs-export.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-tools-logs-importer.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/js/smtp-pro-tools-logs-importer.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/pro/languages/wp-mail-smtp-pro.pot create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/css/wizard.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/css/wizard.rtl.min.css create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/amazonses.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/arrow.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/brevo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/check-circle-solid-white.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/check-circle-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/check-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/chevron-down-solid-grey.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/copy-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/elasticemail.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/exclamation-circle-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/gmail-sign-in-btn.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/gmail.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/info-circle-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/loading-blue.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/loading-pattie.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/loading-white.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/loading.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/lock-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/long-arrow-alt-left-regular-grey.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/long-arrow-alt-left-regular.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/long-arrow-alt-right-regular-white.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/long-arrow-alt-right-regular.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/mailersend.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/mailgun.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/mailjet.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/mandrill.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/outlook-sign-in-btn.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/outlook.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/postmark.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/pro-badge.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/question-circle-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/resend.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/sendgrid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/sendlayer-logo.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/sendlayer.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/smtp.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/smtp2go.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/smtpcom.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/sparkpost.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/star-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/thumbs-down-hover.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/thumbs-down.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/thumbs-up-hover.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/thumbs-up.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/times-solid-grey.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/times-solid.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/working.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/img/zoho.svg create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/js/chunk-vendors.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/assets/vue/js/wizard.min.js create mode 100644 web/app/plugins/wp-mail-smtp-pro/loco.xml create mode 100644 web/app/plugins/wp-mail-smtp-pro/polyfills.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Abilities/AbilityInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Abilities/AbilityRegistrar.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Abilities/AbstractAbility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Abilities/DebugEvents/GetDebugEventsAbility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/AbstractConnection.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/AdminBarMenu.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Area.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/ConnectionSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DashboardWidget.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/DebugEvents.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Event.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/EventsCollection.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Table.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/DomainChecker.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Education.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/EmailSendingErrors.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/Registry.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/FlyoutMenu.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Notifications.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/PageAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/PageInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/About.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AboutTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/ActionSchedulerTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AdditionalConnectionsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AiMcpTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AlertsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AuthTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/CodeSnippetsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/ControlTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/DebugEventsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReports.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReportsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/ExportTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/GetProTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Logs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/LogsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/MiscTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SmartRoutingTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/TestTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Tools.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/ParentPageAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/PluginsInstallSkin.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/PageAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/ActiveLayer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Duplicator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Universally.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPCode.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPConsent.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPVibe.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/RecommendedPlugins.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/Review.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/SetupWizard.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Admin/WooCommerceActiveLayerEducation.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Compatibility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/Admin2020.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PluginAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PluginInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/Polylang.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PolylangPro.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WPForms.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WPFormsLite.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WPML.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WooCommerce.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Conflicts.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Connect.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Connection.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/ConnectionInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/ConnectionsManager.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Core.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/DBRepair.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Debug.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/EmailSendingDebug.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Geo.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Helpers/Crypto.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Helpers/DB.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Helpers/Data.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Helpers/Helpers.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Helpers/PluginImportDataRetriever.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Helpers/UI.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/RegisterLibrary.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/SnippetsProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/MailCatcher.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/MailCatcherInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/MailCatcherTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/MailCatcherV6.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/MigrationAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Migrations.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/OptimizedEmailSending.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/AbstractEmailLogAbility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogResponseFormatter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/GetEmailLogAbility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/ListEmailLogsAbility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/Stats/GetEmailStatsAbility.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/AdditionalConnections.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/TestTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Connection.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/ConnectionOptions.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Area.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/DashboardWidget.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/MiscTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/TestTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/PluginsList.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/AbstractOptions.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Admin/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alert.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alerts.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/CanValidateWebhookUrlTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/HandlerInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Loader.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Notifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/OptionsInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/Admin/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/BackupConnections.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/CanProcessConditionalLogicTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/ConditionalLogicSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ConnectionsManager.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/DBRepair.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Admin/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Control.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Reload.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Switcher.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/functions.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/ArchivePage.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PageAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PrintPreview.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/SinglePage.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/Table.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachment.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachments.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Cleanup.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/CanResendEmailTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/AbstractDeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryStatus.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryVerification.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/ElasticEmail/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/MailerSend/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailgun/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailjet/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mandrill/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Postmark/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Resend/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTP2GO/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTPcom/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendinblue/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendlayer/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SparkPost/DeliveryVerifier.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Email.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/EmailsCollection.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/AbstractData.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Admin.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/CanRemoveExportFileTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/EMLData.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Export.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/File.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Handler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Request.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/TableData.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterTabAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterTabAbstractInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/Importers.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Importer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Tab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Logs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/Common.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/SMTP.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/RecheckDeliveryStatus.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Admin.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Emails/Summary.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Report.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Reports.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Table.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Resend.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Cleanup.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/AbstractEvent.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Events.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/AbstractInjectableEvent.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/ClickLinkEvent.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/OpenEmailEvent.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Tracking.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProcessor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/AbstractSubscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/Delivered.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/EventInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/ProcessorInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/ProviderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/MailerSend/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailgun/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mailjet/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Mandrill/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Postmark/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Resend/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTP2GO/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SMTPcom/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendinblue/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/Sendlayer/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Events/Failed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Providers/SparkPost/Subscriber.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/SubscriberInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Webhooks/Webhooks.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/RateLimiting/RateLimiting.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/TestEmail.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/License/BundledLicense.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/License/License.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/License/Updater.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcher.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcherTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcherV6.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Multisite.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Pro.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Client.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Credentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerationNonce.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsGenerator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/CredentialsRepository.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/ProductApi.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/ProductApi/Response.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Auth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/IdentitiesTable.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Identity.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Client.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/OneTimeToken.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/Response.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Api/SiteId.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Auth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Gmail/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/AttachmentsUploader.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Auth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Client.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Auth/Response.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/OneClick/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Outlook/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Providers.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/Zoho.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Auth/ZohoUser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/Zoho/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/SiteHealth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/Admin/SettingsTab.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/ConditionalLogic.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/SmartRouting.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/EmailLogCleanupTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/LicenseCheckTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/BulkVerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/ElasticEmail/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/ExportCleanupTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/MailerSend/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Mailgun/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Mailjet/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Mandrill/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Postmark/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Resend/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/ResendTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/SMTP2GO/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/SMTPcom/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Sendinblue/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/Sendlayer/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/SparkPost/VerifySentStatusTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/VerifySentStatusTaskAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration11.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration4.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration5.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/NotifierTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Translations.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/Upgrade.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/WPCLI/Options/Registry.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Pro/activation.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Processor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/AmazonSES/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/AuthAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/AuthInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/ElasticEmail/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/ElasticEmail/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Auth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Logger.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Loader.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerSend/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/MailerSend/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailgun/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailgun/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailjet/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailjet/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mandrill/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Mandrill/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/OptionsAbstract.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/OptionsInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Outlook/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Outlook/Provider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Pepipost/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/PepipostAPI/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Postmark/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Postmark/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Resend/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Resend/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTP2GO/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTPcom/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SMTPcom/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendgrid/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendgrid/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Api.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendinblue/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/QuickConnect.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Sendlayer/QuickConnectUsage.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Mailer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/SparkPost/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Providers/Zoho/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Queue/Attachments.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Queue/Email.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Queue/Migration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Queue/Queue.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Reports/Emails/Summary.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Reports/Reports.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/SiteHealth.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/DebugEventsCleanupTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Meta.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/NotificationsUpdateTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/CleanupQueueTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/ProcessQueueTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/SendEnqueuedEmailTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Reports/SummaryEmailTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Task.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Tasks/Tasks.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/TestEmail/TestEmail.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Upgrade.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/Uploads.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/ErrorStats.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/SendUsageTask.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/UsageTracking.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WP.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Bootstrap.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Manage.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Option.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Setup.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Test.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Help.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Registry.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Writer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPMailArgs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/src/WPMailInitiator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/uninstall.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/NOTICE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/format-check.py create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SignedBodyHeaderType.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/Signing.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningConfigAWS.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/CRT.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Internal/Extension.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Log.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/NativeResource.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Options.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/NOTICE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/THIRD-PARTY-LICENSES create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AbstractConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/AbstractModel.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ApiProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DateTimeResult.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DocModel.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/AbstractErrorParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ListShape.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/MapShape.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Operation.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/EventParsingIterator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/MetadataParserTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/NonSeekableStreamDecodingEventStreamIterator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/QueryParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestJsonParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestXmlParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/XmlParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/Ec2ParamBuilder.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonBody.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonRpcSerializer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QueryParamBuilder.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QuerySerializer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestJsonSerializer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestXmlSerializer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/XmlBody.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Service.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Shape.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ShapeMap.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/StructureShape.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/TimestampShape.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Validator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArnInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/Arn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ArnInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ArnParser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/Exception/InvalidArnException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ObjectLambdaAccessPointArn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ResourceTypeAndIdTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/AccessPointArn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/BucketArnInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/MultiRegionAccessPointArn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/OutpostsAccessPointArn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/OutpostsArnInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/OutpostsBucketArn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolver.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolverInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSelectionMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/Exception/UnresolvedAuthSchemeException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CacheInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientResolver.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/AbstractMonitoringMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallAttemptMonitoringMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallMonitoringMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/MonitoringMiddlewareInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Command.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CommandInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CommandPool.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Configuration/ConfigurationResolver.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ConfigurationProviderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/AssumeRoleCredentialProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/AssumeRoleWithWebIdentityCredentialProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialSources.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/Credentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialsInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialsUtils.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/EcsCredentialProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AbstractCryptoClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AbstractCryptoClientV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesDecryptingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesEncryptingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmDecryptingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmEncryptingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesStreamInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesStreamInterfaceV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Cipher/Cbc.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Cipher/CipherBuilderTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Cipher/CipherMethod.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/DecryptionTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/DecryptionTraitV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTraitV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProviderV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProviderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProviderInterfaceV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProviderV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataEnvelope.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataStrategyInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/AesGcm.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/ByteArray.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Gmac.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Key.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/NeedsTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DoctrineCacheAdapter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/EndpointProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/Partition.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionEndpointProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PatternEndpointProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointDiscoveryMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointList.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointParameterMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointDefinitionProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointProviderV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2Middleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2SerializerTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/AbstractRule.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/EndpointRule.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/ErrorRule.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/RuleCreator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/TreeRule.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/Ruleset.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetEndpoint.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetParameter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetStandardLibrary.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/AwsException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CommonRuntimeException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CouldNotCreateChecksumException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CredentialsException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CryptoException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CryptoPolyfillException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/EventStreamDataException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/IncalculablePayloadException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/InvalidJsonException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/InvalidRegionException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/MultipartUploadException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/TokenException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/UnresolvedApiException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/UnresolvedEndpointException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/UnresolvedSignatureException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/PsrStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV6/GuzzleHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HandlerList.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasDataTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasMonitoringEventsTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HashInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HashingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/History.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/IdempotencyTokenMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/AwsCredentialIdentity.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/BearerTokenIdentity.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/IdentityInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/S3/S3ExpressIdentity.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/S3/S3ExpressIdentityProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/InputValidationMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/JsonCompiler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Kms/Exception/KmsException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Kms/KmsClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/LruArrayCache.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MetricsBuilder.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Middleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MockHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MonitoringEventsInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MultiRegionClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploader.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/UploadState.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PhpHash.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PresignUrlMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Psr16CacheAdapter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PsrCacheAdapter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/QueryCompatibleInputMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RequestCompressionMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResponseContainerInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Result.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultPaginator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/QuotaManager.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RateLimiter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RetryHelperTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddlewareV2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSO/Exception/SSOException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSO/SSOClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSOOIDC/Exception/SSOOIDCException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSOOIDC/SSOOIDCClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Script/Composer/Composer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sdk.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Ses/Exception/SesException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Ses/SesClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SesV2/Exception/SesV2Exception.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SesV2/SesV2Client.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/AnonymousSignature.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/S3ExpressSignature.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/S3SignatureV4.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureV4.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/StreamRequestPayloadMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/Exception/StsException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/Configuration.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/ConfigurationInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/ConfigurationProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/Exception/ConfigurationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/StsClient.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/BearerTokenAuthorization.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/ParsesIniTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/RefreshableTokenProviderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/SsoToken.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/SsoTokenProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/Token.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/TokenAuthorization.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/TokenInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/TokenProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/TraceMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/UserAgentMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Waiter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/WrappedHttpHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/functions.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/AutoForwarding.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchDeleteMessagesRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchModifyMessagesRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseIdentity.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseKeyPair.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CsePrivateKeyMetadata.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Delegate.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/DisableCseKeyPairRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Draft.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/EnableCseKeyPairRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Filter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterAction.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterCriteria.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ForwardingAddress.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HardwareKeyMetadata.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/History.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelAdded.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelRemoved.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageAdded.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageDeleted.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ImapSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/KaclsKeyMetadata.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Label.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LabelColor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LanguageSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseIdentitiesResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseKeyPairsResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDelegatesResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDraftsResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListFiltersResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListForwardingAddressesResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListHistoryResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListLabelsResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListMessagesResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSendAsResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSmimeInfoResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListThreadsResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Message.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePart.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartBody.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartHeader.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyMessageRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyThreadRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ObliterateCseKeyPairRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/PivKeyMetadata.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/PopSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Profile.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/Users.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersDrafts.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersHistory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersLabels.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessages.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessagesAttachments.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseIdentities.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseKeypairs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsDelegates.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsFilters.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsForwardingAddresses.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAsSmimeInfo.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersThreads.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SendAs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SignAndEncryptKeyPairs.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmimeInfo.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmtpMsa.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Thread.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/VacationSettings.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchResponse.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Revoke.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Verify.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/AuthHandlerFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Client.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Collection.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Exception.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/Batch.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/MediaFileUpload.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/REST.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Model.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Exception.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Resource.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Composer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Exception.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Retryable.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Runner.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Utils/UriTemplate.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/aliases.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/autoload.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/AccessToken.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ApplicationDefaultCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/InvalidArgumentException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/Item.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/MemoryCacheItemPool.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/SysVCacheItemPool.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/TypedItem.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CacheTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/AwsNativeSource.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/FileSource.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/UrlSource.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/AppIdentityCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ExternalAccountCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/GCECredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/IAMCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/InsecureCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/UserRefreshCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialsLoader.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ExternalAccountCredentialSourceInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/FetchAuthTokenCache.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/FetchAuthTokenInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GCECache.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GetQuotaProjectInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GetUniverseDomainInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/Guzzle6HttpHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/Guzzle7HttpHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/HttpClientCache.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/HttpHandlerFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Iam.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/IamSignerTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/AuthTokenMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/SimpleMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/OAuth2.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ProjectIdProviderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ServiceAccountSignerTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/SignBlobInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/UpdateMetadataInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/UpdateMetadataTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizerInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Client.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/ClientInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/ClientTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJar.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SetCookie.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/BadResponseException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/ClientException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/ConnectException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/GuzzleException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/RequestException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/ServerException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/TransferException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlVersion.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/EasyHandle.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/MockHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/Proxy.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/ProxyEnvironment.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/StreamHandler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/HandlerStack.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatterInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Middleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Pool.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RedirectMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RequestOptions.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RetryMiddleware.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransferStats.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransportSharing.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Utils.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/functions.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/functions_include.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/AggregateException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/CancellationException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Coroutine.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Create.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Each.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/EachPromise.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/FulfilledPromise.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Is.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Promise.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/PromiseInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/PromisorInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/RejectedPromise.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/RejectionException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueue.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueueInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Utils.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/AppendStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/BufferStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/CachingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/DroppingStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Exception/MalformedUriException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/FnStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Header.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/HttpFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/InflateStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LazyOpenStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LimitStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Message.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MessageTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MimeType.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MultipartStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/NoSeekStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/PumpStream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Query.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Request.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Response.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Rfc3986.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Rfc7230.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/ServerRequest.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Stream.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamDecoratorTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamWrapper.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UploadedFile.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Uri.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriComparator.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriNormalizer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriResolver.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Utils.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AbstractGrant.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AuthorizationCode.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/ClientCredentials.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/GrantFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Password.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/RefreshToken.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/AbstractProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericProvider.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericResourceOwner.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/ResourceOwnerInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessToken.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessTokenInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ArrayAccessorTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/GuardedPropertyTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/MacAuthorizationTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ProviderRedirectTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/QueryBuilderTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequestFactory.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequiredParameterTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/xlsxwriter.class.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/AstRuntime.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/CompilerRuntime.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/DebugRuntime.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Env.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/FnDispatcher.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/JmesPath.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Lexer.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Parser.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/SyntaxErrorException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeCompiler.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeInterpreter.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Utils.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/LICENSE.txt create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32Hex.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlash.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64UrlSafe.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Binary.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/EncoderInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Encoding.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Hex.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/RFC4648.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/CacheException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/CacheItemInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/CacheItemPoolInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/InvalidArgumentException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-client/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-client/src/ClientExceptionInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-client/src/ClientInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-client/src/NetworkExceptionInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-client/src/RequestExceptionInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/src/RequestFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/src/ResponseFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/src/ServerRequestFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/src/StreamFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/src/UploadedFileFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-factory/src/UriFactoryInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/MessageInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/RequestInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/ResponseInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/ServerRequestInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/StreamInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/UploadedFileInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/UriInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/AbstractLogger.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LogLevel.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerInterface.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerTrait.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/NullLogger.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/function.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Idn.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Info.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/Regex.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/deviation.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/ignored.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/mapped.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/virama.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap80.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/LICENSE create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Mbstring.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/upperCase.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap72.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap80.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/wp-mail-smtp.php create mode 100644 web/app/plugins/wp-mail-smtp-pro/wp_mail_smtp.php 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/composer.json b/composer.json index 8d116a25..255e03e7 100644 --- a/composer.json +++ b/composer.json @@ -101,7 +101,6 @@ "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 442f926f..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": "1029529897213525bf75edef76bd110d", + "content-hash": "25418c3dc8870c69d786c3bf6e7236cf", "packages": [ { "name": "aws/aws-crt-php", @@ -7164,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", 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:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}.wp-mail-smtp-admin-page-body .wp-mail-smtp-license-update-notice{color:#7D2209} \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-smart-routing.min.css b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-smart-routing.min.css new file mode 100644 index 00000000..7e256a57 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/css/smtp-smart-routing.min.css @@ -0,0 +1 @@ +#wp-mail-smtp .wp-mail-smtp-smart-routing-header{padding-bottom:0 !important;border-bottom:none !important}#wp-mail-smtp .wp-mail-smtp-smart-routing-header__heading{display:flex;align-items:center}#wp-mail-smtp .wp-mail-smtp-smart-routing-header__heading>a{margin-left:15px;font-weight:600;font-size:14px;line-height:22px}#wp-mail-smtp .wp-mail-smtp-smart-routing-notice-top{max-width:1000px;margin:10px 0 0 !important;border:1px solid #bbb}#wp-mail-smtp .wp-mail-smtp-smart-routing-notice-top--no-connections~.wp-mail-smtp-setting-row,#wp-mail-smtp .wp-mail-smtp-smart-routing-notice-top--no-connections~.wp-mail-smtp-submit{opacity:.5;pointer-events:none}#wp-mail-smtp .wp-mail-smtp-smart-routing-toggle .wp-mail-smtp-toggle__label{font-weight:500;font-size:14px;text-transform:capitalize}#wp-mail-smtp .wp-mail-smtp-smart-routing-routes{max-width:1000px;margin-bottom:30px}#wp-mail-smtp .wp-mail-smtp-smart-routing-route{background:#fff;box-shadow:0px 2px 4px rgba(0,0,0,.07);border-radius:6px}#wp-mail-smtp .wp-mail-smtp-smart-routing-route+.wp-mail-smtp-smart-routing-route{margin-top:30px}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__header{display:flex;align-items:center;padding:20px;border-bottom:1px solid #f1f1f1}@media(max-width: 600px){#wp-mail-smtp .wp-mail-smtp-smart-routing-route__header{flex-direction:column;align-items:flex-start}}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__header>span{margin-right:10px;font-size:14px;line-height:17px;font-style:italic;color:#646970}@media(max-width: 600px){#wp-mail-smtp .wp-mail-smtp-smart-routing-route__header>span{margin-right:0;margin-top:10px}}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__header>select{margin-right:10px !important}@media(max-width: 600px){#wp-mail-smtp .wp-mail-smtp-smart-routing-route__header>select{margin-right:0;margin-top:10px !important}}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__connection{max-width:273px;width:100%}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__connection--invalid{border-color:#d63638 !important}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__actions{display:flex;align-items:center;margin-left:auto}@media(max-width: 600px){#wp-mail-smtp .wp-mail-smtp-smart-routing-route__actions{order:-1}}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__order{margin-right:23px;white-space:nowrap}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__order-btn{background-color:rgba(0,0,0,0);border:none;padding:0;vertical-align:middle;cursor:pointer}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__order-btn--down{margin-left:13px;transform:rotate(-180deg)}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__order-btn img{display:block}#wp-mail-smtp .wp-mail-smtp-smart-routing-route:first-child .wp-mail-smtp-smart-routing-route__order-btn--up,#wp-mail-smtp .wp-mail-smtp-smart-routing-route:last-child .wp-mail-smtp-smart-routing-route__order-btn--down{opacity:.25;cursor:not-allowed;pointer-events:none}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__delete{background-color:rgba(0,0,0,0);border:none;color:#999;padding:0;vertical-align:middle;cursor:pointer}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__delete:hover:not(:disabled){color:#d63638}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__delete:disabled{opacity:.25;cursor:not-allowed}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__main{padding:20px}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__notice{margin-top:0 !important;margin-bottom:20px !important;border:1px solid #bbb}#wp-mail-smtp .wp-mail-smtp-smart-routing-route__notice p{margin:6px 0 !important;font-size:13px !important}#wp-mail-smtp .wp-mail-smtp-smart-routing-route .wp-mail-smtp-btn-grey{font-weight:500}#wp-mail-smtp .wp-mail-smtp-smart-routing-routes-note{display:flex;align-items:center;margin-bottom:5px;font-size:14px;line-height:20px;color:#646970}#wp-mail-smtp .wp-mail-smtp-smart-routing-routes-note img{margin-right:10px}#wp-mail-smtp .wp-mail-smtp-smart-routing-routes-note p{margin:0 !important}#wp-mail-smtp .wp-mail-smtp-smart-routing-routes-note a{color:inherit}#wp-mail-smtp .wp-mail-smtp-smart-routing-routes-note a:hover,#wp-mail-smtp .wp-mail-smtp-smart-routing-routes-note a:active{color:#555} \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/affiliatewp.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/affiliatewp.png new file mode 100644 index 0000000000000000000000000000000000000000..cbd0d3ccd06de4fe0658c84f5c60637f067ac41c GIT binary patch literal 1300 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 lV_z26Zv3B2V;yFJ_0_F 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 Q_5c6?07*qoM6N<$f~(BS`~Uy| literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-activelayer.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-activelayer.svg new file mode 100644 index 00000000..5ce1ab72 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-activelayer.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-duplicator.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-duplicator.svg new file mode 100644 index 00000000..8ff0ef9d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-duplicator.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-full.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-full.svg new file mode 100644 index 00000000..6d5d611b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-none.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-none.svg new file mode 100644 index 00000000..43efd278 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-none.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-partial.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-partial.svg new file mode 100644 index 00000000..fde33ea3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-partial.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-universally.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-universally.svg new file mode 100644 index 00000000..3899b8ee --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-universally.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-wpconsent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-wpconsent.svg new file mode 100644 index 00000000..ca05f66e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/icon-wpconsent.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-aioseo.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-aioseo.png new file mode 100644 index 0000000000000000000000000000000000000000..7785f677b31bfea115aadc363b811b65b0cd6ed0 GIT binary patch literal 2595 zcmV+;3f%RHP)OO!-~d_R09xPxS>T9Nv-|)6019+cPE!CGKW2oa#NYq#8>O-n z000T9NklOr(k$(NlhKmRv`!v)~C#8yA%_?ogyc_Rf-K>uBqwZ{akEuFPTP%eI^w! z^50Bzuz{xEO!`BqjKQMM9=j+6M!Wt9<-*~rF9{XG;<+C}rSN#3_$5T(MG^!do)STb zpcD`x5Au!*sRPe^*&!ZRR6WqC)P)^Y3ob})7_F)rupl*JkSJ%w#4z(Z;B+qa;z~dw z!cyWy6o8bXQtHQ3<$zRtV8?5p@%_g4SxEhsiZ77kZCg@Mm}%3o5I771<1rdc8DelsF5<626l)h3Z{`VnGSXcTNFqmr7|t7 zQcV9}rtm8q_lM?;6^3M*&Ke4M1EeAnpBOrzur|(vdQtYZi54 zaDa!EhRHaosB1hS)g|aOOH*>2@cO{0^+2atI_(ozU5`v7^H2fGmF>2!>aTTa2rqo21w_$^zYXSyk;dIdXHd101`I5@g?ih2!Jv(L{U) zW!iI!w_~xv??31w{M=Mrx~)Tnefm=wzj|=v#*K&nl5ZD5vlF8&C8Ko>TvJZI6Pian zm@u@s1h;Lm!V`r-1nn|(nu8pw3W%9G2@XVv5Q|wsc*;FYEZ4Qz`8KewM66D{beuRW zh>TPP^c;k!z-1W9G9u=Hx%lz%Hb_mx7f=^=Ekd3?KOJE;~ zxgJ1=k#?JJBEFHJjqLF%;u{HCy3Fed6kS%9wzeLh9D0?d9aC1-4F3IX-)F1RD$>TS zI(~3)nwh7jZKG=&pW4KN8A?Ez*~)Pf**DU+dkH+WvuX}Sey~4!<=}_i5FYxUGQPiN zYJl6uP-OJ0Og`6ioteaZVDHd4zdlr65E)~yHn&v7BR37U9@M@E7=N%2DSJTMG}v;^ zM)QabGSLq6pqgFp7Y4?w=2vg-so!IE-z>ir_ezr&_W8*OB`#%}@;u%Gykx^4HMvDw1{0L^$BG&qNMop=K(KxA*QQv~m zIpQ!4q^d7MlPcw?ef>D>+9sb=s96uAW1bPE?DS8_=%_wM@jFKE7`R2#p6Yu32MTPs? z$at|%Mry&#sL8CcwfIwJeb-gLuff6;#Yh)!E2G22fzcs8A5E9^J_`$Xa8G)XT zFe6&pL;56(rjGix+!B=f&=nR;A&|%|t1O4VnNjCPau*qCd<>(Ho1GNWfJ%;Dgt{L( zC8j;4j`}8wHjzpfq~pg15;x(FLioEwHJS`R6$>TpnC~=gm@8;9rGl&2?-gP1C&D z8`qbNcH`LWHShfDKC0)|m55K0KfYMv<$ChNfgf|!S)$ymXfr=`l|*NDvev~f7i!Jq z=1DNJEDsLK$q6Mc0k;dM`rli5o?hjOHGtAxx}J_CC?LeX3Q)SsC$JCj6W0Db%8Yj# zrIi!)wYVgU>Ys~gj_)tJl+$@qy`RU6*q!XU6=u0iJo6MY+HtE^p5Ur!*oaqQbYAQ* zq^dbQGc%%_=oqY?R8uI93X z8PQF2hWYnuJ20}=yUKm~Gp5+-4 zT4;IcwXX1$@FFkDKF3-kvygm`{>!yi8U&0QE`e8rc5HxA#J?2ANdCRZlRB$(2EnM>0*E5}dl;Fw(5*`*jY#IC8#*Nf#9tY&v*PVIl!8y8$I%Tk^RCJMv z|L{IO#{G?LK=}G*!Ox1Y-S&*9;oo)^*o|-j+?ux&{>_5n-@X{`4V3}i;u*kArvcms z8_sY$3IA5%@NZrY{&wmBZ}3)~0B$)af=L#46oa{NM!t6oFkV$0q9Pz!bK>T1P6(ji zRuzx9@YH8K=7k5GqVwnnGtPKB$=k-W2?D-W?pW|8A|C$`9%xCBht6tK^{%Q1IuAnC z9t`DOrNW}b4>M1((l?2!74tz5A}A4rc+3^y5OSCIJ>>^+?rGTb&^LKY?F-vPRK2LY zVm7ZTp+tE`^jW0_+Er7)O3pWsC+MwGZ1C-xni`_*(#&UFO%>4=wwEtyK0NgM2XYEV z*Ot`TAbS5`vZY@6r-L!G^2x!Y*ZL>sJ literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-charitable.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-charitable.png new file mode 100644 index 0000000000000000000000000000000000000000..25f227f251f9dda3ba07fb42e893ec9536e5b35d GIT binary patch literal 2846 zcmV+(3*q#MP)H+tO%QA52tHU{Vl-=47Dg zn$)P)zD$j;wWYBF_{c^9WJYWbhOI{bYgOZ`U9CP58JRWEU>QMG#!|0V`8c?KS5uk- zWQ3(Ac7{@wuY3}GyH!^+oUqKAiq`m?&uWcvN0bc$u!5>f{ZA0%YTe<4F^C~I>#b_( z#A;n#Wmtm-vZ$0yjBgB8#-Ii()}>l``t`I9^f?t6b=crb(oa$#DSBcPtqdjk_E62N zK1wCKHTqHw7^7s!DrLdA-WZmu)Pm7CjBnJTt4amaY1POD>@dcMz&5oi8LPgA5|Ca< zy-i}U>R4orPl8bz-1}zNMdQO@2Hkg!->wVB$02v5w|H>~R=>!rtuV${z)mFojao6r zm%zSWuO5Md6gqaf*OEaOj4whSyp$9Dv1oi1jM9FgQ$7nCiKRd?-qd<#gNz3Kc5f|!`rK;&>L&M`H8*d zRYb_k-r{(%s#GLnO{33I_jK?*d$BjCX?S>Ea*>Q&mqJ2lcF)|eX5O6-E^CNig<@+H>c)dlf=^u{F!5*!RY-wzYi!SoI5`kI`L`; zG|VDi;PR`P<9eRf@9yb*VP)Emvov#PnZYV!FTCs99lBz<+gm93i|`ao^I$Gd7<-8= z?zvcFx?)+Y-5y|h;`JOsYZ|QIDOXh? zFIKo-rf5Vb14YX_V_dJMqrTfTNMR)pZF#>z(wat?@Dfs;kspF?NZM~A^TE`P0WF^c z^MLL_?4Sw_52BRD>=336Xe9_*(-@WuV|uOy3{N^-Jr`qV%vbrj1&N~|4{LwNKV3FR zhpTCp7EF1Pa01wUWMsSnPJ#qqrK)6CErA?r0FYwePt&^+)gd zJH4y5#ypq+g?IW5GI3f93)lScq-fOwrH*h!!~5A+GsBiDXw06@p#*COcFeT87S z!nKz9I>@GHc=TUPRJ8&2JOn!l1kC;BSw(x7sg`*_nM;%^?Fq?F0stAP;nB-P*Fy*2 zpO6UwIwHYN0tN3FUQ~>pK7qpGBZ3)$O%K6vg@o-u6;tbOclI91qk^j^XE0qzZ_q=F!FE}J8E1i*! zjqU~umxvY|uCM@xI%ZzcA@=;ZnM+pCBF<0e7;mWM)lk{xyF`{5k@F0iRtSbGoS_0> zD99;|5M3^NhqAmR5ceAs7-|8;EAsAXi5|?5w8TOMw@4MdczIlBIpNEN?~2S2(R`eP zhceV`l@%}RE;3rIAIOJ4W`W7=MJ5dj3vQCo5?aM$ z;O>w|DguUbprNpRD=qBrj-Au;W-FLbINWz(1hgg%xW&L#3$#4gUT}bUY@6@X z>hy{?WZ}$woL9D4PI$Ph=>3(b^ebFZ%)5VAqIpfIjCiCn;^S1-dBkRavo~+z;SvfH zUI*{Z=EG=!R)coj?y7q1txCZ%05Wq zHKs5k%Y1)2B3o^oxCSMxMTB#F_B}>>Qs{~x{dk{>7u50bXtn1}DrF|C6h_>%`PwXiQ=E%+gJJ&!mgY9GW9*42WIAvZ$2+r=l<7x%+g=9u==IO|)79u>Qm|YE?kea` z{+Vc^g~g&j4QnM!@44k230M`KhppBLPugr}L5i3c+bhDYQts(tPHM6S3Fg-NXa~Lr z;77|DqUE8%jB}67_sI{Jh0b=hI35?tdSeli0uxQUl8Z)%<~XGrhAz$44Sr38*!A?l z`yHaHGsZ80?KCPy;Gm4(Msvhh3cZrq%NlaFUZN8K{!StqFqN_@~ z8LTfUB3llrj+S;6y^;fgo)|M31O3Vb-_r&Hm6R+q88)DP+rH#~@fY6b3&4FRVpoi0lYshO!wPi!EUp}X{uHV_$)9CNZ-RQ!{=BuCD z|NsB$^!WDp`ej^+QdDe4Nn1HTOh`I)GdV;zJxYR(o1(C|xyQ_tps99%jkw&~`O4Ai zstlE7PGsJ~!*DxAQ7|Q|NsC0|NsC0|NsC0|NsC0|DON<|NQ^| z|NsB}bc4_Y000Q&NklIeaB5~s zpeb@CTHLAgN1eJ(c#=}m9h?f@ezv6t6bHtPL07?KwWELs?s=2W&0_@us%P2|j!fBY z5g4*tWq1EmlmHCc6Y4wQtO2^d1%2F7x#ovJS&J3!u|1J5W zY!RV%4FbQg#jzT)R|+x+RPFP2pswHI!N#a|qh8k(dwB)o6Mx}6(X8}akK5rb&Q^*L z`Se=X7hY@l4SkzXI=!8m{E)iOy(?{i;&5xGQIJPkBQK*}GV-=%9}b4i5pcBaTemPJ zbCB$zh~h2!a9Rq$N@!sYYUB7DnM3z|gzoqYXl1CSZnLhMcgT3m`~@9<4LfjDe=qtX z>OD?5-n*M4cZ)xanCEu0I1q-er2x&P}~jTj_sg`9nU z=RA2mni)W?+N68rre!$vIfoz&ha=Y6q1C4!;C|&E=zXMvEA@y+M9cxgk=E*tA@}#@ z*O4R3?V&l2YQz8#7(1u634-snE7O z=-P*ikIx2vqMPKp)04<10mPEGXq_}H6cT!Rc&yaA=C=IsiwDF(r>a{vjdn)i># zsgR^6r@#L9LJ85+96VH2K1Q$R?DOONX1Sv#5>UuiROaz{mgjjknWkvpZ(b+{KWYRN zVQVZaaXtf0K9%;jez~dx|IS1e|hkPZYfBR(UJE{Y{ zrfMh4HL)OdngLJLCO~4<{1aPF%SyVNnq;xo6l)&>sm>ur9TAu3oJqO0P;gn9-cEK? z&zHts6q^;Q6X^ou*78$pp&XIcauf6Q#pTu2Hn*kw?&{`Rqybk2F~}n+kOj|$*tZsn z#{1Omll$&673Yv7u;X4U_tFnm;@{o(%zAEU$c^f|DJoF=o z8|sbjN3nu$Rh=uQ^9qAZ@ELLRRza}&XL?XsFKWP_0hR~NXR`AXFK;Iovp)E zQOvwwkeS>(26%pXd3g=ATgQ$dBR8dX+fJ`rgXTeztR+fqa>p=xL7uf+$BwwlZ3d^; zcMdBrzFxCXRWVMeD8;o_Unw`nM#!CTjBVHC06#X4^g>~*GjJVefOhnA?iZ393VwN$ ziVS$=rjo}db_7#>mf!a9Vqjnv3VmWaBdFLE$o0kL<;8VTT!<+E)#UYIk_WJPdwaVj zv*|%q>>k6n0JpnEt1e#@HLk@N0D@Ok&+>MS1Uh4SY|8=p8*rM3FdA9ZRhyM=SG$iKKZ%FxLybX=YEDMI zR7@WN&}=BSB1m6=HDxgbQ>pHH&foG;)*`nVdXf+7mmQ30t4S_aIc$-Q zr5b;&^HXgpM(*vQ_xf___FK}~s+NEyKNixSnamSu7Sk@`>`}Q9d+24~l-O>f*GE~p zI-g|OWV#XvYpJY#{EGNXzOBLZ4%JsZ8?;^5?KQgptHivrRfl0HY6q0wskZffTX=(b zC44de@jRWFEJBBUEAm>~pvaViL`m!?_I+=i-HN~<9-AHefOf*|kw&dn(1ZJKP`01l z_xJbL*Jt1N`-3IhifxTgR#c4c>A}P}qB7lJagW+Zx7+W&7d4TSOm0oN(jxI4f%}wm zAh_}$*`B+NxWQ-NIfPFL7)lCN>~&+5a0-)*ghns)1QcW9-gi9EV88g%U7z38SKRBe zpvFg7htA7usI&^l6Aug>X(a+QrYL8v)UsKt~F(q1F7;9oV|{8V3_@;E!F= zwDdN!G({-6v1FO|KTKrX1|RNos?YM*HAPD2Gis+0BJm_iNy>D#kjDBLVk`>hSlptP zJ;WkzOH{AU-2RQwIW5iam4Qw~ZbjW4oW&L?whSGiPmM9PDFh?#cqTW^sj*jX)MeQi z)23>ax5ly?CuKeq9U-}#5Q$@wbJf>wGaWxzKrq7u{2b-60*r@3xr>}65{U#c8ek;Mj)$`QNHL)yIVZl+;ooXvZ%Qyv}UD#ImCv~0xA#nLBNC^TwStWv8c=e6H1$$T`QxT4JVa zw`E7Rt=Tzy9oyx(dQ3KC-j;~*8+iAC9R|w>qh?Mi0I;s>gI^(aH*t29?az?0-L+~Xu_6YRyhR;0 zm!9@GBf}*v?Qwd#b6ToPe4JHOq!EFzRZ#F^XsBp*uDarLL}%wG65N0LOfx<$2JX`r z=ReL)8#gwa`a0^Um`QFhkn!>H-IA1K*K|)xvahX6zVB$QnYu_M_JoCL6n(?LOveR9 z`{6dPk&r=b-?Bamk=K68c-x3tP?;qAr-Sbak+)j*?HlK@3Tr9p>S|RPEg;M;eUyNK;l0XIHA2oK3?IsWKOi~1>jn|wwj9R3&=*x&HwivNt~r5x7ObHFVg?v!gnc2 z{b8E~0$l&{1t~dtNt9`O?J8u$*DHLpKi71M&8_U_t9@RXKqr7GMQWCj%;dPTIW9y<#d=8x zo?UE)@vaH5p@{YK>eMRl9zaeow#*MH9N)y5APqWreyGO_$CgK_?Fs9wnav&tyjktW zEIY|bb^850-VXH=Dp(hk#1MYD`)qeR_|>}1`q9NKxDpypp3VPjtx{aTCE2jH4H(Os z*gc4T(q_o^^a^V}ZQG4|J!IX42urhLwirYhQA@xJrrfOHSeQE{gQRiMuXrq)XVMkv*@~7uQy- z@dom^U!sn65!5I4Ou|{YOmc<~<=Nr+*wq%5y!q9i_eG8oABFv8GkS$CW4h5&;~g6R zP8}I23O=gdgSh1#^)00QGAmW+P&JPK!5ZbMLdig8l#5+9?QS}6z8DeFqg18L=b(gf z2}dPUeuPOu20M6`eE9MMZ~E`I7EAZ!G+ zeU6sZO`jYmUa0O4^(C9^}@bWJ(c z`3^49E4ob1CuR$Y$vtSoX0Dx2?}8ReGR;km&H2xHUI=8^Ct|{*Cjj z341>N_SI~4+r1`5;*w#841*r;-J-0CKpf8cf_2xe;iqxY9#0lt@`}j5xv7AC?#G6} z8RO793KVU1Ex*9p1vQ@HLQRQ>|4X;|%Hn%tL zc;L317pp_EGZYqP9yD{>gm8RPK8(MJ;}pu+y_?}I7YRZ83Jj@|lfSbKKjJ#kYe+*E zz7;h(*^Guw(uNy+pH98@$a{{YBf0jG`_v7J{4Ub@vZQy;mqg|HQID^g5ki+Q_?|iU zcC4M5`hNO0n)()f`bOJUOd=>qpw=VfbCWq?`+%ky&Lg8N45gUU>y{#U=fZxA@_Q2&+DG4HCFPyg{~4$+3tt>7V8lxu)xk}sGlr^gpglNFygM0z7u-#rcSan%4M?o?HA zn#n&aWF4teZu}q>vxMSVpa6d$?(=%Szgsep=Ve@EoRncnlTaTGutMv>0|r)ji-Jf{ zD$Fg@F+omDieuflXc8Zg(A%iOMr$Lcyz9}SLf+chmcs>$wuKI)QUSz)(h&nFj1`7` z;XM%>uBa+*YR^LWHsOS})q{)!4~crtl*cIU|eXADf?mKitpVI>dHxuoo!GRWY3O zkNraYwdykam{C$ygPq@H=(L+WqHb)0AVT_4K){9VCdsW+QV zYUWBB56&$t`i-0;D|F^6^`{%)_}-to7j(lUsJk?)0+sh$-o*=(ltc!L{w{k(>K(e7TC^Be73H&P%Biozz((NH0xMZdl?Fv8Z!3E+P;kkSec%ciC z`s(dqLRs4MxVY}}M;@e_q+WV7#qUD7aLroAr6=QBI<&|-aEkM3k{elVw#iQGnN?tI#ABnL4wJBfUrv5{wo=Juj3jP zk72m`qN%hDPf>5UTTw#korkc2qk*%+x)F8{YWI zG|{5sS>WX}oCuVZemh0PooolX(&Aez+cUwM;`eXz8oxuGspvb`sF}Rpdp1d9#kxde zOj0A_0dg~?#IvS&Lu2uC9s_A@NxWfG3$-!g<6guFz3u~j> z$Q%V$1M_rfYN#WXb3H^?2x16|VH{$2)X$fs`JLOyQE8}kXkQ!vqzC!cz9|AIIxZJ| zP*nGR1j}c^GMQP-#AZ+ZktubF3@EFJ>mI|}1UppQd36_M#!-cJwN$K%`T$9?#iI(% zluLS3PqG?pZzkZ(@yvm4C>Y4&?5Vl{Q=;J_+W1KgYfL`JZARRD z5V({6oH&?>B#HeNEF*T^AZ(C<$L=F2cq+zd^EgIb7cEYLWrXk~*ht*RB}4O{{=4HF z)~I2?ClNjuyGK;i!&+D8K8Wr!gSq`%ucvaLGhsv>Gkm$Q5rhVJC<-`|SPngacK>~< zm@pKHtfpxtO+VCWmxF?37*iD&;quV?;>GQui6_1*5JMeZq^>J|xrVuG>5C%`xoC6F zu@;BdlI6Ny|NUZio#}7FW9TCC zKQ9_k%Sum;Z@-R>ExRreW+hJWI`S>j0F#c&J6ACID}U??4p4T&hp0IWX?)|yP=t#x%M$G|Ji zicY)f3eVz;oLRWJJ;6)juMNI};X`=hFMqpeJS=HRKM)bY)Fi0}?YR#(ELKe+S=ZZx zAiG9rHlfFEkvq-x5Pku`O5Fz0zfo!$!vCz9J(OY<@ItZh{Yy?=m3?|kc`b|Svr3Z+ z1L?hro{{Ho|K#O8S}m$#eOlYFABfSo ziZ2Pe=K_MFqe*@zSUqNk0K_|ZABQ&V5^j43Ko`#GAh}ak^@7T%UYx#;-WQYdsjS6K61#B=#UN^3LiZF3%xf1H zHCVYY+`Hmx-6_=Mc#wzNK4*0E9o;GYbP{NOebmlkCg?hF%V4x-W^7eGAkVQC+jC=f zLfYRAsY2fo-+R1h5N;FtAU-*NMkr~=Vy(9pRK9n_F8h}ox~ZKkt7F7h7v8A6X>%}( z%kD6GY0X>QD!ML@^pz*$G_IZA^ijxBSMD)nRi3@!!*kZp0ZQ&={&-)G4tJP0mO zgS^6Kik2nk0^iqZkJ?QYdTl)cC8@z>N=$+|~Q%a0gTTV&@#=KHsN7?zzWk)s&C>*Zd{R&U3nky~DsDW!_Xr!fj`) z)me#ki=(sm*#NkQ|GlJx6SWEh?YAWiUzL(*eg9T2Ps~URIc=o7c0Gaj@bRxlOd5Ap zm-ZLCQ@*H4#|E8Q3y3;GO7T4-z0+&G{*rXq0}+ouiR*E6)svBNV4!RE%zdV(aKFC1 zdz(gmvQ#J5iUPk&(_s0sLgufdQqHVYHwaSuM|kn;geaAGmKp~(UVG^CBxdNpW*pwV z#|*PCZ~}(UZc<=fr#zKNcf14+*OpDr<8o6-z%K&iwrj{e@cjA44_@7^7};Xc(*!V4 zIqY8>*jT;5AQ0KfT!}%*+ z`;#8_eJFm@NB)w4mVL9WPo<$iaceKkk_)i$$(Tr?(wLq)qyKg_4CW;!CMrFVb+jP^ zLwV#U1ZBwt_qmnAU810EU_iitsmlrGlW28>`B#vI*9YE~#5 zKXvAr`*&@#%5Rn!a0Bw2eOW_CIBzD!$CW9q(=%K6QH0ITc5467_+-Dl;?Q zgKTc*{$yl%Yu0-X%8rw4z!U~Ef3w}}+BWzq+BqF!6#O3Y4DM%vq#4MyT4 z&3~FpGcEgaWdBZ9!*fv>iGc`N{l)8et zQ{H3Y25m|<_HP3zaO4>8ZvtUj&%$Juzc*ofGa@Fkk)3Y5DgNlHEaE-oSK%8TicM7G zQc!;EE1YzkqRgT2jb}GS1X^^!Q!Y?7XGV$o2pp77>>~XZKaUx|**vq0nNu$#B!vZ! zjEsHrPG@i3ArPq6LJA&5K1M8oR1JER64X)-+=EJ7+5gUoMM}_Q*_Cf;XY@W~jCjAZ zVq5k$f*B!no8g3(pi+-~BUcv4-kUp_ZajBXI;;GUQI3=g!~|(#m>?GQn)F_4EnBmC@Z z`y621$q8oQaeOkZF%cQrtKC{_44%Q!)#D0VgUySJdFQ+CEPs5MzvSn8GZ}N*#bB_Q zuOq~(U%%3U@nX3*M(&ou0Lx@Zflq+_Xd|;_y_%KCAcy%CIo-+A>+^kO0(ANgQZ)=&55XVOYqe}#IctIsfG zGLOI-2Y&5DB{z>5NZmK{_#_N}panMfbf!vfUF8GDYl<|GC3yhoE0_Qbl;7vO0)A4l zpEY?VV7^UhK_?1OZCjfc-3YdRsjnp->BjfCEUo?!=-UF^9kp2`v3g3-bG`FtKJ$u&s= zN9$S;=4t?hrv6+S(V{SSZN(73(CfEgoL8`@kj&w;j+C?qg3SR&VX3-5C?xCP2x`S~ zP2f`n0xH_t1N9)-H&(a^{vA=sfDjq3#K;thjbN9j9p?w+QE&U`zL!GBAJW$N{OjSb z_4m@xYn4-a*zwdL-L0kBcBeUpmsu6}5RU3!hhPy!I$_nL+93!n=&rd)3dggHRlZ@T z+Yf6zVX^)r3W|Z9L9@ww!3#rjfx$7SO^?`gp?F%eto^o}YJv=pSvf%Y0-@Pa64VVc z+@S7(QV+fC0urCL@-3W4&i;T;WSV}K_pUO71vmfdkr(10L(8&zG+`c>ot57^g+0S?Jw3uu66xtro*#{|WXNe} z@jo3SOj}ZOybjdffa-iV)3S243%c?>s2HjahrakTvLl1xmd3ya*`cw~j6`Doe$ugF zrfi&+2o+iQTsP|;q3rw90nG_6=7LyjOKLOz-7HzwZ&&Ws74d*zQ83}VZ8I^umveo* zU}b1IHQ?oOYn?B1@KoQkIuYT5!Gg`s@-Kz6ET1tU*~+n?|{%+{*CzV0(LRH&BoIxE3@3X!2MemgXZ=?59f+=1k#vM+)z z9DNiZSAdu(uC7jg-VrD63dJ71bS8>4{?J9y(2{{+kAR@*kmjnoe~+X`61F3!vSU zO8g?{4GUGMK&C?be2!Bl;Wpsq7?!2Ra?e~$uz~JwN&WDXMPR@A?^+ryQ9+;hjeG)% zK3vS;w%)BU=#>7irNxFtF}-YygJRcNpB={eA5@Sb%pg1Ulj7ELT-w%6%JNDd4PH07 z1HgWgNos|_$O50{?2Bvi7gyEr*Q^7A4zdk=Y5*1$hRsIjWXeFXcjHINRQNz&))qnH z_~xkxeC|R`+$7l}E1d+mJf6xO*ggK#2yi@);p9+iG6S-?x+e*{V|K~8XELDjjF8Gb z`nh+~ZEUm?6ExyBwpT_vz&Ka}9}uF+Meb&kgTablTNb}B&mpCEU2S-*3!5L`Q9kSH zH@Foueoqf)!mz6T2@Z~ab<2_$@zZ9&u$N77pY8mVFaC2*8CXEnpL{$emYaWxxI9tr zxs-9}V*z;hh={w?BzG~=k-J{ml@j|n@Mdna)lmvaND9M{R;K=mEOW52d6&bTbK*$O z)%ZfGvt8gG&Hfa^`p)M!bA?t2Ci7gyXfi!erxlWYrsf~DFsoM1S}sfYuyyG6XZ7{l ztOgUs)ja}CBu5=yPNF|2?p*QeMteH`lNsZGbvK=CY8z6U>>S`jqLu`mtYV5Ws^F8w z26sZuZa{>&c=74Or_8cV^#Qz~F1wE;7RnIz^XpLsh5hXb!!`->e+W}ZyqyQP3HPYx zvCOas(Re~P#;&A_x`0k6$PfQ#Ru=MPF-qh76NUw4GU(40OYsglU#y+?GW>Ay~|k)bTof9o>% zC4V4;|Gp#Z-#Tx2U(A;V7j1pEiPu6xpuMVK%Z$a*NZpkBjRczh%yVbs6N+yDj*>Nb?8xWbgW5*4G;ng?7m}; z4+XvNKRh^h<^3BWx@o}5=rSlw5wo0g6o(CAc%OCEh^@#c)p*5?OFR3?@wa`mH+6+R zKlczsGU}nG>bRD&>rQLUc^Pt>F7Tn$=${rQ(}1BJ1 zr~XpJu4X=F0iU1R(o$dc>QsDhd1ThAIXkW?!$Kx9{w_gK zL2~|x<;uWUA@Fh9!xqhMAKKL+cH9f$zeQ5}ds6uu=3e`?w^?#?!dwR;CQSiy<S z%m(lLW23yFX_dDr;u)9+q-qtYRP0%9V%w+zGG{Xb-uGld;{4ZEE@3VAo8G7z`9>FH zTj?+n8*wVVMeXozNiYVa;VDcwVF&;!RoQ9`DH@I}9D6eDHtx{h-u`YoG0t*sxAC5W zVsYMQf@-`thlKuNGc!oNas23k(5_=~7#Ox!O&xexn2PoF5TAMecfnB{P-jHUomAB4 zYPX^!q}MmDSBy?CW{@)>(07&?>|<`iM*voq>SgF_Jw|X>lZj5G!5AB9p{gY}<)qxn z;?e*P^c}c_#meW|TgoCCP`#7GbFoh3tNB0c4Qa7YY=Q0#TF9@^=5lZDBc7UB5iC1A zlf3W0%46iCXe}Gh0384Mf!WUJIt=^n88b%-^3k~nl|4}c$kJDdhUvhAd!W>CcMo#( z6;x!)lvlW5ftQL+sZyS0D~g%p)c=Vx1@DA18wl>ig<)NB4ylHlMZH-Ov~K92i*?=j z^|sfYW?3*0|MxWDH(jG@4)fS?j8A<_9}ifiu3V>hUv06tQPL9V#FkqF#Vgm4>WW=& z%oq)5WaWR-(7F6?h2(*0lWW4MOd~JsjfRykT)?C+bc-f^emCGM28Nvk{{7Q@$I!G96{bYkI*ZR_T$+<%YpUBN-l~upQ#;`-CkJW zS#nKmDZJ2TV;TbByR`!=X7ydfD~$o*3de4)ZeP&Mjzb|uc3uAh^S>B!bUxADS<6Oh zw5+d3GJdV%S|@i<_IzfrN;cH*!Q*Q?Xsz2@f&n<*lWAu0bE_-wU-Fm7=$w{L#C;u- z{bTz5+F7%kaM5=QCV^K~F(H-lP=Mt&IVNnBcR>|ub5`tqf-r2Wr=9cD;iX(buyq(AM!T>ochwb;X3 z4%|l~nBWl4&-$wPao7IvTFd^MMq!G~eGd1&mqhWi&*AHS8X9!_e!S!LBhjcdw5I;- zR35YPwdq04;)zo^>u;(?^>*vhu{$SZ=NH_IDDD56b9!Ai1h2dEc>4aAS;o#O6g%^Y zE5dc}^D-Z#T^CZ_7$!3aJ^ivh;XS`7ca*y0OE%e>R_~ zP_s>cQk}LALa}~&&@m%BU(hB#XMq~WJZPGEx*ZQ~7AGyzj6JQ04*%yz2WNqYo{{Jb zb?`;B=#Zk6OI+QfZWfW|kexvc2G3GMH9`gvq0K)|p#) zMHp8li1A2Y^OMp>YFFLO4T>uKCP~g>;*n-H6)zW#NLIhbHiidR3W{&#J6v@uE8?}5 zcHO>z3H!@o-SgjvMT`-*MVr0AX_8=;5WgO9@1r1fc-6e(GK%Erq3N}Q5?PM#9P{rK z1(zygzxK@c@ZGxEOdB7dm1`<8rifxQl-YkrpjqbsS7bdM0n=R*%l-r1c<1o2IB#9i zY}CV)@+mc;CO%AGzA-LExZ1qT(rF>^-yZ!6H*D8jM%XF0^dCzR>H2h(oZTP|e`TEiVDFVaJ*MqG7Rr?c-SaK6*2dM+4bs&N|k z=Vr~@@;E|nQrnUBfKl)J^YFtl-xbM_8tIO6!H%LS=GU)tw&)8;ta25(13oIIRDY(m zNj0=doyPrAo05r?8QWjH{BY$~m5!5hzOKIu?Xh6=UhjuB6QsGVR^(Cri3h93Ra2~v z7?nUcjKqABujE>tBXUMm{&vie%ooooi8C;Q=p&6T*+}+(j|?pdGCAv3GUfAm+#b8D zUT8jVNU)PAb6*)B2(J5J|2ZOJ3snL8E-{gMpVnt7sa;G=xPR;FDzSZAhwTM>`3L3R z=VZqA@hTE7&M#XR%1Oz)`&*T}E-s|sgk&{Ohr|9ibOn&aaScPW{}15rPv)e1T94NL Pr_ff{S1Z43ANKzMEPFN# literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-pushengage.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-pushengage.png new file mode 100644 index 0000000000000000000000000000000000000000..15ce746e22abc0607985f0d29bc317c62e1f2cb0 GIT binary patch literal 1420 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 V9(TCp4r>4a002ovPDHLkV1o4zgBAb) literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-seedprod.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-seedprod.png new file mode 100644 index 0000000000000000000000000000000000000000..615191f99c5a088f6812eae3370d95aad2da00da GIT binary patch literal 7472 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-smash-balloon-youtube-feeds.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-smash-balloon-youtube-feeds.png new file mode 100644 index 0000000000000000000000000000000000000000..1b20fc628b056fce313c40167f302dde327d9b27 GIT binary patch literal 1371 zcmV-h1*H0kP)T(C(X`=yrSJRb28;R^B{B@HRirEcki=00jFt;AA7rS zoT3sWTOPIb-6mC&{`e)eMJ!u1!Fj?0a#51AG#w+0yE99qcxSn+ezsUF#t35{|0!X- zbLDf*;TXYRja43`2fL#9g+gbY>Noiz1jn-Wn&oGX`~BW3gD&Ht5FQ_%PYQX-9yxlk z|6)|!jdO$-G~mtkbF5EADgS9S;1BXh zhm@nH0H*RtiG1eFNiO$CQsmria4V0r$emLlSN|P}k#pwYn*u{DP`YM8vef|80y1G% zQUyS6AnzR`pn=|?eB#y-SXTmu)?jBBI6#Gov>m>N0p@w!6IOkx6q3;Ct zIY@3GnFR$GPzsE!ZKxlEa_(FN^n)ND*;T%4=*OUZYFq>r^vl4$2xtv<76E{s4CHlz zEquWc3Aiss^ahqjpx_EUHiA?^Uk38t*@x%{p}a1jp)Z5-UrQsrPG7C?hXxA-0^Cc$ z^ds<1gWG^w;1&h?8iZ?CqXY%|GAR88AVLC!)1chB1?UZ~3am9ai6=l{Al(}x3x-%= z3;h|$VJ88g6*z#0!6y8k%pzbJgyAZ<08`*$Fv>R}HiiBIPz#7W^j~RG1+AzB#2|12 zxq^1o0wN2NfWLsL1;jMywm4PbVSoaj1{MMT1dtB_z6$uwWT2omwLoWs@(%?}Eg1gdrrHvu$= zn*cJ1n*frnOHE{%P0DKK( zNP!G4=|2HnfR~ug0=d6Hy#Vb7J_2bcfocJI4}uIPFoUo3IRw*=cXs4FOad8P#ZcZa zxD*(?biKDEg9zXPw9zGj?bIK;wQjRpf7{6F9Fl-6b-#W`D{PR6`1G(;Bl7hFA@Rcd7O{l zz%KAz0|{3JEN4n-XnUq~pr9{<^3i#&n0^|R_x%chY4xO$H~<-z9hqfIL~kI6y$hH& z&ljw9Uj{pFd&Yzi`?ZL=7uVQ3F?lDdFz!BtVvmmL*eyKuyj&L z^%bp7X8^4<==Vz1KhLO>`pny|xEm+d@^%T%((_x%mPH}uLHQwNQB-dx8#3pM$=sl} zIKvqmBa_c>WxD`>SiPR@^5tyIUfUZ{+a$sUuUr$($l(0{_TYd90001Cko{BJ;{yf& d000005OcY21)4O*ec}KB002ovPDHLkV1nhMUi$z5 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-trustpulse.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-trustpulse.png new file mode 100644 index 0000000000000000000000000000000000000000..cb762d636516a1b595fb80fbe36fc39bb055363f GIT binary patch literal 1281 zcmV+c1^)VpP)tI7VT!O%H_iX-2Ta(O000D7 zNklPp`(@w6t6uT3b z4CSTQI;V0@uPw1Ef`~cFYd6^}k(h(I5PHodwwES`7Q^gfCxf?!VS9x;A|cfg)Rviu zGZ=)#ovPy7a5~V&eanpWRK!Pd)8I#M1VKd0*-^OUv-yLStZ$%BBqBX+zgPv`i+CUa z5$i>xoUl80A7t-lp{bH_=Nec<(LbSI+aHaRPCR^2J*?tMBf7RC{0getzaXL9V+Fb#HUsij>Bq#SPInpfjm*Q3Obl$7 z<-EMU4Q#)>M9WL96@8{hr6;xHL7eM8KM4a1x9spl>A{eHF8zF?G8Uexv56J^qcWlY zX<&uaRMH@44pglbufgj0nnZbDslQyWo?3DA9BU3_Y67nwTCvZ~59W>4Y0~5)E8vCr zSr_b4NN@^+oq-p^hkBIfanykvz}VY|)1{9|bnl_pxjr}O06Q$9`tZYX15)qfdp0L& zv4ft(dq3Sx-7?)7+VS`6S1*AO?yfA}TyXDQJ|aJsz`j*#^|In*90u65((O{ zjHpeEsYq3{8v8dQPw4%AU}DA>3N!c|XUfY8sz=BCjVN2`|LVu&sg~KM!sv^Qkdth{ z;Mtaw!6e-T75F=fkdNO+h7>7TL#rtjNhoEByzfNOI{LQBH%z4{6{SU@ z8DEM@C|N3*v6Hcmb!Oi8{``K=^ZVzX^W5h-=bm#u_nv$1x#zvMxvDDZD?t#Xy4lUe z2ZG=^7KRj%IdbGlpx+!x@!IOQagI`ybrh6z6qL0URCFo-H!7(wR8%vNC##SZsB`la zrTL0fnu_LPisHO^Mr+meO{u!enP zdferlcvITMif!k0Oqg4)OtBtF7MN~c8a%kr@r038plQIpbs?p8$7?sS+iqzute>-k ze8Oj&FH~X@a9T*eqhYRfzpea~F|gC;-uwmay3l!B;ctmmssbvY`CWo5)*(*YTWX+ve5Ht~i}G`6{sS`KRv1*En`FT?U5QE9>I_ zsnQTHy%2xfv3jSWNl2=S$u+Ketkc^sYdq>sUr=9aU+=5F3BC1%^Nu|nvwLyEYqEtW z&a7KM8Nhb7e*z3gXO2zAXS+{E$M<&>0^1^>@r<%NZh|FQo>32WgaYX;=yr zs|D=aq8{KDw+&R{KUWd4k4E(6H!I--W7np@$@cywu`faTm}fii{7v8aAQ*rjc;q0X z>hB@>PRkpzY2r`JtnJt+1yO;mAo4_;(h{>@;OM)2pz$db>Yn5Gyh#Td7Z_KM zVmd=$cUst0e1sgUFVvEPU1=j9aPTlqS9oy*-E--hdfrByO3N@+H!A^@{sE`7Wr@-^Tu8{S|VuPt);uMUHMbeL_}305u3-f6cC=N&+1LOmMk$D%#>t4dALpC zJvd4$TS*@g}ZjnBMIr`Jh7j zJ3)INC1sNL2 zFqjrNzYaW;L`-R4re|K@PUK0r#xxbNIR>0u)|j^vd9lY|u0JWr_@;F4fk2s_t~kKa zU6yj_NSW;f=|zBdh}G~#dz|B@OYK5S1i6+kV{Meb#&6(K^Oe?>Q~&N;94DdO5ME0C zPHRt{dN$re7Nzvc1@y1xZ#93%^yhA(#?8ssH^-lSt~$BwwN=4>7i2zvHaOjL_vU%iQLd9I1rm0Gf@BOOpb|7KSfxhbIL`B08ikhb*6rk6s?DhTAdN0?3`c1H=+>JFn5G;k}fC zJ8TWUoBe_FZv2&lSOcCsWp-U`rb!3qav^sgxyD-6Ig0Z$!DaY9bqBb3S@N!ext@hV4^saZuf%d$6ZdJ*;Onzja)1+ zr1)8^iE8wF53=lHr?UIhxjk+^;sDhlDBz-~F?7CTT5su@KHi zE8uZip5#s5MBXn!cHV|bIwaW@dcXAKN`>o2tDN-ug9J zZ)1P94a*2|9=iO+!$oV~xMC>JUN1_0+{{K;Ijc&43T_NiCy|dv^gUkxK09CX%s`Hs z_-y>vU9$%vbX(z{w=$`(?5`A^A*-umGLuGhamEhZ>|!Aib7WnKk7mXbI2_+aO`D}C zd80{`w8{I(xU8|A7bbA1|Gs9`W99C(LM)EIXpN%B4qHmNeUuMsP0vBGzk+iT!*ph4q52g3v z0kt@j0V*YJHkAk`brQ<>4qW>=0qr8Jrwz zGla-B{hXgvm9Wg~r@|E>BVQ(^VMoyr$x}IB9bU`+dRiDtpeaa-tWmMyvmqq$mDYEK z8Xx5Bo8@?g9j`e>Z=yt6AVbDT;UdI`aDbf&-(6ga71TnTG8v9-I6)w%eAn5<;ipFLg+T4>#0U`EVO&Nr&)b*9%9SAZeZqS9MxF3?`E<7J>SP^jx+Qb4@2@L` z4(<_A%tMs6!NN`a$I_}xGrqN-&sdROP{&qtk;gvSNCtR;K-KP>4f%E$@)HV`1xps+ zyiN#2JFZve2GVNGqh?hoyMNDHzbKqED|4OyNw#I0pGd*rh|IO9ynQ~w65d_JB7MZ< z4>Bohmu4YFOh?cL3l}nwDmC@>e0B`OoYerk;ZwRdCLv=uhJ7)ZZp%IFt5;KpT4UWo z$05`dB6BA_E1QXHzgQTofN1a3pcZ9ijF9@_Hn<4N0jUm(bS| zvEQiGlaxBN&RR}6F`@!8h)PGTnGGO$E%Hoj5DHs>Zk$*j7DI$c5^9y`+BLE40=4|# z+Hy}+mWa(Jv7jtCk>Yhg;)Dise74vEfvDx)d@=LAavWuCy+?yYQ%?Aa9*|>4FJweG zk87u?vD{=PuPVFbA;gI4>HAoO&OpUJz;qka;Tj3>d!Lyl<7YO_W}zHROM^18Hg> z<+irL8KfG9t5Y$0oXn*NH-|1^ev{ngnd0v!W@U`A!*A=8ooILFNzHrt+o{J?^LM>h z-Z?7so&R4SZzDM+0h)(NQn#aBTR6g53e!2agR5L+H#Yxo%c}wgW-T<#0kcNwRUJlY zrs*w_um1Ha-4H>DC4a^Ohr3vOh^Lw9#e4J!S%eI)V;b@-UBq;+P&~B&zxhKr$ zlH8fXFZN+wimdgE4BOpupCGG*(Av2e48wPjsZ~M}#jh4l0y#%}IF4+cg}_UYJyV%Y z-ngEf4t-&sJiJ+_YK_KCbQ7~3cAG=(rF_1ZWZE&gTUI~F2{PAjUY(!XfUG`QuFqYf zHu~y|TKXw^g!3mo?u4}&E*XyXqW9#if$+wezi7!WeOu|9>R9i`zt(uYeq@s-i!M8h z{A0*MbtdS$`17}Lq8=p7vzD8zfK<{SfdV_V>G;q2%vuil~P zj}2GSHssEc+&^_jP{+uFRODLOhxPj~TU&0fr`)(+oHE6HGM>seid*yKggJjgr;m3B zozE#w-L>GER4TMP0)K*MWz|MS&5_@@C$2ae2-o-V!17zfR-Ehf?}aHWk?6ogec(Q2 z_gCzDBa3OT#qBakBHl>Ll^!TJNyTsa82rgwheLyU3`VE;@L<5B z58n?H4`!0tyqoB4ve@|PY1ZAE9zj=SNDa6(ajTCJnl3i`mlYN*sf1_i0mngTEsvjL zqgLQ5%A7zAGlkyNL}f~m%r5;M>)O4$)iTdcw3qfE;CBn<|7EBtr2q7>rP%jT(1#w^ z%C9-H_zYrj;RI<>zfnrae}TwdAh-jL6Y&Ecy>CaQi*YCBH~`7WsLSZ=Bl8l4na+g( zWPyvOxJhJRbGVhlh7L!YF1-13&7*DgTh9*T0OkH^bEMO^yS9wYbSH22e?dwamyInx z!oC2u7%(YVk* zft0a-T_^+Xyqy+Z*~j<>B8ik-d&5kzT%e;HxyU4=PW}LXON9yGEXX#a1*4SVr+kZ9 z%wsD;?<^oof-)nlm-;6p*@og@cixYEDE|ol2hN)5!U!qvX*7JuWsiKrUik4?gF_s> z!Zt&7y*hfh^p$_Dq6@Y5$W2HdIWE;;$H*#vd_s3K&6er0{@W9@v^V(^jJ6-utSuZD z1ag{gPK}(0SL|K2eoKbt@v+EkWj!3s>^96Xvi$JwXTtFQqa7eGnY!5jdjuI{#=kYaWA`RgsNcaj!ociE9<~gKy_kaETioRV$ z_&lZfnrnBk_e|OpO-`bVk)8TQ56!kL+ay0g>b~Ul0n;{46YPW5IBOw(HQ1}ntK+TT zU>y%@R;Glb5Z9!xClpat0>zsvf-Hp|b_7fRJ)nhQKiCcFp~rk(%rV>kGVG^SKBTG| z*!X6nhy+);bHfm?igm_L30Fy3o4s@3e_zk0U{8`CXxp|W;Gq05B;ENj=bkcBdRnMh zfsr3>BalcQMa4~zyWMKVw)G+i{)ZBAS>`I&JoPfaSb(3B?t`W_K<z zyIfy^$b`Ewk9WtrF_HXE_o8ge2D#$SW&B~0r$EQI=knFpWP7VpY_;(?PoVs1hm$+A z?<13BKreZ-4vr`|`t`zs_ZAOzwF>>u{NTIG6_sBe`0w|K=fs(QW)%PPo8SXltsTsc zaQ%6L3a?Sd}^bd>>CdKS<$|j$gCCBOOddyiG+p!pKE*4?Oyr_m1+*%FZ zS}ZB$ib~Tq{GujK2B`EGDs;JrE;l~+fRbroKBP!r+&MCbxOshTnv(Qiee-;_*`E3kiEahCsmZjF zR;^`W*z(@q-+%ZbtW=ZtI^(12jm#hOpC`OZqWq-33~SdN_JbZ)x$0vk51j~C za?0Cd_N-UQs(CKv%w{++y)0^eC5Rxpyt46Ai;0Kv!I?@?A8D!vVUYhDU333R3eqp% zybLLiJYO^Nse|<3Im4J8^D8I?&Duqq_eEqjMe2#?TNCEo4{*}=3s~fZxu+a62f~b5_6sMabj+`!=IegUbAma$O6&^p0dCOyy zEqeUHXZ!ZAKzAK)_F_hHy^`hRBd)>&WZkD6DzGNL8ezy?Z=w>LE5bA#(pR#-8GWhN zpy6ak?0Xpvd2n%Xbq)4m5cybn9rjEX`P{ow1Ch zky?{fYeJlWP;YotP9^j&gJDLZY-8M)C1fRtBg*?klhoAXrj7Y)8>#d9Ya&lFRc z^65`_bQXb*)^1MhQEaiMvAc|WJi>=gIN-1a5U1|7$eM*p~q$aaQK z(x-QuN`Agb#rgB3t0Y}ASGX#^|Hj*(xJjMH!p}#_TTYp%Cuz*R zgwi$P;JFIooFoU+cKk6~CFU5*wmok^z?t@&NuQOO;zdty4T5d5=Q&DIri3Axm5_>r z7-A6VZ~Wd-{*#y^9^razg_Y&YU}a3&8q|lH5PsOPIdqQ^Tb4$>Tg)qQXPe;(8emdf{6(0}YFVo2_E`?jonzUXe91f$4~89j)+z@fAH`InrOPbZdw?rPEwpMs-Nh>d7WsT z8aExKe;`_Rr2nS0CZuWe{&3Vyb?j4@^u}qXVB^GsrX$2{R0IimRe*UNeA!d<$r=;b zRA1}&pa!Z1trc!L#+klN*nmp(7=bLG;x#i5Egt=Fzkh(e@jI*!jYM%~O{KrUX#HU! zAsfkoxdF)U`=qp-4{1uWx5558NWx!E8C9B_GtQVM+24@TYU*Xw~<)QNuMfTFyiNlLuIP^0q{9l#Z1%`93Xn~Ui#gY#q&c5sH>ly6)o#RA_ zTW4X*vJstDC|lBYP3n$s#z#T1jB*B~c3fE_H!^a9ZwH$}#$9!(P+6rzw=YB1(#P|M zmW}d)PVkQhbN%@Cqm08s&d96rg)Bx)85&Sno>Q8G113Q9M8HPhntyddcf$Y_jxRqp zP89X|itOa#tqNN~z%ppKYqOB8^ipl_gye+k%tTvP;@9`vz)r%OukOV|^yj(^%7&$e zSB_xljn8XD1rxMvz)ZRW!ajt27M4HB9hs`2nhf zVurlCVuY~)(e_iFFWx+9*eb0=Zvgg>nV=((mdsKBhPu4h7x}M~3lEwvK0ID9!05(# z=TycRUpfPgtPR_I2=DNt3I|$K6DX~HvXKTEWqeK3bOE-5X?);!(aV;NR#LUh(^hz@ zgy!}D^cjWHx5`DEYe0buCT1M(;G;otcp=TF4^8A})3eAE!Xkcq3bTLadO~Y8ZaPEp z89=$Q@)J<2`>~I8DbTAU!&cH(o-ttiNolmGY{;;z7z(_t?iQDqR5C3Rw-??->D!5x z6->EW#7dLjoWg9F%Vg2CsYkr;fVl#v8f;iXvgNCWnbw~CCJasw;mIVZM(!`5UrTDC zsM@~RO9S&kNKMihj`pPQ1eH7+nNlqINl6SUnl2-YCw4s-d<4v2vpL(i$!^M!D8{eu z?edb4@eL>fki)&G_6jZ$sJv|dtMlLYF3&m5=vgnMxdj78QM%Kme)qPR1Z3^>1=*%q zYVHFt`9eWpnwuF|R*~2upv)<;>Y=&q0txN4`zq|sT`O9qv4qyhL6qL|6t90#gbtoc zU%mF+aQr6j@uUJ7rNvV31JJl*%p6|n06oB8&r*#o^}Ya$OF60;s=W=8_|Ac}Tq2Fg zgNN`O*I@zwoljCUNM89?zY+L7^q;UTEB@G%L$3WO=8=^=C9vO6a5p34Q@9_Pb^x?H z8K}Ow!&OjR59IU2jEoC1n{x1IT|LN{rAD|MI35OC#6dYAms=CQ1o%$Al?bjtR|huF z6`x?*M9e73xlqx~7(;!DV^~d7{0w+G%RyWo-pT-}>Mu*)^JQ;Jv{Qf1N_jhoBa{-0 zxWls?Xw@N|TR?%oMfl&iAcUTgT?~GUJ>}@^FQ36S$W1tWZ=9zIm6YbnK)~Me2j(i3 z%=uY*n;#5&YNz8P#S<$VGJ3zsh0ltZX&p({?~6X+L2W?(`G5N)K|I<8Z%L55f>vgA zKf3&y*TJJh;5E=e{wAgnW^jIy4GK&u_iwyf4i?Imta}cm60`7*P&{a?3|}u=)GK0~ zEw4(%^P*BR3c&m;mc0WE)sUJQ5f5m3Sl8=1Ofy1f{yi{!z+N%{R=UMrD;iLZIM!bF VoO{}N?gtv$ym6b$GiTPB{{fOCvCjYi literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-wpcode.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/plugin-wpcode.png new file mode 100644 index 0000000000000000000000000000000000000000..58984ae9dc0bde0f3d73904029ac6948c37a6a23 GIT binary patch literal 17815 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 bocx(zo>&3v;DR+w00000NkvXXu0mjfM?2`6 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/sugar-calendar.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/sugar-calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0ae54174bfe1d1526c3b2e089c569a6135e29e GIT binary patch literal 1696 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�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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/about/wp-simple-pay.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/about/wp-simple-pay.png new file mode 100644 index 0000000000000000000000000000000000000000..b752526051a17cfa1663a988a7ba86d976eadca7 GIT binary patch literal 2851 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

jDeS{#b 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 Ug@1A!XsDm!2Nl^W8MDy;1E)1VeEN@7Aq8( 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 M_1lP7s_2OS1rp<{7ytkO literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/additional-connections/thumbnail-01.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/additional-connections/thumbnail-01.png new file mode 100644 index 0000000000000000000000000000000000000000..3520a64512f179c95ee14fa6dc9912dd30435da2 GIT binary patch literal 16947 zcmV)XK&`)tP)+kRI>FMg|=;`h4?(XjI;&(9Y?Csy*;N#=u=H}<+<>vnV{r2|u($do8fxw_rm-n6u}$jQmu+uX0Ou&S!8kB^YT!o$A4zMY+&`uh6v^7EpiqNSy# ziHV7WgM`e?&3=A+iIUIyyRQYiwm@X5{Jae5lMvM@PHN+hLf*^2Da>qjdJvz2=KfK83*W@bH?Q zp_G@L_}|XtfIa>4>-+2B`sLRwceJ9GpRfP`K?F%eK~#9!?A&W_+AtV~;U_k66o*4d z;>J0Xz(NP1bY(*uF#2Wt|9@!?8v}OIrimSWDFf!3w_k2KsHzH0(=<)fG)>bqP17_@ z(|$K*|FamhKZ10r%U=mcCIHx1ik^QvdQ39pvMFOv zZtu+3+~1(3Bh!)nESd66dy?IW`605`1>{}*M2tlo;^0b=^|R8jrD3=|qs;n72^k;7kI_n$rt})0)YUMbtee*zVz3~rQFUO5e zAj9#Rj)ky>J~@Wxn1_#f2XJZ zS62GxD_`CEv*Yyb@`@M-h(m^Gp8(1M$0S}4&TWPx{h3Thb`>I|cuz;BBm3Lv$h1%C z$aG|XA03%?bv`lvVNn;_?`LOp@Xs`q^uo>Ef$OluG;jBT8t07<%MPgEXRwm(S+(401eV6bNm! z%CwBp8k~;%^ z)}9?6_rPkmYv>BV4%^ih+HXWZRc8R)(5J#rhCGy1!uMHCX?E+1JnT|w3qdH%s^>*@ z$Ptv%ff*F0A9?^-F5He2O5{@)FvHxsslyv6qVSQs$ma*R^VGKk zmmwDtK;lCVlK_Zs(Y}uar5OPCv5UjL`moQjf%S@F(Xi#7HjjHiMYVvn1Kn*`P%qZI zE!!_yvqhu-tk17SFId$2AdWNNO@?Ww=mg!=N!)@4c`Yq#9+F}{PoglFk$X7k>Nvj% z%qo_ERdIjZ4NPGC5WVUIy~Ljj8>o3+i=0mY49D&aMb9jFK!I@};1u1WS5R>e)WV+l zPB2VdA-pM{5Fko38$pFAser17hmgn^(b!42+UJSfRr{=*Lj^|HYqnbN_uCZ%u-dLv zc&u49dtkkLWLP!LV&AA`?N_;|>x8we)P3M%&c-|?fy|i9lDP~kn=#>80gb|}=0QSe zd(d@LN-FOzhEmX&LtU%@E!@et@`Mjq@py7y*en20RaHnQNpf!mK>5T?X%>tNp71dM z;djQ6Gj$bqBoIJhvw{>ND%>2Hln_OdTkW@il~>W&0HP2u3kWH5 zOCnDl0D=9GVIEqA6CiW#0vJ|wnEJwImx=xa&KlGF4f4;CvoBcRuslHvZ{ug11yendj>B8asuSs1>pO@ zu!b$D7~eyNPy}2Kc5v1ii70-6N%3n~`o|bo zbNLZW)$n;=og=ejn&$ggX8WJhy1LF?rpC^WG|MBq_$yzFi%f8h0p&;{noGQCW z?czYwmHkJkg0?V)F?HD&fptE*tcWoB7G*g&SC%CxQ|~#8?eIWO-Y3j-X8)DbZL%%w zJg_gsMJ$wq(Wu`yzNOzEjRqwsIX~GhZk_r#`=6W&YuUs;21d+_zKD&%pl|%BzKV^J zFN9|6+vYoV&ULqG7!1S4jvd8u4qekMX}dOscCCtp#soh=;^*%Fmavn!J|yL&29n_i z0UMeYL7u(u_c zV{PXQV3TGp&t4O##;Qp@!q6{OWE}Hc;_M^|r{aJ5OVtUW?pZ(tZV7lLAY+{jI)*_| z#XlRZ0D){4(mwK>{}M8f05!~AT?QJ@+;sNZ&=m^;ttF*lKN;L-)Fuo={Sb#y*n=FlBvzhcumn&!?7W&Jx^{dpO zrY5zV;$81WJ#3ezvus2V?>ffN6sc)R5cc$B3|J$W@JrWA^UA-D63#SDh;}h8&u6IW z(JX3riZ(nYp5tHfyT|+XM#*5(v+Q9?#a)(yNxw|F&~+}dHMwb&GnZU>R`z+8kV~2o z;u>=zAZ1u!R#=d9lLX5MlY&XVY`4HQY~7@3^2&9XsyqX6S2N?vt}{nZPl{my>q)T$ zh9bk1VBOaiqCeVa&5gFcy7G+Tq?4vTzHFLqG8;We&@`94#aD_l2D2-M{efOx)jFaz zt3dl8%CBtA$VD^aT65IM`)i~Khj~di#H3*t!Bl|V_+ggvYh05=q0np$+Tz%!sobR@N6lJ0H3=bCh&OAOW*( zVnw84W-EbP0_PkEY933x0l?gNJw(%sL_J9+xelsly#cZ!lRY1nXOwA=s=E6xnS zBU~&%crHAULXc84&wWByhsWqtES@6zK%egyb4#-rv}G8$fGi8`L%f^6yIHsOcdPV^ySW|51bI$pmUk7DR03FIzK@VIoE*X25X6gNw z@(&137`-hBKC$e*SL{z>1vu}^4E2SXtIQFg9D{(3tJfPq}{r8!7phfAuyXPywYBm2bUjRGITxE^`S(Rpt;lvoD#YU+s z28QL`xz4j=wx7cjTt0yGn5`*T>;e6n15S8aMhA!%i+mrPkK28TS+p*SJv}}K<`+kM z(&Dzve@1^*GR8=(Hzl(b8=Amxev1e|uznBnHvtL6yEPDsw*ulrpJ0kb{L&evw9Ip= zAG*Xa9T`our680&^vJOA$t%c?|g zOefipv?G7GJbf)cZr7*HnynwS+wn4+UX1CQ>GMB~uVs5xU!BioMc0R1U?~T_4(7(HK)h9k zI_I*n#7Q;`+VL2kK@UfaF`$oQO;t`Z#*8o%^M4px2eWpTImw2ga|Fv+q>W=hjAK(d z8~b^fMKgOatJ-r}C)sc)$<#5wHja@9;|W(~_b`JsmN9Mox2At5+3it}D;ve4DA7j# z$ya5kGFc=~%&a9dmN?07j&MvK^V^Q$+Oes}m|2@2&nWT^)n4ObmY7StXtzn38B;C!Vz%hbcDeumcV@JyxvhU{m9v_x_-L;yR zD%x5rTfgk6KOz59f!T>N|D3WHzUTLqs(F#zxXoCP(#kVEPgz~935KW3|ACqsYy=w^ zYf%=RQMNrk=r=%iaS?LtB{}A9_bsh_Cj1{L`(HXSMw@$$`rzESKF+O|s?Rj30h=dZ zruW2rbGPnq43u43N5;riC$9{8mRA09E;Bi%;}WeQEW5O-cr1!Mn;3gU+15Pu*QA)^ z#6{o%i2YEDL}83)BpHPfWwPrPt!scv*r$3P^EOs7)==ghD4Q9|$ng!vNt)e6`yCK} z^{#j6+c155yTFqwc<~?)R~1C6R17VZ#*#l|W!k515q9HQ!Pxm+X8M?K(rmXQ0nJuU zI9lETxB5Suz)7)vzeToQIFs935-!)P<=UT&0Koyq1m}W%D{7N6sV&O$+|k2amOMwv zwU6nGT-0W+su6wJ_ZwZSQ-r;;g|UXRQy;UL%AGrRJqxI}PUlFM?x*V=upT4XC2y}W zxyFYubv|XQeels)hRGNx7|K#8Avom#oI)`u4IRtDrK0jdaG5hD0O${@d?xj{)NWOI zteDu^#fvM~8ph5k3!9)ca&HK6M}GW~toNVR_Ze`v#cHvd0*e_i$@JgO4Hrlq)Z{oC zv4^5yq5vR3m8`6ifr}iHm%J#1f|jC8BdAwb%26Evdb%|D0N3E~~Fi-A?}VKG^Z(s?>gvH#$&$L|0z#Y*ldR){k90nV6`mBlHP9_A(E zESEU|U%`3Ei;^jAaw&?+djL}( zlXn4T``Aw~cm0IS0Z^8g0Tm^qvXqpPvJg~iOWUj(mXwu32&!F-QYN&IX;b?|!eU|aFxypUmn|4q{>AHuSW zLKfJVSUw&6o67%YSTHZ9Q$W_w4gG&cSzY-jXLSsJpzH#WZQ21@5D`MQc&y3F!!Ja` zd4xaK?B*UXK|k3$+Z{E5C=9;=gn(d?Gw4&hA#5j#4BMCoy|F={IYoRu2`k}Sm z&a*$*&8}>+d2-InnQ_)M`?0LQlxyfzb_Z1W=sC8PQr2bQ;FdLg#|M*_g5>&VG;?=3 z5KxN00u}zRlYg&kn0h38C&|oyAaf5iTg@c#pOcJ{n61uK+4x~RIy*4e%0TZ6Z5r9E+_03kY0q1tYOqd~eBj)>YJcq)NVh`8_F<>5K zYzT~*x69Z7FD#rd)ax-}?Kdz;viNAe#G+)8$9%wJE9L=HHV-rQ6zMw4E)5f3o-p-D z_AXc|^xsGpP=IMkwW#;-s^%&aC7H1l0Z!z?InWEEO)aQy%iE3$G(?LX!sbO(`k2v1 z2rBF&RVPVujQNRbQTiC3o2y{HAd)Ph82g%=<9c4{+ox(|3&m2 zYb85uD_Mahe4g!1@ZLwM)ky*QJ$L zBqS9hS-*#3O++j7C5zQpA_BlDh2lMu_Oq?Uv^t5MlFWipeXeBYrIPtn zS^$__<+KJ+%K5Ei6Il+_u?J>b$-HA%$!KfheI<@hm+WHM?Gt9V>F590U-%GO$NG{* z)+iQbBgMp6QDH7=W%#wqWFPjj^l+fSNJN{-c2-7bC<=!{#*u%*QH zHifgK)R@?jNythR zj&~y2_|%Rt0xDciL}jTYD+G}oLoqp1g9w0~BuVx38_CQ_J*|oa!-}L39PWmweY(tR z^$YfRzGPj)?2lw&8L!|!89bF6$()ckBon~dE%=0oCI&2B_(0A_a1%Z>^HuDH++`H_ zi#WmsBblw$PXSS!2A4S*Fy-D04}k&CfeA~4%iJwpOw~1seUJNq7o@>uP6qfq!V~E? zO!u1o^+Ne?^Q(V2Mgd+QUBhg$f45Ms&-r}LyJdLI@cOtm%ywATjWIrN$AFgX&q`)n zHeMm7(>2Vu-D$FV`=aK~Pnfsa-?c2ggsFK<_W$^$lxfK{_oUm7(Umg0EoD0TYaWpO zZJo+A521VW(tQ4=zqq8Q7@9|vGNZq-%+RS!bN}q$O=NAoxTLu&y(LVmpUMoqdr|X< zF~+r&xmq&KUFl6^jjojGeyRDy-kB~pYeQl9t;8}2Xjz0oY--pCyN<`{WF~DU{?q>K z{ohU{94BtNw@Fu?@rPNCkN76fh+|pCAKR6EO4hDy<)gYX=V~JMfA;qoSN;aByRuK$ zu54xDgSs*;8NHfU<~WwMO1ARuV`c2qH@C*1->#PZ8Ay_*nSmb=L@*uvHqtHLV3|yw($r&(tSBkce3KM)!axcN&{;MDMcT>; zw3aPH9mC7ra-X07+-4yY2T{`mm}8cva;C^+ULT;CW$PlTnmleAhPaU#J!VQWbsj0X zXwkJ9AeyF@aMe;vrt@tP)rH(bv1w&RG037wu#qaz&PnrCOHvP+KD}+FPtQ|Htwpls zqzD2j1BfJ-83L6mf|bae*kQp_H~F%wK6_L8+pA-LS~50SMb4CJea?y;M;PTypf5V{ zSD7<jPiuFDW+I7PLV|q z$9Qt%Q3U%8z_#2}+~y7ANMscqiS5zuG&Z~nbkyM^r}e& z`oxhBtc%%nh^E9^0|MKOK`*wWUyqw%q;>7ed6A6DWK-0eDL(Y-Orpcyta~v` zNLrQ`Hc#Uw*-TX&yp;ayD8_q97Ea#i!~6=pK*XWo0Tk(+q-`vt64-Kca+d2B>#Sxu zx~Y-}yQp=r3b(s>k<1E8ERy+qip^+u?O;Gj)`9X!35qs@TF5$iFnNF^g(%@=^%O0V zWnIm1#x654vA6>=X-pJHEg8Zr*|VyJ-Mavcq3*M)0MbOI!Js8`C(xd?6(EN?2N_K( zsCm9fMji+(lH~dYfmBUw>J+NkuBg+A?FlhmZ``t$K6hnWvj6XG!rxW0tR#~OLrKPP zxRUHpaI;J^Em^?L(OM+4dC)mpqpr%HCEK1QYu-o(u+btz)01M!!J|3{q?W8HvMjTB zbe7DSlA%9KMny6WSlB#pJ|=OgB{L@=(vpQ^olUGNlw=HQk!D?1X=j$e?W<(Xteesb z^}cMg_9B@BY9;9+S+hu%z?#B7$+E4xw;aPW$AC52OXY zPx9q*Idg-xFOr3mAs!aVym~;9kV>b>6JXPuQ)HIX9tYBB$>N^GU%p62_MyAa*u>bB zI8$VBEm^qtm1IL_#*@29=Jqiu&yrDIIccp9sC`$GnR#wSxm{L9kr-Xg+T^A5U+*OK z=P`I!$`~-3Q5tAnh{d2onL&|eh$JA&KA}cqL!sCQ&DSfRGsLToEbnv8d?5lRz4;{tZ$MqOE@ zU}cx&6tMHYZQX4R+WOk`Ugi z>w6WSfab1>uqCmIku!vRY<)B9tF-<>>F=zPy%Y9t{IVvQIsL&-`npbkB_->kW6ypf zl>X%E+h%_Dh4h!t`NzBIPqsp-b@^<`{NxNwW0mZ$CoWw4VYY4YPyXTB*Lm14qyA^f z*3y6F9Y0&Td^_%PCn=&!|Mi}DEAPka2blh4sBG=ZR^D4O`!W~f^XKd3I#jl@@ZNJV zLihES1!Vo&(h5GH^w;k%!8yD^dW^Bec<3+MC695e|7aEZX3S$@u7&{KcN&6!OL#Ra z1Z`4^Xe1C>1Vn6zC_TD^%HwP^@7x|qyQ6)y&zS_<2G$QGyMNjN)^7JTzTWM2_xHP< zwMh2O*TC+(TXpE^-qwfw-P6-$R@oqvWhRCVAQkulZCZ2kGrCteQe2bgNsU(w5zJVy z3gj1KQmu$wwn)6K058Q)GrXv?@q*!|yMcl>sAF76i=;FG1V5dC&pgIY(?FPbFRC0x zNvkA~FOog*bPSO4wAYpeT~%&LS+v|v`do6O87m6R1bdt%qrLD|6)98w}blb5aBZ+#I8cnH|*OKi{x2GL_y!+y0?Y_C& z-JZUztX-FY+E8x;k#gJ7D?Tu_X16TvZ zjBzel(=;ej9@BzXP@!Ja#|0g@G=@SZ&}flJ)3iAko|J)G1v6{p2B@bjFXTXrCUr+i zaop#jl#yF&$Xa~G)gS>4KNi33uBYAA}K zu-djsd1OeL*<4mzq$a-YD7?UGc!jp$Y&p%0w|2;Rl?;!i_b1`z34mIZFB*@$>{0n>!*{dy}mx}c8|dA7x?=0wU%sm zvhGi}x9SN`clY0z#@*d+_l@-|*_fFz<9qWSbZz8pd)zbU90dxvoqXa3u1GN{e~iDO{ZqzRx7fwPay| zlBh)RxyMxQG-x)Yi4vq4Fh@lp>Lebro6!~tU~|;jO&RhqKLTkLaF>TDof1Xtx2Eft zv;>im0O3$1TS&9B<7A$1I#Pzo(1bJtM-p?>E?tRS;7?-lph$y}TuHXS0qf%j&X+EV zqrpIg4ae(-BAg&K?TqruvjjT3l5gf0ZO+^l9*et7s)!HR5?|mKTBHb38lD_>`S&x z;PTFurErlHTTT%h&ytnUY-Q;v$=Vs)vt+}wWK9JpqJVJ#o5IOOD3oOCFgX|#Lp0Db z&`Od6Q3vOin zoRX373f==bOGccRp6qd_YO(v1O;2wn+dZDXe!9I^TVH%}f3nEk7a@DPd;yb{F&`#h zX8DkcA{g>vy4lZ30)S^hX{J?`Z!VHC85Oo1@<4EtQ@Je$Eg2FA$g3eR&XTE@lv=VZ zn^VB*n3gPVg2~Kl3UK6E^(>iJx@leIfLFwv@E~VeGM@)!2A~{{qiQ*5$yqXtOiQNY z+;a(eEm@spy_W1a835`cZnseraC(spJV-^AW(7Kt#45UdAIXBRd~086$)=Z@Oilvx z*o->+Fcw{8HzhBfvt(>)Z2)9SKu13tn{!W=Ou<<)x0hWCz~~xYV3?XQ3P+obva7M3 zq)M8Ok=pDl%#-A2$vWPZ=5}g$M-M6{V!eGFJ)OKnSIQ}aBJ6lG4G^Q8wz!fEX)k-% zmSrhzcV8G2VZ9t@XvwCVCe@PZPVDLabf>PEr^g@nWc2g}o^I*wNy(-xB(7SyyW9=? z#y5g2CPsFkjMx!^=x|fU1J!x6*#q4UlrS(O293e85Ka~YM>hshE3StaF#xvR2DX5( z$i^N-Oql7??N+A|VnZNs2-Gp1H-G96EttDg*g;=y2n#mv+zDkmfOv)Dd` zXTdVz+m5;HAi~qK2?H8an1Icg1wsH7ZMx8S&axFU;w^bxgBYM51raWV!eRP36eg&z z(}VzgVdzkzHhta3#_^V1uIpPsxY4;hVZ!zR-_glUbZm1@2q!+6cuwWn%Q;gx1}GB* zuo$xe)1_*sAzZ+8bjt<-aKF>s@1Az|dZAD&`k#8}mFLG2uv>U~+C|RgCrV!C`{2UA z`iC<}%dqkTc!)pE7r%$QXTG=;Wwxr9Lle8`97&L?LKW0h>>U7_?cscbFER$lp_qHJ?1%GO(Zt^glXlwC&3K3j{jm3M~H z=b~(5KaZ5HlC9vQiZbsq2YapjR}>#QQuggS2Ycl`xt<;?KEJ+))H)oy^1hOhbvSnA zBcb$iD{O16{8v7P%k2tYD*sQ{%6|p;kfMw+_Q@G*?a5X?Ml$zh%pdER|H=nQMt(Tv zZy4(>7ylr(wenLq#L$KRM&R=HaxKdKFl6PYFqC4Ub}T0A+slnVP&*+=xra|5`GY?4!7mWbC|@L^YR+Be$5md{%puDNfu@GF6k^E!ucQ z@TX8Unc`0|4tNlV%K) zTAmkTifd$scZhNs+xQ)rjmd`LZ{rB<@#*2=>5&N&M^YD{+`(_6B>Qh-uVk^?8`rf} zC4px=W4K;9wqL~CjscV&3+Pp``SVxX7*=hb?||YjGV>lEj!9gP$r4AZ-U02{&;RuK zbh!{SO~*0d_d3QMhv;^~T<=6zjo5W}l}E{vMe8CI(@60nRK1H&8CRS28iZrj=7 z*jyHaOTc{P&-~3KSp|GEyOxY>Wu4`R=5m{%S*!=i;(bzVyuTw^ls=t)S)uc|v8ZM!1k%!Wn?^{okg^RZ24-#F&MD<> z%Z+7aA?9`&Ab;v$(kU+L4JIAGGt4GLh9!Ne;jF^_*t{1W4}{Q3^X-@^>~Qg`NJ`Rfw; z51r}z^9I-|pX|o)5dRXoo6MF3{~ni`Ol8I@PTWYcOOC&iOmS<;epHevZX?;%x=WRX zp}4hVmdf#04BTLJd9@dYYAzL@%jNXm@mVqz!ccrRC&{k&CsWO(;>f3OQ}E_U5~4O- zxhL2x2to({7MERp%=4bHYLE3h!%DfQkJh55_;o4g#uz}bBe>!uV7#U4Rgts} z1;|y2VfI1vGRFbyNAUzpHC9Z-#+}vPm;-J(sA(3rqZd{ zY$O>Zi+ffUSZ-LctyYZ=fNl$Nl$M+tsa;#rizDnzT z48Y#cFz)va*HJYr-i^9N(k(+EPFYBVaG4N6DD)6!emOaJ0tWS9={zNdwNKsb8`t~+vp{z zJ0UuP2@5u5&uE#^L6W@JXjKAWn-Fe_L9FTCpSZQ&iGyUjY$!QNR%}DQVoqP|o%F?> z%suxYnYUX1Lb6i?<9r6WlI-5lxcTt#{QU4>2lOP_j>r4VdlC6cL>pgh=D}{fZH#y? znnQEx=c^O|={aVrJZQJ1=mWoXUL}h+akUyG^M$DMUC^u;^;bT1dOz5vN6EqsdoN6O ziD0N$?A^gQ8}`C@Tyq_-1VvEa%$F^=!K!R}a#<^n(b-7b-9IjxjCE#2>^>a3=BaN0@ONSFqH0 zKY}qjNoFa@6rT+>)!u|e!^eY5C7I%rx#VCxoClZcj5x(@B)jzc zRhLZhxm=bik#mB-YAqF?%Vp;BSxqV_F5ITy|D^i;cZ|6V;y9gB{2N^Q{a?JvSX9UM z-W7U~*wmQv#K1q#CEia?k`Xnyygv-?JwHA@JwDe7PDJ7QH-)P`9EfIf3%`pS;+G^k z?0T-hT|NG+;{ErF8x9AO3_tGEGiNxGc3C3{J|<5AP_fI}O2cnbxR@vS1+FxgJ6|sB zQ$=RI>5?hQ?hnez@#h^Zi;~wnI~OElU}Wxapcki`<)fFv2t%E>Swc0Y5<|Kt%{WNp#a&&w{CKpF*)1BqK_) z`{HUJe+V3regq2E#Z`{kPPD=*W!$mJ-j936y4>$m0D%NR9Mbqj$Gk(Co%DIOB~ceQ zf~MPen=ZHhn&&jD;+^Ykr06FAj9mo2a7|FM$o)nWT`srGezVsKQT_gV;!3jTlVp0f z>xG%+k=u=SYm8=fYe3|a&Vror3}{mTvA}^@Ls1l7XQFkJ2BfpgII4TC_h5%8L-0K6 zG|U=(qod%!;Ul1YvG?_)iK4|k_I0P9j8vEGexdxwvt)L9@(zZ#tD8kxmQl|Dq&|@N zlLg=>IR~5TNNDA^Qmu^fsI8S7B|CmC^fSgT~rQi2oq zJhA}dF%xjSFTXH?ihCj)Nt|KLnBb zFDEX~71=z}Nl8dW!#Ke-7S?PY&0K~6oO1)PeTuougdn3p%#bFrZlm-@3>-ILQTr#&ZM{1Kv90;S(sn4V~3xU7|U`nkHYGFx3j>0qz z2WfpWkCGaa<(8O;TKAz);O(5!<0XJG=hiZxJC=|bFEgvocj39}4P(*HK`;Kt(Y4Kd?MJ>4F>%ctM-F)P0 zIrCv3v^wPEvci8c51B|$2iHHs84NK!NG(o+@ z1L!h|0DU&o0PpY;up^A-uxh7WCN{_%Mx8K>T5ouFAWB_26c6uc&5{V#JUW!v$(#by z#5Hx%)J9Fs^-u_xB@@FSn&;ZjDoA$!j9Ul5$SP!71_)$Q%^>`wsZpg|np`H$5`Y`i zqM@^RvgM*N+|2azpq&oy;>rS$RH+P183(ORx;R+a(eSyJEUVP#2#2c}`}y4SQDkYe zMw_SY+9LWav(GCGobySlO0xUIlN1AH6`}UWtq#{`jfF6y+qp&AV(keIAS#$IEjbPZ zw06YP(5Hy(fP*w6Kp!sJB*}q1@k&5?Ndem0GWFb+Gz{7r`5FoLDvCsC3qs==(R8;m z&q1&wK%LbYFe@#%(NO&MFL8je~ri2q5KdM$K$ZG>5fMSKWR!Z+Wk_?cs4tn>j4;n>tED-$1GGBo9Dv>GMrv~b){Ol* zSY=fVu1}f>6XvwGnXpl7nH39ts>?|bP`$Bx5v|GX${@ZhQ}`~8O8rf=Uv>NMo3ZCmSO%a0bfisrnySbYpI?#Lk0;42#%y~$9yx~!P4TCI;x{WY{fnzW ztU7&Aam$Jfy6W~Teq!hBa@>YtAZ%J@+^i@GdhDBhl*OVz?|-6Vt>0wxW*P{p`EFog zKn{npE-7S@sk?TKv>#kq$o3<@z?E2_U&xkP$gs8q)5VTVvc@1Xpgda6b+kkf3oa^T z8zl@jNsqX-VLu!hNa((?!ZCVEtkQlPhsb~erWCRrg$y+GUH{T@SelC?Lm>kf zx&CkzGBit|-Ynz#n8Tu5o&l%!%Jn3$=zG_XW_gwyE_(iq$U=4f0@ts=j#5@IKboZ}t{QY6mNd3V+$m$=`qiLshR-o_dcp2ancxmXhQCMDMCM1g zWoCUcWS&dbk1IYZB7-d}hRki0xnr_a7uncpSv6#VGJ7&)Sd8n5Y;f~m4Vj@#IMNFg zGA%YD8r%F=jm?zt+)b7!WanW|G_?6QWu3&HGIOUf6fy4On^|UGzuqN7 zhV()eanagt#Ps_p=lUIxJ!vdBBxF}0S!Hdy`uwHF*Wc-FsTy0JG{#t2YQvYR>~o#o zlouM?q+Pg1dZ7symBsA>m3`f(_vDqvQh0%cOdn>JWd5CRD*N9>mKK%|8uJX(|Brn$ z+EEn-!mwJ9A%Qi%0Y1gt&P=mOTX_HHInwQEtINgNlZBw^ccalOOF-dVD+yLfV}t4kqUiBmOe7RrB}K6HFv$@2YJTJ#sk z)*wliEUPlh-c_>bSep*EO*!UEf0680xpxpBW#6-m-cz#ZShF}QOX**M_Ac48WdU0D z1{oefdL7ABbnI|ka;z*b)}U>(EW18hCh#3}W|Z)p2tXJZUq&(&?}^`X$NH{ZSgy&r zO<9(%F#{8dXiXM~EgA39L@>XAhAg`gX)ufkghBu)NbKL*sbqJ>f@7Bj$4cp6i*s(- z^~_m&$L1fL&;+0uW!f5Wsb4p*y<=1 zqAX-tA1pf@BO?)jB|!`RQ5XP)G-yI#^2J{Rv1QgHT9O?W|5V4aug|UTi(^}$qGeB6 zmJbz%e?dEACcVcMhKhB}Z}|hC#j%Z0-m+%FGGC8>wEeftc8_~9rJ7L34%soC!|I|q zwm#}1%fc^z(kde`>6g8&q=I9QqjIRO#j(vuSr)^r55?@7pA>vi$wK3Q8kLu>bp8!V zS$0la*)G=qgxD99Or=qIJLZ+Y%*t(1hupHDSt@1`$1f+DN(wmnI* zVA&~VnYZi@duOxbCJaU4Gd2c+VFs{_jW3`H&d>@rQ8!h)>Za!X->5;&w4^_aR#lsk z`bjJzA>zQNYy03pdcQAbZ-0Rg5&jwc$7Sq3slIfT{~i0waB!aN0<*nk#lziK?hh~F zm1#T0;(Zx=6kBq!pH}{N?C-IUWp}GGE;r0^zrRrY<`*k$KjkuydAQqgsGf)X{~h}` z*tv|`_7a$}Ysa)-K;7uk0RLrhnHIhq{tM0j974{o##30@s+m0 z_Y5_zNt49lnHqVEteEDK1Gxg?31^~tI~KQt;M?btoxjSa{oDeG6I`PhUNW7U%{1|% zNMk7(%cKykJX@AES*9|6b-50=`@ZC#^_2HpI3`N2F5u9sxghCIITEX9!CP;W?_`4 z;8hGDU5-B_mrnlb zR!B{(Z~${yu}q-CgVMPMv`Rfl9`)L8J3G5LbTqyJPP3PKJuG_tEE!*jQX=NfXUU$( z3!tx0=Qbs!eqsdAP39`RWIE0aHS@Q-8vm)Q+dmk{wj+S1UgG5Zs&ae4i11iRi2tR-q?j)zIc*C=t_F>(hO}FtNWYPY_Axu zo7wuYHDn@@6WJ-yz)ix47U_*nh=>7;%idssrU5{0Q;*7fSvEL%GdJ_){Fp;lE@a-! zf)7eHJpd}9Jh8^>9!Pr_a6>j3FeGIIG(PIl$CgY2fFe)|jg6+1<^kaW9{yk$8N>RM z+V=EwGQg*Ej#@$V*vNdUX-LUX++*OjGB0~f_8I;qmyIu3#{OQWtPXeIXm%T(eWTc( zhkOez#B<0$*-LWqM_S#6n|;pTO>?nqzxZdj%1lO&H>Vl@rlZ{R$S&E~b2!G>A9M^X z#(zGwbB4JI!!Q)?=Ef4DPLvyv!|eW7+Em>5Va6X+XpsC)5XqlDyH4BZYGxPaD6`FG zoE>JwM`E@uk!{?!WU5sbWGptYqolV?l$layHvpD09d-qXH~ZhkA=U&nc3twvUwcLg z(X>*g)w`3Kw6EJ-8Xv$JUhB_P+?iUcBGM64jRj-Fgw$pxGl$t?)_|`0&bFxs#6F2B zsrG?CG&W{*FjG%J&+M2bZ;t#voe?>SJwR+@EHpM~u#k$GeE^`CWzX3eTm0vf zN(@rwb4M)w5f-nG4I5I-xMK!A^9tHx1%{o*E0y8T)NG>y76ZhTG3%5;*b10{|d{0RW^hG~mC;tRR!>zwSXx zO;6$S^6KT~<>uz*{{H^-^vA`;&%?u`-@kuPPtWZ8@!s9toi)cyPEI~OJ)NAKtgmnE z?d@%E@2sq>{QP-&c7|wfZXO;Uo}ZszT3V{9sfEMG#>dD1{CPY$IBaWcD<~+as;YuQ zp$!cUpFe-@>FF657$`0-?(FRBnSs+YGWYiOK_HOa+}y;(L=j!9$mnQ58N0W)cW`iU z_@^%gJ*x-b&xM493=9k`?HyP5Pcw3}-AcwA$F_Ut_vPgkto$OP8|LapJAKlN#@BXB z+hD{s;%h&)vudi1G6&`=qVFr51w-50O(?!^Oz)3(X?=5@i_@L|Ru(>;^+g9`9{^xm zQI?m{^Iu17n7gm@5{B;ZTFw`TY)so7z*sj`XBNY=ruGc)jgxQZq$c%O> zXO2t>r?DSYoNjS+A(2Spd)Zt<(+PmGXmruI|E)O;7Af3#2b9%t94B$AMy6va%kj}k zz`^wbiE3sjjH%Ne$}gS?80*)<79-L#zh`%=Xz5>@#2A|SN%;_xm?`voo;boP{iqz_ z8~P9V!UB3r+O4ewe+VvHTQFH9FJjT)Y|6#WaEX|50NsSE zM-Hkq^P@J#6}8qlh(vnIhyZ#JemIZ@U$l!YGxjGn&a_M7Be3jri9A3}^7sXPeaJaE zfB1GzYMsa3dwEo6y$im}Y}g8EiUGE%DIVht>zHkL@L}RW&;uxLtN4wp3tz~Md1!zl zug$+zojtpCR4u?>2k9PjOmIMCh9=Fy8rs4e=hunqiZ1bxYNya6rO7Dbq`QryaIS@` zDi#I`ZUs$3p5^q1&*fzWw0}*D^VuE}D`e@#8|)S8EC=-^`83 zmj^-OBUTXFXmjKdlA@qf6a7z|&_tKx$4vURh^H6M>5jXGhK$3%f2kQ3q_PSc_3y2} zb+c_#AOzC@47RK^VqI{vde-DSQs_+N0o)f6%*_yml=I*X&Oi%)dRZi7$RC4$v#_wp zQx5s*b=VX54GavY7~uwkJIb9fie16D(Xi?c*4>K8`7r}9xd&QXZg92g)C}grV z9RIC5F?}kV~z} ze){mlZ_uiYU%(?=dO&(eW9;3c@|nbJY+t@cBrzgAj2QZ^RggwVxoqDNxwk}A8gAL% zUoZ~l*hajuDaPq`O`juI`a52nfFM;Oxnwq-l~w$!LE^1q=yR>|RRzVuXRj|XQfW9a zFgS3I+1MrUTiNKsY*F;5iw}PUDVy(d^#b29VH`6qp9RJ^G2QDo8u*>PwdjtBU1+8Z zA9e5#4GakIUlYG;hJ2i^SQ92LTXSLGuVY#fmusTbpOfaJ^l+8X(i;V=cyZ03dR;hA zPlzF$LkySBYBDmrx(3Ba?Q3a}tKMk7$)8#nEW4Uj5%d1OIqf6o7}ZMGSqgOAO$!dH z+R#4iI=`U3R4=brH18#v-kB2`U5JRx;cL6_xEq%1--oLOkxJT zVK|F1=41fIyU>UO=@(v`D+;1@0TgM_$ZWu)C~vGL`2Rhq_IL^R#kkR2_4GVhinLou zOi#bjw!;Rsq9Ar4(AG)~XxX+Qq1i-~hGT;3F=5G05DXB@q4{`kWn!UtNFa9wCB3C50jzsFYog+UvWE-Ja3^bC%8d zT4_Bm>9=H4#!UoebE3B_GVW=3!RRV)A?{AJa;soshYGMA7XAg{;* zI>sX_gnv{5f1tOe{@=m7LAXwJ(zTF8U5P; zKVV!bPBR0OGqjkZXXbKEksy3?B<0(;bM^N9Z?&)dKU1f1Ja~yCheYb&k*I|0p)(7# zFwgBek^s=n^%K*pQw{fW_TRNHVs$9r=KX&ZKr&wJx5@IsFbK)&IdyBdrZcw7PsQ*rL!*{%D7HL*yH(m*#U*2AmP7#3x z3}-FgI!|eqE&HUSjjqF_f?kiV^ay~nu&3uy)@fY%;|DXJZFJN-T(M&X*XEm~CFW$j6G5sxKT9x|Qgz^Sh3O9Vjm41Hxdl`)(ZJL#!sZo#^iZ^p#jgRSA%zPc0>qhri{iJEL_x|+d>CNmL zhAwtaIfV#0Or>2wNoE4+p{P6UjL@;o+A!-<))&@`RAD2kJ zj=r)0zc;=Xv6gUYxTAZdBs(SydbEGo^Y9;%%_W;51buQnJPT-Ex>njevcdCNu zd_DyAL)_d7$5lUq6U+4Dzy6pTXWBh_gd1&JVk7K^>^7PgxIlj+XARC0mcg;z3OVPD z*F?(9#L2lOeN_!Lp3kT}nGZITvIQGFE?IZ$Aoa@A8tBorgfBvgf z!SS4hj(ZD&Q+ZXaTnn{@3R2L8J^1AYy~TSVM{x!?p&+69}`5s z75q(O0Is>rZ-q1Frpl7reJ2Ou!_=qs2pok!9L~O0&;qrU>4tT z`ZS305W9E~i;Am@dDo)5R|-cS8$PsV?zv;SUW_&ag2{~Z6QfgR4P7?*Sxsa!Sg zv$~5&o;O{7u%V+mP#SejybHY_@NuW+zoJb5-0ymO_t!r5i;4?~DTEEmg z`uw^n#tGAw%KD$RWE6noG6`*(#??N?;n@#x8?Y?;1}5d0?%u9rNvPO?%gO6ad&zYS z3x5&GU4du(1=XaNwK9cR@#htCV20A>^|Rm`(zWKW8E^2+x8Ia&&AtD}YeQ@{VfWEu z0s0v+P-SiSZ!P1tsq~CCr!Ie=Fo5VGkTiI_yrLvM1O6JiiMil*5_H$~{tl!?pQ?_E#5&(fU~2-PP?h)YQN?_NS% zTg7}xFvN02pfhy1Ue6g}@wf77j_Ym0+P+wq^8*7@oQd>115$9(c0{q$j*L9{h)bg3 zJerUgb$vALef~_u9BJbiC6zwFQKC45mECbrVNGn)Q>W9= z5!k$Y8h1`_r3@bywNK^AVQF234N1lS@<^NHJ?1om@!BwqqvdQWw`&fq+gDrWTtM7y zLkDt8tG)Et-IKZ-enOTy^elAQs=?-}sIHC`=nmxsU;Kv1MmR6ypT{E4htEdsOsi&*G0!(U(l7tf{Hb8jw(oPUdLQKw zA15&)AcwD$NRei3Z;1GnCD3zS;rI@+_K`m+q>uFxt{1ARQrKf42=kX`Hf73}RjIx< zQ+5R2u#H^G)2!*$$@J21>|QZ5_*q! zV`k77O(a*V?t~Yo{!(fe3TCEa{v?wFK>K;;5;`DF)QFN9#ed`V7F6N}IZg{X$>x`v z>~~H6bCw5jju@c&qs+96e6dsSnn?3fYzl93=tnX`5*y+vc66r}OwNS#Sqp)3t?za_ zFCn6~m8Ur^S*==$ICM<{e+_BG{3J#GfA%1TJ9DWDdcv707@%?{3)NJl?2dB16!{FG zg))Ty^+Eq}#d3(zJF<5t9MdbyeUpX^d-HP%fHLY{qN5U7G*1eQ@5q_dyKRpEF8DKIN7lvnv3WZFQ(J})%DsTdfsoX@;^)sE)>nRGCg~hhQ49y9 zEvplim|DM*8e;zna>EBpPnc(G&RAFHn(ZLWq#sL)2D9)Ht}_+ChcEIA2E$ux&3k9p zqQ|A$0OvVm-rtkSbGI9>tKCgnthGjdaZ|5%fqkT$*Y0F}ZR6$;I@s2V%RG#fUeY1% ztjr+u{JINYjnU!Klw5t*?A5Nqif@na=eU5yD(@%=RE%w%$3Z|a4GMZl?CdZbjwI_w zTm8(JjB*a0vunPdpTMr$kgHb6#c9%DM_AJ3pzSwQJq4p z7Q{^{48N_d2a|w#A>vs{0Z10yOo{iGw`M>eXlos;{pg6*pN3pF7USK6R^B&9vQ9mw zEKzP!1XFq$W^{}N@|Th_C&%p+;HCxl=M<;?n(H0awTh@3U+P z&N#8d7v5q9#i8gR&p}$DS7tcsFddr(%_pha6^Tf@vYs!z(giO&}W>kQXlNlJ$F)oOcr~0#fn03M*;{q+GsZayFM5MbUswm_}OZC{-M9&kZ$uf57It5MY1yu_FVJZFM~O${aS zZ(AC+;i_3`NT9knfnED+qaDUtE>PF^3Fd)6S&W%X={eu7h!m+wKa*F2`V2~s=7W>&8<@h)PnK`160nJUm7?jL(0#mD(O#H?IEn5-o$Y&-} zwdrJ!>2=1}x3MHw+{T-*YzgBZW0H(X51%eF`cVL$aN3_4P-T`ex7z&h7DJKav%^v_ zC)hPhO)9~F{QX21SZ>a<_#f>dw^GFEl>~9V0_5mm|NN5}3y7cq5=_-UKBaXhP&EnKU z9yAPlbHV^<+MI#Pr3t9oN5h7Rf` zz{5f}`(f0Mz2@2orT8oGJ?-1@Ps3f%M12u9+>#F6Oa1Wya^}8l7B6Xt_YVkc$m8$F zZD2#iPl^uBfjq)Gf2>qHz3%AgW=?fR?azDf$dBe|$G#@_*JWI(A;&+L*CVI>{ct`{ z7;fdnIFdWPrELzo;Pn%%$uQ%gke|De;wAA!S$yt>{1~Do6a?M8AHqDd-|0K)gPK;~t${#vzc(Bt;GDOZ!O)U*t&Hd=;FI!)!x`7 z1~_~EbI>_T93e^FxO3hc-Ypixn;fj&fgKL3p`>+wvTlU<;B6g4FTs**|%-DME!mb&JXpsPD*lgdU9-0L)|)W#TzNF01bglB=v}Zb09Sjpv+oGPfw#aHfl_udTD!mmn=3<7*D%KQGd@vU7yMvQB<5#k zqMivfQtyb-@YT-z19fiKk@aDwRpVrDbj^*B)gR|h7H2cvn_yOzaa;0(q+D<}K;C|h zH_g(wiW_(0V;ix9oHzv97h$U4oaza2{s+ea;YD=606YS+Qh0B`rPUDJ{dDT z|M!hb>;V&qBA!CeE{lgksXylclxA=HDh#=|*&w)Mh}~^)M3;M#G8}D+oNI90`AfI_ z@hc9)6nnxo?4ZEzzz+G_Uy=N5y4F}tS=!nH)0Hi69m+Qt)d5uk&iug721BVJ-DzZf8GPs-3Wowqirc>nsxtS`vCn5pMcYsvkvy1xC;NfJ*sX}6WrdRQ)jhUVz&CvW@CVghbA z@}NLT3khmVN85y_H)TJ=|GO0OzpgsH5wX6O`ahMHr`Aj(v{6qFVmNY4^gFd$l^(>S zl#!VJ63&PG2Kt&_Oj>+1+GkPGCVZ6s7bk*pnM*nP^`}5vDw#{l(?$d0MzqM2hb3cT zipbr7dEq}q6g2dhvf%!$`z5$uJSQ3i)sI1*coPGHIzd2>>Bc1vBG{sn@o=4kjEx`N zjW(cvGVulbj7xZZ+8xhe=vsj65kzv(fzTjas_~rix8=BG(eMjsJ;E}9Gqq#>MmrZE z8_WgDWc^gBbQ@XeMQ|09az6$4nlDg->=GOyfjLo%;K@JXYRLDG-D3|MlV+ddyhT2Q ziNjRR5?O{SIWm2?ME|IOkB{6~15)KHd47wWB-D$Ox^1weXA^ z8E5RTO+TfG2CKKISx3EtOv*I*Z?xKP=(Y-`@w61jq)DLXV?Lt?%8AdOFLWRu(R;RX zfjDn>oXQUz59kT6@(|u&$WkR`MHKt*xL!!qCQd08Ma_p13-&TLknzU5GrGW+6j3BT zoN)wq&h3?9m-b2md0VKG_uAfihtSd@@Jze8_Rlfwm6_>!6B8NBLIKspm+RxxrGBI_ zwtbc7Yshh$QpI>-laEPWneq<+p`Rn=*Jeay%2wjFcbB9xaUq6(%c4Fv+0vJI5@P0LygvM5MDdy1BT-)D55LYKJfCFEM| z>qp>N-$2yKnyEQNE`}DV_CX0aw)$g0y~H9q%fcO@TM?L3`E#7moDSG-flzwPh4d)hptbV5 zpUG*Wu?2j~@gsItAKrM&z^(1*Dz=&0(`xZ8&?b&cVzox3LhMJ4lYz(3ZC0pbo_d*4 zw0;i0rG5^=&r(3~WncU6D1&N6`#43RP)I)`i(ytc)#|amJx9U8ytz88en~K7W20H5 ztE4?~67i^Au;-#A^u9JCa}sAwuLDGOXbbMD@n_Ine$bVqVrwImlga~Tu}LXV8;U2? zfqDA`y+laXh?gTM=VIz@3Qfbb%bonTBrWu7N{GHTr1hmE_LtXvY^sOWJAwS8Rq#LT z6o=8?K00Egj}_xoV@wNQ@~U*^D$3WP)g&U=Q-xBVw9SO^5{Au_UK73kpa1%U??>eJ z)mmh&%?=~<(yayMB{NtX==oECbAqw@Tg}R2mmnGWL$vyL(#sNrF-XpF&Qy#(&Y$;gx;DpP%DH3G=t-$thV)yO*;hu!Ie1*&RJ|@M6aAND^x$Viy^_L_58jO6}Fp|MO>q^L1B?3ulB4-i+%CmDz~xELN<24{uMP(Y_zc_*GA zB9wi3^pBd5;Zkiex@1x7JboB4Lryz44(hPX6gyp7=_CoRjIG>h?BQIcq6w>4EM=eJo|2&cdJ3ci0 z+9=g%h*WO7OY<+8cI&0i$aB@0V|q9)*UlX-4^o9?ni?Z=C|{0;1%TeB5KOu+l9d95Y#ov3<6%iJIKb3v{Tw&4A)x}^Rw@R{fW zygQK5Dx-rTX#`H{K1dMQ)igk8{xl7r#g_b};L9o>N5+13$q2jL7$<}MhSWY0-u&#= zDApsn+5XD`VyfXvlSPMKZHn#fN^^v|M2x~spdn4M^qJLOl@O-i@H#`T8L~BWhj!1_ zuYR=>JPU3ij(s%}Ua5C`{}%slv!dn7^XcINgsLGa?{m~$hx5fR_a;RDQ0sjW6LJux zY_HQmJ)rl(2Bk|v;NVFa9Qs38<+DbX>jn%eZ@nUcBN8{|1#HN3#Dq3SLe`3RLX1|j#S}pCFUS6K zDk0fJ|8~!BR*WcX%EsiA?S zaY1bkSde}y`B%#_V2{_32p{H@Nx$kQYx(JK#(5GNmYjabke*M<461MOs?{>QR7I}>eg~KZ37!;*l9iQhxnF5g{Kl2 z3S??K3j$4>^;gOkf-sqb2h5CnHYR+&jbYGN60>hQp0+@UKx6Ch)_9j&=H%oP`2ZcC z!Sd-1c)aBhpHM^)-PR|oJQja;2srCL%=} zk)7WAQXFRYz2P*4EVf7$ycS5zwtG1Az%yD<`%N;W-l^LWsrjt}IP_Og9jkGl%bpDW z|M#e8;mBVB{>l6Tpx_7;>R%(~g7Fg}SRXIZ_T?CEfZRv;yJ9+CD-LoTn8JN`krhoP z!6ds#lECm@#KwP!Gq@-+Ut(ByAAe>S04&mRCl|l$QT}amn~0DYqBh!|Jrj2kpHg1v0?}WCxVwZz*cA zKXb+WUAuc3aGvKszm25ssxO+t|0s@vWf`y5Fm=$Z(E6}xo0cpky>Ukna_p(3XkJ6} ziL|NXs)|7s>kS05y6Ha&$@c#z>gH3Jg8PWq4qytc3b*B4Agd$FRTqL9vI)nGYqiSLM)U|p7& zo2@@>vpi{Pam82YUv~?)Gh%a!6o{Xk20~OYXRW_sw(sa%k{4B}T-k{0`TzX&&dG5>8vu=5E z(vS@ZX14#*_1iaMTIrEJ{)SLVbLw!3w}?cWNqn>?8p-OPKKe09^Hp&dfvp+FybNG7 zjq_DpYM~<&+Kz?*c zXMe<_a$sXh?<+MRuK0}z@%BZWR!bA9Sqp;aAT6$E4 zAZre;8cG(+y2Gk%=_HzV2kOOQn8`%pe?_3U0#x^y^G=kjnd7>8TpT9Nvy0*rF!a9n zXgq`4hR^&y#YmcCDZe898gv>|BZ$w@2?j$JCs1w@Ly{-4^gAu)7P|<|rI3sg*R2m( zMC~y5apC!_4$es`qez)38g-3jW$0WqHm7@;Y4y)F-;)Lhk1sy$iN&&!0bjv97?dZpz`fFjyX}wpv>_|*EgZg z`G_AAkqVNUZxX3|1M%rxYmWNmEKt)k-J679OCXb;T`ECCNWXsIhOKlRkwYC<9>04^ z`B>dE!rp62n;OL_#L%@#hYf5SG;_YzMLl++xnGEVji+tjDaZxrP4c3`(NF2?ED4On zvw&`%PTug>sm_yH<=B&9x#TKXnZHT&ndDbb(jub7qHaCb0$Li}9&KL9K3&6kvw&9k zxfmk;_(T9^>p9H$8n*NUX3!EBKIfwJCI^;GA3nCnRv$p%=2H|Zuzwd${~lKi70>HS z#>QDUk_g~YM&D?-A^Z$Q9*j*1W**cU!^-}FE%DG#@Pa{HYwsxhO3M|El{}qq2xLsF ziGdvj%q5dkaPbHNuph1L70TkM1o%ie&J8k8QwX4H;)Ex5WTEmhqSl>>=*2Rt@am#r z`G04zXZ*5%?Tcp#PbKLJb1bUcIp}ivoqs!)!|Psz`6L@=H1nADM3acv|LHp7=3d>U zIV1A3jq1g9^yS2bhr<;j5gaA}dgd*%@`{*B=_OmEaCSwGrRw`J@E?e(te_#^AZr!* FKLC6L2eSYG literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/ai-mcp/hero-illustration.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/ai-mcp/hero-illustration.svg new file mode 100644 index 00000000..9f5f4c1f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/ai-mcp/hero-illustration.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/error-icon.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/error-icon.svg new file mode 100644 index 00000000..12fe2e93 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/error-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/delivered.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/delivered.svg new file mode 100644 index 00000000..54970493 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/delivered.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/sent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/sent.svg new file mode 100644 index 00000000..af291253 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/sent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/total.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/total.svg new file mode 100644 index 00000000..ff843f1f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/total.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/unsent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/unsent.svg new file mode 100644 index 00000000..aaa1d5a9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/smtp/unsent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/delivered.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/delivered.svg new file mode 100644 index 00000000..ab90d5dc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/delivered.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/sent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/sent.svg new file mode 100644 index 00000000..af291253 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/sent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/total.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/total.svg new file mode 100644 index 00000000..b4bc0912 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/total.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/unsent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/unsent.svg new file mode 100644 index 00000000..aaa1d5a9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/wp/unsent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/yes-green.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/yes-green.svg new file mode 100644 index 00000000..57f1302c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/dash-widget/yes-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/activelayer.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/activelayer.svg new file mode 100644 index 00000000..72c5cd99 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/activelayer.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-advanced-mailers.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-advanced-mailers.svg new file mode 100644 index 00000000..f7fa2b7d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-advanced-mailers.svg @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-backup-connections.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-backup-connections.svg new file mode 100644 index 00000000..be5f1e64 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-backup-connections.svg @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-failure-alerts.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-failure-alerts.svg new file mode 100644 index 00000000..ae908a22 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-failure-alerts.svg @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-logs.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-logs.svg new file mode 100644 index 00000000..3e785c50 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-email-logs.svg @@ -0,0 +1,373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-one-click-mailer-setups.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-one-click-mailer-setups.svg new file mode 100644 index 00000000..3fc3961c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-one-click-mailer-setups.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-smart-routing.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-smart-routing.svg new file mode 100644 index 00000000..6f6cca27 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/card-smart-routing.svg @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/hero.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/hero.svg new file mode 100644 index 00000000..a890dc22 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/hero.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-full.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-full.svg new file mode 100644 index 00000000..442ad3b0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-full.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-none.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-none.svg new file mode 100644 index 00000000..99697662 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-none.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-partial.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-partial.svg new file mode 100644 index 00000000..c49ac6b9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/get-pro-tab/status-partial.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/education/sendlayer-banner.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/sendlayer-banner.svg new file mode 100644 index 00000000..fca66df9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/education/sendlayer-banner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/screenshot-01.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/screenshot-01.png new file mode 100644 index 0000000000000000000000000000000000000000..5a42bc0e5d9b8f514053a116581b4054c240bbf2 GIT binary patch literal 70348 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

If93F(NxX*)@|n7hZAqS~(BHc4;GEUjC?q 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 z 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/thumbnail-03.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/email-reports/thumbnail-03.png new file mode 100644 index 0000000000000000000000000000000000000000..a227209ee21be6931bf18da0691ccda8b542fc96 GIT binary patch literal 11636 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 U1N46X_&q>DPF1#A+BE3@0)#b#P5=M^ literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/email/icon-check.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/email/icon-check.png new file mode 100644 index 0000000000000000000000000000000000000000..02e61da0ca40e2ccbb69c6e55ebc10d510cc5920 GIT binary patch literal 1092 zcmV-K1iSl*P)0=U}{=lahBUG*G-I4V`ne@G?+zXpB~jxMN#mScv<=r$n$4 z&wYBti334&M{tsolY0>GksSO75l_fUcLfnw_TgfI@e0oeh zAP?Y4URlRi@FdYs8fUKt@FsVRlYXprum(f=$v)O#$zEGa*3E|=jtArbxUx!ayrU-E%khHWN!<*CZ8KdQVAkyeH;;4x9hPo^4FRSr9k_4=kLx>G;%Bk5*cJPb1Y2~8BFOARvnQHPG|tDjz|VMUBIT(5y{|~ z_F>f#$>4}~VbhVxU_#sAI$9ZoY!5FzWH4kIMd~4gInQrOy$l+%tyC`)H|2h%S|)4C zL{qH{^2WTlRLWr4n5Cym8HCMehEgYke)9Oxx+ssAO_}*5vg?_gQ_? zdh$){GVYp52M!&FOkt@C7`aSl`>RkYgJWBX*GOd$T3dxe83fj9k`c;ej@nizlUX_{ zZAK)6p{wo}N@|OVvkGY>1?k*f6Aj%mDBTrWCYx zd}p8;JeYcZgK_U5W2iBk`#wdH|7fqJ^O!jL=$OrZ*Q8Ugiaw(YIYnQQvFg4+>!WPQ zM%S-PjQ9Gx0{%A_KK+q<6h?8wM0Yzxf5#+I-&N7ycwzn0_lVJZix$2Fq~QQf6!%HV z)*HW@QjEQCYeO|8Ly>_zz@_QB9qOBqNedV#0fJJiRxK1HqzV9-HL{0m;I?J}0000< KMNUMnLSTXvz4WF4 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/email/illustration-success.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/email/illustration-success.svg new file mode 100644 index 00000000..783f52ee --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/email/illustration-success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/email/signature.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/email/signature.png new file mode 100644 index 0000000000000000000000000000000000000000..1c55a4b7c6a7ae1462b9449e687e8242287bce98 GIT binary patch literal 2131 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%0D^R5nG9zM(7RR2u z;3s4B9w8L0In+SLW?!a5X3M8N4*_dBMvls$I0{bRReQ3n@Su z=@bh)Gtt7K-~%|y&nKI;-;-aST+8D~9*F>CxJc>~sw>w{%C_G#s^Gzd@en?C9{tCNDhd@x z|CE1~xDKEeKUj|1@GT+L_41PBoL4q_5Zf70*?z$M@G!h$lQbvhIy&SH?jR_k&7bBd zylw^`xCzcmlLuT=MtTlVf|WPShHU30UCG3>F5Y3LPqqtg`DuqiOW%Ncga7GVb-Hi( z8bOLB56Kt62zQvkNM7;seX^O0!C^vXdq|Y%89*BHL$4@L_|@D-^XvrW?w{XD_P*g| zj&j$Kw%0F33ym7!NN@60?}aF^L)0T%11YM!Ab{XjsCBqH?4cooWuHuXpw`UI8Z1ph$h!Z2?lJ&9tJ-4!)pVYBSl@#9+IM8vL&mi~nHcar4;uF$ zDFIy4BtuVztD&a@1NM{1i{FPJ{}~Y# zeis9zD-hy(a&|@*Fgi=JYJQ|D51y2WlGd66#qKYAe#kg}OcqVf-m}!-BHKAJf_+}N z*sUZ@;!D9t6GEU+*8e(kaN?KXjALi37S>QbJIm4R33=`pg6mRV8?Sg}12rmu7rd2# z-p^9nh;?o6dK}=$qZ9|Eh01iMqB#LVDkF(j9?iz6&X1rWn#%>&=tN7e$NMwuTHhE6 zF4n}t|C_I+9diOe89JzWZpV1VW4)wy{aP;UNJ8>%`PK91YF4o|+gHYn4h}9vR>i0K zGjop6Sx~B<^g|c|wKe$>1c^Sgbquqm!z-V1o&QdCg-ulr91POH#Zo_!-BeL9K zNMw$$%Smif$p|d^u~d6`wd3}%H|<&h#1Mu>KJldKF@OH9{ZQn?bzhF!BpZH`KtgrWkl$YqcLve z7+hGr;kuViKBBMy?j9*4oU<^Ak0~lkvNz_ z4hdej$_)NCb)KcrNfM*@`Xz3vFlQ`dAG_Fry`Qc0;!Dr<*ifgS>I zRFo9gm>C9b>jy+~G~9M`y1IW6^!%@Fr0BYi2;72mh`PjkyPUV>?UxvxapzW5{pQ{VFZ^}&``*Bypr+|pe!=qT%!)3ivoUqEc#2PL!X z;+1J8yJiiF1^&cIHNgMx;?u>|EEEBBrNJ#2yh?=IblzYhQgGePccra~>!gnj%t+J* zAchLHvU3<(9p3iSvS>qEB7Hjg;$T}04hv>kvJsf_DK98 zHchgWmOXwob$l{y6*9<0P5fW0&ZDPq?e=HapkvF*vEt;ykz{Y7CkruVBS}xRViT>} zsnH!fyxyzrTp19G8kVghDuX@#4cU_n<*s>t;dc3zr#ZeQTO#+ON zQMrOolXl*ai|@k+JF~0@n>D|FOlKYq1TYbW_VBDfv2B@uHrmI7S!rLshWcXJCld0@*eh#nR`ecdj&B>qe`B0_#JYwyh|ZH`K^2)Q;r7B6c;GTHZ5*6S;}T zRWP0R7tLTlKCq~GydMIX@#`VcU%CX=ri`Tt8OISbEywaS`d3A%lG-%U)uSs@um+D7 z&Le%=rw2)z7S;T;pZ340qN@A$+=0a_b~PXG!8cUX?U1b^RQ^QU)CDImVJ15w_q^Hw z*0Y{>mKq_y1WDyWY><7N^IJeSL+TbMD>q`PBe}Bmq`YjyA%G+3O6Tg|J{o;tOzOyG zp=+qpkAsy!VaNqY_;TlvZQ~y~*TIIT8QM3A{s{)k7#@CG7&S%GI z4L&X|6I68DkAahn9JS&u4ApsgkQVfor-C7()#X~bVm!_vszKP&L%t0MQWGuUox#2 z;T5nBs0KzG-J2Es>rpY4li3FB;U?O{#g3MQHFyId@&1*<)nIM zo?5nW-OWxGyZ%N`_NOyMPDReYZ~Jwu4H0OlEsag6Al$qOmk;!IwQoVmyn%j2RNZmv kJGv&_U;BlFY$*hJB`6f#z@A}w{x1h$YG`FprH{V%KfE7CzW@LL literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/email/wp-mail-smtp.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/email/wp-mail-smtp.png new file mode 100644 index 0000000000000000000000000000000000000000..6a452531baa2013b68df1675689838ab160ae785 GIT binary patch literal 10848 zcmX9^2Q*yY*PR(g@1jK~qs)?4e{wbyy~?0xS!XT7`5OEA>eq$Imd1_FU7wH~S)gFu8>5C|+pLU4nK z%UXutJdlRErW%@_x@LM0EgspJKeClEL7JPIs+rqqnA=%;g?J~wF8o;2GV(397%h5V zp{}#p&eoQfAI`%M=M_Nk@*{F{b1$y0=T}!JYhPYec+5s9qF!Tfz(&j4|J_!sIXOA` zD#O2Td~x|0>zjZS!g1HkGlkdxU5~D>qg$`1{x`n9_K)bjZhtpt&;tSqrf8|FmvvS)BpPGFExh>Yps@D}B_w8Fhjs*0kPa(AxjADHdDn_M$P6!Ii z@o~G|>_ZH?d?P%o!3esqAS$y?0{Uy2GBidB+RJElJ{gk+{Wg;fTxSIxRNO8l28p_L zCqv)3MYYCdd<3H=;oN1!Mh(pc6rcyQB!X=ZBT8tL4GKH}Td@)c@LE06()QjxAx4xw zIeLZiA|(;}W+0N`P!apqoJocn6ktOdSUE-D(Ln{4zh{*f!SFd<5%dPx{fh$exN8H9 zQekR4`0SVxFw;T|;?lBTevR8Bp;cu`1yrDn;5oq)^~nc! zPl-WHHo% zRuFETXLl;<4cbEczLOs@N~LmC1Q0_vr|F;hDu8rrY@$!NR@|DH?|N~97JQBJl7jo> zR4mHc$w3A3_F3jFLNLqGDmisVP@`7mln@YD^5Z?*4yz?-LDNR-qFMXmQ_veqCNL_0 zKguJs`Sa@Rg>^{X_-Xj8yQ=e%eNv4YCRfXD^zMs_BCk{EjQM z&m>oj&PO86GZ7$Z=}p_Pwp&r=2uqO1H-jdogScDc1UED9E`5UiNzqoxcRu3)d45OR z35HuI%!mh`i7%WMN%A9GgY3^TUUHL5AFS@?W$<&=Ai`CCpT%+};tqBan0)$DnwWI{ zrPFC)iehjF*Hd!iSN99q5|g)znzJ=wE!Q8159Z}oO1lJ=*N?6;OQT#GW!DlTqwOkEg2gJ=>Z8SV+ORL}+@#BonUg!JamM7= z#w|mfB^rj$6+QF{bo8b9#?{JNmh{jC6o^T-2=}pleUzjSBhbl&PwBD886PS+^@4Po z48Hl$0dsg8ZZeRYsPpH*i^EM-g(5u!OGx&Ge{EV4nh-CrMPUah&89%mJ@HpI5wOq+ zmb94QQX_icS59T%@j;X6-6!<$dMDL^+~=Y#4#+Q^XY@52FRm}z?+N{61!h8RxxH~@ zHlED=5OP-fH4%2=Kt#xBf3D|3L7F;ANc-}y6$?JsVH8x>VU`IP@~(@b8t1QVoB%Q2 z<4e`O>kJCwj6bg7mBe;=`O@cy%-_fEeIB%!ND=vI+-ml@8~1h<=0jE=D`WMhQ)tqW zG!QRS%$aZ;;L5kv*F>925^*;`tGbEHm5l@grg4ktHqiNI>OTeKThiU|*F8t|Y@>jx z5I$Xqhi-JUIQBTy=qjg(1Tjww%t~wV-;X-cEdgQ4=r(CFt&!5?D}%aI>9i^Qste?x ze2pD(B^tmBgk94jEv!`$AatL6CQ$M0DZU zw;TLnuthn}th(1&u??btf4r{yA<9Xa=Xa~8jp0S%>3a*wPM*)tP4XqMk0m3zUy=HZ zT)@X3Ypv)`#g41eai3~nK7=lBuS?FtUf$(UKC@}zCpR;f;M#EdpK;tdytQdjh5Si} z;z8Ue&t?8|{=}>S+XwFtc0L`+A~;AoMWh9)_=h!FLvcevTNFvGhi{bs{%iU=7bsxu zH2hnm`Z3qc5mC3p#3zu%o?3+UPi_Cgus`aL2J3;kH{jZ;hu_e-u&ZAw_Qhp^c5#0pJ zBzv1$YS*u|`8UOL)f8=1d0opXu-;RF9CE{3=*cT2Qoqu(Bk!Oh~ zrtP8a8qYO?^r*XW#042~?h@vNTY9f1MF5wmHGoAxU-nI)^?sdQUmY9Xr9V-(6HmKI zfm?IQ``gF7|KQ+(`U0v)!9NB!36fYpK8%9?*)bNO=L@~ZcN5XZ79}CxS0Cn4lG-8? zVx8KTg!@gn+Y`BB-gI+FVe1*sa1TD+9FaCf;)}Oc1m+$(S40TK+XGwggIti{$6g8@ zL`iPUUoHBh|9r7L-m8siH6%(J+D(+O10xvjZH9jvRCgW0HaZ^Wm%NqwPu0_??qk26 z{@54ysX7dKXy20AQ>(a#S~z-BAZm9Mg_^WfklkP$S>aUCTO6e4w`*hjQjl!emLl5T zG0E&OA@%%i$yc$~s<19@Y~Gp0MwuPj@cR92LVN*}PignTf{QIHfXlHy4WMM4JY;$7 zUkEWY==s2`Xi5hp$c`%iS2NA+gElU(cfe5WbRV-w7yB z-{kYAB(q@bj}T+~rrW^6=o%R3)n~`;fuq~IwV379@tTdVk&n(^7zx|xWdl{l@H=|l z#yULwMhqH|42@|7Z@xJGR#@Xcs+ucmjAt3bYeV2hN=rW@N@p`C zd}pCRirC0g*g-PbARZ1HFo&Qm_cn;`pOuw_7JEi;#QV1L6KaxZK6S{LJDeBOxE~n! zkL0T<7`wLh>U7Z^xqrsDTj2C1X7$(Xh~qKom~B{CMquo4XeXgi2b>FEb@@*wWVII5 zpmGvj$BV#scmCtU`u#~E{RSswG}8WUf^qodyBLD3&HqF^()9?0DXb!kp{|**sD#{?WjQ@&oCW^kFRoZJn_)q zpZ};J1^M}@hR%l?Z3&c2ydp$NpL$JN^WY#3zE5ZdmZoUmtI~{F9WcLelKaj>9f%a< z-WQq92t~g5>6Yiohh5N(jvPPQOCK{mpkJcHbwpUkSSajOpacu}5aO&^1J0_d|P zJb_vE_VI2q2;BusbCkh#l#1}GE%_j6#TbcRg;WOc-kIx`d+|k2z`Bo*Qmet9PJ)vI z89@XxFMMXEVGwD8$IP*}YW3NF6 z^Du$%yimtE8W}|2WuF0w@8_xw8x7l6HA)gpB(D~K6FUj!m0{Ktn*cGM4QmEAx4%0xH#lzh5+Dd zFG^&@r^5&?#QzNX(lyw|U zi^R3Hyp52H$3)y+8$M2$d<^S&Io9-XR$yGU}I*$jQ>+Jo3G}1+rrEGzOOu1*Lh7DqGNLlxKvDv z=(mRz6c;oJVXYG&h#%KwE-#nkElb};ML^mUKmEPe zpuj8<>sY+d=ve{6cJ02q5pSK=D1mKckB}!vtiS#*9n<*+qjBR|!zHrUR-6Iif!(UTx09 zA|!QWP6czvZMo3ZYozw?Vp=rcuaS_yB(F>a4@6(7JcQ^JQDcI->a3}BW^A_J;_4XY zxYV^QPtTN*D#I;5CqI_2lX6Ewpbq|tyuC5f6>M0~e}v;G*Z_fcJPCO!(Q4x@Y_pN( zfDcPz3JA`p0KwD}Oz1scgm8X=DX=Z}zf3|>SKa5G!?@#A!71|8h^=BjEhMxudD1(( zIE~0W`hbuTP|N1E8KNh0T6rOm>7YU$2U+I1Uy=wRZ_PHN1a#vy2*`)+t4S~my6eIw z8bhi!oy{thS?NM#K!b=*SsuSc$EU2`+?b`)&fb^?sOz~A$9p1b1Wzrk2nC`s$&rej zGt|n48kj>rUlehBgF2=+-~ab~e|rRiJ2xGnCu^`$?8-0;J)Ypt121rl!!j)Et?}4>zU?hS0Br-F7uB@ErtAUzf9#oi)6xB zs^?wHI42QGgfAgBvwMEvFru4!@H{q^&|LRrVp90mYkAfi&L(B5?kQtO+!1XHF5~1* zi_4K&J5696L4HN9Sksic6;~{vbvTkZS8#bq^g7fdTLAsNt?@8x#|srxO+&vcf!$c04Lpwz9?D4}L$tpCdVLSIk}LNxB=jfBOcck1%6d63 zUZ1!?4?_y`d=arLat&5h6gu39f+WrRYqVXm;JkR~1=KblMVo!lib9lY9+-p835`py zxru>VIi;?2#w#|?tZ8a0M1D9+xIFe3x3t}4(AgJoQq_xHCnTkD((lYBhrD< z!RGJXf;w=TPPU<*NR@@fQ?`jSoEJr{j#d6LwV%x6)G#HZxGXFv| zuzxa)VIM=Sj9P6rYfudlBvqQ0nuiqHOAYN;fGd4a&S=;^}CnE^Nkfj#05roPz1`}72X>xJXYq;RUpSud+5&5&6Z0*rqV z363ruxKhNvt_bWP<`^sk>saADXMRZ=P#pwA<*~OomyLJpf}ebDeQxo*kov&lIj#Bh zZ3JPLK(@Wga478qm`bkNFQKjut5a48xubTVYS_~S>@ytXRNtbXIz{T zFiZS(AxQgnWJdFN5+&5LUlhK3{^X_DKh@fTLeu@tOacG9hKg%=c|8KObC9{n`;rMO zZ=C86!qrcH!(VSI+KuedN6}cl(5}pjKRk<(W{=-a6k5)|$%?Gn#>l;!U6v@c*x|sE zB*hfC;CP<2z4#_d1z5!v^Eh_2=W{f$G1iFicO=$os{Os~gq$5WeW7kWyNnSlW88&c zm&j^8Zt@}AY(F|VuE2s4P^Ej6Y9@hIHr2+E)D{>EcU1f!-jtK=(J&_U+9CM!YdOAA zJI~|}AAwu>nbgbqnjl*{#MRH;sVO3a$B7(;S^(1W%OiJo2bl?!S=D!eHyhO|LY>;* zC;rx8g8gi5`3g!aUrz-|?4>**XlMZI+31GfhV#f(kgQ8ON|LOZ zj?PjXOT-fc4HDTQl?;FBhU+J*LsrP$2GpO6+e$H6+QM%Z;p@1-y9^xdFz-XvTX<;% zO7eMd$9*t%_#j$krT<|v7Oe9CJuc4k6xr7`wjt&M{?rj1D;(z=-y}ba|1~YzLf;9$ zgBO6+#(u7x8=h6-r|EGJ)Sh!J%%TS54)bxnp(5I5NvfMW;)Bb7<1lnT-c=nc{VtH=x$`NOTF_;c{!{alowp&f zss8v6Wr@i&5t0r>a2Kus=*zEx#ezgsjHg&#U%bdXh* zqE<8kTJVyd08v@}zO0OIj#ia!vl7LoWr~8%97GfN+-E-*F)`RUaCLQEY6vC@mJ*gA z0W)$sMDTZ24ucRz8%iZ*Wo7(C!2GT6)Cb8l$Jdkw@fPgQZK^*tHRa{y{k>X`TNKVr zwDY~D#?$uW^1joHJX^GQ)yc~C{?Nbbx?_%A@_j!6nE64xq~m%J%i8Jn^{T&qZ_p=4 zUiuGeLl@REzOuhs=+mL63&toIC5j}7V*u66Wien_-=Zovys*Q~H-03Z`!8o7P|MXOf=AC>eE zg)woH8~=}^Q@jh#FJntQQa6h9CvYpI9(T-6d%Ceey)Q!DV6VyCPJxo*m^u|lv0QB&UN=uncvC? z6dVyN1}~&~{+&w`jdoMheLU)c|K%*r_Od*W*`J5N+t!{#yS}a7AHl* zV3GIsv=ONCSf1PAt`V?)Yqi_T*yhad>-IH#f@)X<$b>Rx>F9%n5G=USE_@wYlBJ9*^)b9&m9Up1ll3ks}#EG;tUP^9)rRa1#-EZ=uyF#DSj>R zoaG@G5Ovu_2PbQXNNHjtZ-zQfS;|;CP-JP{SQC`79kN$YPUM{cgA2Ap?!yWW>+(X? zfMgTZH$1cSQKf$`jOM&mMzF%+K_S z`}r!U%!6O^0$jlGfRuy>9v^&>$`Tzl(_K`6+fXyjhXCLZuh<)!h1&_`}Je2ib8j$ z>g!N_M$_;rACIG_h2)Sy$$)9$hHvl`Bs=RH#@#jrU`}}Nq~jj&o(NgJxNjPjSiJD{ zApFQzU(#m_S**R%eopg9;C%SsSS!+QmAg1llf+Y(*-`((hS2T9pVZMQ@8_sQpVD?R z18Vacai$9SJMuIL`ulvD(f^j$+s_3k&DF4m)PA!W;ri+nQ~_@KqJq)%gCz z=;_G3kAYNOzrra#XodwjIm^Gif5~7jp57WTQixn^OCoCPE_bZpz8sljM@Uz|?Lj+? z6I1WVMtb0c-!Ix$UeSvzb2i0jq`n$in`h@%cU?NvNN0Vzg!IkDxhmHGkR)m0>9~BV zY210W7dS9_nofxRR9)fGTQMn@EsP+K#D~pG7YQ<@}1az>K_ zH4n?XWbM$_-F~xof3`Puo&3sn3@u2ph>;A@U!ORc1L>Ua-}j1nzt0JmrjMZ1qpI!| zG*j3V6E~CDzNEJ}!LHqT7aEQwi+ygHaNOP1D{SM<^b@^}^p%PXAJTLR!plyB-!QdUW(@&A1BMc%fIYfkf2cIesD zc2m+D+Ir|t2lE|dOb`)(j~4{&4fi~5rY}L6k+BZX5Ds*#l$=olJ^?WPcj~uXhvi;RAj{z$JFD0MZ z_h`EFC!{Oxw6-ZhQhAdO!J|?b#7MWZnxy*TeZ}i+$pU6R1Ic;e2~f&Rp5V~m$J{PV zh3w&_5WXjmWxrHbeQ>$Hh1IUdkjUv^Qa-204Zn7<#0+o& zFtPfXn3B(REQ&+Dd@_7Y( zZGPuiiN46Ko-U1o9bHoN|C3!-cd4bH=LHndXnk68j9;yt#%(_Sn9pAv^}`>*-J~4e zC)T}3nRWCbeH)%S8JWaQNtta;9n&MgIr3|f^`n?Ksf`M zdxWp{b;A{DsK&p4a>NGAEEj#JD{XRhS2_oE|0*dkZ|TC8hB)avPqQgG!e+`@G!cOv zc{E>ck58@ViAW{XSwx?_>GAchA%R)1l4`kixO^mg%amb-6e}o?T>0Tb#j((WtY1NWC`N9u;@4Esp~^Y^2w;Ax;)|6}_sj*Tv{&6p$dy+5Ny`SQatW5Xb4ENLv^<|yMy8WGkQp0 zBW%+YUzRUIsw~gDN7(;Mo~4{OCB_~vYj_KzNQG`miT19U#YGMS_czAc>*&zPm`w-< zS@b%jln~p+QHjb@l8cCuv|^gLavxvDEnVmQ89v5?bB%UQiZy9TOABXMh z{aDTi8c)>H_LL~Gdq*nJf!rkV`b3%B4b8O8;7YH6L8VU21Euen5mOV{n zK3en088AqE7v+@TH2KuNDWHns-97K&^R?D9O})sgvyajCgzma2aLqFqFfo9OypHWr z$IAcX<-J3(5Kk0mA(frEN!VVmhinfBnB6;<#TZYIPP>Q(y<)_hbSFkabMEqwww4Ph4;i%C|k zP$!=xxp+n}z%equ*)Ehtxhle;hxWm4SyeuLedYd}xP3zMTzCA)XX}l2VMT4pwq0nU{OGONBl-TG6J% z)y>r0!%pSB>24pl`DwaNwY~$=z5Y4gNBBl&JHU zF>7nslW$(N^OXD6jQpJCEdN@N35gJxGyBb4{&>( zPIu-5o$r2l&JRlH4v`{X2mvC}K!8~MPYTAGsF-{Ftj!@vBk*SGXJ8hu{?NE_h6yn} z0*^lv`y^fKIMdPedXM~L<#Qx1f(k|B-zJ{Q~X%PRIa6&6Ps~-HdZ+oTq=P%Y3 zRI~mXV#3a1OJ8$UqyaTm7*^ zMDWb~qIuYH@G}R=jmSM_5QMW>o00-oKx$J8_qc~3R-f+2bh-ocj25vM6l7hTqzdGL1t`X zoD02w&ivpMYujPx-pmVI8V!|$JsD4;%$DX{Sm6ewj9~&APY{9hIz<7LBzRRsY^xL? zoarXi!0RoA)o;+L3(~Yd_I&1ntV(moZfPbE{U*$pcS&)Of_0qRyPLS#HXpoF#%KP> z1FnBaN1r@^!e99jneo+{M-$GTeZ9NZLk7f?4A(N#3_#H@gU^#!{9-G2t7odC7~mr- zX4uP1=<$+d0nqXiVm=z1|JLF3Iyu0|I$L|s^kDGd`yZQT+3&WC-Ph8P@C$nB+Mro6 zDILAInzj0Jq?n>WyG-gbwG+X>m6*wZERg!9ZLo(ZNp;KTN_C1lIH-~}Qo}uEHJiS> zPCO*+Y%Sltak6f;Q{(;LM|;n`HuxATlPL+2-Qw|3Mv0A^-kVu%C0}F=H!H#h@lxGx zsVWLK32>bEY3!=N3_{&IzsOWr&=j-NU&sG6g4Q*Be~$@fAV|`$XA@o_7-+31(QH3> z;pu?XitnV&5Fm6TprC7xOtuTXs2qC@gBiOqHb+?qZ?hOFCUZkE9FAl7=skj(V5cYs zhDx&!_T&hC!b<%^ZD>+6IGGat?Bzf5r8iS=FQq;gOJL(`tN?CdC7`r^anBp${W`9{ z>pFcrra5?mT-mN7$BZdvjGfxQF8kexGYS4Vw5x4#EVAnhH-O7I{CH`)oYNevY0GU$@Qw%aH+EJC`P-H zw7-9q>_GYQG1cRbH*rni35#Ad@lEp9JtPT}0p#Q-U-9c#1uRI&;U<{fv!_?Oxid!< z7&CF#OGQ?#2DWmKLjJZ!YmWV}wQv(4F$ir@q@yxD__Ye-oPn1Sl&#i~4Drklop317 zpI&+D%-@4Ou#y%UCsTlT>|8U8TT6m5>FGmh=Z)T&j~iw>yX=JI2W@^kh)1sDzznI z7He^X{sgv{W$T@4NEk&`IByM5;VaWgZ;rT492=HX^*aphkb=Ys^kAh;um%y8h#-KKi8Ps#BON)XXknG?JNNA)*$pf79jRXG;R zyUR2Pkj#3Mzx_BjDIqy7p&}{WN!Ikv4f@2#CJixOn@P|P@kD24^AF@DPtcrDqScbX zRF<#%#9P!m!hY|jysWF?1HF}nxye_vDb7-%o-E!S3i0y2_ND=OnZ-F$x=Jp#MS++t z!-ne0CV#N~0pY%#m*%S7IHv+Vz~8N!jt0bP*g%(GJ!yo$z7}%Rd!BIzl!NkLG`yG) zmU~oDbmUwbM+`#UX|Z#={&y567S{_>zPpjKoJEduOoM~&Bw2%V-tv1L&x5$ku5RgX zgUb4AKP24&gPb$E8iOoA_bRmk5NPPFIWZyE>AMlZ9~-`)hq~0g6{~w*pm_7|U*8FJ zxiWw(9rS{1w2baxh(SDJcpuqDV{I`%=LsWls@>BcEuc$~Q+=U*Rx{hno8LG=S{nN5 J)vCzI{{tqOPF?^2 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/facebook.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/facebook.svg new file mode 100644 index 00000000..74095298 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/life-ring.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/life-ring.svg new file mode 100644 index 00000000..21aa971d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/life-ring.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/lightbulb.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/lightbulb.svg new file mode 100644 index 00000000..42a28840 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/lightbulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/mascot.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/mascot.svg new file mode 100644 index 00000000..6d9e8a03 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/mascot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/star.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/star.svg new file mode 100644 index 00000000..85bf4f00 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/flyout-menu/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/arrow-rotate-right-purple.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/arrow-rotate-right-purple.svg new file mode 100644 index 00000000..c978648f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/arrow-rotate-right-purple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/cancel-red.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/cancel-red.svg new file mode 100644 index 00000000..a61ae98d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/cancel-red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-dark-green.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-dark-green.svg new file mode 100644 index 00000000..4fc508d5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-dark-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-solid-green.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-solid-green.svg new file mode 100644 index 00000000..6be58277 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/check-circle-solid-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/clock-orange.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/clock-orange.svg new file mode 100644 index 00000000..6236e6a8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/clock-orange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-regular-red.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-regular-red.svg new file mode 100644 index 00000000..e59ade4b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-regular-red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-orange.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-orange.svg new file mode 100644 index 00000000..26e742d7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-orange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-red.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-red.svg new file mode 100644 index 00000000..8c9c1554 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/exclamation-circle-solid-red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle-blue.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle-blue.svg new file mode 100644 index 00000000..198fb614 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle.svg new file mode 100644 index 00000000..8c80048e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/font-awesome/info-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/arrow-up.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/arrow-up.svg new file mode 100644 index 00000000..cfe6b880 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/arrow-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/badge-percent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/badge-percent.svg new file mode 100644 index 00000000..6a08dd38 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/badge-percent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check-solid.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check-solid.svg new file mode 100644 index 00000000..230004ae --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check.svg new file mode 100644 index 00000000..d407cb82 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/close.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/close.svg new file mode 100644 index 00000000..4ea6d124 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/custom/badge-percent.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/custom/badge-percent.svg new file mode 100644 index 00000000..4bf6c53e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/custom/badge-percent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-pdf-grey.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-pdf-grey.svg new file mode 100644 index 00000000..02d7f56b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-pdf-grey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-yes-alt-green.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-yes-alt-green.svg new file mode 100644 index 00000000..c9bd25ca --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/dashicons/dashicons-yes-alt-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/error.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/error.svg new file mode 100644 index 00000000..b084cd74 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/exclamation-circle.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/exclamation-circle.svg new file mode 100644 index 00000000..c0f26670 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/exclamation-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/lightbulb.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/lightbulb.svg new file mode 100644 index 00000000..46fb1eb0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/lightbulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/success.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/success.svg new file mode 100644 index 00000000..9bed367e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/warning.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/warning.svg new file mode 100644 index 00000000..abc20f85 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/zoom.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/zoom.svg new file mode 100644 index 00000000..1389afdb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/icons/zoom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-blue.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-blue.svg new file mode 100644 index 00000000..5519045f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-white.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-white.svg new file mode 100644 index 00000000..ae6fe144 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading.svg new file mode 100644 index 00000000..84178328 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/loaders/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/logo-whitelabel.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/logo-whitelabel.svg new file mode 100644 index 00000000..db8375b8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/logo-whitelabel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/logo.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/logo.svg new file mode 100644 index 00000000..1295c813 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/archive-thumbnail.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/archive-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..a95952d1443d835bf7e77c5af88883c84c79653f GIT binary patch literal 16537 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/archive.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/archive.png new file mode 100644 index 0000000000000000000000000000000000000000..aab207246ca8f69d7846dabe214f0390591e9e03 GIT binary patch literal 111197 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/single-thumbnail.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/single-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..b7f4dc90d8733d203c2ec3a9c962b477d5422373 GIT binary patch literal 30417 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 Ng3L$h$`6ph{{tR-;-vrp literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/single.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/logs/single.png new file mode 100644 index 0000000000000000000000000000000000000000..fc4fe316454b2df7b5851bf059666055cdd815d4 GIT binary patch literal 71468 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 Uu&A$(@>R=9a%!^04^eOa7nTR^LI3~& literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/menu-icon.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/menu-icon.svg new file mode 100644 index 00000000..a646b1a0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/menu-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/pattie.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/pattie.svg new file mode 100644 index 00000000..2966ee32 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/pattie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge-small.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge-small.svg new file mode 100644 index 00000000..99e058ac --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge-small.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge.svg new file mode 100644 index 00000000..4e1ced24 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/pro-badge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/aws.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/aws.svg new file mode 100644 index 00000000..0338816b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/aws.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/brevo.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/brevo.svg new file mode 100644 index 00000000..473cf40a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/brevo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/elasticemail.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/elasticemail.svg new file mode 100644 index 00000000..96c1da44 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/elasticemail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/google.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/google.svg new file mode 100644 index 00000000..22150fc5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailersend.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailersend.svg new file mode 100644 index 00000000..f1e715e9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailersend.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailgun.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailgun.svg new file mode 100644 index 00000000..c76a86c2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailgun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailjet.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailjet.svg new file mode 100644 index 00000000..37ba9162 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mailjet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mandrill.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mandrill.svg new file mode 100644 index 00000000..690f0dd4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/mandrill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/microsoft.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/microsoft.svg new file mode 100644 index 00000000..1033de4c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/microsoft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/pepipost-smtp.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/pepipost-smtp.png new file mode 100644 index 0000000000000000000000000000000000000000..8e714ae481fb2b9c0ddc08aec5050acd748a3875 GIT binary patch literal 1368 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/php.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/php.svg new file mode 100644 index 00000000..e036c2a4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/php.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/postmark.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/postmark.svg new file mode 100644 index 00000000..720dc8b4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/postmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/resend.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/resend.svg new file mode 100644 index 00000000..c9d7529d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/resend.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendgrid.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendgrid.svg new file mode 100644 index 00000000..194e8bcb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendgrid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendlayer.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendlayer.svg new file mode 100644 index 00000000..59f14131 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sendlayer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp-com.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp-com.svg new file mode 100644 index 00000000..64b9351d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp-com.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp.svg new file mode 100644 index 00000000..770be9b6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp2go.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp2go.svg new file mode 100644 index 00000000..993a6043 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/smtp2go.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sparkpost.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sparkpost.svg new file mode 100644 index 00000000..8039da5d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/sparkpost.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/zoho.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/zoho.svg new file mode 100644 index 00000000..0bd65b4f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/providers/zoho.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/complete.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/complete.svg new file mode 100644 index 00000000..1cb70c2b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/complete.svg @@ -0,0 +1 @@ + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-1.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-1.svg new file mode 100644 index 00000000..32128fa8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-1.svg @@ -0,0 +1 @@ + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-2.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-2.svg new file mode 100644 index 00000000..f129c14c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-2.svg @@ -0,0 +1 @@ + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-3.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-3.svg new file mode 100644 index 00000000..3e47f300 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugin-page/step-3.svg @@ -0,0 +1 @@ + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/logo.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/logo.svg new file mode 100644 index 00000000..317e3eef --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/logo.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-full@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c0e0d1eed0e6d91b3180a5bde2639cd23c5b156 GIT binary patch literal 96949 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¾XI(8bnCD`jl;+ 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≶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�Qe&Ot;)cdNjt-M>vxQTO}&V_#rakn7dGwHL8| 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-tnail@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/activelayer/screenshot-tnail@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..07b7950a5f68d3da8a2c91976d9d4eba5b2a5340 GIT binary patch literal 18833 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 + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/duplicator/screenshot-full@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/duplicator/screenshot-full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb9ef395f4eb4c01078f70eeee024c5ab488963 GIT binary patch literal 60130 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


lD07Pk>LIax1~2_!WS4_IWqPN?1^q% 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/universally/screenshot-full@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/universally/screenshot-full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7c20fafab97a7b20133e0860c203b41c62c47c99 GIT binary patch literal 194685 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⩔1h0-|D+A9ngjCy3*qr%Y*ttcdqLv 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�e>vveRkxp(8!Mh8r(~Cw|)==F%zLyqelE zPTK>or(Ozq$VeL6_dr}c)h@}4j-`ZmQtQ3o)j)=|ICe5)Qvv-iY`pfVNq2bPiLr2s z3l>{u7hk98@7l5rro?8x5pxeF+`$nIK4C;DO^gP0>2V*ochdo#YD0lS3!!_}aXDHh zB#6y-(4{zM9K`DeN}af3mT(2+52#SWRmfR@V68+sIZ*JL9B{iKkBS$B!>&Q0?_oeZ z6s$Et$G?nOW`sqs=zD(mTy&sc{-XE1O=e6X05?|hQJ(T|ZC+MW17bX1p{nB=l zQJp=veabh1&zumbb-y4piLEI31_t=v6*F4?F&oDj`0tO{Le|^Bi*du&#j})OtuGEY zUma*%ZbkMSd!#}`3ZLvkjJEb(eSvLCtl^B92^mrH>kh9H*wpjC3kZ_n_xrN@nf{Na zGmnSz`@{Zhxb0)#8B6wMWEYx2_JmLh*~!+}ca1TYkR_5WAwu?jix{#LkzFBM_FY7l zp85Ws=lO%z%WE*_K6CE-obx%?^}h6Z`gdqY+zx)Gg7kg>@6RtjoP1aOO@T$+Jd#a3 z!wrDm1i8ZSrT3imo@tWd;kn&DC>MvrOAHmGi&4lY(@3-;jUYo~f$h)i1r{1oc0>+e z>-kmVsAB1_ZgDW$>JS)y0aHW-GGvfNo+R=Si$AxJO*%U+5H%2@1WmR09R?6#N3ql8 z-qOeAw~4c$cC+vU$NF3SQY3hHExR(zAjnpK=0JoWKd9%!cxL0n*aPl?eLflb_4y0^ z5&f+kT}`s+@0xNv-Rp=OQs&t@t$+Xa25U@CXB?mmm)do(BjRA#pDB7`rRq6$|2`SM zj~>3giA@&RZ6${X%PR!5q4%d7?)wtY54CkdO0Wyl^r}5sU^q=HJHoe38~un3FA(HC z?!(F5yJ$hAYD4jrq`=&6>H>-k4-HfsQT_8btO^FtVL}vc-fx5xU-Y5?D#37@#&J>n zeE}C_IS3^0b#dAECS3W(`9WV@UDeHhIZqk_=>tEE*?~l`APX<2)z{22DEK`vUQ465 z;OX=(cDit@Y%l!Z(FXbN;XglrouaVt;42&!e(M+U>xpy}c+jgHs*b`Y|IB);Lk25> z!3870@K7FUMa8)eQj|*>wwkHP>B=!c0L*)vDfAtPAV7kbFwaCH!Gv@=xKRXxs9c2r zJSO_9ON`yjTOUttDS!?A>#I+6OKSp|TE0^ItTE?>#^pbMbr$chhDqavFl?14M7%ZG z>#<580Yzv%oBzDU@XMG9o*UM%RHpDYYrE#|7_$hCN9;jYSwq&fCj%Pc6(8>YkXN`- z+w#WIOUANuFt$*>zICn-!ux0=4m8|f??a#bX!Ja6$~z-SShLBEHrxHR0H#35z1`b5 zcye&GZlv~fh`Uv^wL9Og=V?p*7+)K6ZrlFg(Ecp{u)w#-%n(m`tHA2xhNQ$OzHh(W zo>zC)_LZgL>@6ze(?C*|n$^d4ZEwAxIEezqN`yrX5u5LdX!lv`qNV+Jb#D8YEj77Ga#(%G1C=Fs%KU$ZS9J6AA~%%fkoAb`z6gVjoWQ2Up{aa*DY_wl=)if5Y< z6hOf4?8krIQ&0b$2{i(zyJ}GU@YB<-qhP}4vEEKJ+KPCZ#r!e=&UO(&5ewdr+l|M9{=Hn-W1(2k^`BjxHXamjDR-3F^c+9#g7 zqgs00$u5uW%~X<;=%rOMve8JO6spI~x{n1DOeCC}+4Pz%HTd5$4bd6kT3lmDSW9^| zq#f#7B7#;MK1|jBH^6(O29q@ zhUpqCUxoKBzk0v5bMqf#um2#|od2D4o_=S+jN6+T+qI_%xqy0Z^7*l+*6%B&{#`0e zW{afx-cKGr%g7vT(x26N*(Bqb+Dw5MjKzX@KPs#RxKG}A{50fdh?D5-WhpJ8GQYh* zb}x%vnD=U-2c}`oGNs4ET2z4O%Mxeje_M)8$`X9M#dD-hVmx)L!x*BG<`-4fhE z5!jiwjBeAc2u-HK+ZZBP*Fn$+Ha^ac$2g0iLy$cQ@b3l!RfOF?d=6D-8YVh+eK@83 zY9RPBFr*mPc=BzKdPX@~5l}l_Fl-P$|Gbt&q$NKFEv`IDyJI@`x@qrC&N{Z*m=rk1 zEsp-Ttb%N{e~7Uqg#p;)jqhMIDX0CHUl4B8MvD~%u(quJm?RVd=7PHz@;H+)VOXIG z6EuvX6a#r#_uC_`e15%IG&v-liAW`sD)j(j9|DA>ALUGIh|iHS_r;IyWfB76WO?2jd)}yYs4>F7u;fNqo?dDZ5kDh z2Jl`GE;Z~i0^3@MESi=6Fd)~j4jZe;!xz1AYo;^AN_La0!-SrO?{{6Wo}aDVRO1Tq z4(DBqb2gxO{XBZ@waVpRTu}5>$Hn2Br;+_Xn_D+un!Th$1=oPVmoIQX)viu)hdoVC&w{?E_*M%LrMNnO+&+Nd^Jk~_;)5U9 zSs8m9pag4;FXu%2{ro>_z+%yoNu{Ln$PCM(oB24;fZ*pUaOWYNVG0|(aWz!7qmZsU zgXrP!MBCwQa&3@Z;9fgyGOeImSMnh-!NryY5JNS;jx}n-=l&`9L?Y6~G?to5oTYZ3 z>JE{VM0CDN>iB#4CpEQnkcU+vF5J9&WFwF}i|_LC&!zSg4n5IFLB9Xm>SWHkmVFJE z*!`lsVO;d%KThU<`2atD>49z0nh3LDUduQpy)F2Dp2Bb6tJSO?&W+<(#h1lYioC4v z7+qLzxr{sz4>AfnQ;#%-Pz2fkB8^6i2(Jk4`?FH#3idcBZj4`Tk+ePWWC3t&NzuB7%B3%h>WOk*=>gbsL$WL&0fm! z2SI359(t*r7K5ev&6RcNJ;#I)FP;>0BLRj*5T@Tt7X@{}SUBv?$FuXU$n!rp1yx(0 z{_BeT`1GF`kj@C-ov`O|I6Bt|-93*QeolKeaqr`sOTKItdW7Yx&0$0&>cMv^bqc`9 zQB!zm;VA1?SQJ$M&pmJy#Azf%S?8g6Nauukj9N{nw*I5pW|=CGZGa)EZC5+XZz{;6 z+1Qj{w4cw#UO@?ma@LYY1P`I8|2LfKzRAg1d_bcRe(ti$29Qx-S3#(Z$i=_%kqw76 zMZ-TO?9LRpk)veg&O58F->4w%+4_`b_hd74(R_pnXLg;_lNLZ|1?;ZIJwh35@tR>j zuT#Gie=;ja6GZh&>lfLzp%gY=2O&L^>Vz&8#XJj=qxV`%_`@hl_&2nEL}W`UW!pq5 znLv8MbqQ-78M2M;aZVzmkOH2rw>50()}-E`_W-QU*W|~({F@U5A#$a8hzfPR-sj^I zk~Dy8%2@*#@~!K{?ino0vBWw7)m5vEFoL}^BvM#YWQ9@QDc-3YAEZ#Pum7 z5PpX8a{e-$*v$A<_^MlR?dk#=%_uCzugA6UZ1kUEYJcZnv6R<`R4Wmr2ff!`hV=y9 zB6GLS_vnPU1|S}pbwzr(eB0HbE>S1tEmJ9ZaWjy}+xhit^skA_j(Lecd3zofV_h%Y z9jYw9iKg5qEkyg2;tFv?W=+VR5a!uOqlHS5S&KcWe+7qwSFWbSDE<77< zEKOvZV=-G`^boH&1UM!%iz7lsV<3R9P_Z!$5ONO$6JTHl!{RW9zAfDGiuZQwglRwX z8QH4yMq(mLt}yXdLv^H_;=)lW0Y*-jG@qGend51`ld1?0N(rY`i}9R|;BQul5rbXy zWZl9McYLk`Ca;x8$)xxY4%uSfVhPACSM}BgdCtvyRa1pQRx%3N$ZX5b@-|G4Qyo!f zBWW`!Z*Lh%R@_oK;I)v+QM6`zS7y0eNh8onPqpgti(EOMRSOy78K2K>neL-x4%_IM zchdD$L{q}XfSNw(g^3qLm+&lvYN_+D#vqgEO}ZW&5|1 zt67%)D}-pPJ>FVZ#Lnej?zF4sqNnGh7`1f3whu{}xZwVm=~urPzI~kQ{UsiWZ8k$P zP}TFQ`Xp!RAHuSj4uZ-m+$hg^S@%J!Qo<3KT+z-?Yi#(W!WLNxP%7g?XlBxHqpGP# z4I5~3SY60@rz=utg3^7Et@Of8b^+8%}geP)yHasRo(h6 z5XbHZ>(*|1!^~~WDx+I1>D@!8gR1wAX47(avY4HVoU<(!EjsHOdL4d@9vR7WH#{u! z8gBG>n8-Z6$g!|L3n9oU*oBYPc=Rm_EZnbwPg)rQ>F``Kj1AT%h`VyKm9KNVml=vT zz*oqG`>L-6gz`@cg~LKlKhcA)85!jI6;W92nc zx~{<%aJEj!{?HZ^2ep(HW7o<#Qi0vHJfjF68kp#dN}h~`60Xz+JBIJc7UniJ9sLM4 zBsZ4bCsjstezK_^u5U}dB6z&>m(2*QVD3a@Pv?rTQ>omcHo9T7nR%@n%=&h~L3UJL zkds=Fp~u9Do4PPlUgax3o>vQqy+kHy@zktp@I1Fk$LFP8Td>o4TnJlK`3`YhXfdFo z^WHaVbQt9EeKE}Rncl47c!Narj`)+%P7X1t=0Nb0jZH69kH^IwrJ8=ULVQ9KZ#WsO z4I4PW5rY3m0-ygNjcPvs2vyg%?}9APgLAW%6c>;&I#S!LfSC|vjjIeO729I zsGf-WSYuLRuF6L3It{l{^x8c8*dp(s`o2zBhJfx*V%cyx*1D?L3+lu#-shG3j~GC{ z2$OVP^xWdRUBz5@owqmoqD2H)4Tqi11O82;O`_Mq$dn8qF{@Q`vt^0lXdt!fPqxWKJpE7t3#HmQ(*OLF-${=Vj1f72l z3wv@Wn5MwxQK{Dc+kM6*I{Sq#&giysc3XPFc96v8bCCJ;Or{Qt@wiDT@8>~c&e(p@ zTI_@4b=}hj zj`{Ym;Sb$}ZhCyxJ)`kSJ%&;t`l|ium(Up9m*=7yEZcJ~4$v1Zr`YjKO|Q$s0>4DY_*8P8vo*hxC#IE@z7T(SOu;mIn*=^ zk@1eJr;?N@^ywG8a-#bCTXgL&f!(mmbLnza0g zoJ6|bu(RNFf+5(JidU@q;fT;TVL5GAihQ|@!d(8dR%GFtbU_=@P2-G3y$t0idW&@O z9AS7V2Lc}ntg$~Cgl6~F>j(2zNhchNLfAiNt6a;My;)#i`GD1zfYea`N;Z=^Z(tFO6ETsScn+xR`_xP5vbmfWFKah6L3Ki*(H2H)`M$TWI~rH&JOWOta(JH05*p zA(EOIj?4Cl=)t+k9JPi824ZY(XiU`E3y}|QZG|c?oi_idn=v6Bq~YYDoLU-#l96G_ zol73b3`=GQx#$fiNFy}=tTbkSW;Rsg^}15M8R14Ouqi32!N`%i!(mgabgff2 zRo0af<@LefSHG3D({9bvqncgk3jV9^1Z$eNxKKV(_OntuUJy)nn=?z`5|jCo8Ka;Z zI|YU?4hGwGF~~ps3D+-SOwU5E->(q>cYq6jop(Zqhm~-v^mwAXCVwXTZ@TO5eG5CV zkr87-2&=&}m~aiKZl6s7(fWl7S9D(?IU68gzHgLZ5wOABzA44R+_$m!Bn>q(mhW4Q z!TauRur=8^T4^vA<&I5}XeH-%3x9`-G0FheK;0g(1h;hu(me0{;?cjwOeg{5rNT+& zPa&xE12lX+GOxGPe`R3rY)2NCg4E@`OXATuFMV5_ij!H|Vb;y>IE!I~`*Ry^ zssuLtHQ9r|zvy+*yB#yv4En&?f(0%xxeJi(q~-N5oC8EAxT z#=|TfJg7YbOTQ5@%dcg|tBd+E7(7gY_|a)O zm9-G!$!XRNFi=+Jx7vnfK-7ImfYNzi6I5U%822kK{AZ&g zVmeHK+0s0#I*8>P*G}5RL{bJ1CRKAq0OiDMlSo7i#7-gWOuPmE*wgLTT~Ef4jJ>puG2UW2 z?l?;NIVA=iI;Ir=lAbpW_C}>~^?EC@_5ad_4+#2deFJVUu1g&`2Y5Y_adVaAT zv%dH9aqtfiUUn|~X4~VTQ&UEOhXw}xq=XaGVe4FMd={`k3>iKpYXm+Bk&!7N5=JZ; zfk@c;!g71&%z)KwnhcBXsLJ2dikvgj<@fpBj70T+7qw3$dADfE&tiw}KV)u9{onep z2hiyPx1V;daU12L<+h8VHMAw>_M_HSDPQGY1flfYJXjH-Mme7Dt3tt^_Y$%De~-N- zbmKl8Re!*M7KQ5lO~?<*#)hmeUanO$`E&L%0#dJJWtzdZq8m@nMzW|KJ1a8qBGhl0Eu9nA3*EVse{a+kt{H(-*Q>?hl@8 z(^end?wEvj5sMa>R7N(q_l2gdI1-WZN&aac)+Uva1D#rU+Lr;(X#;+vPCdUYs{ZZ+ z!#JE!%^Ix2omiU}jB^?(KbGlct1oqgt0l&G=J36$IS@gxK}0nr5VGFq`UPu4&?be+ z$e57Nppi07E0VjsscrpO5EB*P5mSI;03XC!Qmk|o ztYugq7RuDZSh+WZmu5zfmY;hfbR`f1`;3mJI`Z_}*-kYiPwfgqr0F?;@S?=}%xA7- z`CFJ}1j=RzLjU(zkCDKn$AWf|mXSE7=-0u>b|}jJ&HIs-du4L%@>1`xMa$zF$G(7Z z2F>;Hm@833%ie(xrF$|u6@6J9U#e^2rV2t`8p%IW-9Iml#AkR=zI)!GQ=yN-c(@Isirj6sAZ*tb8WK~FB0GyRp<*Mt483QdJ!ZhxWs1|B{ zia7Udl7MhTZ>OUm0u`~vut05#?PdrxwTBjMnDZ8|s~LKAiR z23vjj%B6a@k{)hJ#*0C@G=^IUmqe4dN4pS()7I!>Z^|C|-It#=0XFtlQ$KSS>k%kK zBSK8?&hKAB@4<^fj$)Gt-WRw#DU5_Uw+yF`5yp!%yUKthK8osL%A_7^-5dhCHuVvM zV`sX^Nj5KVfQ_b2aAz`zrW92*GHLNg{3szFrzxZUn7YqW3IFIn%cVEFZ)wx(?4F~IFa)vK`>x#qbIBo_W56QhQ@PcD!X{5}GNj$Rif zL+|lc_vmB@aDYv+$kVFU8y`$5QCuhueap$U!%_Jbr1YkF8~@@?=B}f8FC=ojxiYI+ z)vt!~S+`*#^34dhVEF%f<|_?QnTk~-M~urUA!5f~6KN{JlwDS}L2@yQ`E+5}?}7OT z?+(TB4?kp6HZt%yG@2!h#F4FQe3sv&RJe{%436JjLQ5ffO!3tsyuzmU2MxanP5s(X zzA1vbcZY`3S%~zSK(+E~n2XDGh=(#pE|-Qn5?1eud3{{QLZ4u`is#mkm^YDQG>k3XFvwj ziyDvu2ILQpb04$%Yp5@q6ePX|D@6)-Pp)WPIrX7kx~K%1B^wX}&pg{n>MQ4>ejjWB z#9*5B8d~#rM9j$hMb`iRsRKS4%!@I^R<3@g;K|ozBX%zm(VQ@{Bv1y5C>k&=&n--e z;V6G^W>&1JJQpaQ4wl>K;nW>HH9s@rKYNnLQ}zM+Cd4LNTjQ(5I;BGG)6S*y6xpk; zlcAH_|F^;a-6|QR@M(QkTEM(%^c@&xE{MgbQX`thB!b_>Ebu|BH7T#_T!d>J=Lv`H zq!n5n=4%1Yh5!@{{m#GOiu z9`J$)k50b6X?V#Ww-mK(S9U)*FCzmRf31iKU^Re=FmsnE;B6u?#Z!eosdk^){L`6S zQW`NeHq&~y)a<_|)LRd+ReTu!{3dKzxh*}-{#S31bj!`T5a(4AI%Pu&BL%Rn{*x?A zcYU%Q7S-Ak+=%)PI6}HhjeFGHMhS0ZB@sfy#Nu8QDzxV#X1hK~$JutM+p$)4uix{c z19vlc;YlcFN79}1l8gz~3wb2*N2m5U^6DK9BhDXcQHc4@^g8qv5TJA42XC{Ki_ z7rl0|shq@iUc+~{QimMm1=q9fyb%#tnD>(_38KfoVjE}dk|3Y86wKq(Y@3Jd6j`b& z-VofKqaWzPp9C&IA(36VySO0NLOhFqSOpIAtSo6u6-*Fps(tRj0jnOMP6$N-1o)m4 zN%a8EX1DrIjqpjph<0CCbDqxFs1>9z1m7CWR3$1}^=BcH~UxVJ8#Y#lVfC=kYs zo#8pfkHp+Sm{+IR*10>pA%k70fgupqsZ@A{2M6Te(FcJU0iIh_fGR%^Tr#1t@!o9z zcT4p2I4LWgf6jz6kAXVxeQfgKWt!dCIgRr4992{4*9t@i$bSi=iMzo`5JX|gfAlxa zSw-)YupIW=NW3Fje)h5`M6=0=F^)M+BQ#_?y|mZQH4Dbr!&I6*8{|f_iy6X10;v4|*K>QU`YK$K+>Mb_c5D8VWdv@_zO% zP<^=NRjN67a@o$qrpuYKCwCM9ZxTEt*L9m#4{S7HV2aL(D@r%X@zs8_6{BF%5s^A@V@EJ{nzu1!$0eau#3r9Q=0z*rSy35^r&f$ukW@~ zZu>xwvESVpYzSrZ!)kD8O-hXOeM6?>NF+zCqB5e4KpWepJJ2b~~- z?Iw-DhhB0S-a<{s+`lyHr9aIKP7Kz6JaqUYr(l}4UorAtyf|m>&eLqeFD4?3tr1tS z$82pPkKV_`^l9IIPLVGde@kg{kahXMPTK4RACa!{NnJN$s=>)bikX%+u9dvTM6h&9(HMVlItF>lX#g4f@k8221K#&Qq^NDm zaow;A{5(gInJ3`HIPNmQhS zBcH{nAZpo@PYBxd5|MU&#_Dy5{GfWP=c97OwNEzsYyDHJG5WC_uyChSawx&oG2H3X zPAmz2`>TMB2PsT5{6U)gqoq5`k23{k1MZ94IA`wYn8uELsr@xJHipyc3f8R+eIWiy zb=39QQ=1o-Bb+$u6h}|3esBAmO0%G%*o!IepxgWaIp4pR0{kClxo-wuQGPx$c4Q43 z^l@|MSBNMgcNYi?I9R6MUy#!LTDp^*0pFP2xekIQs5Z?S1sohPBEihn>9@a}+*kl% zx-W!CBV=!nhAOW3_YlLaZMhNb@p;daGpKUva>9`NEE1d_9xvq@o0$(^&@|*2u2A5L z_2bDFBG>1@>YOqN9g=6$|0xo|k1xJ5x}N!TEes*^QjC*Ej$O^|lQ+YWGg}JatSE^>JAP!Zb>K1BzwY0$U zfom`fuKSxBv6?5d5nhFCqB>)jBPzZa-~a zY;QL$D{L78@!r2|AC)flIyW}#tsg-d9^_l}0!#iP1|AT$j1cnW15MrCYr=BeViLl& zsFfP|l%^xUgy8NS)3F6x7rvijRiCi^;cL7Znf;=PRtkx?9kh}r~DdV_mn)9Q*&j~E57>*RSSd@U#dk(by7#G{RDdG!zye9omBkAO((&wJO> zik|TIeBM*ZV1`~%M`32)F>9oE8BTL&^t-_J^++}4U&pwFU=AWTOc1hF4yY9_2~rGn zTcvNm016Dyi^fqpTXp+6d0ZJ~u2rY5@K?C#(BC~6*8UntYD%(Du({||lI^}mMG*8h zmmzL^G_{^$nPm}&`fK^5c-vMHCb&Xj<0qpV^7q!N$AZ+#``F69YPgC5S5yQfPe-W} zM#D|A?zzXrhm3D(qimG7eXEnGzAvF~%=D@!eI_HTdQuKw+Mz|{kY3Et38^fXii+DDe5K%-;kwO9`C?9U)1+J-|7D0GnCHZeDTJ!hR>m(z< zwt|JE*9xi^d?Ep{^C*&SshFmEG(tM0o3v&;kJRgHqe=C^tx*L5qnHw-ygLFma`1wY zf}hT9!u9mfClZCFl1!Qq|5jp=tx*#~k1!jf?x9Y%{#)qh&{)?0s?eY-++udWc12qI zO!XWkDrw_vk!0#n{q*uU zw9Z|_$1Wr)%RE4vUa+}E;+??u6-TRsT%PtZ7Ee}(!GKtst5*gB`yQ-NA!K;p;YQHME zZG6T~r{>8w{xtd%eYz#b^bt)#daT=apjP8^aSaIG&%ulEgeHrhvd_9Wlw(E+ED}Wv zNXy>XD8YFGNUX6YE!o?>!+V-d(b~0gZ3#?qkVr4Q{bT$$B*TG)e?lhY$%) zi!EV9SCU~Ip9S#r*d9#&w5V{hnygT`c74$Yx-ERz0&b)Q^c>a5e+Tz6uPnuUFu8E4n<)znRs;8;R2^*hyFlp4&v9*q%PYF%q|pSIbE1E1uQ~wY|?`%2`+9hyv`UG zHkq&XCHbltT&f<{x5Rx3=?Bk9IVy z!8jj+9l!AS)3;F(<-5VA$%k25t7R!PldKI-rl2@zFVwv4FIaMrXW^<(6nIF2e~;V$ zr~whqKQBh{G~Eua;t=wqWSQ@nTXN=Py8QR#y@PBut+@Y5+0)Z#{ofT7b5u03@_M`Gd`tM|9+w;Z6vRS;KsIO;&Q(Qz0|*_fxY{c^c`WA1w?3 zQw0}M?uF=+oXOZW2{y&=4{u@)@}|1m$>;))dP`ft?o!OHj%7en)Bb&cMd#vhP^ z!nbo7F*(g%ye1vK(O6Ve=YFsPFL?3m9<*6k`j=!|Jc)(5s5LLL#AIS$Kw&fA$X}rb zlDxVB^P@4aYPOSdB={q~F6{k8?2nU#-qrpHtGxbi(BD}LU0*Ywr?XQ3CZ8jkIuB4y zx1~}knXawRGosf)`B0cX*R@b_l<)*IoF;=w@A4nksMHS@aZE}Fn=I3WH$E@|(1!4@ z-qiOz(fHl#D9_gfS?>S~)`2d|1=)el@jKQ?_P2DLFfDlV#R5y|O&|ebsuU%P8xc}p zV8`pS5Z@$X983tQ(3*b5_yA9z$PNF%zKw`sS4S%BlrcShY@A?p+l95wgN48I-h?QO zE}_xN86?mj#Rpi2RR%3AfceqQTC%Ceo)(h)n1xO&dE-a!D*IrZv$|e|o-)#h!)u-S zg$;-a;6Ppez0Hc*-~+f2$^Ug4@#5ks4MH?fC1`vjc7ceNL|E?K_)~M3I5`i^-yju> zvr*0JwY1^zE`x#ZtQva~YRQW{ZZwQkYrkD#TFFcJXH;DTJ74JoH^ulfEKSoKsoSrJY73 z%{3m7VAggq;ul1U1U|(#4$4$lK5%6ecC&{rca+A~1gE{qVD}D_6%^I-kD(&VFeGL) z$|rL?aV+QrI~;U1yI#$zV_^B;Up*6L{HD_ZgPx+C6qmksLHqY3-bMa5Mlj=Gbu!|W zW8jOo!TD7jf44w5sppsJSR3{2S%@8ALMB|X$xkDKl*35i{+P)CerF2jY8tPFF^D1K zD3WM;7|rL(Cj>!x`C;qiNd-FmYYs(MHKXSBRZ4`V`SlGFFsy=~1W%jcrl^W~%~=;5 zQx;%A@uL}o97Il+vPeB2fdQ8&lnrvfoxb2Ql6?DCqy~le%Qt_#BX^6yPZ8=@wi`Rw zf?#OK8{AJ`BwKiiRGB3=zA4~H)n zp7-AcDk4$qTkr0iLm+O05_m>+3I?1_MEiF9wVNj`qbc2VcG9FlHF7LzOCQu_h(jRF zIUhRb>P>Z{1}gTt%l+?E%!V{DEFwBO>pd!i8Z`&l=`{tnQ>AQ5g`3~mG z0XuXN%PsPp8QE5@lz@HJ&I~V!3oR;{vAsn`jd^sfdL07B3P4s&tUXdcyf#v2R79h5 zPb2(xs`NRx)U>9wpNr@I$nP+F6WbU`i*ZpN@kn*ZFd~OcAXrrv7`tPHZ(#$3o@II@ z56^COZFVp@y)iUY(b}nMcglebeczZ$2;GFjszPF*2ifgfryUEt7pn|Z-WQu(*oB1| zQ+-HXCJTpC-Ovwmk`pqR*V!PX3yYE8PwljMf*N_KPeCgZ7 z{f!U*ZjRZY*h7YzccI?R-#!*biAq`##v(wCcER%h{Y_T_f{lw|p z+?;RLNOr2QRmD{T6Ih+`&b=~Oy?!h0M;)P;S%NZO6b(kt&nHCrG<8Osg+1TVQKuLb z-=Zsy+ecNH|7og`@%9aAd+@2am>DrRS})vTaQASsd*gRUvp)az;otpZ0yURmFJ|Y% zt!|Uk1wa0KeDXzB?b@ebCn%hX#5uGik-b^0w(vf& z`*n3DFMRJ;9jq!kq3#dan!DOOZn50Rm(ul8`Db1}Wtos%VQVe^*R-O!xBruO(6^tH z-p`Vx-^lQZGjBb)m0sgKQ0wh*qBip(F!=M4n;2O7b6s)qS*UDd!~M>S(+mb~6%{o# zRaKRh9xB?Sv#~M0Cbq>t7wDu92-kj=z<_j#TpwztYvSC<6T5>T2QlKN=Hov@&ix?p zw-Aj_@$5PbjL8c9lzj8OGsQKsN$Lt#3sg|Uhi(G-Q(wxH1a8aVp0M=kKjX>!n8et< z2qw^;LkYD1jivn>Fz?k9aQ-w0T&<$c2Gwk8jmlVg0s~lxAhwIoSfj`8psuXVHlTG|HGS(WXZNDa2GZ1q|S=EVw*K^f?R z(D3xL>na`}7bQ{~$b=g4Mvx2SJ%rGoio#z|BRG))AGog#(w`R0lxuH*nfjjg)a83b zb9{`X|9rjkd6oHCDPM2wyMQvL7BAtby7oIScY2*g+uKPLkcghAgy%mCW$~>oB5{um zW-;`0KVLm9^*|XJ8_)jiQ@v3kE%q;WzK04>P-yye+hBG?e|PUme5uJv$jedwJ0f1l zgypKHvKwUZcVkjzZL*F;3YWpMz_&kqfn4hupv8~fQLs>W2xxJY^eSuc8ALGrl7haP zf;FO%UL_G z(|++5<`BrRYiIxWwi{S%3K$5}{}Oshtr1BfumRdg>7usRQ*bzH;Xca(=Lh~hdCmtg z5sz8FV)yq;+CvOU;`evv0Rlt){RE8E-w*vNbg*O)DVWl7Cgq$OvBIR_p3PJH$(iW& zYovr%js(bkPc(LK{X*G@C&lz#w;%xm^0OuJ_jp*5o!|0y=3al3&ogQQK{~2yVYqc7 z&~8}93~L}$5HNBi?qW!?iQ!2n8NVS)ede@fJ!q;Q`R&4+3=^Z7hVClvx5UL7BSx*hj7r54)x?fC(~U>4fqWVqPB$EI27;Q{*yHl zG0t_WPdidX;tgisy&bVc%oHE_Jk}G3NMrGR2%T#*q2i&3hp!J|0nfO6s&Sgq-_;5U zb}m{^a&K+;X1#lsS`@pt3*yPK%P_RfrIxUldL|o;k+fxw(0Ny5Cqp<^c-$irt*Mh(_~WA2vO1cnje4?kYMZVF{MAF3?Zq8cFsl#Ynuf{*-9vN-n9T!7G#-GsI=BS( zlkbuGkQyTHgNpBrA#AV6LOb zQUKZQx|N#PbgK)ucMd>9}Zs^m(zF0zPH55>L&n9 z{H&UYZy(0?0m8}es7LblCA!kpiS+Q_oVR!o3$zt1@L3nA z7WT^@Fzyx1sg4**)h>cS@~R{ada7{3k+rmNB%T&dAC+QtlmJFk-aS@E1!ji3LV@nX zFN+X$MkOrB4N_;%MS*}TEPPos3@;~vDSA!XjF7?$iu5>9I;6-OSR?i4hj87(P%M_0 zlm=lSh&%^%Wsr^T^VIhA?{4ZYL=OnS;E3DYYN(OTR3c|avKL7dy0sM5aNDKb@ZkXi zN}vc+<*)#relcRSDH2glh3J<$je{TN6f?WfV?!e#{oa3Dzf4H$3`B%UuNHM2G8;_M zE8YLB{7kNa3`d${i4?ngMj&ywI~Mtd!Yr&o(J+g@AnGP=YTp1pV1OCH8OXXAGd1z& zw(=xX2CWa%YAS8fe8g6<>NNz0RZQ;7Aw_xJ(aB*|Wxs3N*QFlgF=p!DI^Po!3neVt z@Nl{Gyr>KX1!`~oQ&H!(yEyu#)*+JGIpV5eu&d$*~u-=~>|I zLM#9gVpa^tM$aSaG@bq44j$?x2P8QUk-fTdRQ>wDZDZ8`cHCiL_1E*R(3ps40v{OR z`%|1*079pXEOO01KRW`CdNxJBHc_%=g+(MXz2I5$u3)$$m3HG%S=es|oaK<}3UNyn z`TlI6b2hWPLIuf*5E>0!8~vPIUTv_&Ke4xP{}&6wGAKZ>5|yz}0XSzvvHOeg#SmjM z;9r}eT4M0tt()yH<|!rJx6Cr~ccQ78dGqeYc5d#a5qrVVg!Lf?vC8bKN#YaPp=2%S zweBsH z2_;-U&}NJ3vK)%1fj=ny?|ULU{5wLOi+deP-F}&vL?GkTL{*VM+dND!|Mq;7$6=Y@ zZM{Dea6x2q#{Km8Ldvgno~zVWeOE<4Vu@36aLw9U$pN`4mv(s^>*OpxSAkTo2Ei=D zjU@7?695-2CV_q`?MJ65C@u-Ue=LU`F(&-s60JAYrr?3FoM^({MFpfq0Z~yhKCxem z-05DQfv8FykmZ{-gL;1@ zS|=`B0>;;>a7hRxWAcZys9|t*MAuJ95BK=XZ3h7?LX0v^!DE>s1RQnoWhhb>q&Xz3 zHI^0NwfY_E^V8GgSI27GnmMn!53Wlc){|d?s*wyw9+(RkNF%EMH>R}yTdQI&uz5_K zWCN?y!fex6tcLHit3!ZLIfC{}VeD3;!?%ml_dW;1o*_)F_|A9Oz+BoKR(IiO3L1y& z_6*$jrC}e%#RH9D`A?jXFpnXDz&uI@zdp0{d%FrNEVFD>aUC^-aQ9!bY%7`>90pTiXHWGrb=&`M>T|GuiToF3oiG8-$s#ELJupPu&{nT)C;fNAadWKfr5WK6~mfs zEw-FLSB?)t!_m7s^A8|w7Mrw13Y8gy*;1KrYbH?&_YPlt;0@i;tf1um*t@A|VPhdE zmvuuB7CH|bMq(pMS2D~~_LwVUwc7=6+D$!>o20O-FJs@BkJ9~VT zKN6x2hf_zxxN3OyJ}QV3B2%j`!3P$b>> z2q^`PEUiMO>4y8?!bccc8n$Di(8;C57mFjsf%Lp#xb?Yv_RZ(4z39|u;6Rl5`|caLl#vy5P>gQ4*~0d;8qw=ns~T-!Qt`Mt1BQR}&hAh|Lc zQd1rizQyY@(z$7f!v@=xXtIVuQz)*5t1Y=o`bo@L=Vx30=F z{aHJCbGq*{&C#9I^cG&UdXn{Ytl4z90C(xl2r`mS^aDfDx!h>)R?xw|-bA1WxvL&+ z%J`F&lPruT5^L%~K7OnYOMEY8l!>4tP$_3+=*-Y!Rg0|>hb~3kSApKTkMHW8h>oDJ zj^n&n?ESjY&z4$*A|~gaF9|a>t;(yu#V@OPGV;{Fso3<3mNl=YEP<+R`2mj$bL#WgrXjG;*%DS;jgl z(AvL4Bps*yO20ygL?EOXa)SkGV^mTEwZ^A||GmlMfC3o+_N7+fha&{>pnM=mZ6OY1 z9A$}sl>mV&xX=tiV(5&f2_U5O8$KK=4#h79Y^&A>4)uXS{PIKSQ(y@WCLA#EsIHGR zR5Wi8PfF?-O&U_)xB2)L761xNui^E2_QhVfF4Rhv{_*l-L}EAfnlu|moTW{)DCCV) z?upFM=?W=xt!Zuc^Pl%|4O(e-7tX@r6<-rMWAD60vdp<1X{X~y7oyj_PlkQh#CppK z{uHV&udbGI<1z=(RJyrwHiiO_lP_Ca4O;T^+U>JfO{$-6eXnrmclvR`>tr_ZJJWgb zSFACk%}BGH9#0*%-9nV{kdM&C$u8wX%eywq<9S_bM+8q*vr3O!)M+$r0egttG?Xs3 z*|?I2Fu_0 zk3~S+(wBr7#S?fuAK%Hpt3yFCWQ>^L{Z$bklK7W5BhK%;JEq{KD0oNT%3H$yFdAPS z^R|rckH4Q{3zES~FaWJR0Hp1j^U8qz>s0PvKHh=RQO zyA%;Z+}IX=qe^cGbYu#ygwC&W%|3L1Ko>>5K9RzgO0%Y2{>ul|;lomUj0Hx?C(uG! zd>M-}Ak6jc%QuwZ0gpGLi>dFl9O8Nn$tSTtuJLiMh``&@Yst|7vthIl?Q|)2Bu9SO zL;$y#3RdCxpM`7wIET&`D}D-vKB7z+ccub|x@IqbNj_GbvH3bD#V88b#g!rjqe@bn z?|@bkIZwraAZSs@M;e$ZwW$caO24idn^HvWkI=%tPsIbbsraXR*s4ulBJ9)a4m)tL zTomPp3%KydA6ZT&$RvQEd$JFilRA2#)J2HOoCtRO@d`vEhqN}Z2OS>G0!Y_(2cV97fDzJQkP=t?pvqjs} zF}%%8RKlo%3@Or{&gq$D3n^yR#0iA}_*mHDKq?2Ou68zO~>=B%%o8ZBA%G3~!9 z(o`1iV2X&*@TDou@cpM<1SErX({gbQV1LY{s>|JY@P zBKPi6lwkq^+v8D+IL#S1KReidJT;bjr5Z=}f-n)>&Uw%`v0lRpHF@o(yj?;r{SV;< zfY=_Zn%EZPejC0N2ZZER6C46Uj!_l`6M|&u88MZ#=dNG?P-4=c+*DE_=Mh>ccVo@I z65F%27y;;~VDMuDSc$|P9f$#;VFXG<=e#KZ%o!GKzVR#-0CWT*htXQ}fNhnuZ}O2e zaK#}6+LC~fEDDI!w4yGhhU=ODkR?E*3ihnlQ6a#78d_^u!TPm_DVB^Qy>%k3S^9$m zBM^eYwt`iKoBj7?U3DCYkQkOKQ}jt?uV(78U%dza`{b2$d{|->LWw9hVjTva4lw1j ze8Cr@ugNn(0dMOA0YSQp{BYBEUl{{+P29}@u$f(S6hEckj?-VMoSc8nHK-{?{`$*`{^^R zZj?xHO|{;y_#r`1C$kz-psGj^TK)OM&Yb61w*oj&$>6a60D8RnTzR}mgUPz{!53o_ z;TK#G!l87rh=iz(ikXos^xw*|UwjY@LH)*|lL{N)0uigH+Om*>KNAK3Dz#ugS%n(a znWF>Dw$gj|Sf8Vh1U;JM7Q(F&eU_&~`pkaxdUdm%LU1l>(-s^s=rO|N1#Yo~3EygG zR(vfj?3a%S3pkWjSZOWMKwXIk1VPuuDdFfB^~Pgo9DyHIl^30vT|sWu0480V!PvI` zdUS-do-kahyk2Yw&7mLVXNU`_=6q&EA?+XmenB&cA6Gg5^fL_v_VU>W@!AjhmlfPE z5L^b8dLJk>UF?rVb<74AI=VM$Y-c*0^}Q!!SW1ylb|WWfy@>#>|?Q$_TZpT!Q7p6;oKuLuQUaGf$1!+i7vuC5q z3%^+n+Z9pcUiCh@%eMD7u8Kb;@V<~pMdC{~Ny=QsDob1o)`#kR#JDrRk+AGkzp+qQ z>dFl5vP?N{R6XL4;i%u+{i)LMa^Rr!RMq%IMPPYtf*jOkxKD@%Csg$x)g(_4Qx+R#xc_<>>6^;{*akrHu-N~g!}8E|162oEMZf?d2;`NOiU&!@!_;bhM#D4V=o#G3 z=~uKn7nuSrOU_=~eki;)l2#qXq?Qv-=I$U9AdZtr`I}=Kvi9!zZ3L$Vf<`{;9hd!< zq}9Ga!!1&7P#6xG(i*DAg|Knum9Ro<-Oj$b^)A`r+}*8OR9y4(Ed^Xmr5=Cixwp7T zCX{hou4FDDe=Wyh93@@7-s)r-Cn3!O2PS4r6tdBv9w96$7b4hNc2*uTu|Cr7>7M$~bse&>{r+;^yL~|95J|vtUQl8*{9WmHp{@exfGi60b+M@5 zs?n&M4Umw>~9Y6n|~5ADN)q1WS38sdq*%cPh=O#6u>Zcofz%NSJYW&R!eRXCl-T z0QaOJn#z6p`nSJOes(-56y`H$4*V}h)qtD|sIqN{J*S5jN198*FLp4hAZU56y_@|u z7=_VTRi(?MSs>pHBq|_b9va%hV~9B9CRQ?do#9nCJyRo!hauT{-QT8FyKZbQ_nb_2 z|A+e*Pg^&TCE_IiQwwj0^eD#u&t0U7sT;SVy9m2zyE;;jNKl>d=`MPjoSE) zvId0zLqlIHgp|q|+C73NpiV#`9biVrT_<4FaOB!~TC-y4mVjILe&%t{a{NLWsRl{l z+O^7IccFRL$+Qg*ZJ};NkgIZzY0Fy6Y}FIbf632 zD5)VhJN*kOz(CIr0Q9GU>d)s!`F}vrpy&HAQaLycy*URZGEwn1LyAl%kD>?V@|Voi zHWoR;@R@Fw@LynS4oOz##>}-QD@fgP=229)^1s+xP5CKU-{a50u7f9J%8w~@$Da+^ z9$umT^m^>q1MGCEG~HvD@>Mhk17e2Y0I4GI`gl6Odta|`?HAt5003bCIr|C>7$yN5kJdn*JMf^r_|)SC)87>En_G2p-#8f=``|O?U}M=)Qkzrcmzr05Px!JnX3R6m zq#R~9ag*K3WDl9=<5>sMO+SUx3?)sFU*iQi?bWg!Zq;9abqm1h zTR_cM*cHX}Kg%eK)ohLo0M+w#P1P#w_lXTFzg73TIlZ#G;npcYQtnemq+|=WUQ%=@Vjj8zLENC$M3s(Svw;?z~1&9b2CleWv9O3a7p^-xvZ^tiW{UiDx*p=CD`E zg&Bzl*jZ&8*`I_k8TBPQhTfMCexp?J?kvv2%?)Q5%BPhuf)1fz0g_ov^Dg10zOp-K zZ7wO21GV$ngq2pMtZ%@CIl_lPRVWrM`s}4w!WK)w3?nO(;HklK%6Jj-Js^}X4=Q3G z)0sqDot#a>mmig_u5px_xAmA5bGL>0hh;dNt_jtF_d<+GYg zs@;>TkiaWFt{5;@FQ_aId!t699t(@ z<9~_Go!utnMjt<&V;+c3?ATTE1kp_oOtDD+b&}}huoT7byO(L0tsRWN_?Dq}X*-Hn z&gC*0_hE(r->kQvDuD=ks5qDtajx-BLY4mAf>a#`*aA+i`BM7%b`-!0iVerSelRE? zBs0-r=Y7O9Tf!o~xruNkd~EC2dmkFolbxr+Va(ehrudWy$bIO}AYKDXhbPp1N3j12 z^pTu9x*QihlO_+rG@DDf!p(y6M)?x;(u7BygOjlHL8BKn@r@siU(n!+6e$E%i9`U! zfPX#Jkyl+eSF==<;bgdPO1uj^SVEq1SL@{^<^KCMF>3MPSSQZ>cG03*e9dgH-KpK$ zgnQ}D6x{MywK_hbeWJC6t1!K>3Na`RM&&)!vjw+^mPt-L2Ov(sHpP`chP^ zD&GBF`ADHM99ZXs;+A`&{%4UkFM>%i#)78R6v5#k`ltT+D{^BK+a*`UnNk@RBSP7Y z{T0Wi+@x6KM7VV)*J+b55p-c-MBEdnng5OJEUvS*>VuXqD*YjtLbS`LjAA;MeP6xr z#)re9)OPw60^Vlt6My(=r>lEE5smqQb0ih5mGRA5xHxLylzw{QGBK*aW!Z%ZR?*WN z`3x;Y-*3)0#j1cdTTF^8r6!?`0Fg{7BzOaJgVo1PHQpP+V^ptmDR$HRx3>X%Q%Y&% zae~f-^oWxkYQ_C>fF1o|&hG7q{Y~ghKerv1dZy!KF&@JG=I9!I8jc5P*yM%>XtM*i zul4~@wPPkY(LFVy?mZdoml*)b4rD$AFym3St(cjU!5%ZhxHzpcaUqw^2pT^`zFLwA zFoc}uFSZ+i0a$RajNUT94B)6rM!}b(KCpm^kN{w^ED{UkqHJCG)J=J&PZZ@JgDO0T z8-NyQtn!?Jkr)Z*=4mvb8$yfu$vr`OGa>Q__#E-ZhhD*i(!jvjV2J!XwQSDaO=aPq z;+h%`a;FDLeQqh^zPB+SK(YotauI>IZ!8UdFP3IKBixB_lyRYRA*74f`ub`vKsUm& z&_4U@iuLlOy}8DAf^EwsU8-14@4iGu+)b2 zuYS~AQbZu|2yB(1My>?q1jj|{AE@r)QF`Iw)fHnAwKSZ+Ou1iNEw9l;E=9O3D);-~ zM&xSni--EyC6*#_g>Gn=>?ipxEWUcW^0&VMoAD0wC*(f64Sg-_s=qkL`uS2ks9>Ru zaD?Hdz4iBdALkC0?IS%6B{JjM?0M((l#gUB3uR&_S{)qGJ7NHco>Zs9D5Hi7;e&^K z?qme~o8rbX>v3YKuVFHGaM^+ySraN<$L6-k2g+AGU*wH@l>R9Yc?aY1|EL@7EdObs zB~KSF6jEByK`4%itvBqWB5&;j-*%jPG8UIJ|LhSW!w@7th|K-!@6ndi-Tha1>O)PB z%iv~5MMU7PfyxhJc!<9WGHmz$saO{RABx?E&d$OD6X0EP@T+=AfGzR+>jA^^k4lKK zNx|o1FgRD`=+}c(U_o16+~`eFp=sD=a70xFii}HP1C`!D9@~>WJxcxrZOM^x9Z-P} z!yTso98Pf5RagD29hA0k{zi0Gju@K(n!--$NMuTj@$2sMh)0NBo)~HG^D=-3I!G*u z%HK6$J=TA3Xfjl!f-MBb}Pib zv1>&%5csT0|4n(VLFLQ08@WKMW>bNOP7DN5KUS8X;6ls>%&Tc&Gckg*ovP?M%n##m zblv{Zwm}Zs{hGwz?&pp&EaHqEI5St6W! z38?lQnC)#-cqb_fIq#hmL{rWtdpfC$tUvuY&skm`Ka`HnJ3Y^>U39hKhrZmMtdgV$ z_(Ux}{F#bxc!?ljemF8?;tphoKAi4%VuwoLZ=XL=F(W9cEOhOXm(ur*-&2q&2V$0uGt+LS` z+gv|;nh zeQOO>nLb6|zVd@Jd2Q~s+jnk`TBH+4G7lV^E6}rKGeOGp{|X?w+;;SFkp36rb8YmP%#*`MlCx+hyQ)BWGjx8fAeuTXDe0n+P$yUf@d7HVNhmFmC*diwZ4;x@44ed zO|z*osWQo_uTmmWsF#m6L=ewIUFZG^JPwDt_CBQ*g(@^jwtH93Ni$((GnW)>IJ&sF z@a^LjLZ@IaHT7$jsS&bn;K37Fnq9sJ+pr4m5-Q9f1q6GntrEm)+aW@<=4R&$fF%13 zFHD&@o^jr%E}jP`Q@@njX98Z;#Q9X#|2KAY(O!a=a*7Wtf}1=|y-}LBSH@#^)Spg! zm}CYos^CgI+Jm=jz+%bPoObtxOc9<2v5we*S5QG$Xr0^U-_utJ>}{ncj;`jwAmS-o zNqTR~@fj-#b#~otds&;M0xci(O9Rbm7v_P`$Xy?fxg!>6q%_c+xx6F&5bCYIfCpj&jj;xw-W_% zYw`b<)!zM288-M)@Uf5JPI}>fuzY!zW{@Lu4tChhRcP-NYz8nMryC=VpcOxL`(mWd z4)y_aDl@;r)(Tsp&lE^>WC5VZ`$Rn54CZ;U0E2VKr%w$ zC&o5-e{&g*1moaq9Sr|_G6%~+jW|GePR9JFjnL+yo^G)(7aovYg_%g+7?vf8Cl$38 zk6m&F6cO_jgagrULK$|hVxA<(0+}%|9Y5DDhF*V3UO*z4V(<%uY#iQ3b>s;3xkj23 zoN80C7d~?lFLnf?wD0rfLKE|%HQhr)V5l)gL=ht`a*QT4>*?cLJIO{gRow9^ZgXN- z2bYdyZ~o7hWJ)uBLKZOn5z^l>HF9+G5C2~pUEH`QpKj_C%1;ECAxAWC&`*$%Hkw#n zSk*SDAoJimPG&!9mxHHMp)~vM0Y@mf_(- z!!BmBL@V=8ED+7?k?#$J1S#@e?Ri$Vpo01E|13rDY3S)0A!K`k02*o%U|-VrJ8+~= zcbw%`DA@f=p&hji-e4?Ueul9mqWPRq`Xe*cKRgf%$X;&npXPTB%Jr)s27qK4FRIyf zdHR2SSP*n}Mm*rVH&aq`c1ybL*N)joo~?yawEF|yyq*@NGaU)OhtmgNL}5d7taLpd z%B%jVDTCoDVjcY8gTxJv6X)bXQj)P`a4Wn&_{;uWS~r|tEFm#l^-~|q9%siSp|41n zVGu5|v78Yq=(>Irj8mXa6~@hhdMrHe$8a5%@P3E{VnZr4ciKS5m-~HTL00@N{Et*6 z(LmRDco@}Rp00jjv-hqq+zt7Kj%)rtoM4B({<+ypbDL3B%}`0k17hBNT`rcx^Ctf@ zR10Hy@NR7_fb#$>NH8ADX#cBK&=4t9g7)r3kX9d=B~D~%rP67^)Nyb^LJ`pG#?ZQR zY8)hAK1Vg=J5X`#C5+8KvEyZ61wKG_pq5)8Je$V|>$rH{nY#`Olb1l69 zLP5u8ZmX!6D5OE(X+zmPJ>)iEq={1?dK||WMb_(bi5Mg4Q%TWli9y0%M7>R@duAwt z3yM|#&eFs+R_JS-?Y#@cW;dFCK!6vEwu!X+h!RPS((j5ln;OZEb<0+phAS6Bmt*MY2()7Y!YjHdks@Ig^#WDokZ_LGi87 zBS@83l;|$|@F^XVD$Fe>o(!vgrR_XvDll6?Js}Jlska-^Ffp|Ip7C$uD%c4b1!Ts> z7}i_g$+M@_){eOrvzIG-@5(APJwSFpfTMBpNkTtf7f}mTRe$2KFgFGP^RZ-OS=WHo z&^#XaS_+QU>pxegr&qqOCHv)dhS6C6tje(dKA!BYMNmw`Lt0oJK?@XCXRpKZDh#-b zxr8-8Va0$2!~fO`fEpMQ3IM^exK@kbK+Ka#(sTj`>*bcR zWnq7ejE2HgNucGCk^Zf4HZmc`0Fpap!gy8H$jDB~93u$ObL-OJeiVRQyk#nw%YiOl z2mMc!pPX^f^@L!rNDfexUaMf_cLA1EcRO9l5)GTY)}q1CjL^&w7&kE*5wb`s>6$b# z)|E+qZs2;Ua3u&FFu}5j#*zd@1Ggv9?tVpN|9kL^IRI!dfkfM`VSXo;E-pWcc%Sis z{yMc{=Ym%Zg?Mm zNk!+Pe}g-4D(_#>CW|Z8RD*1U9zS7#$T;C*j{%&G9ZDE%8=V;Zd6+|8C%X5yvwCXs zzloy~RX+2@QS;WlWr^3kq|3j%G|FUYrKtYov(#Fs3Zbblu)PJ9*^2*u!Fd}*5WbvR zmSPAiK3EdaNpurLAyyj^CMZ}UxD_?{cL`2kOQaQWfkq#11Gb@FeFOmDu_(emwc*f& z8NM{{C~?U5X{mZXEiSJ20o2K9axH$9RVEO_yzVB^ z@OxUA%{3O&LoH3`0~ViI=knhH*+=8NVGQ~}R6zhKDS~w( zdfkfJK%GQeV{!8?LyJFxv1fKvm}c>7O?2H;?{7oydK2qQT-1M$KbO7#etyFb2(gbo ze9juA&ep=868?c^y`nI&*tTe=%|I^D)aSAXBb%!EKj5p*JMpPtozs<6Y|XU_ZM_&-AdL z9GOn`-kY!W(s@tI&(2F5_{iNvKa6ZFc|Rw79a_5k=eHQY_dyY98}Hz&=YInIR?i$o zx{Q{dec*f^d`A5ofo74q;-`@52h|RaIIUP?mml&S5fLnthO&;%Pp}c0&s#{c^p)E1 z<%|432ZY}wuH&9E1zjmZFlb_U_8VBP|XzPi^l z#9`cGIf;-dJrm`5?L_*9GlxH;aTzy>VN^&gUM)S2;r;U`?5fm7J;hdB(Nj*7-xVRy zw$_;8U;5(D*OzCFB)0^A9@0BBbIq;D%9>V<7Nt5e(PtKQ*?!vFQavf9iz{uM8iTX- zN@h*V|NQk};ygn7#QT9#ugx^sK}P1Oe*IG-e|!zL6??CC(QZ{a>dvCw7Fj`g* z?M`H{=$nsxo~X45!!iG5Xh&)eeOLS~M1I=@6L)oF&#R@l6uQnA(`!QTlMf<2|TRpLATttn{v{k#J+@tc_ zeg&ID_S599HTxr7zVVOW+$*Ehygq(V2E7(9KEKV)o9^vi|McyLGy#-bZ(gkM2`EI( zN|iaN+tn!A&44Gl_)vLnUp1_u;1OIt38oj?8{=NjsH3(F$X-o9`t4VGu;$+N?alO| zcUKY;hiu>HgG2QAD@%h+OLv_p3Q!n=B)f22hQSsGdEoQ>1BBCI%J=EOlhEx&u|O3~ zguO+|AOZm03mr$Z1&08jVvJ|hC@oGQ7Udv3`q>HjL>jnIAdKg!6=uL}SO4l4&w0Nd zV+0Q20RN(@wb$wPKN1*-5EQj_DrLxpuYtSLLkvrgqYn3zKezUNQ8J$_Rn%ryEs?X_ z?BaM&mmV$HFm6+6zO=Nq<~&cYA&^$FY+4Ob#KiiJ#>J(jz1;jx)?q$fu$Z5PoDr7wG3Xtb77OYlcs8(fj@K~ zlikt7{JJ&lMCcgki4Zb?q>V1wm9Na=9b6@-@^E#>dy=G};==QP4!YNO7ZjAyIA!() z4Bt_OK?`Ij+7XMn8mdK8Rh5UDxUl$ySG`urquaZuhVlxb8vAUT;FrR~U|6{~u(8`O zz!&6I!o>_l?VM6-0Dz~g1CqxBAKXKEEw=enr3M;X^-Ja*=ean!qD`u)%y)miQE{Nk zY0qY(j0tac>0GxrR5N-NGpzIB*@~dKO@q?1U|Qec6!GzweYSPyg+t21&G9TVE~$yG z;^&B&l{D*mi}U#a+>e)@yu9tED&ye%bJ=G7dIkC)@6_M_(%LVHyiz$9o!f z7pO-mWj*Mzy+uGLA110_CQ4(JJXVz3aTE4_=y`T#5SX~{8$iVOE!fBK1t~v02n>>- zXljon17j?2>~vw;fFf`ok|MBASyN>^&Mq(9-hnLwZ=1>(W{qh3fP-}vdr6=(4pET* z_s+lr!N!`zFieId4kQ|PNsEyfR?~_lbHd~E5$5~Ue#$Oy9wbKIvV~q8j+C^15pe2C z1P!icC4kVxd=TOroId^Ghk)HUa6LC$_07}RyVSRGv9&cd33Qt4n=~;q;-|=-Ow$xC zSzA~*_iC13+qf6IJ2;=bywq`!?>6RfaZ}W^yY*@Bx{*t%tgWi5l@tJ=I&%Yi8d^qP zJ#sm$)}&XxAw*p^nv1V_{e4cuP9{yM z6ew_p!5TE0$>^i zjbqB+{qxrcy5H`pGMcg_+vG(2B#iM}L!(W@g>*<|fOK7j>zrG>V345l3Mchd(DGG1sakTDr}p-b{pXL3St>nVNHq6n{E_0C zub+O~H8L{Wp)HL>v>$yBsDa`#w0!qWX`_JI>!{?P=`G`Z$>>ua-(n9XBTa+SqdkGQ zu%?+7(2_b?a4))V$eL`K_zX`91g;_5Le~T1ndu-W;U+clL#JE+ z@S(L+z-^ir$Fmjy6l-qKVf6qNV7t2YS8;PoPs3-L8UdH1qtgJN7tM+Iu>8%X4GqN! z-A^9UcYL|`UV-u%CU06+ep1K0moxE2YUVggY)Co@XLjjnq;iXVMM;8O_ne&tm#i+O|&*OSDvuf-XsXzq*(I zOI(J3@Tp+=6LW-g7P7PeI>U~T>c;lQq0vM^O~GQ=>x{&MK(vP}?2ni~2WlrgglQE> zuHv5pzRmwpT?7qm3r|I$VLA{1kSa1i5CshBc}vHvaRP-ZKYZCRyi-?=|9MKIX8HT} zUF)t@jc#T0>f~`yv~Fd~+L$Z1dP#D|`Q*#q?~kuS-#`5>@@%4Fp2$CQ;_`3H``2l* z0MImbjLE!pQE5fPLOB(_1Jy&H5?x2JQ-1;+lxdMZ&x6R}>f|a40*M2Ii8S@qnp(FH zj?G$)6q%G4G%!Z3-$SxsNM5fn+^|%Q6)*Lz>ez+lrzigY>l@epo^>J!0F~=u+)f53EK$HLHQ(X<^1b&%T+x~UptsFvG#R~x1uo=O zfRG*qAQT5&Rk0xm*J86|cWj(OFufxOHtky?j6Ru1eWupJ+MarxZRd*zB*8!^FK=M0 z_L{MA1s8UwZAnp66r{4Hl?e5FWo!GD+z=K$eDT?_{xPs%hELCO(Cp3c0H@03V(`1K zZ&v61CQMBJimgATr3MrjnQ4{@OSw|!)$aWMONc;MqNn!0E)xwsRq`*L8@KVBnG^eT zQWSE~|8~o_@k2%EO83-#PhkuQvz0GU7>4+@W3S-7K2;?uVfI?#(6@Wp+0s``ZTZ|e z1`5N$JWp?f0!c#|R+e&a1YB};aFo%hV_P;CRNIUt+*ENIUu;oh&mow;P_+Cde>sm& zIS3X?CC-aM*F7{3U0|LHVVPP%lB{%&mgjCY$ac^u>xmL2B5TrL^QM_jQNbdX9KTGc zK=?Y^cM$n#M63=sp5AVNfaF@eG9a|{TSE!FG*K!o*N;JQM*xlPdLOV+{^iufBqDdh z=3$(5yUkJGx%M1(*hM6Z1Xz%|nzZZKT-_|9cdIXS)54aPEnV@S)+Z*49H>2QuWBRh zhl|J75j1~s!2|4+wnF=#@K7IL`Q^^y7|h-G@b34>W^JK@N%C-OMSspX`I|kbm*wYBm(T>J_`` z^!F=cgeFG)ehz&)uNT7}JoJmg@Z7k&^D&+jny`@JMVQIw{(qGJxhl90KYr-VKLnr( z^4--pO^oR}o)JLPjh`t_+tAy2I+Sde%uP#*e4)-UUm1JJ4gV4t^Y!L#zC_ca%U)u# zAct+xYJyi>J?p8AS*idC#hUTv_os%Ig#~@Wv9Mc0#J-MdDb=|{Dajs6`=+lw;ckiKa0I)nBrs__ytB_YBy;E9@ zcK7o7#PEI#5r{vUY7Z`jZk|V*dyRcaC30V80EXNLQ!tdZhrihrQkQ;Bn-v~aJDzS* z9nhmo?;(Q&fdFPqIv%B(x|S7|+&&#?P@f-JJ}qq%`%so-)Q>kCJa;u&v>(3y*e}Gy)^-r|x$sZHmY{>lR}^mXgSrOCb=vZ-tnL z#N^5x>FV{B+v|QoTaD!})CNB0VJFj8cRJi3GCeMaYqfF~3+y={>NuaG*^C%r%?SfQ zHv=U}_z6$>me#{)PpwwYIj|c=_QXVr(e$6%X1FpbE;9wZnX~{3qZ7*pQg73@APR6o z-g1GnSEc1Maj_E}fbDevG1V~?CL19f-S6$(nz?bs-iXa38Qud-h@h=t2vP5su_vrO z@icH}FpQ6|dFYfL+Yb5eIj=ARHt{gW$tj}#+PV8P3826{QgD0jQY1B~(qWw5j)s=b zyskOil4p`TEqc|v)`*J_G*H)jc|=>IBBpf7fffoOx2l!Ru>e4Z4s(H_Oh6Db>s&E< zD-xaW!0L&*_SY1p1_Biw049)UrXG~fpb&&5W0;UCa|0Yv<}-s+AlL*qg<$6VA&@-I z_(%LuwJf=2`0)DrmVYqp1vD3k%EzLd1V~W7LK>redg95!Zg7v7Bw%7<>z__gQ> zpY-7+Ln?*G7wMLrt8;m6?5Avt%pAllk&gi|OXL+f+*gPPX-azt1U17UXdy=byb1jHxpo_*l$86MWpVqlR8{GIWeLfMW&tq75auU@ zw(bJMk1o037c%5?vS3XA)-yEhGZFz!nn!Pi%g|9!hZ?Gs4#S6wTU4tn;mMZfZA=JD z5S~!rknt%WpZWMcnzBQ971_i;(Ep&2{9iA+4oj29P@o;pyhIY(q)PmeQZikFA#x+F zSyKTYxIO2*up?F!Obo?1*$B_Mxw#cLszrhZVvZ5*K_5)-#n!1IHxE9n^VC!`hi)(5 zX#5+FiPK#8IG;vI08JxATPEqqbT?m)oA8$4;$dOULN0ObS@S_K~ZDW{#NH*~~%pS>h{0xc+6@{nGPH|Y; zx7BfR3KND#(f;sFCBpJ*E@=|Lwm75+1^bBoD0jF)RzWvvdbDs1buIu0mD^4I#XVNv z2Dwz??X@Y4YgP8e@e7w%lGaj&-6Kwa}5Op-hQ;v(tdj907By$Suw_zz_0SF{|v&272Aw)*Y z_4c~$SHM?%@LuQxprz6t@31%l4Kz=~Kp=tT=dq#)8lYsC(X@9hc7nl@F#Q>k3q&vk z&%P_(gRTY#@>rJwBX`BjxJCk>d5i6hVB&|c^XUNRIt>g-GJ)H43eZzHF2>257`pi{ z1+BGla?1(a{u70}M2xdKj|L2*-N_MB?ac6Z*!(YeplIef1VT0hR}N?CFM>dx{!GdU z$s#_r-i8|RRmHZ}?GFu~Do3$PM998W^p(ZMNOFY$e{*)ET1!!dQI#j=O(9$Ads5BK zs^_!6HJ6llEVt9a#Hfk!vezs<9`9{j74LIJ)F(}8A+Ffg_wtXAn0j131;8z}{UMktUy>5NCL_yfJ~7Xf>`Xqujen0q)#pqxu&#fu2rqLy1dfq{Ru;f{n#&`qk+8OR9&=2H@=OzMEo*y^V6o`Z+jS( z!4@lVAt{@@TO^6Sm{?Y^l2)W^aS(VFPujR2(JaYAf#gdt97Ur zazdf>Sc+>e5mP+qE4kN{vdr)J(*5yM&0V*jBRx^W-%{en(IO98WWG zCHN(wv)rRM?3%jkugLKA%5tNp%5>KwZPdER;bRS`U@5bJN9=U>G?6nUNQU?2YA6bo z1#i?o@Az4EX&E2*I`i>x)pvL5t?r($Gn}f!esFg0(Fl$xstcmQmO#~>?PzK-_Wp-) zxM8F^m3}GsNjmf?wz&^92-HfwOmS()i@#aMs&hDu#ECP`L6o#xaKTxxL!Ue~$k^U# zklR)8^`h{qJ$=o_g)c-naQMU_eyM)pFdvcnkdY3yQBYeR@b>tV!YL__K@5K;O&>}b z&-pTp%hQUKq$lXFi*s-Y-ak)7-ye}g;GCTp3-TG;R9EBVk%=2W@}OB3HWJziCFF=IbgyM8(yq0O=?g0ZK@A+GUqvcj zPP@Gk`PUt65~VrzE%8W8Kt4Bk_h0@eVGbL!-n8TX_l%!7^9c-bh+h`XvS|Hic+rVD zE$7gkZ>G$e!4q*%*iX`!s<53^X~xk``$mzG?6taJiFr&~Sh3M$M^nRFxsyf`4T1}W z8xi%Y6hM)L&%{{HffXdzZFb+SE$bsbX<)GxmiTL;F4)&`vRHw?_EznT;e}OZ;!E=zV}Ql$9t5B*AZ&!JbMSzY2E`}r!Yf26mzlA76) zc7P0f+tWh2(sNku?)B%1T}bb&XUv>WW^nceEO8qt{L&xD3rmyxGZ4?_Y*2-E;=g2O`C>o`7!4>z zlKO{=BSTB#cNu(7=GbtdWF`vN`lvOQp5@I-woS=roWqK*5K-Pw#(g?BYv*t7)Nd1R z1syTP`#_Ep6DaQF3ENekV#HnlpX>1RWR1{%{CQjLCO#tr>3A6#VO?zm48Oo)8z(rCwBowr$Z#w7q@3eE=LNBZA!{-wS;MN8>5>H$S-+BkTT2aZ^4!V-wz) zyhko{P{_gtVKgRwU{~iC&Z-Bp&w0v$(-i8;Y#jTj7+0q&W@DGEMk|a7wa7H8S@2!= zb^`{B4*U~`g}NeBvEf?l4?=ithfshA{yk~qJWu>I`U>w~X~1jCc*u5(u;5hRkx!Ce zG3P%k^8>%=0khhSQzp|7i?b01TJR44Yc1ExVYrI(?Dhm2LKPkq0V8@~c(=kS*{~Z< z19Q#)27`eD>A@&$2{bR%8sSUfXN-mmAuOMw$@HiQE+z)S1G3vA>h>I#mgqP=6f1~g zh!^triLZOL=k^`;1U1S#&h@r(ff;`d=1V_3pfn4xLBpzy_vhw`U3p9if05ol!`7j( zg=0dp;A?j45FAt#v9ItfY&a4I#%vu16~8Wgq18jwGC^XBezhG)KQ&d~D)JLmXO_0+ z%PHskvzrFiX2OGup@CTsM_)pQo+B)AF~bcrw+4W1GHQ%ZNF8F9H2S74L7?O&x!NE7 zbv}l(CypN%WAf67xh(ca%zE&$HFxXkAoDgkwvGt#)$p49?8;-bc)fa##O6QiUXPBs zfUIaD%RtnUfL~G#TL@2?FYh>6AM#k zWhWW{R}?|fZHFA{eLlf-POITT#1zo)0!9DF*IS20@kNir%kBb04-HZSN{KWgAhAnH zDyg)DfOMDSE=!1jC@GCnQqnELqJWYjq0&kzA_!7~@(!Qxul{-;p9gnl&b=q^nS1Y? zdk!PH_$?V%makI@-6~C9F~7#|+mMl8S4nxU(E4f$BUhmN+CAZ?cg!d|8GdHS`;QbG z2q5MwF1{j(mfh4GE!_z0Sow!Cgbp9R6uUWLnyAd)Gt*7ic+qI=5V!enJ0hUi=_3W< z!QSTkTtCQ)3C8Do=_+~g}9(w~&w_SBI} zL=ZqDqrSRZP@$7p(gHj}8_N3&RFuGzD-mB5qm|Yf$RpWfHEUnhcvyD&IB<@teB_q> z8-3h^ZsR&=mObAm1BmT%UQR-EV?O_fSMcprKA~Z5nC~aEL9TV z_dgb>q6$wRHnB(KDRq`oC;z%1>k6&n7W*hXx&&ficD^~{ zR_Wt^HTs)7y8dtpb2}X5a<{vf-1;rsc<8_JW}EfX;EQ+cPbDP%!JOGU93a42SOE|| zu_+@YzBPMvc;4Xg9~6x;phe$6R_9b1Q(_XOTsxzi#oMLu&!Hx|aO}mw=VobEu*k3N)fS(#qPY!6lAk zXtTn24~OhoN%y{@ytHVk9ribH_eF@e_)f_+2!W_AR|MT}FrkYgR|Kx|{#l{)+FjH^ z_Kct5s50aR_OD$K;(*f|zBY7t9X(T+OO8e&5Ym@8fuM`1g%gc=+Wn@+Cr+VUID={U ziEW7yB*aikI;=YoO#iwsC{ziqijGDgMkaGHKz57Vk#+%@i{S+o114>O|TEZrTA9k^~X;={h~2d_CpCzki1{NoOQij*%04d&zMr z3Ye1sm96g6p~3$E2MaMmGWcZm2fQU^k<Zd0N=Rj2PGvB0Pw}-A zQog&=%RTO*BN@>>a_Ec5{Om^;uXBJ;gn>QCkSk9A#wLar#D^&HCRxf%MCrDMr+E~&wDQAI&ctzjs+%-<)B^U zcb2qv*VO2^VD-I>PzLuu&yW&t@evkaJ?W%y0MGUr#kvkL5CTKLgXCT5;X?9Wz*8U* zOpdI;+w#1aqk}ygD;p(jFznP2C%1(`d=KXCs3L!zAh@%0*38Ej#oHE)AIrS}A_^7D zP`tT*UkYi;S~9>6amdkPzhs|H)k4-HJVi!j^4zQ7~oa z(-;A39srF)bjxs|Rpn^+ScqXCNizi~(FnGZT+I3(TYu=nOWfkQ|G94U#X~SUTWg3g z13W$weooo-T6vuUul%=zs+kS3*l*4GfBN%4@!q}1v9yvsNV9Xm0&|gug%SgE$zh-V zTpQ<|1^7q`g|FAkYK5G2$%Mp0h$swfhbdfuq2C3v?hySo0alncgizesZ3#H(C==T7 zUZy$O^m32N{!d85j*Yr|ozJ*Ozs1J(XJntYr4ygAM6z~IfFf09A?;>Yzt zOl$Z;bt+ml5tbJwvJ?X>wfl-phXupEEne=RC4_iyi^4y*S26Lj$fX#oyAelc>cP#C z5yN&1M=|m2+o$30&KhTCV9ZR=sx!Y}pBUx3iQ>Jis^GK>d1{~3opn~8jV9{ESC&x_ zd0=(O7h~dU=;ppyDAL9^8SIYg!5UQ-;{XiJA;RHzS+Eb*s5hA(bYU7vQ${XKcSdZt z8_ru~J-Q2NH|yO7bFOfilqfNA9o%`yO4L1*vMs9xnX1+?6B_O;xxx-2_%i?q@4J4G zx-jS()AHg>+8^&v;lM8nSjehW0T#Qz8!Z7|ysIz@e6xB)ewz_Cnw)(upr{4Ib`2&| zco>&5aRVe4WII#cegzY;OhFKZd%p(|col3RD?XU-Q7N3k6h)=inqu{t&5@#sEI5S9 zk2y)Nrv`{_pPlP32!!8MV7kwwCXA^9ZWT*1efw^#NYx&!s?kv##o=tHV_CwRF6b5iuc!7mQBk>i?cg>K9M+s z0O4|lGfYVVyx`-wclDUb{~G1}Poo8Xko(_%#Q8#s?=TYMI(N&IJq0NYd^yY4c;RN{ zPp;AshW_c;*f5@vQH}|gsaMw$;cYwp(MmQ^{!USX`i##8fIx}F^5Z@WoT17fQ+>K; z-r;(y5$CTA>S&z3C1kIwuYPI2K**`!){sHyo#l{*Zwll(~ zAs7;Ys3=A*ef#!H_zLLxgOJ-TG(mQ%0@l;RJl}2hVg#O8qOi}81%c7_pIWMs*AKEr z@<9t7zz~$MPyypD0y_uR!*t+1?wx9 z5#5_O$SW=&cB(GWPTudu4J2}j+B(SnEyKMG>KeD18b=r5{!d%pe#jmlg`L96NrDUI z!a=9as5rN1`+VN0vmM*@Ox;f!Q^9i7y`S7K)B3-381#c+KZZC30S276I1bJak$JB< z{UwSHAEB~+&!S?6=FeAXfC9a6uz#(|!ke>WLzHE3Wj$ai#C#%abvHi{+t2gj=F+{F z`7~tNP6rvO_c;lvKG18k{Ng$fMgUWB1&2KhZn03Z>crd-xHRQZemJeoM;B1-M`>Puxk@Yv?#_QDk zF4tvedhbf#+uW1gx$wrzD}xLnG!hYVw3nt6;U_;miA$0G7RGs|K6rjg7rM5!Q9(m$ z`=F%UbfS&HST<0Aj84m4EB-1Z{?_2WC@PwtEyXs9hC-Gj#CGTOlSAsVfQRS3HHh6;>y2XbgqQ;^g#=>r%KpERnx;x?voAtqTDr#`v${_X7x3B2m``}PmB*zHi|P> zqWQ^gR=A`;J~>90rH*ghk!thd+~sE(8TvVxJ8U7s+$MI!Hu^$FhC$c5UPd(GO)%M- z!xM6ICSjo}AppQbx5a>0C%*LP#ea6OYi<7?uEZt%Godm7|1s2ren#6t89yfUVok|< z%)vB5)z#5u$*Q!41nBomo9fxSws{CiI!*91Rxfqx`K}&#QY`C{(dO=}^VLibW8JX2 zeDog*A>dRR`>`W0%0$StX%5mdmpAt}lrso?tX^YKQIR7cHu2XC49p-`+vJJ$h)d}c z&qAjC^udvPY-#FmoYT`BY!@4>`?BhW#f|sl5i~>PTX!1sqg>Y?L~rptyb1p465$h6 z$6`Qq$Vgy5_ii^Isd(nrBo<;+o7iP4lv*vGn|xFDR@xZbmBU5WTFS8r2*# zG8JHaF4IT^@OXfaf&e^zo8(j;t5x}q62C@YSm`WOy}wpZ=lDXX3TmuwBL$5<2b6t% zzVTFOcI#9K(0&DJ(7${ztpoU{o1?JH!CA(VuE&tSuHM@huv{N9Rwq%Qh71HxSF--Er@~ zKWHfYyu|3MmeSfoytl&SgOn(VeivhTO%*t5s|;rd>(tTM;AG!MvKLW?*7V3nML6lEeZ@Li zzwW`0fmpvoi}7q{R_ws8*J>UkJVq9ngYe!##eCa)#R$?RucgDTx3LLzmC%G91~W&A zEBz73BspH1rFCSdMlg7OO-D4A{>760T$g_Yu=`m)n(EfH{ZHt&oqlH zudC3b+nCJQI3p%D zVvLGJppbJH-Kh`!(ZFmE&Kdd$8ovg-QgKMhUOOJ%$QP!jP0I zv&n`Gdl)Tq)pyes8^}18ms6lv4hL3iy|xC-)ul|d%Fd^?(d1yf8CHy7!)s59>dRuI zN<;F;W5pRE(uYswY>J!Wvx3c?`wW?PNp6Zy*(k;-5~P~1vy=qC`4Y@X2!C8_l$F6n z6SlCMhQ7U|s2%n~#7#QyT_=yi&sB=M3**$bL)T>xoq0KqNKz6kh$dZ%6a)sJ4%@vu z{#!>Xol-yQnmTdAaMeRVLQ3`fIW$((<$H(uJ-9C6WN#8Q_EaiqwPLs)vi)j*kq(u)H3SFw8 z3z+yb-?KxBMQ{Vebmyag_x^4hGZ9{Bntr;lC0E6;YO;b79!^4K1T{WExM)N=S2N60lSCn zAMdE!-^_k69zX&9X>+mUp<6zm-1g`Jm1-j?|MJhm;=-+4S;^a-o7;qmu=gb{6!&4r z_;+c)@Php(avU?^EB%>Jm$v}qdQT=L@@#}}pTipb=b9GCPQ|gUYmWHs53eAgEHw+E z)0Q%`ixQ1!A!@!FRWQ4y2v^FhVT;equDjY+@-lgT_vl z>?P@|SD^SY#$q|p^5rYf?uKaI=pIVGu^_s{McdmQGrM~A^rtoTL>5gLH2Tril{248 z^|2XNF3$i2(b#`A5W*Q7yd%;3@W4uqu5vcb5d3!yFA;8y&H4|38>U#NYlfdp$2s-- zv5l6}w-3U+UZ^ab+4pfz3wKpKhd(F+A7Q&P!XczCFHV!$X4?4ja~-z208lD-2?NSzN@FnqO`Kc9{tq=YYXFz+BViPR;WCaSKcn4_* zbJ~Y!>;p?Vw|q+C$Zo5V5i{x8cmybktlN+IV+;O;V7+rc&vuFHwy7$N)TgMI^(l)E z=*t#_?p76Qx7^F`4|?}_jsjmxC)j=!+3E>uXn$$lWEr9OM69;9FV7hTRBwMG=g9n( zA)7pa3OjcNb53E+m&t`@&Sd2;L$5?tpM0O?$6uRm5P`R{qW!7=4Oqe{;;iVwXcY4) z-$DVz)^!aF70Wo&j5NeriCYET2>V=JIc0>&uIm4ICouF5z1xeHo%<`+;OF=G4*veu z8=Q8tjd5!0`;*jmpIVPahxAm!+*Qs4W%W|5(H)UId*`2iMeL?0I%QULY(@?Ix(?Qt zl3>Sl*=#x*!gRH8pC8 zYu8mb$K*MM>b`qVA|f|hS%@A@`%(4gwCb;-mshoYauGJGSG!l21CpZ4Z3PtRswvh| zj!6Ow3+M?y#&WjDIrUi{e$jmA=+f}|Fu?)ZI$S%SmziL+`~7X}CHO5S(VuIM6*}== zyYs}VNP@bL`lQ@g=aSkvrQ7xrg6b2UBU1in6lDVcjS+a9z@uA*oOG#!nb2!_*__F3 z1mE3Ohss&b+pI-5)l{;C&KqFK4`-hIAr#p(R&YMtR{*ZpIz=dZ#$?vW-x7hP$Sxcy~TvV=+| z^RV3+HZWFzxR>P}iB=Y|2BFD%rW3(s61PRt{<%TLeMbt2`xdXb<1fe^{Pn>lmw zb0wlGE$I~%VW-yM_g@FMpL(@{YwQ;YTC!sKMAyEZ;!0gotzienBmW{4~s z$Vl#Icde|1KBhAL@<@iKT;lX-DoWzDd)xXH#IjFE%1J`QfgN%VAUCqDWQ;2QSg)3<}dm85+WSiaYYTV>i6R&)z~Yc!ck^ z3D`B?MJU+9U=NdCUX}G=i$_XavI$7qMpjIO#E2;3c<;pj7M6~ZRvSQkk{0YI!(H-4 z_j~Uj1Ssb{QuzBO^#Z)zgyF{-=tH@`g*8SV-9*`OmVK4vzA}_)EH?h}bSc*%A?x4O zK<5uY&)UzUFF)h$2INcU{KDl8Se#$&fRitcYE+EKyHC-obw_ob(YKYT&_u4QU9om- z3J41btdV#s1V*I$=4-Mn5c@MpKG$*sZ{$HSIk8)_dv4hnX!g~|twDzro)=sQ4-h~u zvDkO~z38QVZwqlZ8s2|vN%2+Yw8;;IpO#Y+G%n<}7ui?XI+{a@Y&gUi8?M#{g8T@X7L-OouS6AC3}BLSk{ghLz_+(Tr@adOuUvLd1@0`^P+w6-;)Drt{FoEoHM@g9v1 zV0PY`neT2o$RC_rneP?<#FS;1wM4(WP}U(Yt^K~mzIwl+Y~A^Nzuf-e+z|5oA7z`^ z9<$g8>E8PF5BB(TNt$21RxQ!QwTny7(gnb5%;8luaRROS?EPIC+_o-B$Tlk#dX{6W zqee}Nzr<-GK%DJWR8jP`YZ4@^i4;{pk-aKHujh2NjsX!n7pi-UMIqS4D_$39#a(H0 zZcQAVrw!9wTTXmWMP~C|0AU3pM+MhIH$wWdD)uN0dDtL8=>GH@`v~C&>F4T08N@AX zn?`=s+@7v08=bd7^y!WC?1$E|{3Qf``$_b4t26F5~*2`TDTfhH%P|Env4=%adEP8+U#2QwEMwdC~i6p#9Taz}Gm}qRl;>OQzlr7LJ?@(KtWhWuHix*eR~eKU~?N6#pcHm+dHS+Y!Z=qr$j0Tf$J;nP*iBcfEP zdO+W5Pt|=;`d8ZvUjCq{l*C=oU$3NL!c2!=f*(Iav4}@ugd{3%r?Ts(OSxs45mIX)zDWv7g zCXQ}GzYVUYt#NW__9b+zUn4i`(P?6#ZGMo7_SL#iGoD3yLRy-bzEQ9m!sc3$YlsGo zocqRnztV!?m$g!Fzci|3J3<0tZw*uvd^D3ZpZ&V>3qapZ(1%XolV(PVm6$~t>zU?| zt@zpI!Huf4&&}Zns@^sB#PQ>eoqu7+qE|lrK{l)@t}$w2j$U#vYHndnLWV8q?#kPb zjO1N4kkOoN*+_7kI9J5^Ori72tX`X^Z(FcsQEzPB{(A{@d~w2_ccsyb+grD)#FgC@ zd0%Innh1N?zRKl@b+~HllXT6bZYmnl_l1_-RdlS}^^(V2S6rd~XW7ArT<^Yo*)BsW zvf-N|uJ+Y!jaonY!>rL2<)Ld9jBr&+u6a-v7maJmM?G3BR1&iD6*~x)=s^98FR`FI zf9Ug)#%@L`XswN;ADW)*?HXj`O{R&jxOrW8sAfY^p1yx3)N#`G;qIPWq*Fr%DnKtS zJtdC593Hv<1R^j{l;T}v5e4CFP~Vj~cX}C?)&ry+&WVZuHYz_=ZUQ4z^zfQuA0=XH zvzmTyeOF2V)0h+Kjn;Ch=>LXrYS`Q%>?_r=liX85m-*Ah>G4WbMQ2J89CSvQDG)gz zQ6@HN+~c8ddkr5@gSJ|dc^evj7%uHZNB?z)gRyO z{4Y`yReLpBoZqXBn$e?(H}IDT9WCE>tt8*hJ>`-|<>3z0HFwySe%khWY`+>Yvp zS{~AgcOIyM#g$~dxw;B_;a%#H6Q4s{(LM4S@sh4qz~d^*j2h_^`saDAjd_Giz?h@7i}t@})Fgu#@TqzV_J8^IUYCzMJ?s zNm@kM-MBh?6P>8@1EO~R9yg`tNqbgE>?>H^hDluYwcYf>SqYRO%mh6+QZ%u!^_G;y zqzVh+vZ@UiC6S>@=~Z^rh&UcHMc3Xm+~)&O5Bip?LxQbrY&&03A=i5Ofw#d%`X3%Z zi+aNFyg9{IW-aY?Sa(}iU>6ki=tDv7uQ#0Iee8VaF9t#wz6VB1~y=X>gM>;~|)D-G*R zkt}Gr+;$(&s=NI^K(>yGs!7^k5#a#-_w^0*5(8R-tedxpBe6&4PWLVD1?Au+&Wp$qON5-|NPD<6^g~lv|k6u&0UHUTv>G;OxOH|0zjex&P`~c`B4^8I1} ziu!cH*G=z9s_snvk*MR^?LWEZTd&&QZDfPCW9#*Lbu>T`-5*tfpF^;vDpTM`seC+7 z%{cVP+c3PUV^h*kNQ*`{-fKU8bADsF$}v&4ht17#4pjHFrQ_YFJKzJtAJX__jVO{X zwam#o@FwVjA20gyG)<@{R7lDj>sJT+y6+CP-`LI0 z31^G{t^wNP6Gia`LQM1Ne87SMc+UkE)5iB%nZ8a&T(Xk1(+`*7Ml$k;r;v&C&#LQD z?=vB9w{XRU^mxO=`(U+KCWNMbj4*1yyxII|&LbBb$UVgZ^om$A?tLXPO^H!i%>rZI zgdv2*i?pRr9Onh!^~Aj6liEwm9s25T{`w0y>icK*O-`MtU1S>-k$pb2qf$Ve3hHV1 zw3N!%b})$BxrdN@Lh3t1kTeHNd)fS0(PH7ADRXbw8oB$Q%vX6>)@EHl|6L5=VJYA! zn__DyxYV-u`2s-obTvgq22GiB(hpZKcs};>Vj5*PdeAVm-XU| zVPmWc=G~ZV>P0$Ev0#OmD7WCm`CEcOz-2EE?0S9TdH&4-hvlkKR*ptfeFYt_4bg$p zwud~|m<9U=C>-_PBt86bM?n4EvC_lr*qGWH={I12Qp`%bU9^do>2R-9y&D@1@0v@= zk6&_eg%heGMh=}mBc{I(soyp@$JB0w7C7hn8#VptJX{>|Gj<(ZJB|^Fp|5#YaXdhI zJIyycwzDdL=EFxwN~^DV)86}i%*CU8R`)vnW-dDKYQD7^jHPUBWyt{YDY-&!y2*NG=$yFg0x`8fykhoPEN zdkqOSgKwp$DVUeEdNFVFY80YvXDh#Kc$W-5qL4DkvgQtRA-#CtwGAHw z+D}R%?kU%fZR+i{#T^PZuhJdFtgQWyxBh#`O1dOp2M|vmBvASR*tfTe6xrsQxsHw{ zSW*R5`2O4h>!gg%7-j8>gO;nWZY|s%xFODjJv92B(4W4W5XStB+VF3K%HZ*ng$qaY zoG8AqOJ1aB??t-X=Yh3`jW;|ZSbBnL(f=A$8X)XAiwq9?@Y`=re&6Q<{=16Yy^P=P z|8&E~09~|wH8jw@7XncRje2YOJs%^}t+2@HZe9}NROn+4@kMd=D9(B`@#5!BUb;V+ zENA#zKQ9V5_Jf>gs0@Ef*3yLFzibqQU+QL`7nd-1;kBAO#o0I7pbZqS5n2_F#KP>t zRp129LNm&Re4t=SME92wOnedMQ1ooAR#@{IHwJ%t_4a^HUTmx}{RgLW)~gUZKo76* z&VZuCI|36cAuz^n!c527RqL2*W+a0%N@xYJ7GW6YCR8Dz(jP4 zfj5y@2(i8UU;BrYggqU&Gg%74U-;=+Gz{YQIiU9}bm#cLD`py`-Tpp??IsM>zWoMQ zdyn%hghGLW04KQ>g~9TUHZfUn{F$5_7@H20l>Z-_D(pbR=PPi)3*sDn76-mP3Y`U& z{MRLN=`11uxugRjqA-WBB)U6LOZ%BH5S`%v0TKrY++AAmN*DfO@4q~Ti640IdT_;D z{fC8}U7ThHdpBclawx{GUuM-+0 z&gTY?Owx@NKA7n2@e*l=OVVzb{?y%E>6` zZZ-+?l|0xTo0WeW=#X9d9@I~l(hqS1aAP#M?fwz2WjRWe9h2s~K~6Sf@Jil`ZuZvG z5VCt0G_$$C7C%df90>jN)V+#WaA&bgi55M@D-i{aTae(@5hRd*NJ@r6M> zbGs35)gPrlo}R2~EyxY=N)U2BcVtk?(4D^|ceo|{h4{qf9DSxIq3@iH{7x;(mz53gIE6Tyg>ujC#}HlK ziyr@7I4E7Gg-+DaQH3;wcU?>c9o25E0HcQU;1oNUA-c-jRe~Zk){vIPIA^$#0f?fb z@ZwIVRf$Fv@H}mm<~=o}9t7X4aS(>9OIlr1vTkfZ-D9;L0YT`hR@*K<<2Oy{x1pO;m9%s@#O}x2(B`RSc(;G zLZV}oIe>l@Bsyx_AF&j8CMUcWj1n&W0oT|2hv=zl6DJ`!A(oQE8NgL9_~%br5buhz zqkq9jgfAO!j)0@fn-!r4s( zfUpbu-vAWwvnUDWD2N`8Mu+144WZmJ0&WMF9-|L)#T3@uUHP#IW&T zQUECaKd~VGUlGlrfl!1UeE5Lo{|fE@uS2-;|1f*TIQ9YziXa~TZ+gdrp?LZ;0CE)2 zLiK;qb*90&WoD=Bww{}Q z)U0;DNcB9I^W^cKDVk)gSbZ}G>gQoK;e4whB|`53DNWgh(F6w73Z|veGS+^^B6>bJ zgIxbm@+(~cjfO*B7|}$fbDOA304*F71`urd!NU$#SDr;#bH|2Mlb_xI55t9 zhyoXd!9ZyGr_HpSdZ_@CmrU9;;&mb>9@}X0A`QTS8n0)~f-`WnSsNUD5C{Uwmv};K zzheoQ_@2iVuoD9bkMortTC*d06ZK)g3uEFXtUpPzKI)~$fokR1QR*NdY4j%^#d--) z{)PJ=kfr{*6gP~J3LeH|qJP#|QR)Rdv?|&u=T0)N*6a*XOEnGJl*1C#InQ<;6a#?+ z=jWcI06G&6iOl(Wp6pCIQ%$%*1~WgQ1^!lZy}6pry-0)M3MF^wd1yE>W*+nPd+nBh zE*t_K49V!{(U2lX{%b)pLWTdf5}_~|5Tq)Ri)CYB2jY4quX56wrmNUtTQiD#Pgf7q zP(@9B>;yNEy*&DF1e`GFAizTjPf7x7M~IJG^5fA|?q}73+nMZ3#9Bz5_s;}*Wc{mt z2h*3G%Ld(eZ>~S-d#js&l~`5f6QZO6r8+q5xMOpFFCb?#n~VyHB*B3XV-(m8kVRLZ z#c=RnUC{oe`WXW@*%~t9N%ZsPoQR98V$?s$63al>A0v5`%*f{kDXC_8R9MO+A9~;t zMKRDqafLug;XASndFU+lY0h2QcI&z)vD16{s`S5Bk#Aa~U27kYS=*JN;w$7!O=hG^#F}Ev*wpvsalrzx{k}7;3=l0Ay>t$ zC}yp3Ov&1wtOR~N8k6HZZ7~tJu9H~u0!2}iu$6O!zGE=W@ZH4T!m!Y$(6Mr;BCoSA zC0U&A=s!=fl*jj1d)%tslj)z_vU$4)S7A+~OIx^m^H{zTbH?-5eUI zJR8LCuxAn6H6tmQ?s-n^SmpN~*_!?E;29w=UcEFCi8xvr#=>&kLlmVLZAd*&ykDp1 z>!h~+*mLEO}PNHJ7dW&68da}m$m2+27_n*b}Cw9S|ADo{3Tyx2GcKoq0OseB^Q#n{^xPAa9 zly{%*?U2s*ZD$j?YcfYwcTDtmJrPTrGg}M!Bum+}s+T{0^~wI?!|!$5y zL9D~`Yg@Z_9#I(ikJ{h4>Ye|BTy!HlRN8Lhp@n;WS?ZIi+)Qy4dbWFeZ)KGfLHytH zSe+Gm}-xx|91ku;X2*B(;8>wycIEX2&&ZyIU2) z$ZZ@s^M~(zj{=E8!%T#aj;2&A9VOahX5md1DxiYR>!O_KJLhVCLf!d>lpm?b>t`-_ZSMxm zDfc`QHTmi5mh|PWgjO&+K20+z#AX^~0+wHf9*b7{<5%e220)sI{In{SeUW zy-xYgJc9{!9Nt_(Y7!VmnaCXQ-lV&s^y3Q6K1*~tT%*f|%f4}9d&+)Z_`RrpQ#g1| z5HmJ^%Y}EW!0z!=b-hbUuLh-->DM={r$U~a6kJu3O)@k#aeSWi@yEtFX9oQYUT=wN zNr!nQX!fT)Z0wT=JIf5?I~!7cA)W`K*zTfVADC2co1_<-4l;k#OCgb7gHyuaZOu(L zb}xPltpIJPGKaXQ-(vb|-X*$x_E*L@E-4~-N3SuLN>GnnFPIZaFQcZH^|^JS$?rpP zW4i!k#ht4DjTDe2qEJf}w8(5$j?M5}y9k=_>-r1=j$s)jxteM&1OaOVZ&_Zc%lZZ* zl=ZA=*k9(;ynIY_w|#&obD*zUgr^lL`OQbAwjL2Agzsp0b+eghnDS|h0>ltL6SU=9 zu<+OF0a-j^$w0c~Ak+zW!D9NGvY@lg8+n=GK{v2amxeeaGXEwj?}W2MOn>JwdxIri z1lsql=gaB}M3OZJ@s~{4(HGGKnJTYK}Mw^|uQ2IVQ8`WH2Vw>wLi5I{)b4&C3n)Y+sOi%IS&8v%P|FS#}i(w}Z++^_F0p zJhfii+S`btPtPJ%<9(RI^NmNd>j~zzOxnw`eP7?V%&yyIJ?xh+JT~okew>dVn!fxi zJ{uYxWG!P8i>fVV^Bg^MWRnQqjdtPoMnMeQcM?P5lWj`-@LRm!6Mgk{A810Ds7IQW z*A`Sr(E|KSU5r-PnoFUa>hFu;Q+?kE;Q?f5?Jdhe&Zp)Z6@iVNPvdl|iH~$8yvqhA;LpUE<4za}dNJ2o!+krN|M9b?BqSgF6n7GarA4)T^D8b6WxIb9PF7Ok z)_5Zq#l@sVK63fBNHG+61A$BBXhQLs*_D z_Od#j7dZsQ>Kgf~k(rRixIP zTQQ-RUTm$)0}CW9+sru|lh6-0>UV?h?IqqL+VPF^Y{D81W}gKvb}Q>Jf&#EK054IP1Pc#2ED1>k)$R^*gIaEDavnEP1b*qoXEyu1JjiOr0?=kN8=xN%(1BWhk;_6O@}5V)T&tsMsh{lqU{ zo9cLhb!poB&gG|YcEbG05HD8)BLKr*cq&XWzS5aMsSX3c$47Jyp$|NUk zAHQF9QF=qB#C*tDKB&nCPOS4-`j5uotqdv7G)9?hg`=9jmD|*SE;I2~n(g8D{R?kk zJ+p#^yTO6iIS506RG7lq1+SMO@f@x zu6%mtiE##k>9vhyZXT(kqBor}a$B!uKdE8ZVo5%jye5Y;|NVYmw-+V9eK5mm{pun& zQdKQ#tbnBG#<`zO+&7{yC6Bo#>q`z6=UfTIRBV)=zR`|v#L0OzdE@7C4wkxN6CR8vZ(D)18(c= znA$2cB@|#H?su*u+|7gV+W}K3l`0LXQ$8gHzi9PhlgAmL3pWr&O9uyyUn#(`ma7i) zBv5D1vy4~DD5TB&*;>eo5iF*a^qxPs*r)CIBs*X_-7$C1jsz`mEha|odel;LIv`qp zj~fh=0L1p-a~z1V>%2wq@!!3l_GTrD*GEiaYBT&SGW*&~zh6gtv8zzBF$NP88ZIGn0$%6 z-ES$5XXHhOxbq~OQn_}~)%<4qt;m{ic1uhnLHwUvav6;j4ZQY0B>_$!okN?U6z4}hET=#Gt&(wXx|>^)*z$t{ z1eP2wxr8sK1dRA}!fy1p;pL>M@AQzCi=`&*53MK=8I=4>8cO3Y`{8b?JvXJ5N{6zs zS-?Vh#+2$5-KgSgjF_U|CVMC)@OjZn)u7_73JLl*t3*ko2}O}Z?Rxl& z62Em_?~ zt{t5l?AIH zAokxNSt{evX0kpZm{%bTsJMMUU! z0yj!kzr++Tc@4+#^io`qVKhd;nPq)r^M(9B6~t0B-M{m+NI!{(gA7imib>oD&W3fk zgLm7>F}1oG)N&4sWZC0#7kCu4Mz5D{AOI&Or0476%!S58clJ-z(cGD8mw2uz`6`GL zAKEx@wSP^;^ZYDke_OP9z^Ca(pDodQ`!*VQcnN>S`MTWPmey@~IfU}<6TACgOQqsyg9{st&cI-zpy+1Rj@Z7<9_Ls!1h=G%wP4@p^YuCYXZgSb|_rlXLz|0uwAiJs5Qb_We` zzwTDO>&K&J{r_?G)^SZo|Ni(k1`Mgu4Ff^CyGJ(?(jg(;-7;W=gwle9poDZtmvpz% z4N{Uyi@N#ISP9EbVAdUHG`9crFiN!E7F?$v~}=huL9}PWPzv6uho; z0bgFqtErN=wFik&Tsq`D!VPK0bpDu=qiQZLOOzYe{18^&EE(0hS5K$z9V|7xFjYTq zVK2$|m5iP3OxC)s?BO_|$t?Mf-_^jc_gYD0UpXX8Md6bT7m%yWHF!^)izL7&F))LU z<)bPo+(#2qNrY&@lcjmB#I8z+HPcc9%=K~QXkMQuJtj-v z_ZQD4o`Opv?!Fy&vXOp_5cb3PI;{VB54AZxUUG2f%o)f`3Qsfah zoa{coJ)4`+ZbhE@<4qGAKQHO4{PD^~>vWpmJz7drH8gcLoMDMVT(M%8WqgT)rd&{| zw#&AVvyJz^V9TwISV)w1+6c(<8@e4Ck#HwHlT&_Wbd zM8XYgFfEB7jWFo6fCm@GqOHycY8yQDv3Jrb_2Ua1Nqm%zDw($K-M;aaCMI_2VoybLP+brdc#*cChE2Wrh8)Nm65_uk@^k zvg}H$i~DsXe$^(22{|v?(M6Ki1nwvh|L=I0Tzj` zk2nm6Jl25t_2|{vN~+KdGOz}i1VCr|*>AW@0LSL<%o(82Huu?$(6JOC?Z)q}!YuJ9SgMypsWdv_;DP@`c(0xO;5Z!Y#fWe0}^u+RQmG}|% ziz7|}eP0O@>+OkWLbJ}!XIDb6dp`2!g7N~-Ht&hIG<;2`DCij1rANK{&wd3Ye?1eJ zC0u*rBqfsHVK(1)>iWad|LUm%uQN&!8{8eLnlUy-q~8u-PZ4>9L`~ZtO1y+0eYn4< zKpWd6;O<|H{jiz79&ydf>JOstUXu3lSWodnyslfn5alLMi8=|aaW}aqGUQ9lOF**V z^Wbg}7{6PcNsH{-Id<|8I}rL(Lt8+D(-jIqb`~3L%Abf?ly7lDz?qCR(T#rXI!@VW zV$UuZniVe z6Y~hnF5~*5=CG>AP#8W8eSG-Ogu?oJ5=dwBofbR0zRD@=`%BNFPIXuldOvLRXAZD? zt2J2v8!u5)5#0Y`XUqv95u5dw2vPKiXvW+?709b{W79Ol(Ph1F1<|XBh^=E}Vu5+M zy*$up!4^lALJO)s=83~MuW6C|hNcYo-4pCzRAn!R8I3gT_u|2Fw{` ziPcSx?3DOq*{~st-))=~S#`M5h1J7@6S=XTLT`_ikYp`9s3;@n(~(Y`YKWNxKOEi( zX_8FEJN-C6zUBb)$G}2Pl8QPnE&0EaXvMVr&f4h}X5r(5{}-2;tk@)u;PT?^?yx#U zVM8E#hzFWw+Dhqjim<$Q=(5j+Sy40!S(y%ka@<-c>f-ds!oW#&fXcxP7&$HoSd8+X z1VLh)KT)6|B`&+BA7qWPq=$w>pHkpwzzqF+|AKq*y=^Gk*d*?$hC*Zp8FqIeq?a%e zo`1kslx%MwDVm*S%7>3WFm(u0HQC=;fRO<#i!0yw3vdPVrhuHGz`%qUZguE6el`Il z&Ao*mZlMVKmXu5ecDxJW1Lfm=h=*%IkVIfv-Rv`7nEMF7x6{dxGW@Q5Z zQhvuo2tneD`#CT1;2^a#w0#inR0S=(O_2t*N~hf(d#EUlDPxh+TUU&}r4Yz9T<1sH zm!b8JQ3D)G#lPq=HDpT(3W9l$V1@&c<+N}SniKA9fb2^=C@m0K4CsDN_VdqD#K&90 zjY0sjZ5bo6y8i4HfW8V+_I~%J%OjwyIqwVPd(`zN00=Ai?u*ua**fH|RY3rR5<0=F z4`!PycL4PK_+=NDvf1LOuq}?b5Rd&;fF(Wb@Cwka+UK{*zHB5jI2LQPKk!J_@py`@ zN-+onRn$bCg$Q;Kxb#klM5ejFxC1geMQYIEZp4c_vJ}z ziuhJzQwneYlZKxGn{TW!A>8z+hv7dD9+Vmu94ZR;$3i^>|9RX|66nV52(Vji5ZE2} zUyl=@oBN9h{5o?Ru)%aL0T*Ga`D}>??PxQ{hQc496x6U~R0)GLH00aKGr&d$)4<1n zzc*3CMBx4yFt7} z-!`BN*p1Rqc=_J$f`?dPrXavoEKcazRLt}gc%L~Co?N{#)R~(BsD7npp&zc@kx6t)7djb!-Y^n^0Yh4@ zTxpRDSAsBIsHQA zxAQI1(+aLxsrx}{OS{d6qmCbcRyKcsS6`vS{zOQ_2Y zdOb%Y`@1BO%ln)tedp2x&y$1wEFkb>q#5&=(8Ph`cYiKk9u*}ohkehX+C9ZVZR^nn zM;Th2rGRXqrE=We<}X)PvOBiC1_lq~8r#$3yi4zp_K&o%NIYja6j%Am_c}p)No=D3 zymiF{gSYKVS-8Ed<&}Y~fw4aIZF{5xS}IKWBnJ|vjFJ2nLkj9Ctf5wCYS7MsbI)5O z&gjzGHngdd>s2v(M4FLf?z4QG1*GSS#jP7JgDzCO85KvjDWibI_P9%{6Y^JSb;Ba` zW0Fk{WrhRNjeAI}HH?Vw%FOd&BW(>wPai>kHV5@^H`5CY{M>*M6AM~9Cn-BRxNCgK zcg9drq`P)B+HmbB!#V*J*OR?@thiLyVLZ_xOkQ1yC9#KJ;)a0~A?-JP*$@M>P_M`f za3#Wo{9WOH-Xl6Q-x*XC4>>Qj362dNWGJfjJc%NW+j=Iv$mK7cCbk9)Pd&il;n9HL zKhcn^;sPsz>UbP;DO}o!3J=;SG)htvB?|ozJe2$s_ICoWxuIb2sqNbuZQ?BvchZzOj`h&QOzJbD1Q3o1@D+vpKTKiGu4FkFQxtLWsSG#%X_QHMHvf3v4AN;_Cv3IuvS+Cnfrry-NXZ!9eX zHx!<<+20M*iG1)woHkGAQiy8!g1fE~EUkF^f!$>2;6yZJ)gy=^YftrEK^pw*8HTrR ze{a{gI9`vKlV;g4dGe$ZJO%*GU^#s-bK;6XqqZZhCd28*sjM9rf0WOJ_1IpjSrO)c z@(NqBDt`NvH}eS~sV3;lEDHjwW{Edg?BwEN41w|!!N?PX30J0MU~(Y()5ft>K0*kT z;lz>q^=_0OI!gm9ccOal?jGgD(pehY#)Ekd4NpGyZfU^g@iK*&6N{qa{{XgK78*Gf z**@$6q1~42;ZL`2s6()HFTrq@dA^AP@;;{MFDG%-@7>gf_)NnZqUdT`8Z`j~KqV5Z3o{e(&w7ZZ(Qz@}a3;%LJJm|d}+#=AD zcE&7_9Bl8)_WJjFEeujwEXg(RIsYYJ$5*pr-6sQ$nx~Aos0>q(Amh{ z_7g~M_x*%(rdYQq7;EtgWk_Zx*l&aLqxlbESx+ty57YX?#zRaV@1${%!!LrFt+N~-E_u_ zTkuZeUqEqkvW4kg{a$yEI_W8#@*;CBgx;;u(uBU<=fDCbcy%Em{SH)7RbpMIV0g^y zNtPcw7?7()%=@1)Wh4vx{%FV$Ex+Y)j|V&yx=bbT%cj2(x6Gxl`Ap(XoAuZjK0mLK0Om97IzCpoe+h&NjI)DafxjQcH~715HEYx{ee376Ck$;CQ~6^Jx38Bcs+;uv@>DmV zM$qD!K9r`i=vd;B0H5tf^wmJk*Y7HXdFZGq(T@ty_xQR#%e#5>L3uH9G^^gd|HTG= zYaH+#3--NP>ygH9!?fHt2*88PZSBUpiTSlqFgU&-zq6tCMVv8iE^8tw(Ys>$t}a)- z$o%&(Bv7|stdYmPXExQ#+n70Sw)L`*BuuD$#-=_Kh zO+`l=HLNY>pZo@bA?^r4&RE6T#BKHEnj8mg{&Y8?AV`Eo)AMCz{d5S#Vo-#cbHmDv z>0tGBKNTvFl3na30kn4X0RU+=i^#e!t{kvj_9c{sPB+MYNMIv85xk%Sb?+;aC6F5P z6XtTjoXM0_96#rBNngttl7BVM|5&i4y&2x@!FXifDF4HwQ!=?jAgJ8Wc z=D3xF;mAMkdU6@}fI4xY-Ku50;C=b3 zwkr0~({WDZ%k5XE4^Su`CPT)oVfLSl$FOx~E@Z#YPpd%h)H-v42}SkRoP~9vuv-Y)IedQ5EHa)UcSAU~bW3Ayp5b17t z`qm98!Kwa&R&(cQ+B8dxgUDQ0tTYe>U4Ee>e zyDFy2SBstyY-CV@Dcytywka zVoPE@Y}avJ`u2q_N0JE2^NOcjx3oUBiRarh5{zh-@bN)lt9ym>0)e-pR8ffb`Y+?8 zl^-}%JSNo`=O5}7pBD6>?wFBZC03AJ$hct7zsDJ(yJv{gT_ozwd<_eVcZ1l%tW!Q2 zx9pyB;KxbY4S56+PE*FvdGhMz6*JYx1L>D*cJi|yCC{n-TFJR6kTwBJeOmgy#BG1( zuld49j;T=O8w|f@Q4PyyX+Dd9%9BuL{`5wTZ)|e6>zRN>P$eQyCJw_x^Hq*~OpRC+ z5Ey0xhvi_ZdYJSIv;(_eK%uUFDPm{lW+)+c&tHB^Fpm!T3a!U;i6iAhCogr6qeQ0&p+`SA%{__ukWa#{gd^W z5eVaZNtpeAM89AKQTX*S0`YqokvQPnad8~HCbw*TQcrA$kK#eS4*B0^W<2#(mtRyb zEqKv*eDzGdYGO&kr~xKedZQx)q;fzLrF5jGgt>Ac3g6GztdlCIjPwU1Ni9HVbaK2k zsn1C02UZMe2}Tt}tMnmXFBMGv91r>j3bZV`+4mspiS`l_$Xy`54$#{cwdLW)o}SZ= zahc_`j|4LZpOTWX)gAfZ%Fa1RyhB@hYB*;TfMY&~+eZWpgRu*%IGJ%0c>$4iMDy3t zf7}xawtp8<)KNxo>%2r9q^dC0Tz0qh_~k7zk-_&O;9)|`TTt=8vr+QDX5;ThhtjhH zY$$yHy965PuHst{DTt`$mD}9N66lk8@hF!zfKWW6v4E-60b|w}B-$e78?xNa!8+<= z({zXL#+&Xxs^i|;n2dF^^rgjTrFMjJLEd{p1u=e+gnz~r*zAbxm?Pic6!4&n!nyi| zC9`iuUnCt^RlPONGaRv%&B-eLFf`v)Q(as;>`js^fIXb6z*o)fADt# zicvfu1r3S+n|46bi~TE%UWQp&;uI7F}`;)#aV0@khm$RM}ho zUb%wiL-`S0Q~s~&3AnsJDr#fczh#J-)5SNC zgS9siOY4aZ@kg0XKGK!SsYE0qXQSwa`O0rDcLB&;({Wqk73F#GeQd+ z?sc)ZFXb>Tn7y@trqy`^o-S*}ee14D-YW#CE$wXb3cOA(K>BwrNTKc~vQVUPAP(%d z9l++=Tn2$qrr{Oj%(;4TT;FfaB-JREd12Vxsk+#4(C=;eM8Lg zw!6sXFm+lv_Efp$MF}KqF(%o!GY+P+7w?KncT0PY;=am+&*J2hJh1%qezeS74lS_LpA?a8TSN z?h~cm9vc63LR>>weAB>KIZPn;Od(V-ieCPVcpb)x*n{?jQ|OPrGdqFgJdTMEX(Zj zYAGaOT+?6Dn`hBF<^6v~baKPGe^7>kyw1t|&+$CkIp&%iJWtJ^ui-$*kKi$2dkhs( zAwfJR&L8I1NdX2o9vmcDkicH^_!oaRMb3ZY?N#;|zgf~RaX_TjI&15wXQ-9c^H!Z? z{Sv-B&TZ5tf4$YT%{tPKR^+IFmxrl;y9BIyMJw;_`K5V z+0=SYUw4;v-=QN8W7f`24F(Z+5JR%nN(_VhE2W+rklvgMF%lyHuw^V{V5+8UYFThy z+%hMm)#F+4wYI*jOT{rmgX-5(3Qaaq{e^o9h{uwu18IE)u-nnczx0BEHS0%S*yN{~ zlOcd54Yno5oKF%Z48p5Spm3;j%1<;lghQ}&G#v&dYewlk+uo`wFDN*XGO9=prj%BG zX5eEMd zC01BLq`7l{I>Ai4_!(NhOZ3kI1#!Fp0~X#Rs{Qm6Qy5NdfEZ8Fjvn3zt5VsC=70&? z%-nj7B}Yf%_`Vv-Z_@ip4i^+PKqMK02S+Z4Uw7f4(eDuq0J-Wt&&0YXLGb9l(Wnp# zjb5%y?l}#V%;N@(9T5g;*ato8lL@IW&6(n^tQFn;b`?5-2EJ1CZ8@hdn{U-kp*u|z z5Bn{+rS69zo4X(RbwW=(oojRlBb+8nFl4D?On%2>!Hx7XpC;pvwSM>`T<= zAAC5B5cLNH^Fdz+V#;5gy1&fEObh(9ue9m?iiSAqS7W)S@6dAY$}kRnhgc>R)@Tu=zS-sn^(Ug5w!1hI|*yQW0I8UE%1B>|9B z@C+~vCj37c(V)vkf5!~~KSzTq|7-By`~o1)>HeqO-?_j34FD7Vr+P4~jTG|r;UyRf zMVv;jh&2GA7$9;H_~qJLTC7(BZ6xDpO&vWTBuaZ^-w9TZ4ne@+${3I&06ifL0D_HT zLQp^eAxs_wh69i{0D1z{D=ISpg5aTpG5#xu1#Jj(cf&`iS-?O2D>)q+cIu7^hWk^% zsLB5S`N>*+?9t#5fK2s6<%3{25;osi-jPiUk2zo3(Lp3apbS7+MmPWr7x|w? zNDJaFGirOF3C)`1O3V70rMQEcR2P<~D#$@v>u*Os>!li>^Agn%~Mi{c$Wzb^~w*D5hq&= zho5iivI-QMRI2Aq*$2HT+?T)5^V!(9If!&)>pS zLFO?AXvZcDd!~ypc8Cvdfn#&){PDoVsPj73?$| zhheZP`D3nTf}7bI86~XXP&N)qP9ANaF`aLKXzxMZJotly;QDedqt@8V-MdD@99f zP3#cDJm~Dl3Dp=^^)xntSIAet5o0*7FV}qghg_=A_0$%1TjgBW%B7#2-Lz>ogkGm;yTPw_ z0r`D2dBnx4W$|VngYy#nk9cA%bLRxnzw(E^=y7+=yE2n3$~4;-d@Z@Ud*i$k4{Lj) zAZhsa&4$$LT~o@0HQYLyEC-GdF}qaDZabVv~=}z>S+- z=L86>fv663EfRSPyNz&d8IkZyF~rt7#zy#Mb40v-PL){o`*N*ZEZeu0x@Ugyrh%q7 zO>MYn^lZS1!^kHt_s{v5^c^Szdx2N$>tHp`Z7q(t{UC{a1%8ub%Gw>gp>mM|B%Da66cmywJbM0>kV`@jcF%?4m=IxXe83R?pXT|{&@ zxa4sgFh@$RZC<&Vm|m<)J-76uuzb>5p@i3IIA6P2WnXaO@Lf_qL(#Kl;gbB#%(v>3 z#BbYlk1AZ3zZM>r?U}uNh>hcBm?8#XAMxo_x8**uV%Z3O7h+v^;BGVM|AjBtb?@7? z=CCQr&HbZRR1f}Wt|Cs8g<);L4P1yD+#ju*3X1xMNtyzxb$^DKWOCDE4dlzQc6NEA za^T}~+Yp(Zun_u+)x$j5jw7#5pH)~4R#)9uk?K*c)#$PohqC$c8Pc-MJ#b}tT+BKk zX^|UAX(bT#^yQMVQ`ahOlhXXshP_aK#c~$0Nmg@W#Yd$INyeH6=!^%f z2`|5-!`L$xylXU%xwb?gYB}gZAw1CW)(fWzOayqAlX+dx-IEp0lMP_Vx&gS+TjHHV2xOe zx1jBa;?$FUDiW-_<1ZqXwtjpPskou`R41(Txdd=Sle?thi9voE?>-pJcdT1NP9rX> z$EKT`9CwD1{W)mypf-C5@a??$^OrxRx}n|8NLw4Hc6mY)?cDA& ztVw0-@qC-EldCApMsrKy*Z)ImsFWe^xe|VN84)-#b^E%PweZDYlLgCol*MT2QHNS8@l^m`O}Z!bp<8?V z*+(EyN9Pf4FX?Vuv{p&>T;f3Fcyk)JJu^4S7XZCt;fo=X5P%>}!_|Fj|HzxRXrGIQ z<1_$0Ha?sq9czftzv7ZcZ3zpaB5Dn2BooU9=(q^uF4~Cek znRxa$`DaW#wSohx)8kt=cMdGSVnGgn!fakR9mLX#;ISUq(#Cd>^lACS0rdAaC_f`H zG;wlc@(NE~uM!x@*m(MHtb5~v1=5;0;p6=H1$5cZ~}FjvEnX`llAc7r%%o>jcwSPrf`&QOMs5)9_+~xf%Jj?<@YU~oj-%`Abp9>2T@s=zXOb|%f zU2J+Hb?RtZ=lHJ$&981n8Vm~=kq<#Crx8HO^^0A zfF(I&J^0)v(n=G)zDKvB@uj5&v()S*l!A!>MvaM-UD@Krp@B+`{K<=T2*zvDQF-M* z5jd}P7)G0aBnDe{^})<{fAwQGsST^fa`%nXu_<~^SwP0gxUhA&jJc^zEdKBzRvl2l?@(ok?Qy%9HUl)oV#* zmA7V}UIZc-d--VYURZ4o8^v*T+>o<|!t8d9!5f23%V~I!?+!W`oTH*IZ~YUqrm-MD zK*)Cw7w<*!(0X@FAtaNVytI_MW~wF@*peH^PVrW%6>QIF{-j`35QB^xA>$seQP8Qw zo&UEO0Ni)0FE7ESl6)0nW12=NYg98X@tz)}bYg~p*Fs@?SucU0_82{)aD5|QRr0Mp zaSBtwUDpsw$4MWPZ_^RJdFm2k=w;hk2m z|KRqxdw8gZy}ouAqvnR^VFGUw?=(c)DbIpk4b!2}?Dc2LH!-ZT&Bqf4!H+Dx*3b1P)5AwKJ?(MUK6M#(X$61|nzbYl1 z9(OHa>0Mi~r0sa3P9L}y`(YT{_?a-AMRg`UR|>fgLfS9LuB^(_o4#` zLa~R9Ve%6vXXD2(^~L7dHZF2%_BsiiIV_M;7u+P>>X83|7Ha!!tVI4HXz{V3n$OZ* z5aQGy2BR*B2*(wJrQ$)Jx&a1XMnQ4iK6ZQ5t*~hBPQ2C2IiICGqk&E~i3}b>K3@?* zhjpQy9u1VUheHGt*PXzcL0KUJ~EpT{u9Yb-=!gDnmmzboWz+)ex{Sqj_viS%zGzY6v4e025uza*|&iZsz6@pLd{`AJ_L_v)CXYavKk>7j(WFq#VOKy8jd+4rb zhB5!|9*z+s22N`_Zn!ebRz;%27qsp(=q3nULYQ+tb~V1hoBoqt9uq89%BF;u*4-m9 zMM&L+@mLLM8 zS1Nc3Q55^3qhK3&007FctHSOQpXI2368%Ei^LsReAeD_NO%s_QpVlH+ND2b?7Zu7o z>sxVZSr^#thnl?e0L$hpRGp>Dr*&F$5!x4qm%G!@OQjn1pI5FP=YIxO| zGWHKgV+V#Su2on0k6Zob=*!AKh)_)U(@v-#350U8e{!CbtvCjRZBfgzDAc2MQxM$7 z+vi^08Nf3fbR?mVt}lsZ<~jDIufp0|Vu^iZR0@;KH@*IX^t;C+;xOg6Y+9->#bDJ;L~&Ow9-msDGNFZATegj6I2%EB zBqvg9PisR1dH`mRYiPek=2-W2BLMBMdK_lt8xiZK0xD{FwqP1B8q22}JJ`!l1>K8thP*)(Z%#!DR(iRTgye?f=96YZN8vjT` zd4HK+!3l2v@pI)c7jX(ka-%^MYUFO0T3VD4)tant9)0#Jaa9K5C;p~~4bniw3^_bJ46hl&iDNP(tV;4U`2K{zz&rFg7K10}rYn z9ip1V2ccJ%%x+1QG5q8b%_;!`*ULYvIRVPN*8G{G$VhoDrUOy!+j|#{u<*e^UZa#* ztf6_c??K_yP8eX??qdW`ijU!;G%hq<1jj{GCJrA+-|y8Jn5pl`9yy4B^`dUUP^?HM z<|P=Soln)%%nlq;oCkC_aahrYkrE9}-M7 zD=M^FFGmNwyuJ6FDn?mLiMVKZyRLr2ghR_(O^a9$cKpwkSzqTi6qd@{T><|%fMBW7 zQ7qLN0jwj{V21~7lt9EhB=1!Gij+xbo_LXnm1>j|mQ9{+>oivGNYBJs(A|)KHc^m! za{>fzk+WP~;6aO01}@h)8i@4joxk6SjAvE7#s_w(2`mnbp#YDT?~N|yOXBc6OP=a{ z7pkaK{-;Lz)KU`9A$EktszHv^e;DBz=6TN$1%M@d7TYB-1VPIBYMT^#I8Hs8hMvwigjjEv z5*MLR2z?=q%cj)^3Ws{DNO9^ZyB6qs*D);jSD`^78s4gzy;l`tbh@2;r7V*@!7jh{ zc}yhXM!O)dBk)89qUP?4i41ru%Xbo9hZblPtvOb700NX&S|-d^AJuYIKh6v_v_kk_ z6H=1In~x;#RNEy-MZk=0?{q9fbh%!@J0GXgv=T%NME<@<8Q<{)FY_}{V!9Uy3lZ2B zztVccwrDFX&eZ!H53>JT_|pa!#2^6FO$0?PE7Vs0+Y1hP0K$;WFw`jq3$ixfs&6K0 zMQ)64RvQYf`|IPj6XoM}1g$g~2OFD(#jUe5{H13I-&ei&I%TC#XQuW1PKs&<2Htr2 zB@b~wf#?LPDm!w~(7-diXm6cE;@!9OciLQe>l)}jmN0j)L%jN-EE5#2MvES)-CYqL znl+RMzOXxl>ybX@y>puFC8rKs%D@~($K z#nt@EJmT@;>yM3mJ9pzQEqa{G563RA>FYFphEMdo+ew7hDAk--Qp$#Y!Q|3fX{hlN zYWtX>)>Z53gv(WPoAc?-)t=8=zIL^oXh8*AAA(VVn*5{Mc}uGJk-hfS&sIg_^w(9w zeX3V3r=rP(eoDG=R$U#wMX{1LUhMKx=wjZimmJn^#zAi&6NI3oQPS>Z(%*O$`%<3A zDIP~#_PpUt7aKb4cTd8hRynkeCSRdegv$onHtDnQxH3o98G78(t&Cl{JJFUJ+a)Pm zk77e<-@{6xf_@)E-HLk>UsXDqS>paY@vCO>&f#prbcV}p-d^5psUU92{w$}Xd9=Bp zk>k1Za-qo|%cpdnR(TH1haaz8w(OegMfp8`j%fK>XRbVRQLbguELrQ22aq@R6yD?s z>>}>klO|D!o-mxHeCk*g--daoJ5r~;Fhlof0AZif( zU{!LXNRxEuu7pWOkp=Nu!%k`lTlDxcP%3V*M16%_q{C-HNYI|hKQrsvC}gZL$-m*1 z^n0{(meRBR>TdqogeqX}L={V)IE|l%O;)9KhuS&|M{>rEp)K3XkA*z0ox&XCe0%6m z_(ESHEd*(Xy@CG(@;o0xmmKi`-f&}pl!q^>Pd%fywfzj;VJU6L4CO9tqZ{(#b~XT6 zF1SGI>k$n#CxJWKPU%+g$Dt3s+KPRaHK{SiagC;?#@6Rw$Yqr?7#1#nkO*m+>$DPT ze=j< zx#CUjRv`0kX5r)Ex%y4X{+c!;EALZ9;dzB#_Q@FZjGW`vnDAxhLSTsRQM}C^m-0q{m%fz^t2&beOrdfd|Lu;f`COS;fm|81KCgeT! zY`&S8SzGHL+Hk;ToM(p^zJk8uH>$QBXwLuxxO1%EymuXj28G#}xOK{1(N9x879`~5 z68o)_OZH~` zcOQDdhx?@}qvY&nET%q~&mQNnKOvDb-B|SsNsSbJVe>QJ_l~dS*YNPCsOiIRTVS9l zXH)L#870}urf*S0jUz(idZdE=Vd{l9UE}sG#CrNyobFou@|+MKecX&Om)u`yfN<`O`o3W$uRnL z>xDPy+}=4)iH7ka%I$FfiGMI&=^Sv5=(6;QvifKT=}&Te{-67jo$XRACB|m*9Gow-GPT65FDS(S)a8q&!{QS^KvjehqhF;;|UrMwoMg}Q&~;ihh9$LR~ZZbE1IV{JwK zNuv927@}3Sb4E{GM@M#n9-g*V68Og!y<|JT-lOa~{7v8c3_Q?x?-PWXO=E()L~@uL zpPe>fmZ+yi{v3<*TlrB%Bn6XIC^HafA)|SMmSeMF%>6vOdWDXM-tgF6*+0~X3(*D| zF$}PLYbfl@$Ac3l8|R$~sqnF>**SR294Kh;tjF=^H?Q@EutBz7GCH8M^UaHG*4;?j zB*^Z0G!c@!8YykFy%`#QN+EO2&bWjfJal=;ordBGU*uxfH2nfrF1`|WxXka{OuUTZ zxm;*el;#_F$_V*lXj-=Rbam;ow9IOa-$2`|9t-k;=K;fHO=G&kCz%rd)v27xHc^9d z3$+0j2e`^Z>#lc`WYMMW;iOF;zZZtwV}r1C8B+cr7dxKnNJ&Rm7C*H{p@pgMd(3Q` zntwUrC}RD-ntnpuX-0bHF#!M}+|L$Uu$Kn6sDI}m)_*~Zrl%T{ThtTOebk;OHSTZs z83QaUlWYI@ zIH51~CBjb8wt!$T3StI;A^>1gAVeJmMS+&6boVbHgyUa==PxV@KyIT!4Z$!}!s0I^ z41kPa!m1&E6P=9zz~Xd#xCO!AM3@Sy%K#V$9ikHjg`yHE|KMapDL|OQzr+(DsyRe; zFaQ(>0HYG9Hc>e?p6xzlNh3~={#T|E{`Z9hG0Fz71|(bfj6d*Q>{LejJ9o}lzuOI#c*p|A>k{mfpt}dMWpJ|C9 zm5r-k+ffXudW!8Qx~}aoS1AR^GWUW-lk-6e_X49t%y%J(b2*S^Ybj%`qj|u=I(O;D z0tK|~NgS26w_OLa6xTs}EG8rrg6OP6jyAI2Rt2kubK%41F=5no04VZm#ljByU5av% zOvq^8N&1s@=KQL_Kvl`RCvE3?dYX5vS8iVuD)L8S?=pTzTZ?@!3N6FNzzW0`XSy3ZL7%cm1@kE1cUH`J8^%IwHuL@eZL8G_z z2}3#g+$Sm!>zN2({=qD}((N$gxtc-ML4%%bTZfPxSN?Q!_E>%N=iKBpESV`1AmjuH z!Q=#hUDP2-MNyVBhLVDACHpVQ(~>=Nd$^dACUr_2RSzd6LkEkg*bKhtt7xF&bv!M6 zQNX2U&pNCzgx_PE;;8cNYK3R|*+CZlf!VfC!8il{g=FrH7W87KqgPqI=hxrfv~W#CF@WS)CihND zDo^%6G{oqvUGp?3T?GukcU$GZ0{pY#r9Sa#ZpXaWgXT<@p9L$Y_R5V3LO7x`2L zRieceK%*|vaLE1wMUnHEpFwNS%!d+@iIQ-K?PsbbbitV1_r7pA+YHIAP?pV*V5V{T;V74t%npa~ijr>iPaorz)r*`~!xH>>Mx@-DC^Y(?T*5h?RNyMPV5CQVJnklT!n zo?8<@nCZyoCbww?+cfp7j5rGJU$sfrqBs>P8FZd0yy=_6t;`;8BnCqBiGOoyYXlIS zIW3hI)y$?{z4a*lZGuHR{A|3B<*C`H_rGo-a;OX z;ny3{C_}peKvU-yIWBpcPeurHFx8JnokjfO*rzevbC*_xl(Gy=7SQE;! zF~xm&$~&pz-Ko-Fx7wk&+ezNdTB>}#mrZQBs09&pd)r5@VKdr3XXeGu6Pw8JIhNQk zP$anAX`AF^l4xwN?b06;1~Z@3dyBqX5UrctLlDJ!h6WtelSp|*l}~N0^D{Zs-P~IA zt&V+dQPOKlv$>XX4uZ|1PmV`58>?*eFvAK)+pHOBKc*7p3y$Z^Z6DEdWw|`Xs$tUiwM`%WAXImVWSD3hJUNfZnqAo3<` zit^UcRQbh#v=2m4h$}n~N1n}RFWhN+=w~*6c8a(H=bt$P9cl`68~gI|52-f$v(=-< zpRIXVRmQD@81_Y@&jU8w?3Epc@JsM*CI>|YhJ!xR>;{BNgs=4fY`SLTZJb)mP<#$1 zLm!$zSB+To^R9fdWv0aHj?5sT!DaCHy!9HU<2B5JB-T?W`8p{OSvh__CZ(-70IF1s*dZ32(Dc^BTi>TI&K7|`guW@guc3OHe7xC6ahc;>jY*U49TfDZA+ zzWd11&q~`P%tG)rniJ)!$FaZ4!K~5a{XnXE-D4w22vyP0qRk>wz;=1SL+u4`Zv)LP zfm6}d?Nhb*@4st%tOtJcE=eS?fHy`3k|w4(uxXxuNM90~n3dqEq;Of{vt?%Qt&R81 zwx$1&aOd7Srd$555fC0vz+Wv#w#y?DiCnj~jRC~rQ%!+JZNz4AGecbb4lRtl@Qxos>e!o~J>9bfkCbReA^M2na|Qkb3ib-|v3^_q+df z@5*GYnU%@RIdjVFefEByhgH-}`7VIbg4{f>Zn3H+4Z>J$uw9U;7@sg0Jeg+n#* zrDCC$e=k6tDBSobwlC~@9X{n3@5`H^8eCOnZsq$Vm5J{iXSpV$6}w=}xC?bYG9r+r z7iQsCanj@E-%>jeAsnUSE^4?RawZhY*(S=&!*T+H9c>&XEDokIDnh#YJ>|;;)YCE;~vI;C5TBX*jtNP}(X3ZuT$}T?VP;Z=B>U~NS zRhfX?Ly$^*`cDSEF?-XUDS zG}uRe;%%;#Cie$>Qc7VmEM-+n%lkhoC)ie}l>-b)n^?EPK!gU13--Z zCpx0BiH#+p3t~xK4!i9 zEP1jVN6#mIC%j6rZsp>S_{%=ovbfHK{)}oq#AcD}eU02XIKhU^sJzZ^6IL8Nn_v#Q ze%cP`2&F>09FSneu)R9ee&Coiq7Wi$*QLe1vPmRVlM8<#Z^XsEAgw=m(lz7XFWb3aiQekPIfQuAlL zK@WcesV1+@Ctv6Q(DK{oVCc)2e^0*I@?-4N$cIzx&qkg=!2DvPngwtyCOj~^9iLCBAh?ZGl9^r7laQh9Q{hZraCv+Kf!5(&ZP;MrBe2bY}_dY7=3+a zMum}{QCSxRbdnpYXS|B-y#blWi3Cg}m@ZHu!*vn^I3*7#VyA_^m`jD$pMKF+jb->? z{`pD{c}xIR8IHH4kV|W5aw3}JTVIc_VINq5W3-OZ-EsLD1*atYEiC0BmsbmNY;rxD zV-}0mz6#8et!1kUe$6DjN4w5oFbk_DuJmhpGLfLy)DMRScGQ^*2C5lmbD#wI^ZbPM z(VgZ-v0KL~I4Rz0+vE;;fqMn|+XhADDlu(EAW>S>t2_rdEAN-u>#c?%l#3rWx~_j? zY&fk8pG&Gd$K65I9~+$A`u0`A4^+cS3)pQ7-dusbEl?J8aaLQX>y>>5q-%P!C}63;=9f#<%DY zB3iC6(N^&g!5h4xG~;IZywm&=3{^Qh%^($Sawy5)VEc(*jsgEWJhETe7{t)c?XoZM z5`4t58!NRB>|SkUVn$p-ps$5B_GAqt$;Yogp^hxVzd^udp#fogo9A^Th;A&{1j>=O z1s5j2vJuQu`}l`#1-&>p+`cwC)|69VbJ4ZJP=xpgm9S{Uv)@zWfcE#{M%?=128&J> zL`J?qbY0npI}t#(ZAye7%e54;9mPkozeW_?Q~!70$rK2z45NLHcXu$91T?#(K(uef zJZE?=6(dW95Y!p|YXr>hmlg$~<$nd?>pgf^?gs%IhsU2ahmd}QpzYu>Rg~BDJ@16d z_O^oZzM}vRrBkl(#`MD{^CbvKY%g^lHlUjPyBMd+anM5j&NsEPLCqI0G3mz);`yo1v_TSP>EQ0z z8-l*;Xw?3N`T*ar3#lBu!-e&m6s>Bt=R8ISV1voHlGJDkl0G_*kbmbIYNMS#4}gG0 zqi!n;9gp@*P-8Y_+H?JtN1OBuufh)eSOfNs&t!@Oqhmf2tUQKue$LG;FQ)uK#r=#a z?PFFr5wpl}8|P_(=Ue06^$fA}*77FhmpBN){iAW!_%Fo~@x-s6K+y7C&;H^;!%_A| z#`*Jc9Dg1o;gY|d)NltQZs0hm7>MgN5%hZEjq&6)gGgt-AWK_MkH;zitoz1ThrFe6>Gzu893v;ja|n%cc(BdOaH z6v-mZyJtlRiUnM@J$(Rvcbh7|(^N-c0zo%Xvt2zAFg`EN*;fTatCIRH0Ks<_NR+_o zEav6tQ?V(D)yW$}wN%M@d3P}PFi*2R3Oq2`utqN=w=}4@K@5;hM~z*u#g(w%tcA0M zFtYyTMk+0apS=+$LAia?Ji!v<5V^Qj2Vkg@6t23F(+8LdE#KF+ORba+qjyq_;k8^T%}Z-=y~>ZyWEQg^XK@tRAwun1{mOL98rd*lQ#g>h zf+7VPJ@?VN%o)dqVt<&Fv-Fp&XCs&!Fa>0bSoN7kON^1+jtoi1RV$Dj6NtWDVJH-> zGi)B)`-u(nmRjBS;Y(})0NGCJx$tKM1spy%tBK>|7SscSBVCzjZ3|pO7caLmU2rEK zi0?JocK)F1r(*2l-FGJ^jsOCpzmmbK(D=i*$zbhtuC`igUJXIgh8OAMlh{yRLRTuz zP|sG&X)C?Y50P`xI8!cR(R^M&xlXFB5$C22h7pjUzel(0hSKq+Bl648!~*@qwvkGI zmMvZ)0TYIV{}2e)x90-MZsvOub+-vv@gUg+hez3dWp}qNmNx@rr)>=*$Muu=n(0++ zy;L&2iIFxgeGylg`<0*(_s1FrXLT|BZ_mP+q|J4Av0?J@TMkCNhU$6u{HoCJM0Jey zZKQq2Qi`k^J+JKrzWX3c#1ek>kp%8r*rZr4tOFR=4dR?Z;Kts}!1c65A`+&Gw9erm z)(?NfP%ss2tyH_{X{759GDNo)rpB)#gYhw?YzD8r zUmcvd{%QYM^dPz1d+?KUl_@H#HoYse_yXhbVqD*)f4RT^@;Uy?BeuQgLyC&6zQl&g zH6%SB+%?@Wz{>Ui6e-37Pi(l*Jla?}X3kZ7F8aO?vLJStPFCtQPd-AyTlTmB#PDhP z^ud^5=k_O^!o<#vr33F1L<^am^0>}C{vrA2@(|4|ypF~h9|F~+>pwQ@Qt9uLB9$+* z$mu>gmLxQ{C8{!Nnx#qa=EPO)r8uXC*SyA~74bbycGSW>o2tuUUp@_&-Q(n?`pY2m zBUCTV)H^Pm&wo82N!{ahOxw_@7RYQDUe?P3+`?d{zI8Z1{m{b@Y{@FFJ6(HWQ!uFM zD}H*#9pU^7u>Gm{kI7>L9cX;hO1`Gp<7kV#G%8CC9l2w_pFgN43S#w=##~;<+2a|{ zz6yJMU_j?BtHQK8MR9KOo?A+6GR@S(WF)8h_X<8#h+$-sZWm8zby3ntGeXU3Vy|jY zdEv#YsxuqyrHFC~w%;R%VWz)~5qiYGYBizH^FIQcu3{#C0)J3v3NX+I8~>o`4{MB> z60|F<)+}q#f|bMx(KnrSYnCm^?SHd;|MXSDJFK`}j^5g+4aI%+W3k7IjHVhh6En`#WBqrQpC-X*D-mVyal2#O;{+&foY0iyH*!q zE2yt`$f()6aTRAkhzBf`BRZ~4`%+p^&0X0*lTBj#pCtgPwV z8`iYNU-|}b2vb(2=#e;thpT)(3M1|1jQJ8Vk#Fx`ggC1{w zmv~!>`;9_Jp^DNltK`Q0IYBGGVr|Axi9}nx=hYj8%O>AW<7rL43EB7YUTz?}e58r3 zGgQp#D!N3hjlTHGoS>F7BW6|4gZ08+9<27qk+O8P-EZ=R;bBVz4((NvMiJSrKJrx% z++!o|-+i>*am>^r+WEHb)(^;3s5xEPHBFXBopRd;E*QMmTi)t86cu=S82UzuOFKs8@pcD9;6>OGU?Yuw;@Yr~ z#hb=)MdYY*ZzRLre?*zFC#`ls!H1jRsRo5J5G<1HDmmKl0CZdeg7mj6nA&>%v&~9x~7+rMT-B1y{)<*zecX`Vgrzr#fC8;D%Tg4^{Hy~9rb6gXjZ?J z`XBruGNB83Gc!Fta0>@;X$Hc7u48QE&SfN6<@~+9xKJ)h@r; zrqNFaXw#i5B1LD`+483zKNq~fcg>ccks)@)OEPJVHpR;w=iqT4Szu8(z)?x_8^*RB zt(IzBvl%L;n?IrXr778W^|$MZu_#e<)uszw`7-gZk4bup>E#&?UOs5pWNJ0ZT?-I) z5JtT1btb;JzUVOYkoBODH2F-~uA*_l*v+ahS~)|LF+I)A#3|G9o6e#TfLTJM0)s#u zTO&!UZH=EF&&?UpAR%V3P#IUupS!qBI66{EED0o<5ld@sV#kaudPZ}>Ezu3q{TFb9 zW4cZEanXqbPtRA@8P-u19@4pC7kcMQ6|Na4SAoC6>-263@*2K+9f1&UOk>j$y)6gq zYh{o3+qd7k-+XSB*mR_Wbcs4qf2N}opurd6=M5*P95_L~j^h(}hf<*pe~tBViE6H1 zs(w1XfXk%BvA$(EL&mm5-j4R+K}A^t0E%IQxQ`+18P&v7`=M;|PKZO7 z8RLgv!=vK1GX|!)=`?K}M|NE*dgnzvTGlE3C}t1oO#qz`EKGco3m9}N4EJKwlE4RR zUAtnIfCOQYVi?D=)ba8q;;c1c4=78c=WGiMve$`lwGMg}*$9Vjz;h))*|u%CP~*1g zPcy&gswRQZRD4XpvknaAXeLd1C@Da2)sXs8==7ZCX_>}?FOR|R$_Z*a2VAI$b(B5L z;nK+nZ<3(u3{7HdrhTltZgoDoeO`PiV%+baq8mkA9Nq`RaE830Gff}9W5D2p^YmdH zir4Qg782dobmSBEw$9dFX(jTnq!Xe8YInkkz2uyhS_q`(M@tC#++>b+1F`5iU`3#J|PoBE^T;1NJo!W#hIi)Z#SPvVNowkt= z&-~KuUc$2I#vn&q-KP_mLOWgTUW9z71ry>E^Yd1cTMe85hPutnAdpzPR|61XeE35B zZ^IFfg~F$xA zW~$Gn`>Fq8Tri;K>9|iosG2|L_+)%xoxh(p;eugIn81coA#Ny;sHJCoBaNDuYrFlj z!9*MV$Q(v^ngxgwo9T$6d4+=lE*N+1BPPE-%^X5i2F-_>!R0u9VP~XwcW3pv6UTmD{A-EjG8%T<7 zS@N3}Pp{Q1R$EC7Udo~7>%{E=pX+k?zVU0nKrr=cm<2s~doKbsgMYwr?RgR0RwbDe zGMwp}RzMTC{t*Z8E4|IC|8`_#s`cVfgLnS<>&M8??mWowg0wo>g0TXDlC*R+fdR!Y z_TmpIAVDK7K|3{}fM4{BV~kg`Q(13(Lu)~cvP?9HtoIea8olEMte+XWOw<;vA+_+T z%Xc0JY~y9I_aL~}2Detkt~PU`%$OT7q%OZEL7l`n+(_@e6#!a-Y5a0kE4Vf66V}

    RLh^HyqfGSth<#?@3$Gl^1y@tc& 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 O0000GD{W! literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/logo.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/logo.svg new file mode 100644 index 00000000..98bcdabb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/logo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-full@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..77d33b2d45c717448b560724f0f4dac64a510e8a GIT binary patch literal 135198 zcmZVl2Q=Kz_dkxWwOFipindBvo#?%W-IWM}=qd*?py&Yca{)ObinLQet$fyh*p?`wlVPy`4> zPy)rr{l~23Ydo$?(^S)aaC39>=g*(x-zPWMH$Q&-I6pr>{PpV$*Dh`@FR%9Z_b)Ck zuCK3;kB^s@mb$yUf1jMxefaR@%a?+Jg4DFMv9YoB_4S>dosG@S+1c5cxVV z%F4>D?CjFgQbiT@$;rt#7_5=0h10X=P}@skDS6hr!n?^?`Dp>eiNc1XeQsfdE?ut#~aC6FYWcy z%|hPwG`Ph?R96A z+a0aai_TkO)6u4EZ`+@w(?|G6D~Z<>DcRAVr}r0y`$rc!u&SxQ;)JQ&JW|wYGS06T zhkyf-2mtzDSMlNbV^Ro?zW`N)vZZwt%pP0xsE#_^d%>3ejmcxtXC=6GCBAY8D>>Th z`W~(*J$0HKqxzZt26rzHDwH)#nKRK%5G1tkmryo93 zMh}-id}#ADos&@fQw!-6wgvy*j}JBX3d7a!>Xt<-7qd-;jHwN~CpHwFZ-|>K{vWa& z766JpM#k+*HhpK(z^PU1AXX8t7Ja3Z@YOVI=}}$p$+En9l{oL-S9oyCV<*vQcQ>zC zENiS%#7_lr2hK1;F&v=xOJCIgN$jdhm*YKq2La<$Mdm`#^}-_u<%Mc{-BW-2t>}L= z@|(YY|Gl1xsod{pnhVj>_6o!~y+e90^T650rTB8`++!$?^VnWyBi(-h8=Y{uOqq7+3dZH3GhG`ul{2f$Lg1dFopPnFX~&Op$vml*9}D$POOAt z!TG&+eu-;yFB^N*c7hE=DIG=3#g!-}l3x8Zcv$)B%5x9f-LFQ5Gt*)<3-C2-a}0pq z1EB8+{=o+Yg3Q^8UBwz5?~-pg6fB6NJ#snsXRxJnd#o$b0QCKW@!39o3{1@KshPyN ztEX`mK8hr9ZN)n+KioN8NPOYiggo8AQp-XM)IKL^@tC}A#qhvxx*2n-s6RP_q*l%zG z-|)ef#KZHvXQB}BQ;Z-F4lI%bxO)@i#z8t9pGRz`G~@WqD%vml%_x@W)x_iGLB*{L zvg{8E)JX>UTIyDzZWm;G9`IGUbH2}L2) z$gty|pLQ1h+FSpAH`+P6W!kyD^R>E3(RjA~E&K*0?PAX9$!#?@b4q(%hC^}f6i#RL zJ-+Wz*s`;w>R(toRFq0fdvcS*q3%=LIgfvgeS@s2 z&O2nu9udhYtYWH!g-qSSzqr=mv;Id|_EE=(Nn(n02!MvJz1s-X^m<%hW+X9!Eq#i< zuS<1^$qiZM57^fH?{V(Nw~Nm|3aTC- zp}#d5koumvx7DT#+{XHGjR|TN6@k(oxnc?M_Mzg=9{)dFPO~ zMdL5Y3LR#PO1-oi%81vXmq@AeKsR~v^zq)GE?!$^^7*_R|HDa|4%}LQFxb2qII!{6 ziJ@6V0_v%@ewG?}lZNS|y!85dGX&kHEy6hxOKLC`g_-r(HX@?IH~&oNz}84Grd z{m0dqh&LGTH$f~3w)a{wvg4LG_qh?|_8(sGDfa>FFWxaLF@!GVaz2ax%y7}=PXo9+|rl$VQMfpNKOTiV`B zl}q8m;l4FnUMNlEXR8{+5MF3#b;ELG1#H+dww4Xb|&3U8n}|R3U0GnR8%4V7WAGQ105wL<2DhL!@e*@%P&C zjlR_wAk@4|&Tbu}ka%wOD4v(1{fXHAd$Hk23kpb}cGs~6Be7CxQqu8_!YZ?Wbe*sy zvqv%SR_l`NuD=2Bi79K`<~Jc-hi3Mgz`AuWDfqk?gRDR-It}sPFk4%@dREq8+}e4= zOvx2W5D05t=+g7^jlmWMD#*&Xk-nZMw+i{x$FedBkBn#qF*5ln8EQ|x*9#c+bYrK6 zB~q6Bp%3MUv2EUTeN57U_rH{VZELM7_{ZWt@dy{OC*u+GAIN()AhZCoiX_0>A1@-8l7z1$sPhMVQUhLHS`n6!jNYOfvSCWu?G*UMPl>o$@dyjxc zz23Dw7omGl;uJ5`MMqj>v@q>S_14lb*~qlf#fW5-E2zge2D3UV`^cD&CUS$sCu+p@ zFS%`$`}4cbtRF^>S-Fo)RPRwn#XEf^?aC6R)sTy{Tx#|AHLjHL5Kx?{JR<=EK?gg0 z-Qn%wCMk$NLsU?D9^$UAzh$*Q*J$raLLPt|qQxyJP$Rd$sd0KP=&Ea}F=l(LWdEo@ zC+$!uGJ9+i{2Z0}2_q#Da_kzJbgQ8*s1K|Ppgx%x_S1n-8#zU)T z^6((W*uX|O{Ei0|#vv;ivS>GSU2p3LYp#bMoe8_u!=IWAxiO1~Y$^%PeohdB zcCDllCdqL1v7>`6P3@jx1M^-1n(g`dcHyA6&(qOl+68w97*oamuA3y8F6t4FH|4EIpF4y z3qdDI7ZD94y=-gC?e7FE4)CSHWWv{dOGml^yRKThzZu0m*7CmOlEADy;jHhdhw13n z7I9auZ8tUc-0%FHV0hK(?C;;-V%gpg1(Mh!@)Uj|o$3~S^-jctYT1<;LzI{tdnGW4 zU?*b-y?0(4Xb)!$S%FAU4U39ra$%LER9}J*FZ7|$hdTzbUT^;}Ff|ZGN2h=SK(E&x3-2JETzTGSnUoQ!U`akXCj`u&mH*2rb7c8&Z5ez-V z@NFFjQDaro(<$Na5UX+&EKCt$eojwjY+_uW$aS-jO3%C|$fbp_hd*M28u64mE;QWo zx;4IC9osJ@@5UHkmS@QKy!6{sQPmhq(%g96Z8Lze{f)35N+^>?n``!K*13I+Az$*N z{5-(4V@c%48(lad3a!zRFBX9@;RqMTzyC!{%jWztLng!7oiSXN>3ImbX=S%2SJGuy zv?o=m5&Am$G1tl=`Dzp5g`nW$$K~A^P!+J5+HG4SgwbrHg&e2@tbPyM%Ml<=dI=?f zHLbs>bSLB3)br@I>xaH$Y;q()%0&zx;0GWEJA~s$h|BIKJVTg3TZOpkHIfc|!q5Z!OK*Bk|08 zlDi_ndi?JKhJL5&ba2MP3j|d zJoEeNWb41KR-Z&aM6CWCe6TvKLngbJS|g@I$d*qycy`zOX>OMy${0VV3%_ZBSdbQoK=U&9f&>LcF{L zaSsj3{cG}fE;135;#6L4t)jF;JwV4bTbyU6JB%e+c)Ccv|GSdo0q>>+A(o=qcPn$> zZj0err>jWsskntX0b-lhsuvT`>rAkgk%~Q*GjQllm2Rg1nJR;tEPNgVU*Jl2FqMeZ zY@52Y1QRM%0xGT|WpVl*G}^NCWH$@}9Gyqe@S>2R1eUFr05&B%2+_hsiESW2ZNC~_ zr+}ho$S^tet__$DZwx3Y!ZLvNlS)JoqT)*sW_6q|=Ax5)p2_&SjZbHVe4^|p?b0fE zNdvI8w2#3$xjiM~ek+Y?`jostjG=^ZLC@%qZz!;N{l}agO2E(CLX|ffKtepC=UDnw zj{F$`i;B(wDT`E}LjyIWP|xVOP6<_}5ph=uhs?6-o!i)%t*yS$E0*A4TL(_oij;Tw z7!bfBQ@5{`uH-^=1$KYN33oF~su2#>OK2tSYN&r>&aJ{z|MKNs-RIk#DoJ$yVNx1A zpN+=R{Li4)!F!B*P-Y3yKUyj z+c8$Xdiy=}L&2@t{svL6-x6z#v_~8KoLm8*HL)xPv2>(eC0~>Qjh=1#51&jnz z9ZfEeGBu6$Fy%Ff`&#++`e-<6Oo`7*v_*iVo*H5nv1Ikgr5xVy$Ei&Z7;DBHHvS{1 zk!~H*`9H3_Iia%7n~0V+SBu_GHW>FVtw73~YvOKLb?7S~p+Is=z-`&9sB=?mxf^yL zq_5}vWZ(B0=I|`h@{jF!rGBc2-WzBaoV6Uz$Vz&Nr(Z!6nJ@q?$gxN>@^rV|^ruG! zd|LE@p5i?#vMMn^gm$mFN%>PpvV=^CxtmbI&&L9WF}kFdoIKhq)jQ`Iu@JoUt39>L zmSIv(k0xyoOOh|%sD6)9aUD%v`xo-$G!m_sXY#N&mv{m~0#5{(nf&Fd%+32S?-RYE zh)~=SmXGXscAAz*(9a;Kzt`^&eup2nhtp1E8PxX-dz=F5diig$RSZ~2Z}cAg@=F$gRLTcr7vpzze;%Hk%hvfncs@gf{~g81^$PhLS?xn!@I-(~I@g;&>Ef zzQa(dqk8xjyB7{!^f_YFahbx-R%Npv+L=0^y0he{*jDvlOK#Hc;2nUSbhQYbfJ4Il%7bNQSNdxnsc zbzHXgnmWLfn)yw7<-1z~^)1%3Sj%k`0A`^tX=A?3+40K9d-YCyZ(VDt!PLOUr(LK+ zyGYb^lkKkHef%^Ctzju^2wKJNpjA9F_;)BJg8?7$a*kWTzX-r+XCgek*)g!fAg0b4Pkn1fCFnX=p3F0RtEW zRSPlZ9Q#VWv6Fa?2Bvqi6_`R>@5GXVeQB{(d=%+gQw^Wg?@;_z1-jBCvai?K6`9Ax z))^U4LXX&@A-U4r&}a%sJi)fY{K&04Yabc*)cHf2Qm;~9?LGc;(<8+=1A=rqpJiRU<_tPmIoZM@B+dv~41AfJ?5kA6|&G zx=PsguhB)A#gc&Iw(V=+QAi#vI(FmyA`0^RGb2PnO~2fAT#2{0L{e?D$1p-~K|;5Z zb;2$V+eQ)22`1*g&Ecri@5f!BOBbqnE%Y^x)=@%qjt3w79Y@`*K2JPUiBoC8cl3Qd zek>UKL%(AihE^S{jlm|Vd{r*_k}AR_>Ne7KxL%_9XilsbZl9n$f}ye=AcEuH3yi#< zrV^p4Nu^piskn9QHqw`i|A@aj+uVPbw9B^n`q%o_;7fcCb7FK{c$KKmt$B77QhUZ$ zwJWbGG}ZbZ6l7B&TF$1NhEX`r>U*(t=JxbUfsv!HxY&i*YEd!jW!kqQ;5RASeH@>R zYjJpTEBx_q8VrHF&F?j;CEUL`({w@YuC#XsoW2zy#QV zYQNDESk}$aV*C2Gwl;QnP#qfxXr#m-5Lc<+{ZYAmiU*Y{JnfBu+6)+CMB_M&$S{D= zI@slNBE-5^RfqfDA}rV@8I?=(1eFsNi1;9G4d+rLUiU!d5?Rew#muMx9!e# z*fuj}wz;VRe&!AQP!ux}9vECmgXG#U1lURCZ)_8`x;w8r4*C02nP1LEAz&Bx$Umc> z0@wwbHpxiM)7M0-ojR1veM|ewI`9Yc-)>3u{Cb<-USV~UxU-zfup!?8IXg_-)kjT4 zH>a645!Y0L4SVBc!%!=C@Mo9tI=0|Lx)9y`H^&LZ4nAILz2E(^o;fTuZNkkD-*Kpt zQ2c2dX2|`Q6#U!$xF~=T+Dl!a!QQ2JAH=wUMQ&rWM|U=6A)uy9V<14Sw5=LTq6opF zH1WP&jrKN4xV%gl8RPnzq(b+=R~9fpOTa@H&WZD4o+vV<$PKF5l+}lobkS;+5# z(|Tr+2cy}EAKdzv2bj>t5)yZ;-Y~dP7h8sn*edDhm`bXuRvIO|WU95ZJqVP)D@1{;M*1$F+I3)BbcLCnkFR!z^T8@H? z@4hsDQFrGv#5DM1C0HE_h*Dd6TeoG~o9Wqh?n5qR7bPM;U`1@ZB38Kf~lSM z-3P5f{Ef`oz3f{Ap$lVkW|e#nIZdmzDnYSm2`bDht-oe)3*}Wt65)_n!=63DG_et`oDqEO|eMQc@%s( zvEI!Vzlr7Is*S0hLR5KE@V~~{N~RP6>B|8Pb>l-e0N)9lYcJ77$a+I4rpu( z842#$u~f_my4Ix*T7fR$R(FB~!dNJySsJN>M#9oIomN-W&J>d((|Wirt(QD{P7M$N zp0Ju<B#33gEGE#@nF^j4AA9n#Rl{0m7wt`CsYQfP+@jg}Fdt_FJp2*?l^}g(ck$ zOKOV}i{2~}+qJ!S2khOkg#W7xb@B3**8eM>AnG>$AYM0{J!^c%7@us7tO0+y3OlCNW_zR}cQx5Ii#9K@=P_~da#8eAy?9sl-| z8bZ#d`0gn&KWB3`5zi3{ZJS`ub}qI#zH7eCeB?ZwTl>1GH-mmTT)2do7U6y9t76ao zf5k19wh*LpUBUEMzD(W>?~>Jbp>rb=C~KU6dk;O%8Yk&9b9@7Tq&__U*ry6+$qYgE zd%6)9`~#1i5RTpSjD1V`tQ}Wi45&oT%H>Ws=b7*(6cjdyrkB+9R+pY`-}*#CAC*LFM(Jh_s3VC|~j zT{=hiZAPpmy;qw4-86TUlBqg#sRDbpt@^e3ff3sSvrb0`Gsk;M9u{H$tI`3E4%&Bu z@EvgU-VmX^J@=O%D1aqwdoq;j=w%<}8%3)-eJGBr}$P#OO*{W`8r{8Q|watCLp z-w*RW#HYu_PQ^Y>i22bEmi`!`Z|u!;a5l*=cAz&Kc2$%T_hafyDkA@XH9-K1XRt36 z_^;G-$PGy4V>!hFC7%dKt2?kaDt)OMufQ3K3eC1>|G|B$mF2n93$=;a=@-=tGxmmx zh#VEg)nR=##s8J@4kI_NZHXR7UND#FD};!V|=(wcfPX|sdM_qM^N^fxT~-FJ)Jck#Ow)o32FRi=1&|FB2@ z!?mIB3eKI+Ln3-sUP930$bX6&)*0rnz^`^1bc)_Q_k5+yK0GODaOX<$n83(r? zT;4Ogltp?6wRPs(2gBzQIXwQzICHk(i57B|vVTs4=nJB!2U~IiU5@`Cs^%A@Ke*=7 zIwD6Mzfz9Z13;jkHO9C8Gbhfc9tMt?vQKWCOfp+*Z%thn+y5=8@em9ZlQCsWb2h`d z2wTV|*DLRw1x^@&CRL>G%IeL{S6`iaZkV?mvNP2qPi5-s%sX*9xk9Z(?PXDq;^4CN zVh@|Y!v%di=gZRf>R!6UFmrlXb7s{b41uaqLm)(EXuuHQg9h>Je_}eJ(pVN(YQ>D^$R-lo_nj( zY7^s!|8fv^QS#%ovsrKS;ZBTb0Ku9$4ffSyrnSvi2^t6~*l#+CMnA?YFeQoT)>~PT z^8&S>{D~5FWqT+jz;>nr~P@i zBX?4Xk&kcgzA$`k^E@U-w^NM?kr?x|%&ll%BB4(PT{*0#fCr)}Vg`XocSzBtDP$BEqgLDDAZWuei;_qFs6(f$B%Zi>;>7KkM?Ve)-jsODCmIG zI_B7fZV#&cbGZN4B!x$hV<^&s*!Fh5iS~(3oTNEC=5*XLC0jIVCyKnKpcwUt0>tV#Q($l{}~}ge2dxOU5HKA-uI*+g4}C!E+y3JXST+M)7uKS z!|BYel>$=-Mw$g$iNThJls%pJ+$7=jln?fA=s#H$ze#UUEG81W80z7!Z8;a&Ir9I) zj$mi!M5P@%bv#4cSRBhc-Qqcx|DGzMQXtZC0beB#MOII|G=h+U>9MKBlO6+%cb#G~ zGnoC!7=QYsdqmm;-rA%WNw<7SsDXr+NLi8fe)ADaQ?wbbNj&bZ#uJ0ybRcy3ii4*! zJSusH$5;?h4DN>z4~6^89(Pc3mqXUN;g0oY_>E_z8yz7JA)4~Lj?@BX)5&7=5CD0N zu;I(bFB#lZ^v{LKKY=C^OG34OPYcLSd+i;bk#&8HP>E+n$p_a_6N^FZ0jqw&BvcT9 zprkj|n-hxabzP`+mYtR_EGxTfL9Kn5_G*T1VSr`s+^B!8?WjcOf_VURKuAI^-3Hqw z==W3bVAkIJNqqJ6KtZzvb}0EYVI{daPfb@u;Y<3c$_OGTj&yCY^IJwI;i~}O`Zse3 zH|vJaa_scj#4ku)ALy5i2b(YD)41F1lEUSjzNw7_Xqy+k$dU{dUYDX@=fmuMlCvX@ z+e-SHSF#gzz8(TL^2+_~l`qC`^v!}#-Bi+eCQ6t~@y^(`MS#MdjM#MvQxqPv2&w8L zNw?&wi!k4x4~Mj+T+=tfT1LHN2u6)fxebU7t6+@#v_(?5K4GG0KswCzywg?*ZP-dLMdV{Rb%E{J@_&QYW7>8D_B>0_d zGbe@*0inqp(rt|EH6~+P0Wo|dw1tTz>&)a;{rz&4ik-3^MIa$_P3!UGg!GsDyvJ(; z;Rt1rJrHBW$X3UEA zcY;6sG_BZPSRL40} z?gyVGvf$;ohjNc+GW&e9Y+U&L^J9uA)I_dgiKgRKU@atQ zHRcLU5Gw9rgh9n+*0*)05j|k&HCeQ+jBQzY#k@Hh6x1{^&46tUq)q1)#7C<2ot)e? zuH(B%A5e#0)XTpgot^i;>E$Yky#lX=eiso3dyzVn4>p_PQdqGXj* z;(t5gD7aPD)Ri(M55!l@$Hwqz8cc{+Wb*rZ)KEopM*Zs#*Ww@N{uKO+YBC|B5Bj~Q zcqk7h@3ubD++S!2pWl7qh>iAkw=42D6}C|7wOYY@Kz)yOh zA~PxBIWe_8JnEPGQ~r>=Esx^)Af#XvYlQiCs7@$}CQ~Ah}P`PIL~L zHhCV8TKdXSXZIiZoy8wh0WH~tHfl0S+Rz9iBv0wYo#jvNq|jyFslLQ`F1vdDy95ya zk5%UQn6#}TVfTu<{N!nYKw2yKg!Gcl?gZ-tob;x0-k0NeYFq*~Y zVd!o#o}* zChK|UeC)X?x73%MgFcm$qZc@N+;71clfK|=qSl2a2)4KR!*k)yC_6mMq>NF|U967Pjvyrz=@sBzt-fX+}ij@!Bjg1s417&wlBm6$rQ+~Ep;2FWm`LuN3DK1AWQ=l7% z)M)gG55YGQt~T!y@Ytrp_az#M6W}A%=KA!52}N{fNtvm1`9QUm&wEb;J%Jd?yePa* z;O&+r`)d66mIZXR;4ZRp-LX!qC6B*;y2TE?xggo8o7YHzx~Ql_AR{s+A4Ekz~LZXuFK1v%LwHkwq(%aI-!thrTiDp zYESqj)JdT0%F8MPhVfIqepwYgrQa9l$7VcdGN}fLrVV$(erMFQxahFOb4N@V@#NJH z>85a4*L}z;BL1Ir5CAawsh1teIo0J^j4~eSXt|Bdd4E5zkz%^F@{>R4r|4zAOEi$*Ot zN&fpO)w(pJr@=lqT$u^?*}HUOa@7SSSB$V0&pnO(+^^r`)cOB3sM2CFn-3I!m@qoj z6ci8;ck?Mm2?+}50{|;O;_;p-s+{)vpX(=Mo)bEv%)Svt>|rz@xgK0W6C4B3CKZu>acIeGb%im6wcA-Xi7i#sAj?m?3DdT zueLeae$`wxkKlyd79^juDdyls?TjZ&WtQE+}IeX9ZUiB`<= z6Q`R8cpHh7+WZFhAu9^6@IBsy98O%~qLZQKHO>fp+}%Pk-94AKEpBt|Z7}aw$F^3> z?Mbkik2b%2>r6%Wrxs6T#VASD^+d>rIF466#Mal-&lLw-gEy<5`j?(M4jVeS9*^&) zpBzt3Wo!-W55NC63Fb1Kwkhr=*|gmwF#Lx^U9YjqLCh}Hwq!a=UR_VyTm6YBZqCPo z@eGAhx(26Sjp6&*w$gj%Vq(YlKZq$-{`m8Z=3<9q;?Lo0^-oS^diB3jro>&M1O9Oi z#{92C-vO?1{PS@$OKKDkJ|d4zW<-0;m;Dfso-kLOm=hOED@x>aoN=B+$Zg>CwctmH zO)Q?f-J8`yA3LR=bXq%3$M#B%iaq(rIWC?fz3!+FePdl*z8@~>1;LuoSS@DB9(N88e zywsTSNO%20SgWu3=T#gLfn~nw;3hYx*YxiQ75U7*9&V^50gHF*JWWr}NV=KuK(U8c zlpqAdLDHr3yNcugo+ANY@{;53rjmd`>~GZShl^uyqdjhDHaB!9w7lq{=^fV125}wQ zkfGSLo0Ek0AbqvDAakEo%xi8a*Jo}h`CQ7RnecirCI|7JtPz)|;O_fs;zIoYx`5a} z%|9}Oyo43iiYMlHIgek&l$qTm8L90)VeS-6*`>}$e0PoM>-+sqoO4U#U4E)d^&&}E zAsi?0iN-Xde5-%)+&B#><8IcS2u|#V148A^$Jg!s#pD{ z6tbrB)DVK)2T_RLd)_H1>t`|jqG29eBlU);5w58 z%^y>U0`$m+V(jzcm#fTgx_!xL(`Ao!O0KM4e~c+GNPp_^qQLP5yG?cx*>)~#GABd2 zNj7y-QPhnS$MG9|%)!WuhUr|F(X`-F#yTw^6=!eV8Rs4|}H=%Ta= z#fhr$%7FvN$vJquNXF+mKkttc&qV zc=!Kh{CBSS<9Ozxv}^h>Yx|z**|19AOp0Gl zFE|US(4&N?pK;r8FLotq3##Fm4D$&W+d`CPd=}|@CBMNMUbefnEW+qn3LdAy@npZW}k?-o<+D8Rm~n1f2CrN@0m0A7g- zV`dhEn%M)ORY_UziY^51aOy+ciyukjxYH?;JIt&~d4i`Ioh&FY>vI)klQ9f?_9+I2 zrER2yf_{s2i^es3ROX%L#090{>2k^$?&qUf`051z#2lfUJW?1euUQUQu3c&i1+%}6M7alu@SA#Gg(0Il(Dxv;M=185u zG5v)=O#%$^Ppvl*FM!bmJy5&=-P}0Y5!zBT0WP_ZN9P73xtrn=kcL;`;PjYVSZ(b` z=PCweP)HyGK(`(vi}UeWqOU3jctSwCD3C?ZYf@|_P9;W<`v_2CyWYlPd%+uZ|9hMB zc0;SLPwR@hi6KY?6k8GqylA(G5*|guKh?UNt-w9L!w-=4O|AD474o)d#EUmp+z${H z5=scTbrjO4${iY_{0gA)A6k#4fcC=K00=me0HXureNTi33dE6#L?8grA4(_`0=f&v z!|`b2ibxU+1j3P^)$V1wQ*IJ`%T%yvqC@e3-_F&I`ODg76W38s2h%a*mA+Ja^lR$S zqXH@^ePB@-BLo8eeK&+$wrBMg%I7U*@?iQWDM9+JY__8HRAP|Z@Ly8QAvJ*AI_tdZ zebHL22*X`H$%o)IzPcuXb{$`=`dfcoo?#)xfI8R+I`3c(^DJ)R_HMxOCZZ|ZSCDXu z{TtonI#bw?TswG98 z;zK}<(iFOa%1vX)o<+f0AHc>>oo_=}KXTa?4^yavgSYxJYQZD1xzRKz3E`a|{HXukE(p6d{o0#w^~O>zq74&;b6n4yoG-FC^@# zYeZ?C^xr?_*;F#x=r8kHtadPNJ*y6At16h6~7eoJT{H zRp62p=K}ziSk4HrqEek8gjhcIEv16cJLdaErxH;Y z(^XSoghT+Wzcm61U{`iC_NwxP0OZcmIBcp$81FmR!A$}wF3mG(8ayRgq!~T8OfuxE z49OlR4(n9*Pn^BGA0t_k93SYyCsn^ip zg0SqrWk98tqY(L2fUPV_*)xOKng)6stGge_RVhrz4;3gF0S!QOfG~%&cJXH3amE~ zDu9fjlMOl^fB<1YP;d_qbXmbnpI_TgJNc|?=hak;MLH`K8G)ydtatz*?gRTQJRw(* zT*se*8VA$#NC5P2;RFioRHL-KLiV8cC-d!qvk||LFu&XcEu~uL20m@}=r76;JY`|r z0^`J(vzyE0GeHvc#AKs!y~Bs{>Y0XnX`^l$_lHC#jtC`rp(1fgYq)!fP_SkYXvHvS z_39df$TME){TpOb-)?rim}SQh;o+475L03E4%_-`2kxzr$gXCjrbtB&?T_6D5I8_l zIL*lipj|~ggf*vL6D@u%f@M|ZJ6^5>H0QbVXRnK07?SPc%Csx*%Nr{$+biU#pUihU z??11ZP9uhbFP|PPdaXk(K{9|fDclg-vloAJGcZ5oD(EPoomb|lJ(6gR&-5T%ihgYW z)2ufj@dS_{zeb_PUXpgvV26wCaMw*pyEaLo7?L2-dwY0^w70U!gI@#ON6s+5rk;<_y|*2 zE3eiUmTL1#eANa&Lr2x*32#kl^Dx;}T7^v-OK|w+c;Hr)z6i7I-hCADF0z^vdMJ!C zR2^edwLtebw{t>)hdiq~SRPxDc9EmDOGZ4Vj9(oQ z;@t=6Ikk-$Vk^J2J$a6rZuV-H0w_&Zs0rQ3@T*!(c^*4{YJx$s2P?Ezp9;m@b;AbTYHm+C#&<@| zGWOwoE$A^9G~D;%R@dO*;46a8yBrtJ$7TYT;?4A{W((#?A$&>1Ick|~N}--2%8Lx{ zOEQx(D@q={Ln2msB#xD)Ui)FQubMwwqoQdQcUr6UVyFX37CD7P$$PpIKWTf2yFU`S z>x5KSzpKH+3JT@#)&6Mi4P=2TjP8w$=?F&mZ%YpN7u2T)vg)V$NXoxDJwq3U*bOUhyA20orQ3 z8`NDfNXFRYP&`&{LZCjyBI8q<-wpY!sntZm-c*CfYvp%0k+Qlp#E1)0=Y)1$Ud5A? z$23v>)bE(_u^;_GUHAY~z#$0Ru1}8i0d-w%uMlB_$(ES_?4f*7NnJucPjL0Qb}qMd z!30GuKyysu{~|S0&lJKU+f(*j-+*H)3^H{z{s5h~!)*uC$g(578(yD)`4H387+^_a zXjM!2b;O|y+u=k%02p!YJDjCN&w7qA(ibHJ-vizK7i1t;WCt%Ud&n-7|Kq?pfy8LscDu( z2Q9>7|T^1hrB^u zrFSxyFSB1hL5|IPAHkqDGk9&4Cne&;19G6XXJc3XLliyIzhv^nxH;4dfNb&xRgqgH z_ZikzJ_vBUSfyXFbR=>h`U{07ZK?-fPCaP3ybavXWMktkME8$}Z%?^Ir9lf?Kww>B z10d&xTB30lUf}Hnlb$DLAe8GUFZ54=(FHL8A1t!yi;#st(!PrAX`B`*G)*73H2$D| zXS*``=_8GKrnQM|;NP~Dy(nTw&m*`nmo?Qy(){aB3%=&hnEtv1#Ic1wE;d!@RGuXp zA+7oO)7>(*Vq@on=qWlpsqeJ(z*Buxa_ZvsN^yt$iaH>2PsUL zK#^!!C&Qp8%T}m;`VfwKM_pPg*TU#Mj1Me>fSk^Y5JmThj}GZ}$1X_C zvh0}9b3Et`fS5dC24gY4*^))lv54Vw7w353{tiH5Y|t~m4}&HU+;%G$X@=ZbeFR~E zbJ8Rp8+kJwg>`2tk@+mhTR|l5K^YNrmjER zCjq`Bpa@#(AQ7Hw^mmDT+?O2MnSetH-hlV= zzdFRCZ@u<%4c8E}h4)z^k?xHnl|XfHlO+Up_-a zB9jvlJiYDx<&|j6Fx;AhJNY0%bhDQ3-o5sgmJgM^>15rPu;&`zILW7b>??B|{(nrp z1yozX7B-rM00B}+u~LczcXtXjP@s5$mf{rG;_d-bq{Y2>ad&qDMT$EVmm;OOLxIP= z_x8Js_!VJlqkX%OT@*1+0xVQaqpirVYy;QyV8 z3k^l4LG8_b1!gJuTs6)f6E7zDNizv(P7G3=U-RX^cidMAMYaX_DCHZWd^j)P=v&#O z_L3<#UEcY4h#--I9gZ@-P{jK(L$*2f3WknizEwaH4LT>A%t2t&eWN+&#q$W|$p=j)jk@hBK3dly)n^0gMsO^}R zyZ&=;#@ela=Yqw4zMZp>vUgL<8T#}MB?9@?bU$h-p%#S3g|NX(k(K|e_ix)_@)G-Dr`;Sy&@kk#u1tpvq zzQ)ymW}F=)oMJq?ZvY5bA4-T$zym{M2O@DLSv0=H6WZ&tQ2_!X&FHtc@Nll&@YksF zk0LCesASHEe`eL3B#ujz_MquqJ@LUrbznfGl#>)8SYIQ^Hr6-q%=u6r6*?v-dT)NM zCq~EGyM)fNf!;|f>=7oV>YE9sI)*iH*omF)1{PDcc>qu%A}n8}T3j$tvOi%HWyN*8 z#K+lpE~)L?ijL0VPdhr6o4B3~d{v^r-<^410RXe;%QfWGYqG&xiQs>agW4WoB_GVc z)m>(`zEDo&dAz5M{~&IgE%YUh^77F+H@O=HNIo8!byp{cKh&`ith|7p!2Rj}TDpoI zQ=Sx)rWkHSQy}h8*B;SR6eBoh%r;o>bgH*Hcfm8OgPVMyzC}?n!Vm+fcR-afky;GJ zyy$Z;-h;9uj5MV?JvFu0O`H8#Hc7V=vUAdj?H9&pyKf>!wP$dDv6y}M=T{K*NS6m>^v}69noPtIQ2GVy_XU)HMJ42OWBa+*aK7WJ2A`KG66Plu# zR~O?{U{Lkbp7j?ar^xVNvO{E+XdGcC|7gB_GqB*J9rO1F@zt)xGm42c_al0O-6zOrqfxu-7yXg0cae@W5mUxYg?3~yW( zn#uo~P3x;4bdsPcDD$qTBrN9j`RhnO7^-*$nZ!gfD`K6@4U%t9>)unnB%9n%1O^nM zQ0X0OPNP3t27f!qdB|g6K0oqDzk)(Sy2JXBD4{0e4W%y%P0=dyEPgrRF}N5wf&?^J z+oY6s@_!%~@>(SMJ zMwE$gCDGGRJ7$Y9%=`Hafus#)!i)#Q&z46Uyko3F{8LzH!FlL^6YVMx2TRV7{Z*)t z#&QfN81hy9v@9Zj^8@0_3}LuJ&_m!qGhT4SO#s9(T}!;{K^TJGfS=Wd|V5o057FJ=-hzWl` zLPPwc?iEwZe3sMej3kByW|q-}9H+r?bw}HjU!(MU90Fm&TR(Lc&vwLp=kly-ss4MR zkR8E43_u(q-*Oj5yl-f*grHNB6d~D=7~WSXIHK{v$2W5ua@7;;oDj^e3ta2d0UAwX zW1+Ii^iX_40NBqHJ?*P|-?Fq@T!@z;BH2WLZx%ULzuXE!5xKPGZVr+nRj;kZ3^?5p zas!FK*$`wA>ZWe1o`S(ZBeL+1RDiBeDI~Ca90b`%U|<~>qTw3FUu$cKu=J4u0vcKY zQXD;9*7QXFnBW%TA9Cm0s4G4M@~uu307VP}7$J0>>3kIZ@gZ&B01#3ncM9j>xE514 zl6!Nhnj1d0d&WZplejvav>3m|09A>91%9Wnn9#z#U&0cKWs3@V)q;p>u_A-5UgD88 zW~?F+Gu2cjB(UY|C<2Ip6(%Yw>j3~eC^y+Q&c4q&WTRAJ(!#ZZih=-WEA+2f94Z-J z1`Qto_;2`Tf?fAHzw+%%IE6@$!x&NO-H%V0B-n~suN$~Ax>5=%ve`tjJ8EB2teWBj zz)3J|#`GgcF)q{BrRYmqzWm?qRX-SK0=HYA!bU6(unx8ses%9KJ9}DwaUyF6p|AUp4n_zMimkcY zpHMj#2)4_1myuHUGlZ7GBLW)0{f+Mvfi#Sp!@FuP3+mp4c2QCT11hmfWJU^JzjBO1W%k&^lq3Ow-I(K2!>fTV@3hFa5V?Uv~WC&b5h zW`*()%%szZujv{qzPu<+C_50IG4NL#Zz9Hx$ANC!d)f zRfdGhjcZSS_y2i#3OBq{v*n13sL3?H%QCLeI&Pchkxu7-IyJazO|^e>FWnfxYDFB1ws8SXeXW^~&2@ zdmzoICc*<70egaZZE}`8Mt05YNS4-ufevLMNZG=Iyj=mJWy9%8YXq=JVuAyJK+X5< z6??xQEqzqL$Fm$?04nFz<>N}~oj(XNj{)b0LQYwr_(LAO)ihAZLYew9R4y>hL<_h! z8zM3zkl0xQfM9>`!Zc5Er!2jH0;_b%H)sced|o+bZA}_7EL(}jY}McWcbIMntvKQb zy+@3Ee5>P(U+r9Z#pLH35<_)^n|{HC0fKj5Ce;6xggiwWmbK{Jlf%M|KK7c1ynwSu zj!ZQ3Vsv#o+*84%I^W3X0#MEOX~^j9Lkx&Id*$VfcOEx^3jdZz8V?W@W#6T)vjURp zIGxMzsu17>haJuZ8A3M#)pHE~N-BuQknjYPuVcWWy*zj-zr1OK+EcidlOTF8~D9k6LF<|*DC@Um04TdO=-qwGkCH?@TuZ$ z?#{Z{!xMzzPO>ptAk75f0OSekz(VDRi2*^op;6W@WN&#Wmy|C0e6S&O;b@lF9Sr0V z&TY0k#gFJ&M%h)}+MJime z*&yw7=^hC4aqj$t`_2piQ9KL*Ub+pU@nrVhXof;X1%?}tStVgV^Hyms&3CXOBOTwh zMHtJuG{63;4_s?wR3PUY<>Hu0@Z#3l5NO)n0e046nMhv>bC@Xju?G& zDNt{w&v7BtO7@uGV*@0zJQ*Mf86tA^=Gc34@T*%Bk^J_e)Bw=sPXOUDv`e=Kto0*d zptql%t&&Tmdk}5p+94f0Fd_QS3MTw^iOwV)uryYjgIDwqRrQgxzd_+D-d#s#%kNT>I0&_Gwa_BGe2*X1iuU|FrUKwmwZdiCVR#l0{N2N#9k*M#yX^2S)76muC% zSb?CO)?_=85FSzX%uRK)2F1)6;V|f6s(mB%QpoMT z6nlHqHir8yCC8!&nPbYeL#RZxSvWmWNiMz6ZU&(7Juf+x$!1QYWb2{7os$wL zXuZyklOhfqLLQ3;v4Voa>9|ZdOB@=iYoBMQniu((e>{A;-MKEvIuIWjoSW|O8^Zx# zSiU&%>bG`{Zzdfzrq7E;0VRw=9Az2@heOkt>40J~`-Tysb$*V+A~yp~RlI6B@9>;j z(a6{CEy_fXRv||oU`Q~+LE*L?=#1wiy~t{Rvx4)euqN}ZX*?OKH)kLzMIzCidU!M#iC%%DGyueiBBELdT)70^@TW_4|o26VJS%; zbiN5Tf8;dv>yib2w`v!b{WJkBVW_rR;b#-%vF^Qmk8m3b5G=44NCkc$2B2n@ooj!zI<6j}`o+)-ATS^{t39dk40BnfE^p&mSzbExIjsUP z0J53{47{ws2Gc75B>7r#At}BXs5lV19Gl^XI1*qOq~$zysYaS4;R6la&Hni!$gsJP z*McXRv*e#Plt;qv`6H770~`ko8~}8^hr=muNM28>mKGE_=kNc~!mn~bFz@nRw9zzM z_$gTl5+LMk9|NvqH3JF*_qfb9iIPQe&rbl~=f0ja3$9EHM}0iFK1Uk9O7;H_ivRa2 zH#b2ZWqy4T)*6)$^*m(Txy^Yz1Do7OF;3~}P+nVUZ(A3>N!bR)n(-eg^&s?ji&MA1 z^Zdn&v1P=}gR-uT$`a2HIJ70;K6Augfry~3m@v@!(V5t&KCPLbsr4nrE{tkx z$Rp4N$v))zTo4zc+Xs*L5gb1MgkQY90sMYe!!oiZ9dT-kM1b?z*Lp3Y>aZYr%PQ^9 zVXZ<4=80aw8ml0abs8oBy-2z^toQdtp3>t`)e*ur;;G8L?7`a~dD0HZh5IIL@n9bk zhK;$~NO-Wi09}AJon5i2i2q$ZLU`?d>TaLes3dol3p7w1d#zy*&}E7=~s^3{=P8B}v?%$4z*(t_c~f;uX9&LdYT+ zga#H&2XmO>eI%YB5eWFIK2)r=2Nu)Ov{r+I0)asnkK`0#5=bf{Ipy(dIBbUq;z?RN z2>K2UKr&!~K|<_2(hyV?&Lf!z6hIYFX%hBb@V{qL{YwE3Pz^wIQh~*`LJ@}B3@`_5 zpgXLP1TrF7*h%@HAN;RPIINHgl1Ro1gUkgX5vxB%9y_%~4}(1tVsNq0&E*;JM^7Ro8@UTe z!I#e-;HofY2r(EJ)C3|%;W=ANYBg&Ux%21&41y4iUjtNO{q6ctVwCP{NjNcBO7c;) zOpY0X3Ow@6jlX&1>NSJEOH?%ES20zK$1Rs%H@_VYmLo#lBn#QxIQ-{02XAIbfqoMU zgbQ;`bQW*9VXFe3J4FN86L9@x{@|w|MLd0N#_vH%`4U(hs^)4WA`OfAjJU?3a1>UI z2}zx|2h)JV3#|fG=_muoG)&A#Z3wcQrI*xPe*0oSs}UqjiZ`~Sro`wO7Xd~i53F8z z2m_y%&W1vMA`d&YB(+L-G!Vm)&%p17pPRRQuzR=3t}crc_^lVRvjjF;V8L?7MbBIJ z;-O7`nyDnJzbSUBgrls`vH2cDB3xiqjNdTHEAthdBt0RcBhfdAu6pjdMIXv-i-~=9sB0uc8PZDxVoYWK+(xdKwTGo;# zTzvsr(>&$!p9%wu^scOs>VGY4?@s>%3JvU1%mJePgWywjDEc1nfwTxU_otsNCFB~4 zU-mAg<$VCjmQ|VN{YmLA;K6z-VFe(niTD2uw=64B@Nb-%dN28Km6qPrYvh?~h$;7hKex>q1G^^|qpzKrr<-4! zf=<{yK!Ha$Y{3?tuY9m16RX*;%JX4<-_)C53A%mDo%;CgZ4zG3S%M}ZgX03?9yo9` zqjGUsREkOJaf`KSc`KvPA2u5yb$4n{Aa?p3Z1UF}xLiik`^z^es?xbU>mQu;j`x*8 zA+47X&f14TdWaeBC$`a+syuU9K zsKAmxwIF51hgKm|@USfPVqTt82{*j4`fS0_Jn85g<9%+neVA`f2tQPgYXayT(gzWth|A@;nuWY2<`qsXV=Z%$qw>ag8q{*iI->_((wh+%H zX4?;8id~~^bQuVZYG+;hfn`QxFB|-iB=OaL?^u3>h8It?PHRBaV)0j6U{smK%I(D#&8mpn4>@ zyU$?TQ`bWJaA~XY$qws*{1=KU2P0X9*iVUNnHbhju6G9Z$t-4XGLY~+x)biL6(4(l zoj`8oyFbEy;-0od;A_ZzJd#3^yY(Y8Fs62XNwcJcP?TZFY4Ljl^hw*%NK7VSr<4?=*${KJum26czH{3cYK zOn6KWQ}MpCLE`bR+p&6fl}f6FYzw9>E8{vDH;bF-TfgN@PhQe?czQPyM#WnYno#|> z%Nv>JKH17XRXHOR$Q?N-7WbDTkH?|jX|ug@41;=I)$_`rUl-SQ+}ix!IR zkb?Q4_U>!R5F`#)P{gSq0Bf2s(@RZ|nKKAPj&Jc)mr6k%m*oqT@TRhnR{rL5n za{QNi2`my;L;C(KLtpY3H8;IFDp*E>ws#L41;IXgw37}>sp{I1Sxn*`$Cp$ZiOpBg393y8GobW zPYSY)a;x(AV|WVFCZF~&&IiC0l&*=tJuP{6c}fV@vA@k&Ps6~g^cZC`6{g%L@w5}w z-&6GSUTErZ8$qL1Nt?ly7Mw>wGlN-2mpnNu3v|m}5nj(i+SlV58gVK!G*G=rjQ@5y5vDL-*EP<--OWchE=i=S~&(6*1z3E%#W0zk?aEKIYzKrd~ zh`^JM)Jzqlz}0;x=chzqp+-XCK+?n44yn&3nlkRHZJ8Z2=7mj*VY!HHteIw8g?V={ z4XWlxS8r$|zC|Ti)zq0ZYi9iKq*EIwXRh27s-@nxqtZQC+3pvD48NQnmly|!KAK@{ z^=-!e1NEFQL+aJUpujAD(Spb@J=G^f^V0Oe#?o&U{t@QIXil2EzI`Wy*;I@gR-1FE zB3a)Ms5@&WK7ndj>{$YfNw1T^#Fit&1kU%bT00gP|6r61H$5-a>MeyQxn~qVvfJ}v zWg3d{!Ij=c-iKy@#^{HK87yf)^G?f^mdh2?p?M$oB8%*R(LPj3(h24 zzLB6$zpInt`t|-W@+k;X^`^Z1xA@Oe8h_JA#H1=4AjOWSHGXsqMsh<(qa_=uxS}?0 zIh)#D$M=}@GaXX=fiRt-`X}H|sl6g?5h=Z3#8F11ajx$v0P@sDY(l<$Qynw=)$$|& z#T~JOQ4<7QM(F{KyX7Tsy=RJ4973F$s2_t*8AS4?0I4%U3s|)z zJ%iT0VCbay!gpUvr|j(CoG22E(EI}itYUnYy2RTTEbgTYJMrdQJ&&3z!rP`wiEp$`94+ za@ubT#!~s6h@~x+VmK4OGBboPG-!9ER-*(^1=y(kJvoy8Dmo^{whEgUr*>~s4HDTa z$bhq(>^nj6b#-_X6xr-y$`wDvOY2=BalZ!H$;;$N{`4O#x##eNNbG>GFfkBhYwpq0 zXV`F48oL+r=<_tZ2I0R4g?*yw5-xOOAM-Rk$8u7c~n%?l9Y6pGZ}yU$9en^6RdY?i$oq;FdUaN1zP6OwlmT(MnW^h$VT$050PV=IuEpE zON_3?JYV^&^%r!w3*ud{;o&o^!sc&+rqQ?jn10oXpidfg`=aCVa8D8F0%^E(;7cv- z#P>OoOfjO->Se!@i9WWnA}h)1u=p5TtsHaTkBv7u(r!jOiB8_oAfb?%I=#(x0PC<_ z)zLdrA`ABaNuE zO`8qs(6d91@jLGhj#b9-_lh&^$bTB-RZ*Kz8)E|)iDPTYPEq}uHHm*Y&{Xb;x6h^= zr1ABURNAa(ksn~VyUd>ZB$kY%z>*;73g>(Z=P|CVTPuUyt~P}gZq zr7-NhY;Vosa!q1n}b(d1;< zgkba|Iss-SeCJc%<&Gq`%DP9M{yn*C-_eVLmzeC)Np%a_U+=ep=o5P6(R;eSRci0q z!_53^0Ez%{_5T$foQOs>^aB>GyyL!4R{Lm_ljqErU54+EXr-Wgx*FaEEI&cdSH6HR zF?~bLST65VzJj9Ba-7g*Z7TpgpyZkHEA%-stC@;J(&eE!(nR{_h{p{7Szvodd|=EQ z0;zK7N6zIvwU@JM1*ne4SeZs#gWsjq+Z!y1dUicxQ}V#NFsguzLL~PmSsN?U8*(D+ zZ=`En6_?D9y_g=~4IdxDf`3ZF`$D!*OX4Ib+KNzRrWm$NxU zSmzh5qv&ST4TQiV?5zQT_2vtt>{L|ztFMlWmM(4M>YzY|eVp8j=deHI!`P+#JqlCK zDwVIl2#zGiK0vLHp2LEh33YO`d>J>4)>QEaI9xX z%n&=xpTy|VtV&iH_G~d3Xh80|LrJYwE_whm=0rDNGz6!MxQSbm6%!rjlV6-ns1xIW zo08Yq9f%t9Bp|H&KEN>u_}M9>7C*2!&%RZlC$c{d>rrr#$_p!H2Eoq$yuzuaUBzE; z!A5RYx7{G4S5CEE+mV!WkgjE7-vRZ0>;)k#uVW|L z`!sY12W*>8=ZWMP53iYUtUDD9i@7fQ%!6<8W6Iyvp0QWZ_rxWT1+NtV(dRE;U!HTl zYH7c9o9*BkKCe6Zaa-V-&-mm2kHSYhY68~Y+;IeNnT7f=G*VIf|`LD4%$V9f#p)a(|keze3gHm2h^Le@5*r7-@tpFH0?%!v7|Zz>D` zDe%D%R!~cqR)JYW={fQ}wH0QJ@yf%?9byQ^9S-SNixpaO0iu47B0Jf_GN{aGZH1|$ zhss_u4z~c;e3rK}@IFl&zE|jFJ(GVBfM;>2*__0{hLVu5@B&}Tb4pmF^9$J5k(^-( zQFnk8_Zc+n`YixG_AV6bIWw_QJi{ISYcBf6K7?DEW}R3H@wjv&Yo3y*y@Mxk6zT2F z#shBH1RrC-qxn5E6(7Czv=rx6H~3vy8msb&l*4I1!3xrqmaTE}z=hk>E{ z;te7hh|0EIs%JTiHsB9P(>W6JYc*YDQ}R1H{IJ*EJoumh=$- zVGGpgR2XxWEhrd|d;e!Y4QSLb1IMt_W;vGQSG*X`kW=NbST;Af1NtBR_C+3T@pl*r z$^PC;84sj$>+$*~g;avFFn~1xK#s3O8A|i+d17&xuwpaGmVz(GJ~&N1(Vys;gY9Du z42e^StMJ3tgMgo@klb|R<;3Yt>=mHEIzIb|rUbJ78rN&j876ty)S0Ms`()<&ktq+Jb!9E+@3|EYAUvg@w(mB_fT&h!W?HbPZ&VsQyBCieax z2$o{hdG9bs?Wn{B`2NkJ%?detU5$I($`EYtJLGs}j<^C|KkU7f=x8TQUvW%zFWb7g z;QuHus+UP0RC&0k{gPaEvfr%3NZ1Qz%&qbJG6(zn=rHzaV3ICY7md$QK_W-5FbE|d z`mSt*2tX6%s-sp+QK0p8F^C)L@lNqt0D+*FGOQO-FAb$hXt{*%Gysi!fRBdO^(z(X zQG9^@6ym=EfOx$~$kxKx^2CAcktLL|)*h8?a8apT9f*%^LXN8!0g~7zsap(iu|1Q> zwo2_ShsnZ5tTTA6?e60aM%gY^rZ?=DcIKPIyw-Cw>022BDg9TEk>;NV8bB}(@Up-Q zD?|(=W%tA=Jol0iCcILwcYVpvUWL>t)+Gq^2Y|zVG$*d5dimAv&ji8qR-uaNKuGv; z1qcKe{Pm5K>`rCqd*{){wSuo z;&=NLsjBfvS~kqWT!CV2+i7xxA}mBsK3Fb1;&FSo^eF$b^dl~Os-N+*3~g?4GeBzA zWbd1s+ano!jNf1~ny<|V3mi)o$Lk(2wtJiT$m+^7-;t~YfVX?bgQ8>EIy%F04lz_` z!~p0{X99GX1?zmC7XT$AtHtgGGr&fjij1jKOxeMX%^1jPqzlhT+ZMfHmXz7niUjCK z60kVL@$%8m+(03ejt3&bmyep@tsjKngv@5 zaCYUC8Rc38jpS!Un7-rer9WES!Z4<+EJ3FwLC218;RoYCXr{&LnaPuX9glwd_7(#h z{b&r9;>hq&r7swzL`RMQC}M!U&PnGUOl=8x3fG$y`st7S8^dG-*HjRlP3o4e7QZaW zSE47v)hv&y7GKX@!aLiw4$bmmF(gFX&E#;3WB8o0)cP5XTh9w&ZVB+?(kM}tU@@^4 z6UD}vrBYc?gn{OFT(}T}f*{>lcadBS)0(2oodVb8;YI3;h{@7EPn9!eTnVh8{ z?m(!+4-9&f*1$Er1QhLunI%0)g9`%wT$toKMrL&)8GcXKfQ3R{sK{L^hX!C>2%O$~ zWUx43E#W0~tu5Iu0?^1DV<@o8)1vB~xXKPwZpOME23U!#1!)Ma)L`ymxF?cSBIB_G zeovs+)Szg^Bc~si4CP7ad6M3^4Co3>n^xUPP;{+GzrjMn6(p-&2#8TXnmmEAj?Bb) zGe`vKC_cxo>bVV~ae@#6AM@Vn?Qi=&Lw{rb9urK*Du-n|2MqX9CO2o&hyf8Ph?dUz zf>iz0S--x_+W)uhRr-^<3GPr$qyrjT^jlcQL2xY)Y{uL2m}rR&Ys8-ADR;b#%uuI> z>eDDpDw_=(LLg3oO-c0DdO zE`44e#YfU%pR`i4N89DxXJH3;;)6gnmSrq@Ngn)I-=c~Xkn~dWl_yZDa#XB}9;(G5 zaUbbGj?wKxSWf+Xvczc9!i&K|0Z)~gh4u5mhTWR_#!{;4#4TEhi)b@73T!2DO3LMtlsz|2 zt|LVx=dZ&G*7H(Z#BSHG27rDq#k) zR(H903HdeBct6)pxiWk%A5edK4k~ zCGhdf)i~nN_ER+Z}!E5)((v^(?V|TM5y*QU}9Ol-lP? zL?M{NkVur1{zsX%3RS)at3hzkzT=p7~Y{_np&bE}}z!NseyzXB$v@zv(LpYSawB}o} zZ*T`nvc|w{0t;!U=yL2dn8Qvup0$=e3e`?*V?k;OznD;Sut~{-!TnydAH~B-DU<5W zuwfogGy+ISBtDOQ0U(4|q$y~K0U$j3^imv#GT?WK{3rrqCKL&L$H>FXH*9m0`}{(9 z5!25F(K+j{6I2-xFp7n0c*D2pHs7@YB{;azEoJ^`$nRNKk7Z>Ge6navhP8G+WQk!^ z8^HY-u(5@Q3&LbyKmc2j^nHo2vr=wpnRVPdp`F{zqt^oZC2rHNQ+}|;NFvth^40&P zgJ{P2hPamCtH+tg0QJ6Qj1|Lye$9 z@)%?90JvvVGEryCl%hC{hjYQ?{r4x%KW+XlQ2>zh;kmRa^?ohkKA)F3{}6A=7yYPe zdY>um?xJw0@h>1WeaJ^m#myvx$fYIIx9DB9X2Lr|aqNm+14VHCEnI!B?62=cWO6NL zSJ0Y<{O1vGL2odAe#4bvsm!^tk1KLUYb*^cX1{#38z?rB zsP4t!+E~r$vy#XrM$%-tug@0XTl=i!TUFVTkGLE4aW|n&-V|;$4}g5=9*W9|Qe!l% zz5b4F(2Wthv%m#!9kRjDlV!)#gZ{XGbq4(}Moi`HU|8K<(}Bi{j@3TH9ET66aVD}@ z#b&RG>3LV+udLrmFsBId1H6BV(h;<8DzH7+ne1LwB;Wmnb0+U4Tb3C*rN}j@Y-jP* z1`#iZC8bmHVd+W<+vA_SK z=o*|dAe7d6A1Grd2y<9Gvx{9H!rU#if_9$~LrfN5{rmV4E-Zd*y|E!?@XA#auu`I$ zk#JK(t^TR3=z?CNiR$lfs_DS#!cdr_5OoYsDz*zJy;^q1wwUK8*;~cDXCVn?4{{H^ z2GQ#*Ej^!zr{^}sypC&V=-<_tQdkZQQ*!i)p~>%!A^fB} zZGB`i;o5_Bi9&7F^tu)Ad1ahP?ym`Ve{Ov)zahyL9b;?A)v+1I;ZYA_oi8&z814U} zrP?D`L8Qcg5s_^*P4LiT5*AOPubqS&LNO&@9Fn+lX`+lFpqZ@}L60HkP~=p)7A2tI znj*HmF8!qDQ`m$)DvUV58BF$@RK{Avoblgz!0A2zpEmX9M_4*4$dz1TZXu^PQB|X* zBq2q=XTCI3=WQ_-%k%y@H}O+HVkZ=QW-|0fjAaFvimLt$x?spH|0ZfY#i-n5^J$5A zQ^%Sk#n7o*>A5nIGFUt6S>fW;qG}0_)SETjc$+Lg%K0JQ2;iT$S1Beo_9RrP8NO3I z*Xl21a@Ot*rD=vPzI@jx2b1necTHj>FCrxpzbgBq62g6iQsUlU+7!?z?{?9>9sM(_hf>THTP% zo6Iq2T3Y>Ey<4@xL){uXlpWJuJz2zTk3b&TAo+B3Y2@rCqu4W;0eY!+7HsTU?K3Kf z4ZElFzvGp53dqo(_T6YV=-v(_E(3W^${_~l5p9#Bg>?ZUxYOG3A$Ca%<;&GvE276I z%T!KT$}i9WZ3zkzDzIYHn56P<2!&P(s`LW!T&3%iNd&5^aZa8M^s;R;QET4Oy}JAt z*$0#8*vfyn`~2g>AFHYnvkR)aO-xKQQPa+~K*h7}p-Zmo7d>KXa51alm#tUUY2TSP z_AUue*FkGhi?qfp)l>h7%t17H$?76JC zp34dCV+tR^5stC`*ybPs(V2|=yY|$>QK6yq3}<1u(w&_|{DjxNyKmsTlg!f~BLKug z;2SRd?^js|n%tnFaXnb!E0Ihh%d2KI1M*1KzU(cvEWoI_8}g_`;v<7##sC=2!Q*KC zLwA!oG}lUqS&@>Q)LYpc1b@UdzuwCS`^C{x-E+fH$)`txnotZ(d<@@fBYZa=Ca7!YO97>8UX+^&~AdtzT znzQg(LTI6F`oi5}LXl>Z0a@gF&Clc5&f-F?Uf=?wE3HR_NAlXbB$pnEr@(f70mj@#B zV|p1p8y}Y9j+W%*CDEsmASwDQ6S4gJ{}w}jF%>-S!#0?(fsyos$-UV1B}4{RhBD>Ja3-7O>{97AR#Q@c1zp2Oet92L!7XU}K=zPhZKptLN66 zl@N3v>Uk5xiofi=C!rvFA&ctms&>K!X|>)mX=SeIti0X3&)12E@0T*H)0sBWTt@~x zK5nN7E@9z#THfd9*)1}dW56XEq!ns_36owAf~P1|GLCQj`GjD8ok(_o1vy`U!{Koy zE?5hrG;n8Xn8U#$52Dg?d-4H8Y7zm+~9ZGB?%j%K| z4H1TMzQi!={;H~wA`5L0A-};K$-wm8b8=^Lxv``V?RNW$;tXdDiys?W1E@G~ThmU9 zZrUR1{8K#I$!agYe-5cdefJZpQmyROfDr-TK~^Aed} zsdD{FooU0hMaCyyhw z?+L>cGt`a(TW$>3yHdgprI7wt`my>fUdKozKLILs>+Q>hQMmBe`BrwatO>Co#{T_WJgOaWs#;(O z%Xk!AXAaJI(34rP&83Um^aL`<*Qeo*7I~cRX{N|RAtKOa*^Z$M%?(f>&+e#80|vbE zie{ue;CY8QF}zjU%37fODFL~KSBrq?ORG@-8u$8zh*SibK?`K| z%P<*Cjw~AgI>GPqzM}k;R>qZ3sBB?>#+GlpHum$?9P(R;5f;IhoyofaN|eaGJi<^0 zs(kn4jEAn11c6*@`-S;OO@6Q*I3?m@buQl-R!s`z7r|?R_?5lVeX2_z| zH$GB4bDxnS}(1ELI89N+sIkjHL zhT8=SG9L$?_E)LjDCWhkX+HK~cVSn%H7ro+&sy!jN)KN1aXZ}fvh%!*QV1Xla-PhU2qQ~~^(Ta(ld!?B{}kpI zq3%B$p+sAjIGxj+2L7-avUsXKqw&}5!ZO(}xQ%<@hx^=tVDMSMT8jnZ>iR1PJ=qcv zHJed>5J8PR0z#gh03jVT|E)e24`A?zj+%wesK(AlKi{uLnBY&aO2qHiR~UnzBs;M> z2@?S-tAnvaVWoF#8<1zgI-ri8Y7lz8_K5H7m1aO34Uz(4~=G;+z>tFWQ629#a4!bP|=0 zB^1diY|CNm8PN`L_@zkVl##!K_N%~EW|s-`tn7!SEl{~h) z8~)C?<0H^$V*VGIZljt>nrWHml}Y%B^K*d+Z1|C-TkuYXdErX{_&DhiLo1l5Lrwrt zHXwxn9W?+0KCV8BmiSq231%xnV4Kb7t23EaKG?AK zb|wTF7C>r@z5a!>0g#4l;uqe;im_2qzXq@XiVy>5|fdiCoGpz=Sy^4%cgJSbm|AB8a=pQ=0iz6u;4~sW&xe zwQ+H~y54V`0sQVdLWVQipZ24jLu|(*>$8J!?Xp8T&$sG%SYBG0#-C6zR_?eVtA81M zEN?f+;&BBqG=sVZfct7|3lPUfJODaVY^enNDkdFG$<)ZNz4$^DP0oM4bnBhfPhFyr!+`Oh@=QZONgR? zf`r6?q#z*a*}wnyopY{hVo&U~SMF8s``nSw%$5B6dmHJcyTu~(s6E?t)~JB1NdyRL z(z@V@;WFjr;=JV0pj-0tP~2>J%_m*>sO?n$N+D-E38sV*fs2RAK4}yA?If%UXrv3Ha3Vs@|lG44={4k1#Q;wYkKV&LA2@C9H=xTwDvorV*a5r z57gw9dhwX8ul&_H4-%D@v`&IT5g+-U-ob#l1}X_kLv>~~bGDuuM@I>ps-AYOx7g*I z{bYVrwL}X4c??ndvt+?n*j5tc*?@pxN@%v%t>C^N0)EerTX@V+_q(L|k$)f|kf<&Z z#kVrc<>){AR7(*oqAKrNsp3Ax%7!nzRQUEgTzH7WEB{cGQbQw9Y07nCU}f>|=#awV z^R)%m4?-AZR5kyuP+yHUb)6*_P3ROX$9=^8XJUT+kdA?Wjx`A9E23&w4=3E|$Sl+k zfT6Ag1{3rsZ(8nh%$>ZrIfAOgzu@4!Ow1~kKt9-Tkhp{w^A95w|ve!hMu8Lg9CORyE##4;0 z9|$IeppFf&k*N;Az0_}JFew`&NR56w#|o*HEH_Z$`GqZo0Rw)JjI(bNEcZ=n7A4Wv{2EYb_)JUN_q5qx z@YG{L(oL5HeLnAduW`%2y5xiGeNxTe${ZlLO=}u4LkEGnyAj7)VNdz?H95#GO7<(6 zA_IvFA+||qjiDUpR`@Wpzh?y^ukhlyVessD{%PZ}5ELh!ZM!MNp5wxBlvz0Nc4(cn zUh)ae?61d03~KJK?O^1nzP%t4iib!_==;W?wSvsd6TVcwZNT)6rWf8;2}nM=Q;n8! zkG(5;cdPK;$#rlQBO;R+rB>SwyW9WRJZ68`kOc%DFqn(};`#|t9H5TFfT@!Exi<}C ze$*TN8B$~)&blz2rrYb{>ZC>J27%#v$6XONYfwP7^`NK%o~j_H#$f*r<^PBXhEURp zAcMDx^xLCt`}&;IARdhOzWe^pw*rIH3@RnTn8PryggMHe7t{>*vOBaIuiR$4L9ntF zR-?C61cA|o4hyp7BUZ%e`9g5!SHtO6hZFlMj_}mfVSx}BQqJR|@fmMTNx<*NXu>7O z=aU+>kFKfiAQ8%4nf)$u$+D?jyb!JRcl)`2E5G2y{n9oC zWOK9>O$~x)yMU<7n0v%s39*kGm|~z?cC1g|xEUp>(Bv_NI#e?}7k}K8`|;jwDmv^O zrUV3!&p3boLpU%u^#^S4_Yq17Q3B=ct^4%jaN0a`XFFvJ<=3u6y_-=WdI1+GJNa_Q8@O#?v9%jR>rvt?B#KKzG_6mkNyiW>uGWA{d z{?sf9gTC2T?_$mS1cKGUqhbn3*Pprq5cn6%{eOz`lYGL;BaaBM;{!>Xw$Pp?S;ihh zSTE=n9hj%yk`#m`;Tv*u&mCKtL~9ftm+i+H6yK=uuN}yvcJ72(1^EP%!4Iy0P1w1a zH2iI3z3Tx~Y1vyLu`n^lCspth$^4UQ|LTBS*H!53@|6wykF6)^;?8R_;*!X^!cgs# zkx9E$E~Zzlxi2gjSYt(fxU!F(g2X5z0okD}Wx;Mi^4NU|?ZkkSuI1|1h>vR<*NS~) zuEAFXwb9|jf}{S_$eu@&c8Y`WOLwdSZ^m%Cw}<3r-D`_nq6%$muZ8Qy``rU?$?hd_ zxOF5w!F%O@x1LU%QE01eB0)`s*gr7}2$MEo9NZo>GHcTblVdSKZJmiDca;J-`)%Tu95n^jPnrEFRrQO8G zqD3D1seX9cEwi{S42o9WQQug#YeUxP5pT`2(S*~A%ZfWg$pr8Ufd6_e!|thjxzWtl ztpyFY7Oz%nTyZ((RRZ$@w&V`7NuoiN*M2;yhKcI*aG6%BCrm1O)b=6_y|SiX)L9ft^3uA6&?*Z~_EU3WECxa7YxF zVQ{^jjV^)JcWMVvTs|I(Pyns||1Z5@JGUrygh{eFJth;~`{l>{ipDMd~=wJw~mtaR$CS`)yqeIl5q9r@&;0;o4VG36sL zgmdo^L}}Rc2cxpeAzEF@2$k>~is%(YvV1pOc}W+?PhiJil=+7VQ4tP>XCjtsz92&^ z06hjU^6;^@nf;#^L^1w{Q7_;R9&@%-$}hD@tlyaVK{A?Ia1RDbEKHcA%X6 zL#7kR1}8A~6oQDC!Zdv=Oz~oQ1ce~_tw2f_v-%K#-q=Rt$e|#@Kk!~!qNS>qDfkzF8qBY-H-Ar>>kw?i- zHo2zd#N}MxqkSz>IiV8$II{!B>GR8iT=NxU_M4%uriw%Bhpnaxu0HR-Jo#191oQt(LE#LylGMiD6Ceua*aVovT!c{ERJGZ5%ZHNWuaie~uxz zXNjOg!G)CwzP|Pm*v}^@l@*YDt{>Tcc_wmPyx|45+mSzxuH-74vej~7sy1g}AU*p^ z3Bm#I0F7E0y^b$xzI<2vEyB=Yro$V#)6(05tfSc z^}p&*D`uS+qTC((GaDlskm39(!N&|_clgCIxQCkeW)6e0v3n2MNcSb?-J^23pfkvQ zXDF5YTBOrZrPvY-HeVvbN*%1;$RcwI5Gy%&sru{Yc75AFQ3P;|>PYUWKj9-IA3-K( z^IjM%j8_05<3ddDVTuC5a;cUfG4xhsf11sPYu1>k3TeRb2m$KTiVE(Z90rW91f=}j98%wwmLh5TW0Fqw55-y4Uy=1{=L1w>pw<2ap zO`U7%qVNS>*QbIY*y6u#(5h32edp?EbbQ;*q_M>Nbf2zbpUdn4_Dg!X2B|YUbebOl z79!sW-c+G+W#DDTyI$DgDUTkmB_lTBsCmhcVc%XbzI>#TTh6CF?ik2oA|ePc9XvKq z;FQ1W3MAf?n<07Oxj$>Gn}i<$XssBe|7f_$X;ll*fX9^|e;;2-3l$|xu_ z+NBtqpyo$QAxbshmt9%S3`csZ#RcN#vB8UtM8~UIO95%lJZQjPIF{D{H{FbFOMUqt>*J6p64yF_`z(7c~dG=^-SIT&{Rn3r>%9Ly#6X|ZI{wy%d;2eV& z5Dzg=+C%zH@L^KAfclF4o)Y0w*ieq4zVfe2>0w~vZ_K9(ipd0)vxY!mOHHTuPX(MO z^1%IRZIuX`!9eEwWT?Y9^yiLq7OVoo8Wf{BmPD0KmBZn>ctP=_z)8MAK18AjtEF z%RA}VR59n6K=H=lEb*TU=0Kkrz!rXvi+s<>Ak z^i%kUd6tauX7_v=F=_W(``x16g#7BFMSRJ8=od#vf|G_IAk%N;vxwpEtkef(%q~el zr7Rnwn0vuO&4pj?n%}SNZ3kfswQ3@HRaMRSYY8|iLzM`%%X~k3j&~;aBe|OYB1zX6 zKyVpTg_skZOEz*~$chfr>siorD8u#-kHNHipP|hHmfxFrO<(C9{Jp(&TM7ah9+<@V z$Y3-5s^I8~iX`9e2vF5iWCdUC6p=+;>2UiC_|^IA@vo42e&5z(l^33$iN(|B;R6=j&PN_qch(W6yQO-)7n*ss0#;?c_}9yd z2h_nqUneja!FCAlL{5c#*owu$CG%%N6<=^7kCLh|)7y%pfmw$AhcNj+pP&A;YSoov zkfo?kWa5ttLF``Y+v=t8d@0YiI}qx%4zNtVva#Rx{nFx?sA&wy`dD+0+#3jw0K>0X zQc^bQPaB)seG&0VZ@7>0D&AC=KMS0Bt2C{8uB`er7ZoXxl!nadl zpFf!V9E5lxp34Ss00KYej8jsq{&u8TB2n!tqto#piX18Oml}#>X?BgaP3w2Yyq_Ty zoZmP1dwi2-#_~JtaqPE&Ka2ma_#ra`4Pfg&Myx1HoFC^Aq*x|@;P5J0gA`4k^YkS} zV$746@ojp^SgNmw?|fAMAgeE%sWZSr8n1a)*s4#Cc`1~_b5jSXT{bBF3+K5w5j@v6 zf3^C85`&BzJ2(fK7`V+u-j-oJNe7`Xc0_;nZpM7glk_Dy1|J{+?fLBIU3D{w9lBW^ zR*qk^(%j07ZYlA=vyetH`u`nVBH?{ZFb-jM<8nh!<7`S2#x4&d0TkLo9J}7?gvf1Z zRGeD;kR-a+ z?Gsh@~-$5qwNl|kqA(ca(@5)LM?@(N;`pwj! zjHSD_Nt#@aO0IlNpM~FxZ)cwk-Ag+%YM-xuD<=oGVxr?7lkATcmcg0il(aKB?@(he zk6+O~X>tUIGsKL^WQ>;C5EskTr=ec9>w?PPoeA9M$wDsL62RCk=@{&8RK7?Qt>X~k zJd6|PWqLQd#mA5`Zebfqr&%R#3tn$+W>+kl-u9`ndwm?#HbV8g41VJ^K>1k)@Mej>&X)hkfE0Z(uX(|Fq{lbYjsw=K` z3nQOjTb`xglYSx{^Z;S+o0%G?NNZkUB-T4>y8C$BzNqkZ@>QKqn1_?ph*V6%V$?gG zQcIysR`UG9?(IO;=&q@wk)GI+uwL8Jm4`y`!f^|qbt|;F!qkg6#5gRvbGp%^3hQy7 z`2yn3{Vo+G!5kU)!gS1z7c_djk z7ho6;XI6ToBs%qOb_v7ED5b#fee047?mKA;23-CGZns>RD?ESZGFJT>IQ>z$Ud(j+ z+*%ts(SQ8L4e|FAbFBLQ1|UOKB} z@G_n9rZlw$jXD!B&Tv~(Bo?RfWc9T(0T>L2qfikbyYK)$q)VCb!Y5)8%_G+%jc`TP z)^JLWp@sQkQe@#sFzV2i>Abh=`}j?<6DZ~uBWC($dH4K_1OkJhLLIit2ou2K;(}Gy zS#QZ%1dkm8Of+>AiZBrb1A$TV_*bq-5V#UEy^;1)MKQMCuewqBTj`OpT+U`;dOcD? zd!}pl8iJi8L4gn;*c(Db?Y=@R^0+ic5^ADwJ2g2(N!*{TWQ7!h8^VBKUq*-Y+dv56 zKX=@EJfbS=A7ADUZu3*nD3so%AD~8|06rdKni!QEAZ8T!qZL9?-pM3Z|5xEvYNFF; zRnQw%KGiRJ9SkY4@{McP!7$8pm}!lBzC4YGm{CM?-`Qn1xZm{2Yo8`>xsn9%QKOEt z50~Nwusjexf4`fRzz#|5Fg%#+v$~<6wEXXu6vsYRy{6E!VdkianKG}C+~n^rJAcXl zk_|%SgK=So`XG+iM$CkUm6UO>hGHwu(6~EIaJW+#W_q>q?q6D&q~}3m!Rmwvob%e# z^S5mN{HS&@cRPn?e_qHSgSE*KumYgpGVowBn&M<8g0mlM9=4NML__nMG6=|X5aOpzEc3hz$898Ba^su!+Wa4VcXBB1gaN4%n(yHB z=Fw)}6r;)X!g)Z8dK{EhPTT^>dQj<8cOMm0#}D8T4Z(gV;gPo&X_HG~h{fa%{r9k# zun(3i76B>IcZomyKb5B+gx<54mSz}i-#l-d9GpG+I4gG3LYC)gvvj>D_gK7pQY+zL zKHI~uz-r8+Ztk(i(zufVHp++@5$fh}#?_Xa@m&sV=a~V<=6};B_y*@_4y`B)40pvK zPTLh5by`2Xg2vQwB1>MApb{eRg#X17%nY*o`;e`5N&~YZnbgkq@Ug>Yftu&6TJaYr zbD~v#q|u~PZNYOPL#=FqG~2aWm+}ld^q30Dr=a8;wzZkH0FyNXUe$qPrqd@!z@^Z^ zQ+|QH-5U0-njZe79WJr#pVSp#~|l( zi!)hHvG(yYJ%5 z72hxFq4$8QMFI^=g6>t${pZ4*T@2xI`S3WXEb=4EfNhIAj`_YX0`6ml6n(lvTgo5A z*oi`Z%l+{Vt$G``m=QfqJ5$1MY;*n>1&84l&`1yjd^4R8j)X%>Z;QhqyQDBD15bU# zr@OiQ)bI#qkN6P%^xI}7YRn>6 zaEsFtqSn(tiNBXlKkZXX<7c7sQf{YyLqoKiB(K^>0vv0!zJ(+rA=vN0sY?HuxuD#O z=n}C9usl|oUe?U^42e`)zmGU1MN`$pW;mx1QP;)V5#M#ipDaXYr;n*2< zLZxmi_25SRPYlmo#|_HFOln+Gmflr%{EDB8t$jPRm^yEtfI{}P|A_V(G2Z{q3eP2^ zeSIB`3DN%@>Wh@a(G7GuLi?@1!Zq)TpJcDq#wY4$zIw~89gCY-eD`kIVBDvp^XDQn zz*5DaFta8wfJ9Yz2jN4GW3(jzgFrWUl+Va-Z!B)mHHxIHGQoZNy>@ipE9l!p_ z`_JsA_)5tCM+&RczgE|miW7EGn2+q6Flze9mLRWjPniIRaR$4>sDU9$cF z6y2e}n%Rbc!67hk45*KMOb@|%APhG)W)#=MS@=E8ET^M@0Sr|9=@=8EWzq+Jj8{FYTlD98Y@Xo19l(DT(upBofncJb903p}AfkGG|!ZhOIO zsNwN$;V~MK`B^u$eAcd9$$wtm{0mOU^G@ZFSMi#_Obg5B$o=9}b0PRYJRg^hvv`Y@$=nn7x$c5K5n2KhLuh%!Tqv=|JBjA|hEU8`M- zE#2PGmGKlIrZhqmA2R=WhSb(9)ScB`YV-UiD*xQUw1ys?+oMoao76d-S1d*hb^xj# zXcvIc{8zC5)x3;+q?Pvsk-ag3An4}#>t5VWtGukqA6P6 zCLLAyL-aEhki9?{AT$Ojci=rd8F|82%#RzGU7Ahm|62t(>vUNw# zM|R?m^K5ny$B@cDnsrH^f11#!N_Wf}8_$)>kUQ5u+c{rMBO%Mg`xs@9IBe-cs@b0# zD+2^7j8uk88|Sv)Bwy+``Z_Jrf3lP5YUp6T{HpWaC_Xp@#Mo39#A=U(x91xQ0HETrpfA-eiL7=%&Be1(mi@@zd zsfvQf{%(hRZ)?!gZqHbKP+WiXGhy4rsIg1M?VnCF6>d66EtWp~Gb3ZSh6M|+2qx+r zRG)ZsNq#+9ZrWLG=jlYl*d>P1{B?d^POC4A_MKO!Z154!mWV*qB@^r*r`3NS=g_5= ztorrKr*(&Q(cIswQ$Hgljdg3EsYJ4C|Lw5Q3Jn*Q@q}Zehj$V1T!S#A?SqEsK3~yS z!jRRUq4Izdldjbdo_G(>CqJ)qFZ0AUQwqm9oed^aQ<=VgEmEFtjwB?HdfzQOCNf3v zQk&qpnC9r!{ep8B&EJAU`AjfKGzvK(ACCe>gCM2dVyrNw4Nldpx-!|#NT-QwlzL?1 z&j?jyaQ6d^L{x5iExoy^H`@mp8B-5Vgi{4m(%{!8@6;*p*F1_X;p7R971&@YGb@-> zR;cI9JH}N!F<0?$M8a+!-P9XW@xdU!pd1e%b{3DK5s+w~Cl*QSHKZ^tuD+1P{3|%a&tTWUZD74I3G9+0Y@U5ypfrHVaFN490*xJK93GQ zO9fX`LzajkgneJBAxWS9FCpNM1WI}${)4p97!fOb3JsLQHo+3)it$9j@FDI-aDJ6jiZqxFe_@vYBC(lqTjww)EGKkrKG3c3~ghAlglqZMln-tMa zo`#h01^m4zSDgYm`OQDe4ij?;A-?=_@HP}y8l8(+_VXD z$ex|E!-qYh4}%KgQNcIe+wMeH_6JtE%u><7oDcr|G4tR<$UDlli7f!AvHw#dLigv} z?V-6fMStpXI;9-zz$gpk)Q>A-`20E;6@2k;a^^n1H6{A}dqNHov+F2gFgOdmN_4;r zRoVxhgjiCz`6F_8^+Q6e^Uh`+2<(vVc(QSHsL=EM`_Id3dBqSZM#Np@W7Oz{FqmV9 z;^I*~x+XuqlMb{hmK88Q|2yrZ?uZFn-+d%GBB)P@6`Y;{oQ17MyN$J!E|w@SiitEC zf2wb(-Fnq#earf_Jw>$qC_i)*b3Z1s_O?J+e`|Zh9h7wT4lF-zD{OeH^8!irwW97L zzP(oud-5(n^Lu3v9iAlE#`zykOZUybx`ENtdS?O{$D86|Uxo_1AtGmbvbIyB-uzbA zZ=wlOWD(ZR%8DgGqDuG(U=Bw5$vkKF6Jb-kVXUcZe#ZyJFFs?eSu00kE}H~f!gqn0 zk&1P-zKQW#%{A;S>qG~%s&dKDE)>7=E_0phHAl9LI=gqooVFV)+Ky$Rs)J^>>qJCJ zRj1y;G4FU7F!F&fye(zsx69wW_Gi|d&9Zs)S|jg|!WHL1TU!Xp*O3pHG=fkQRN7~? zkLQfVjN4uD$l0?Oem~9@B+ZZd_sj(dl|Hl<{(0L#VS-RfFtEI2I(kF?>3-1Y1uq8W z-W+WY65vs+rIZ;U3Xy%s6FU10L4^!{-_muvf@Sa#L#m9-pUlmk-#r*mVTZ>Vsp4Ie zcjJCvWH}gIm-s2c9AzaDgjk}JKbYylyMW;Kg$fE`A7*Y}zq1zY(<@k9aORdYOKvZe z%5KCHY$hqN9oNxzz-~R$At)lkj29?3?-m>yC!Tc#k2^z>G-b|3*%KM=Rd}H=jt4{ z9WM#tTi^vUY{ClTIOY&jyuDLgzzfCwLop&+fI8*~R5C)$;pwL+A~-?i@Cq)gU&g|i zT}@%Sv#aOeK-HlA4S#Pdnfc3UG6-(OlZ^;VpYZE@QMW0IpO^y@4?mC*vj}@nr#izQ zTq>RF%XhD^OoW)wQo8jNSzcAb_p?ThVJygW$bsYA z0m$xFX>BbDZGzC-^XHtsrq^V`9t&u@LIqal*WF*cU; zFx@tUDzY87WLAS<({mstYb}}D5SF<%c|{03;2MNPya2CKgTU+W&dHGcvgNq{)dkElk~|0TAr|_slj!$V8*}7FbM_x3V`Z zUb5w&z1un2aD}CP=()JqZI1DyuNyr%Cjw9$Jv64)U2`U3PnPejdo@-pN6XU@?lQ%X zHHW5rYp-3ags7ARFg21I5|AKMFe_qD)EV1LW{knNrtq6@bZ_zE_>a22;)$^Te3DA} zh49BBFLno>|Cwc26h7&lG0HrO~qF!%a(PQ+pUy{I`J9r_uL9-r`(o`w3 zF0{(~!|s)EWy`n~!6C2w2jv^X(aZy#4t}{03HsgLgII0k2iyg?LJ4L26FkPq`P}=* zHgA?JyLx@B39+vUVVbY!GJB%0B05t4Y+awY4PwJsUNj`fdZg0A92c)IzMgAuC+~TL zl+jHsIEBv+3^ToR3Ok8gSqQx6udOLo9$lFB-wj0qpbuER%HuuAV#p%4%iQI17FFUm z&N;xpZEgEb;zXfXtPn+p+uP&;;2gs?XWc)%4BRKaeh?d`lg>Yl2)Q*uHW1UG)Th#1 z!hxf!7EtaE zVv2b-JFFURe@nss;e!#lJtCR`+cwP}DH`!zI9>{6`}Aih!QFvZoL7`@Y%oJl7I>*x z9@@+eb1S<~e&;Cmy9l-H9Jn6itG46chI>Yw%s(4B_@i0 z^o;?Bz%_6mQt@n6-6t3jx%wrWtT^2ZHCxD@-kg6M@Po?UQ)j$ZbokOdf9K@s z9ldltLafA#GhYTOWi`{Tzx@4P=^dX zS5ylO-eM8aCn4&Zs+froIOEUSM(2|;7`-rC zli5852z{v6*h@gSr7>MtzFX09;ywhTYp|C{^*k>?W%LUizNTKwE}ZoKmn(Rco|Wp3 zT*cRz>H6G0kZ~saN7 zG8q6ce2PLHvXv#xUNST%{Lm`N3#rQbGCv@DeTX^y*3F=#-3AhK@G+&sy*)z_PE`i5 zA5V0)uKRuRvDxkVHOXQ6MqVN=s@2EwsT%Rl!7Ss{Qx02y%{cPWTUhca7d z^Pts7kD=Hw@Mg;%k@=bYqv2ME86(zX$DZlH2h zPz(~b8^rCT95&!mPXQHm}2tf(iAHQyPqWQxfVAv)>V5B{s+$NJa96CCGRQ#gQd@ zq7Uj2diHA}OCp%G_W;`Re1l3PNdZ3MaOG4NRnuL|(pxwD-K*xqcQ0~-*{D`VMD}L@ zP`c;Exjv~OM(eQj=wF9sh+K?tIzc9waWz^vdIr!~0!H|1Q|Uq@3}V)^*&w?wgav>C z6cSf2pSyUEaj?bw{=t{+(W6?D@vEZj!g1tvs^nKDe|t+tA=~wRS>d z-RRV}wG>J+8?oM^C68|MdQOs#9F)4gvHUjE2Sa?y6T%!}NKDIgz%xsu7C=543L6Fu z)1yRryP$#?AGxXh9sc5f)Cda*b}Rd@5bE#*V1+&L{zrz;uT+tZ-w6Z6OX}RfSP#5W z8yJ@g#`^wQs*2YQHa`C01%}!Gt+DQlyP!a+OV5eUuAd{cuhC>mH%NS$^ z_mY7^=^F|-T(PyF&DaDA`JjVBCJN|kY7jqy!5V_@PeZ{S=Kz8?#O$C_4j3Szh!JOBC6GncOl9fJ<=Y1Yk#ZUV) zn%gX|h~wkKe#U@o$$0vHTRbQN`_pO?>Fxv?f&Nr;tJT;;vHytS0Au}ON!#Z;ZHp6% z@~Ws3OSMejFL^~^bo;v3R;9o14h4q|N;gRy87#-vmBQ0XXm_yvut?l}%dCR{&J%|k z?roan)z&IihB%WDN&!46dXGBO(6pTyl7>5QcW_9oj z7&c5E*FN{I;yf*$Yhg-$C6nuNn}I<;Tq~=Hv773Npl(t~qc9vkoR$by^>-&-QnisC zu6iX%`LEeXlY=dYB2oo4+Co8qbw1)w*7Fxk8r_a~>1fgR*naxK3qz^fl*!M59jQBm zreF@8k)4)+zk;c}@fvmyZ!YjNKkKA*-jyxL8+&68kr*FSHtaE=O*A-7wQ*g3`C&@m zo=i2Ha2=M^L#ITMKxMTR$*&dYrT(%~b}!lRZwvwMDQYI1g$RY#^?6_Z(f_(%0WMM7 zqGE{zE72FF3gAQ`I4`NJxf;t3>HZ}ZvB7#czr{=^U>uhOgTVIG{IuWV;qa~m;-};+ ztn8+SED_WQUeIn5%MUOoQpFV3z|1?A?TWjPDk-pFOg4F6M}d3#K+H%V$@=PV$CU*JBB*VzcH*Wqe|6U#=t$xXB{Pd*n%epJPHfq5ALhSQpBXrAhQLrL| zO{~Qzt?CgJmZ{b4!MYjDiwb75%!hSosbtW)W-vK@F#NDQmAV^$HOL=-e4f3r;_Ql$ zjnU<}nMHEHLadoxpuS^)R(fi`7lvvg2bY@KjQ4s2hDc#Sg1@?0iSUxBzv?Ja>-+8q zpEp~LjfGVN2w7UwkbBX#A|VL-+OlyTz=lwaAn9za9xWPLQKI6IBRb+TJ#1HeXvAK3 zM2P$|uFQ~W0YbmJQzWlWy#DHf4N=9AC9ebBe+aO%KuGCA5rQnwxlfZ<$cH^oOido-Q|RP>jz`}eX?KYJwc-eaCmS1;8t2Fu7_zNX-?IJ`-3f;O3TIP;VY_TV zrLLnU8cM!r{npE%nB6o|`;lP0%*B9R8{}V8@6@VYS01pXCnW`#+euL(;=wlgPNb=a zA=;Kat;w2VdIU_#aiSNRFI`PP@=-uQouY%p%J@NzSHYcP=|W>JtYEMeOOpWdFuj+! zP6Z_zpP#a4N@dgg%|Vq>&P{Z{8iV?=sG0f4P>qsgFx_)OB8#VS%dy_$Ltcg6ux=C- z(f3?p;`@>H0}O^tNTWmMslK5ycgOU3Z2oVB@%nyQW~2vT`HLfj|9=NBDG|LF?DyH8 z?~d(w=p|BiH?prsF28krJ(KDG^=GN~L>Q`1Fw^gpOu@vOi{y!d#8->7{1zh z3oq)bM69qH;f-wvL=yet=b&F(q7I5R1WN-$<4K?HWA z0J$!D)fdxFB6@sDy3-mj@c$Vh6T)0Q0`0ZkfiaaZJ9goRsMBM0s?fa2_jmOkI`kY3 zyLhUjGn_CYC%uSn8je9w`#0tHDfx6#bUO>dMVz0dLpPtKbu|v{52AJo55zr^ z6vWNGeRb!oqVKH!Fz}@Hw!!B;ug?=Nx;C%8oTh(!y2%RvlGWGH4Ew;}MENGWWpiw~ zE1598sW6k6{qf&cGUpq}&?DCz&dGuUeQ~-Xa;X zhir&KqfA<&+&klTTThC$dXvGTinc zW-`n#RMvdFRXd>yOq877jG56vFG$n1!C{hva+9$Ak8fC__cI@C-)D<2A0_zs@#0!pee~u!Ytbr68t_jE*_F#k*;um3z{~X!U@o zF2OP8%e~&nb0>-pqJKvi)SYdu)P9Looi!Hyr7x@k(6uCJnCvaie;w0$c)-GTP@f-G zW=2(GOxu+=Wx#m#J=-gI3psf*AXbXE^5!Bix{?eEUwc z$ndUvO+25j70e6V&Z{*xBc7Ou@>**~y?MVe5aj(qgl@=(9tpSWPBAOt`3>ev^q0@R zb7gvaN_ne}0|KekSjWjSz8vzgH#7+ztMm+W^yAkO;X)cUK%`%O#RxI9f^ooP`pfs7 zU0anz%4@ugVN`<`Fm+i^2ctjaw#s9l2wT6ja0x637kJNv6b)Qvd9dS~wvK!+twc)z zt!@+RYtr7IOmqB;gP=mS$3A#!7gcHejQJ#sEp!$ja+y!O`$Pu%W}|tSaYllo$nD&i z?JfoGojp2C@6|J=FLGWj57>U&=@f+9gczat`M0|Q^=Ty3RIS}_;YY%#=0v#v@6DSR zct4^K3wl$)k07vR2z0y^{j75r*bpo3XJB4nC`}@|h@fZg4v=kYo@6Ivi0+Gq*wsqu zcwD~q9PyQYZ~nB|eL#9}L^*s~=b;?KfKh;5A^Vr=uY1h#u1WnT+b4>6-?jP4M+Qg_ zHu&27Y3YukH`dofYro>}>(!DLLZu(Muf6e#yI)10TyOE_wSOj3`UY%9J$$zw@gq=; z?sh8yNHIU);*7p34H+V1PVITyIMKFl|rS^OTF&v#R}tT@>vwco-Me>pS1 z_qZJfdEJ>|FYpYy%8h`N3G5!c2S|}tsz*G#5 zRi^%5tc3klfNN7}G+mrWAir=>5Gcuc|89TlGEj8M5-_r zm$QGE)&^_Xq}g8}#+x-O-Q#*!~*OeOT`GJo5_a9wczhD3HZI3YqUmH~CeD~@-1I_H45ae}f z4SNX`LC%Wt*#cpe8^Eg_Lx*5DV)%ru5T^wsxXn0Xn}?*9(`7r#C(zJBl9#pp{WKs|YD1K?OL*d<#_T58whvJ50>cIWb zz#pQ6!DOpLL<5PK^DGd*|CCtQH^<>9qA~+_CwK4i>YrBjFOLVAKZZ5thZ5L1eF#Mz z&UStv2&NV)x_8zWmXH%&L#Uw{HTF>D;i*9LFU5D@HQ^jYM@mA^@}CBR-O~NeuXjoH zsXu9c-DC#!ccKoZgi2|?hP2N!fIdo-{pO3 zH!*z8Gt?VSj*>1SM+K80YpA+4HclHr|;^l{g|=Q z0ZDeq=Ers<%&~i`OaD0T6s}hl1zzO6ozp$ddEQA2*VrX+zMB`6`t`~8JfacwjLY16 zg6PAmPJ9#*g`B+$ldeR{j;zOm=#wAkrZgd(CNm;#hqn=%2RF&A2@18UKe}#T`^&sW zhW>MJx(}SRC6Nl?d5g7U{LXK`7Sop2u|632+S!fLe?$X+aEJ3GOZnNuw(G6{ zKmlp#TGu5(U|a3(ZKs2Rk(AHIQ`;X&fVBwUUQH)O(&Y;g8!kUzXVrD{9|Wb!S8`+` z5vfL9GjsFtU`DS&G0vOW-BwMWq}0a}92G2V;iDkTVAO^sX~aJo_&@$)$f^M1|9$yK z4__66!#b~o|9pHI7D=t2N(~2vaDgg8x$8;`!o!6Rk1XcgiV%s@BPO6-{|7LxHiW-Y z^k>S4d!Pn8n`~dchU!-pnc^d3-z^;>eT4!e^UKQ^zX=tYXL&P;cj(harr##8fcwH| zlERtO)asK(UoM$9>|_|dLVjvNWG#I$6^I#eG@tvNi?Hdo@_Io`(Nx@{7qB%vKYj}p zYPy!_#bk{C8!dLAW6FDn=@)6^tYCf7BpgOtvorD3g3{^iK86ju`y!ckY$O8QEFK z-*T4C$+Q8esp2*T`=_1l)GC?o798 zaT*`RTWc@H!u}tk-ZHAK=WEzbAVAOtcL)@h;uI+~L5sV)dy5w+5+Jy{TZ=mscPZ{r zq_|7*LV>o>KKcFccdhqJR?dfWCRsC?%*@{Vx@O(=D79?9xKPWRg=A%xRH#-u0K4xO zh4MuSbP-}4rl!1;GJVwGY z>n02i#|+*2lEMq?e$QbTw7U$BYA0bRQdcc`c{>sqY(M{M6&>S}*=kc*{k7(m59W0w z{&H*#{J{)kKp{iy$+&ZUcUIIx$k7R2xVkR^a-s1Um(R7QeV1>;hl(^j;~A{b`d$t# zVPiFkAD%vV^#c8OEpDIu7tHs~t_{L+O#M}bjWsdXqrtwlwr%Nr+wR>;GMiY$OFE3S z-zWec*IDqIE4WD7K4z!06qz+NImAf8lTs6EL60m%&1 zyOfA>!9LAzodM~`g#*uMu~_K$L4rIiV0X_)X1zKR@se)(d|ZLUp8eE^gLLgR zN`<~5LY1-xVqt4c;;IJ4>j@rvu^Q6~#zp)__FvsVc(%-IA);wKjQ#I=@+?1##G*>m z6NKKpCQxIvd+DcewA$Q|feW0kXHS0+z3rZf+%HUb*{LB)Qa$8Y21IM!5v57} z?{=_6q8P0okERwgQdgV)Glt5gjZk0>GSjY!zr^B!60mcN4StP$3`LmNY!Y^~b<0uh zKSfKfR#dsR?7Z>#imHqO5QrCd7E;nRLHn z=&?+s+2`it;?>YJ$&RY@gr~=qk~p2vL3k%XsiU85#Z!N5*l(3`26DMcmoHmTw5=^u zhGl+qNx+?V8~^!clIU%ltPR?r@H^&%3J<=I3fPoLpJ#{iIF8(U7-*~Js!ive#nL=5 zk~ix10?lG?vhKM{U6k1I3h_5&#HiiS=W;|-G`ODkp+g^0IZQhQ;8UDM^)9my`WwSnR&k`j zrL+n6A))t`LJs*B)_{&Z%%^CJe%NuY#S3(MxtE09f~0P7DPAPsnAy23FSu8sH1edH z<5#+8iBs8H;AhO+jvq>;!oAg?@NNSJ6(^{(yUv7r=QpUytO{Tt)CTkUH#9;YwKzj% z4@HS1Z#8o!w<1N&u%ALxRhA$qS0csZJqFT(x5ImoL=;bPNElJ;dwtwy-^S+fO|71v zC_um96{ahOh1SNED2z)Q(Tv+@M-sAA6uqx#LVjyg+i(`v?qn>@mcO_BdhP58E!~lRGgFue!Tv75o|_yF0-hF31pH zQG5J3A3(Rsa4PBDs(vj!VRohJ09zCdb=eaiZ404a*;<&Wp4EgwbcLI$xwNmfbn0=J zV>oR7diu~&XxvR_x49OY`|m&{8g;@lBLNQ52#j2QuIm;MG=&8W&e&#V@fomfPwwyo z+lyt|VE2Fda$MiN*+wR^WeW_Eqe>*Y4tc~QEBr-P6*+e^0OLttvu)$RjMgogRPyYR zJoMi+!BYYhxg^~Y73|cE&+fD=o9qx@4EPl42gs^6B(6>Qh!9zY#km=rkyc1@4C=a< z!1;J_{W!PAGDJ2MfyoJz4Hwav4dyClsacZ-_5yuj;?N9+#O;|Jg{Wc6Hfd z=_q3-A8yfOM}y~_*iv*kgAYkJ5>5=zsHy`p>(>gEgRu!-4qIV1PwGoQ)?@oaAU9>EXOPc&83uxOsp-c4gM zM4w=_dPmtLPI3>78G>oah}AkuIQd|Ukch;1g1Zk|IT5>)MVR?sJ%28lSjQb(VCAek zlV)D~=?&UI7ez$y@JnN3GuDZ($HC8&p6TR1MU9>52)Q!M%EAwdRpe)+>R+X_0UFqU zPjfG9-%&p_3FrOH-Q`}>8Qz)UJ|F;VZx^5q?D?MWwW4Zs=UcxS5>RInN@$jf9r)CQ zdnNDJ@wyMh1lRxUWyM-1tH*Oy<;4#5J!JgoaifAIs^T|B_&g(Hip;aumnI(D4GXEn zN-U99Gy@Jc%WsCdF+6sezgY0cIEGJJualsg7%RZ28CLpa< zlm@x+0z=b{YJB#MYKK36Ib<=}`V4aBl;=`k&4p{Kzk7Z(F*MDkJoX*E@rck0H_}Ma$-HQ~0>n{bgNSIT zye{hxK_C)>-aQ+5j^~dya5?sm0Mc}zSb2n}05;ghE&)_NgB1=Qtetf`TxWf)dXcfl zx%&xHlnIVlBUiachb9uYU?e3-78Jh%Fvty=V7!MxKp+-cEgo1JesV7wlImecD3{Uo zg$_wTQ^ucZSTh1!&=wi&{oTJ@dz<^){?XZ9f%R3=G z;)4r>x<_*~DJ0QUlo=F{RR2d}1cZy&vg_bUyJ3Kcjn!ZXL72I!C;(16^uTqo#-+uW zw*Acv!0?$?qoNL21ffxBV2_h#fVzqkd>H(30|aZUY%p65E+cqZhxk|I+T=bW!|?&Y zzj8;r3n6SEN6LmRKS)^Z|GxAI1)< z0t*YKzxtGMj8oO9j*pfo%wLaphXJ|ElY~7O-~)?6BB`l;1l}65dojxSQ=BIVv5{$4$20lkVl&=xu!fTGxqTwJQeL?veJRX8iTL{}og zRNIzBja`gQ7Xbm>+^KQ7LbkAO>vJ*gsLtu#k9p##sWb=zMp{XynX-bXi*CMB|q@D(DZ6_0&m#fHj( zL~@TQVV%HZqEN^nEriJ&cLUoZJLIE6JstA)dzrVNA%v|i)Ij)hMlHDNJ1GQ-NULvZ z?7^Ri>SMv8cb+EDIm#E)pd^{Qd3g^ZFY)t<>7N`kWIB{hc|m)Rr1_L1(1uYeU6q=A zP+dcn6(9M~De=0F6Mxoy1bsM@P}Hs8SN7vO%+U=Sb zBH9RN#w22x3|mKfd6y94U!K8v`g9=sgKt4=%&_9ftd_3hM(^{CcAN=J^XYTm=hQ)J zsC|3{*DP9H6VJ+Sox%uL;S8VDXY!Hf7aw|uc=5azQ2bwddSeC+RuYZVKyu4&H#OGZ z5djR)Cc_|YS;*GfW{iu*b5PK+sV=i&+;q5#^u3ZOAh>ZiGM$It&T7e)6)Wuxde0Ga z{+KWiB=1N3u`F!VKYK&q^UZ*d(B9|(du-9xl2qjq=~=svRleB#L9$@RkI(vR-~WYu zGq77X7M>y_!#6dTU+4(L2lKKRPov0|GbK^3lm^D^JQV3&Bj$zOj4uMJdU99j-viKo z`h}5;*ZP#&NJXNBF~FA@TDi~8d6qAp!U^5?9lVG9dFgn(zMA`L&=6jwi?kpQk!_7eSb|Y#8kEB_ z@`eY1glwWq{iA~ukj#CD!`<=bf)_=ORoz8f7RTmu~Vbq}=2pUY4sg0$F(%4x8p=JjKT^sEo8ysQRoEv`tTG0E|>u)YeNCXtL)uO4K(m#d+ z{(3_TIa?ryt$2#_JfxKKOz^Q@#I#^U|HVd~>EVF2joP4D`pLl|ymi7U6m{gL9*j_* zAgXwJ#bkC=U_m4&P44=q&mTI7|2+nz8kLND1qLuMW-Q?+gOD^;9W|*X``=n7X|Yk@ zrRSPU%XcHf3Rdwkq>wWpIGdX<)v8WREEcX;e=83IqtceKpw)v=>0LFB$P-*thbPzW z;zO255%u(l`T$%=coCY^@|d|`^TqgkG*GFLZ&}I2!Ro|aNC6AfxzZuMJ-ZR*iF&|+ z44Zuur{5H|fJVO?rQl{i+Fcs1Wf=YQm)`x7G9t-Fsmb9VsgluRvUz>N-pH@bxszOm zmn9d!=%9u8WjP4m_yS-sOK_oej7BZOTr_6-SK_8^AS(5%zs88oqn}MHFU}huJdv$S zopWSZ9ECk@kHWA)?hvyriu`3$JGi`c8Ax{Dc6v% zHvy&Uq~)lguT6V!z*qZ*^aNcBb-&jAtYx;qEfl98l@yp_DX~s>HebR<8R-y-i{+#N zCM)PtoVQO8jk35{@+8s9uPYI=UXr`zIGr!?ahqjI%H??MQVv+4^)w|>S-^qW{sMRu zPtx2Ze7_D0S<4ez)FC&R#*8Cm55lzKwXn}r&(7KsLfJUGUodw3B7%_BT*LMH%F(1= zqCtKfW1JXLOwTQUJx}e^;6r2T>IQ)H71y$fCo3Hq6(%&F36@x7A)h4zKsaw$1|1rR zcmToE0gZeDDqnSBA`g}bYG@*~gsJ+_-L5<=h$if2x}Sp+N|V6s3?wuEWIJ#Y`bI@| z;+wJnpePEH(PU#7=yDct=su+a%gm~_TNdAcv~0Sg8}o|@jd&v6d+Efaw7PeR3Bn07 zNznb;T^^cfGJRtV2oANU{1rnD<$dwoqzw}kl`Q(0G>${Mm&&bNAv$Sr|GT3_lJpn`bP{iK$ZG-<%vAvx4DlQg6XRGo z){zfMcimJ0B6HiFC}uFhmc+LDoH~w$q^;$^G2;(K6Wh-1dl{cCU&D!T0_b6CQ=$RG&OOHTgJso0hlj~X*1nxNL-<@xrV4D4E76rF< z7BxQeuGDTSG&k&I&oJ{Itj9u9RPa+vZSRIk&~ZX1c}gfyNfMtMWDiG^aU6tqIib)U z5%C;h7}(OFuoTo#2e8oMJF||jir$&}DdlTG;Fd2?b)w!{I8E7;z5LU3Xj|qIu=7hH zEp`WjpY2DlX3v9LnWK#&aWOf*6!4@lv~m&n#^?dEo5dLK_|o@ef-Yq((p+BEKDzQ5 zlB*$E(wZQ>wIvXI`4TPgpc99AD6832Yk>I`R zj}4|MAM)8T{>ZS+`Fkou+9?^|mQ z|9C(y3=Dm{$SgE9=W?i&%dm!dEkVXjs^%mHC*LJY9r8b=JHhI25(VzuF+rFL`oh?f zdtN*Gs0Qq#woRC(Gep5MAw=EvyWafrHPN<|+y=EFwDo` zkEBL|iUCF`5LfFoMGQ~xzIj{-&cDoz8SkNEA{x3^IKgEGcr2tAoS4i>eNJ2SzfWsg zVc7-*v*vz$JwdZ&%Dofgm@)a0>-exbly=B00XF5 z2yrD2WGJWaaPT(P>~~dXqFdeD`onn8i=?{n{1muJgpR)GYtWYba_Eh~nUjghGjVS& z$Cy_^!+WdGk}^gbSJ1npsT$voSIIoEE(OP^sqwMu;%krd_hiK8TK&sw#(O9V``!3! ziF)wulHntJtIKlxejlFjyo1$3NDyPTYGiLwRM3mCvw9l~jhnmj_Q(^@-u$T#-g|}9 z=~47!z8};a->@Lb`1T~3oh`>g_q5bzP&ndb%XH_WH;K&`HKZ$|7}s1(BH0!%=3q;= z&2o7o?HFoIJ5tRGs&Q8(4u=ZyJ`?^EE2T|&qkH(hv42~Xnrhj#O4RrbwXtHSI z+_7fnM7feWP=grm+6J&(?iA=_jqsXLc}V0oedX_NW^!<+Bk&~RG+(%bIeJ|UL@akF zh;|2UamYUWQYKU`XtLh@aK={i-OX^jSYU_R$U65n#LLY$TR)wB7Ck2N{!(4YBC@#NQU!-j0TKB!R_y@@ymo%pQ3E=eI7I^)A$4jNUGU|Rk zs-q!h0qgKbzjlc(SJzwLI*DrHiMK~JJn~ObJ7{luD&Mv&QI$gUgvBr&XRpL5?X0@4 zz3_ zUB8H7-$zpTTH?ET4Z)9&Q*E$ny7t8Yp}08KqDP76OD<01)<}sO_Sr1-^ZT7`_|cvM zE81mhXK}tbV?{l21lE+&#WXotVLj#kv#^>RroUhY4ptEikL3Yk9d%Bh!5X ziOn7!G-3?XJ_-kCL=>H>Zu^X8MX4_%-qX49#Z0`Crrh$>o@*_jpy*eR@842Wu)4(g zfPE@ldF;I8W(XJUT^ITnT|nWDs7FWALeEf33a~hA1vo$e3B-p*Hu{&F=&oEiN(}0V z!yc3f*d1?(@7!}&`mB(4G}MbzrM!GfgiMOdN;H^9AAS7Ikrkg)^LbGHz_+QC@E8T) z+YIS*%kMU~uL^})_7PdX+n~Wp8pmS_bcH5u8ou>AK$5r%Lqc^{UJ3kIM66uc;ssB} zMWQG1VN|Yyevpv+>=WJm>~{K)n&CIorf7+0>!rob6=X4Y#4kf=4-M0lEe*?!VK=d> z;i*)i_-Xrgvd(zozXoa$o-ER-4+-M zv)gZL$SU>wUhu(lYYpnDblu*85nnhdickbPUDCcc5UFnOMqo}ZQ-)riKxJLLF1*-X zHD_CDTr|uSBZA%bb_3*H1PQEYqKV`nPiTo7-2flHKO2nlLAY+xKbKO}89q)|kIU1hKpg zGIcxB>2mj=K5xIrSobu!YX5whv*4%Lt=8L}ny<|Mrs&Dgw zx*(R(l(u(GJ7c;p9?-X!COl)%+p&LncKC*IpwTS6|LG6bM)ro;!_R(ho@yZu3Dd-R zjSuo6x>7@8b6SK5|9patnJcdc|Io5Ce>(unJ*e6;~+y6ii-K=WWufk&Ah&6b)K|C{{>NA{Pt?>i(M7eplQ zP0+y;M3;L}w~4&eGylk;#j?24c|N_*UJ@;R#>iqffu|7e);4ji;uexyr5}t(9AHw| zm#LlQoxVk7j)aA>Cx)@h+J}0x50_s4i_L?t&+a0dslJEbJn>S!G_`nIpU4UW1)rze zyF212>Qyv(Ng#NG2H4e7#*;_OZlqsLs2+w>JhAiiAVlFS*A$GO(>PS-nmVaoXC6KK zXWSW~qWNm%_fh)~bk&eDXd;6wB=wytInph(^yH;{^b7ZA{DQNl1I+Nk3TPrPCs>9U zuD&&ut4LMx%vhGMRg0h<3xdqm00lo{1cQF^UXddU?n|RmF(l@g20?vkPie%+s^3*^ zpSH}h{EAOQ)SFt^6r0Ti!j~ApGCC8Ko&L_z4hpIUz1PjVDD{r6;`__{fX$(LxlAl- zrY`$_At9=1`wno|&iPbAd|ok&}H#%C;C zg}(V|GbR)|q)3{?&R~cp@1P~Ag>C+4;bPDeLlMs?v>SFSK!$(Mva5;HH*MOyAsV;eR zBZ}4aZ>u(COW6-ztm$eLS^>e1iQ-!*VTVw&=$k^;H=Q=m?GuLgeJ2_|X-Y`XYcbn3 zdxy+={r8bkAQCW|z9W7JF|s5i`Qzv~NhseSa?qo3?1q7$pF$iw@IeBM7I9_dv{ozb z{~ruvJE!}ziTgtMz*RPw&)i6=0E(PVEa?7+d-egD(~^~UF1FF3s(Y#2D13~Y_xuEi zU424B$*?*u@+vA5(O_q zwHI3{DmhT(+5aw41tVxui)uJvilIwA*J$srd7=d@=woh8-U5X6&>xm1kC@A}lk=`7?`MY_kFL>XgQqtE7^{S*iB4 z2-g?X2}sxH7Uu~^Weg-M5IIq@x7^bG=piyq-x-W~K942QxFB92ze;|wXf{ecBChzu z9Ok2L^pTd7b#uw$xZsu(h(`35ieq3;CdWoNW1?#9O(E0k%V%7YgHG|!u#i3Xr@w!m zHgesX;n~zZb){bRfu?(b0~LfzFCvV~5m!tIz{qI_>P#xfA!2RhWTovrl^I=G%K>pv zv)%4vUci1`7v|1<%jSaWj?TLiJ#=Jx%VD7Pdwje?lfR}WiT$pJ9v3(7d7!as=wM>V zwz^{ct0=w-&2a6ndu&K$x4xm~4U*^atZyC;OTJ?RieCRQQP%o5T!=`D{wu+1T?i#t!MoD14VCJ@)aKk}v#7w6@G z0XuSfT<~l4sIsKFSl$o5xgDw^Zj!z8cqiTq_ho7)xX*k;c%|Oe!3(F}T$wmZb?+_7SXTnz4^B_hC>s3U)lvv zofh)#gM1gxb-=@t8~BIvC`W+qe*^IU22p`NDTD+13J2o4uYzCwSGb?}7`==lygz2U zFzfx-3uI_%lo=L-9~l2Fl}urr0#8bx0nqn*@}p`4#k=;@iH{-29rZJ;qqw<0dQL}x z8fanCw?^o6ZuTP#e0T@XjtU37*%TJ`8La3EirU3ZUNGQsznP1P3JfA<6~u8>a?ETB zM$)gwe+2C~!~t++7x+*M1~WTSq?@naH)j$rTnv`lo^`oQgb6J5w3zRllv4Z-%(XkZ z11vbY^iUnyZ%}$bXbMj?SDodUe|-C_IWbV6y6ohJCLAYEFLbAt+bGqQMIOfD>FGVnfBY&{HtvFUu|(x@`g^`Y0M{L-29Z#K${Wa-Yb3D3t$#CCi2FG@_Ukz$O}6(AkvkfY;knPC(pHOokOZ=H z9e&kKHYEMy>jGYA3ww+;SCc0dMKB`UpF$q^s-Q>%m53LQqT*+JIUG z4*0*${@?Ti+|l!yYQXW+gn=0jn`%XsxO@DQ+;tLDgE2}(-ig(vW_qQ5Z7k8`%u{ox zXo^>yyU!CSNw3T5c$P*(pSVk?s%(nPU95DU|J`yMWImngrBVS>=w+|rA!ljgkfNHd zX+=ASl9ZtJ=Fb>j&`Cu`md6&vW7*70#Sh;-A0f3KgySnZyv&HsZZ>pKC{nMwH>9(P zKMujkg%BznsIpqA)3LK#i{r8VrZi5}M2$sFO8MU!Fo%Z9Hw1};bCHj>mv8#Hl{6`G zr3!z*{&x2K5d{Jxy^KHN!4>~3DvpUtva=~l;0><~LFgP$z+E&ZJqwy?6C~E_H(3~Q z1#eW|S~9cbQBHqe5Y@uxqx!^hlz2PgkgYLsX4`s9(`LOW5X+Lgu=)oa!^ZoCubW|q zM>d)3?CVa6SYMFI^*BY|ELlj@Tb;VEKiB`wHtj#T(Pd7pArNerXdvVVZfbSL@2*-v zg5Cv7_nPKVb6E0{$3z7s8T#bH$z9Xy9GfKA-@%Caj>~hKm!iz>9`e|Ne= z8gv{kT}g9DEc4&>KzqEh>U8@y?%Kr1pT4crV<6Ne^rXA>cC(M~kSl!NzRiC(-m(KjV z(RYTWOTE)Y<9BjSLXyJRd9w*b|tmZ8y_x}{enVdU$z3HWBN(lNP-m_OOuhA+>phAiGWnZ z|GmN#npv~Wn1VZ{A4i3^_3ILv0$ixMWD&gLfRM3lY!RX{!#NzvG3??3hggnd-flrj zXnJDAH7Og?UBr4NmbuZF4^Z zD1X%h1K9~Ey3Xg|6IJgjHTp0h4aORqWRHrEKzvu@brH$Gyn<5+8f;Vjq!gv(w_BiJ z(g%XW7^;iP6bdo%D=a6O=cc;Hvo#gI-nHT<9S`p-qXH4xQ#V~)Ntk{l_M0&~>`!Q5 zc@9j9F};`p7qVG48DUNP@c?H1^|w>%c>n!^{fPp&pj>k3CtCBje{_;u*9^JWTjuKwF{rJz#|-$!2}X zvng|4z=YaP1w3YrZ9{CAN*VkY4IKW~>V(~XLR7A_Un$Co7-(Z$lD^g=CzHSjfH=nz zr6t{&KoIIlU~H&fDTTGU=}xk?vBuv^ztSTR=xa$c;cy1SdMU-Q+v~1=2oB*Zrfd#% zQOzqarNS?k%}8#5|-pmJ6EhoORYr}4*$u4x8=v-{7ocn)*5!jTl1BFAa{X{P`=$Nx`?_N zxGL1WrVO@zSK>APaH$5<$ALl}j>bs_f5m=5Lwedv?X_-s&O-l{^UEOW*_jIY{e$O_ z=)d9!;NZ^+-&^>DIY@cV`j90dx3<#kP%aM_HPrp8a{3on9Yv3|p zo!VUj(_73NHHFr5zezz%t*lp_o!`tA2L$1RHS0YnXfdsdT{FF9$`as_BuLe9LqJII zGm?Muo~pDdtJjiBVm9`a$&81ArJ+Axgz#znG4oo?MIfXD=^&dgY_0uFk}&u^rZWw- zww^Eb`aCmo=~@=E`Sp_%7!oyVtRVd19gYxRZP>dQM-dv;K*A;d&G)X)274$5duBAG zy*Q!qI^DgVlPLR@X&O3&h#h1z_B%+adZucp%a43^SK-8h!p=tE=-dADzeD2TWQ4Rc zlznpVgj#n@KHQE_f&SVN4D^zI(*6?nFejGn;Y3Sr-^c6eqcehWb+2mw?u#42rJ%iX zq1SJo4W{k=m@3(_SERF>)frCk1SMcCBkk0ov9KO2GFwtKExG!FE2rd@hy>L>t+{w0 zKP^yGE)M|f`fm}{QcFc2ZF-XU=M>azGwpxAS4?qW(z{H zLSF%QdXL{@A?+e}_D*P}@hBc>KO)CU-H^}QDQ?=BIG5dG1VMKx|*QSU5lMM|TfPR|hBjNUb|@r*JBlf)!cJMpmIX_8fCG;;B5)Hvue0CL3|PCSc_ z7HdW(SSZiu`di8P!d75uK4(*JHv~KrkcxZU3=Mv+CmQWA_Nt8 zso%ffWR&4&I?|k`E$!C1RL=<-MFgwie5n^DIshMsw@fwdiX0rC{ux(i8fD{^lZo!Ng>D& zDO3Fl8RUd*o04b}6;H{jDjK;HB;mz1FTDdufxWuJA#MWY`7)|KxT;R<7*cpQ>o4IN z-<(O}Q(KFfNlGcVj72^RE6{o?s1J37-|Q6ZoLHerEeObe6xPBCt=sjdu^P+Pro%uK zDtB#^|BU(m;c~Hq_7Al^d(5KBdwjm_*Z0rgeWSE&LmO~SApFOQe@V2zBiyL{eWiC5qM0r0Wp}LTcvfO-lMcUPEhT2-8l}Bjoy;N(}-RRNh9Y!8x1#p+om>t zE?|aF4}j!TGY5dWQI(LF*uT_QX#`**PbGl23P?2T@#+gP@bDu^#S;d^r!K}VUJb~89~g)Y z9u0!BPiNQ+YI0B+Mu+rI$f>4ytR*1&R_^pqv?VPX=e=IAi{NIG&)%_Z-YhYoI0)od zO5=(p-{HqV@ShMGQ2PgGoCaRW=2$O!YRGSIvJelf38KbpaxkjAf4>z_1m;s& zEMsn7Wvnc8mhj0^XrrNBeJz-BAB*Gw5C3`37`HJ58c}jEi?!QAMG6GSPKBh6(W7vf&rs!PJ_p9JtWfKk>nTBj%b2*$`v4S+F4>c@ud^EEvMtu&vy86q z#RSEaOfl&1A`!nFA)W~DGaRr=^P-e}nw51O(86+Qe)Q`mCnfZfKL=^{IZfN`NFGKp z@~E$6$z>T^>>wc511sqai0E#bo=d8?!^QQ<*8s8!?zPOJ-&17Kh{zxmq}razr%AFw zmHp>jx5{Igt$!NAHY@uV%~esnv^Rxk;RQATaOYaXZxWvsRG8PzEc(+7jl=^z0668~ zK^2bpMxQM~DKO)v#g3gce#Zjc4vHTcZ?h(eAD2~00qc0HFhY5BXJSn?1)e)MBuYJ; z-RB3I!4OAk?y3*rzMUR>Krm5$wx%F))IKB_FT@PEb2yg^*A}0C;DoigS(Q^;QmeLG zc=Iq8QXLvwj0~Y_=)J4xKt5MEG``xUeD=ojEBpmfQ$&-a!-aVeY$a1Q7=TOcMLAwc z0>1fC&&GSn09)fn)S|f=_3zVt>jYrn(F#&snVirjmuc@Biz|?lc6G(3+>$B4f{LuH zLdJ@4Io}L8!0Req@FkdO(Ho?G@O07LAEhy~j6fuvSTtXJh$@H)Y|YS|;jx)|(`OJ} zbJwW5Zdgh83T$c5m%W5X;Nibpf9=Ws*E#Y%CbHC*9=82i@;$v+;7e?R)JjqWV#}o@ z_wvNvI3@5Oz&>%OQ|YBGX7EaUY*5bgHiF}ydweO- zzJqUjJUrd+17^%a^=M2x>A@jRYRt;p((VtzDC8SY#*CSl4UR6%-X6M|u)TZBnjB$( zJ#ZQajl^4*=SHIM=`wYi;?LBKy@9Zetp4uFdr|UbryhcgZ_>F}Q#af8c6{>uGTU&n z&_(yUn*B%-B8*UkT8-z3B98bj9 zF35&dRy6ir_;MzfJ1>CXMhujD(x{U$c;To7h(Rv1M3&eC{HOb8#p&$4@~1x9M#Ho~ z2Jw{Jj3M3H(^72A&LtN1Z_p3t7@1QP-!QB;iCWBWLvPA-3*5sujB$X&#a^Sr)Twp> z9v==)Qb$u)$rK0?eyKm;Y-wz1-E_}E-TX)kda=S;c=?T*{>~woULGQ4&N^^y7BTV4 zh;xD?Tnp>Ufz{jxwSTZA+<`Jq!0nnkh>M~1zU=i5en3^e7e!M5N6+J{U&4ukaZeUq z9M^UMZ>hYg4OfBFhUrO#SWF%uxvfC=Bb zDYSDawI|CCxV@UZIVlg8o{Dt()k;?GN5T%HDrnv)K^Lrh!;Y>PrfH;MO!77Cm;PZ6 ziSb{2_|@Ncgvj~C`lb&KoG$*S{)`v38!h?~5*C}cCSO}Fg-Y5q+(OVGk{iYEeE&Tk zqL|XF*2?#(W5k&z>n8ch2!-ENxJ2U!r+&yRc~AhmUAHJgE*QW=?g6hSkKBrZtS>Gl z3=3sSy)UUVba8Lk!;KJ8t4BE1@%cyBf*SiOvw+rM8S9FjaH60A%Wp0(Tj#I1FFuy| z-*58ckeWT@GbYacXzijL=w$_qzDBt>d^`yT%%omkM>22we_w7qdIRph_^~T2n(~yd zgE#ZzWU+O`myV!&l=gyPhT5By9u85Qj?_~2EzQFGE8^a~C#|n5(lk}OtM}@JBRp5{ znjRT#Huv{g$o9I3tHb=kY^xIkm?-}7rflPIw^WD-w6}UgX{4agHFGm;G9*ug?4f_L zC+Kir0Jz_d{3(_{Lw#^E6B^X=OltaaBKuS|S}ph=3=(PH?J<>Y+Va?xANTF}y?kD1 zJ%0$aztmdxBAM`X>aEE`pIW-*BrRwsZ_%e9N3&EPM&}gw6Y=HYLss;=v%CpabUp}( zR)}2W*XX|>b~mU76R#UuwE@@X`gJ>2j^4!NT&q%jl<1*3%PsC0FU zNaK(`Dx@aSI{RP!Q0Pw(!(MXrhF(_u*ETJ@se7Xi z>BVFAy_XyM)SBcQi12n313g`xM(pX^dYELuB}jw?ELWrfP(uS zum{dOARHkO>~}fOofU^o@LZX%USN(!^~Mw}70~-WQ$zvMA)p5}h$IBGeVU&TYKIaygu3J_sUi~Eb3=JK{(ID6iyZknr$B58 zxra@lvMIITi&Cidxt--WIv4!S{mZ`g&d{y`Rc8+*MJL67 zeN~qr;D#51ih#)wfxP)-P-XMblMJDLN)WIX3U%w`mVKeSob3? zEY59BHt_HMn(bHQ2IYU5?{s(wM6>dU^%iV0BXWvkfYd`Fyo?auJ;i8V5E@zMGY~U` z;Qu33i{m1xgX_^zsAkZWg^CXw z)Z8r-re3pDP6Y0R{}0xTI4TIOKYI=VN67x)br3n&nU7{E9kxwbq;E70hlEO@ZZ2*L zJw+j;Q2@062+s&2wEjQ#taD|l*YAWs#`|_1=2C(g<2q9DqP3PcoghvEbS9_S&`p77 zim95;R7qO4P?1Y~oYdsz`6$udG*r38TifoL@~E2+Bk3nvx+vfs62_)!GNONg8Ip$< ztQXE$LLyr^o{eV()lPR%FHY5FrV)T}h_uu&1r<~D;yQ&sPv(1kw zBvO1vgNKTcYcQTNy&n?_pXGg~xEL9k{roN8?Yn!kPZI}D6u(U;v)SC+0}1LdPdUM& zT^YPYBy31%cJr&$WMT_C5{_8Q&A%F&__Pa!Zx}rD{CZEsz>3gR2PY^m4ow1RAYsd4 z`18mi%$zD!^sgTD*j@tiy+b&sYqPhf*UU70=f>~pq3yoaZ@*h2ulmWuFr;-tZK)+d zd}H$V+}~A_=2Kqoy3cmqO!SS!#4i(+`{##k;OF>N#5h9&Z_wf%Vwub_EOeDguoXpD*t8?FLZHZP^H1uEpR#D zVVZ=p&$^R8YNG7#>&##kKS?K(WnBmg3afp_Ej=T<6-11j$SvWDJdG2j_%i#rv71QX zI?VvS0a?(%cD)((1`BqzzD^O<4hLPPUOiJ{2_yJfM&xC3`yn>F*D-~Bc%GSW?=iRL zSKHx6@=5lF^VaS4U`U%)RPS;+)eYB*WK#MJrfK^$y-k!Qzyju#^P;8v7Z>-f} zP}y&-IKL14FNbMq$#3n8Zyl#3AFq_N?{kMmeQo5%rf_0K>Cb{sQ*D?XEvB0Grzu7} zf2cms)ZQYzkvm@P??`4f`;N(xE@Zd?cww%2^ zdA{S)mxuE;k}93my3vVZQ`2@9-)dTQO!u%W zg>J_@TNEY2L&pFBT&Fk*JRX_BJzMuCOT1(w3K^oSGVZ22E~wQ^c7vpf<5asdzf~ib z%9Lo#o|r=1$p@>k@gFf;D0jhsW(J!O^-XSYMxdZ(X(@BDi}*sms@Fz)0ao%C)k?*2 zVU>`+ILkzvtgKqK%1X7VmsSCcW7Beq6$_uoZg>KVWKsSFf$91wPT!xIPk-Z+pA@a` z3gWL@h&_%1|C61(0`ztCwxfg|$SK^AMlLd>o6tK9xDzK>cIP+It(S!Vxf6Fu-fcks zh;07~6zuypmJ3|6SnnDNQRmd~}sp zTYSUI1^0hy2fhb9e-2E4l_l5corDmGY?63IBE%A-==8M1;dfKBDP-AU)rhE~4#ZF& zqTY=y#fa4GQ2IYP31$IGEaIa9cU{K`fw+jrT2g@E1jizGPx=!r&A2K-ul}O40_f^6 z`fo&*PoQ8ks@wlWAoY}RF!$fLFO9MKv89%p=QWJr=GF6J3bD-0`x($LC6@(1Nd8Y5 zQvVhX{o=9i%RJQOFn!ch`?379;M*i;YYlsD8`a3u=tR;X6d%T#Wx}sU{JjM8{~1b9 zSsed&xB0K$kV5Yn2uZrQvG``_5gxUfhb}C-&8qvXv49{ViITIn@C4b4>;sBZ>88FJ>cDc-yii=)F8;R z0?SCwJf=A5@k$&dDH1B8Aj&nDc9~I9i{)wRmcKT#a_?c%8N$?nBC)V9|Je6s;BK1s z_+5z}Mn|*-Qu&f$o0#I zh?Aw-Kxo)VrD#d?V#nmZcd=P-u0OWq zd`!{xOZVo$QuwO~@S!(^cZMhz`U-?heHr3Iqt!;_eiO;#Rys@fIo4 z;uI}Xv_Ol)pZB}pz5mKuXJw9!oIQK?%--`n{@)niQ9j=U_Ge+2qakMiXe5;iXZQMO z0>oGN2O5g@;2t}~siX=OqiujBJ==Y>_u%xEr6i zi5_0Zhc_H?8y-kStx^a)23L10K4Mt4dL@QX2vk^jj6YP_^+r+(PU;Cy`iZlLJcBqN zL^RMn42gUtM#}&aR&z~xx1V)SZZ##UzbJ~6%>+?FK!5%xA4u3bB|gGG>{Z0@^*79o z6E;YHP9GZqK*#)ikiyEW_=lR+u{c_o4E__s(_%g-is3N-$TOFh4D<5~{SqR(i*Lb? ztXySfda5SoVUEnU^8(q==DzSzQwg)Mq#s5|NX==#+^=#(GZUSp*$XWl-~IGNh#EAn zVOI-K%-Jw~6vF#{=p4o3eEhsDv1ZEPpAe1ycl>DQPnD79euO zo=PJHp?u|j+0IXM_4IQ!^a?1#_40a>7*SZhq&M3Gdy~x}J$_+L zpt=(YyO8|GL~i0I-c;xhMxTc4vn+=)uYgcx=?xdi&T%vp-OYC z$=VGluG2rcI8RX1TmGAz*9*qZDZ%BwG5&Q#8SyIn;Qsu$@6r#lfnT+&knL-2olOTAbe5RO7JT!P9VYXAO zTa935`!-Us10jQjkWbeJA{Y3o9j>8HLC!HRl);Az>od9)l z{nW!>Li-9fmJDJk(~5@<2Q!TUsZ@(OB3PR<9BfCN;6F1i?xALXCpp2!G~#@>m7F(=(@zmP z8vzqkEP`!6=NTFM-%1dh7yQ8nTdG$%oo!X|KMj?C6A&+>zxC^k`tj*g2F0%eH%g5# zlQyBQt$HWWnS7*>8L_raw~3Kg2TXE4{KyqtM4c86eMypy;r&JQ9ffuAj^ymbt%o2m z(1xsW4j(t~vP}?cFU3&)&=RrGtA>OL#_B*lqyYz14}0Sa1*7Bt5C9E6v^`LPxiQEd)pB~-i)ddvkt1G;kxpH!Nc4W$ z3<)o`BSopU+AaJ^bp9#&?Xh=fp^BXhwWTRniJr%x*+C`UK87cu#mwfvp7vkPeZ5am zY5o*)81C#2tUO}&snFIl5d{}?Y6G4;DfaamEyok zd$^lM>>*ztLoF0iy^SjPK14smUp-G69~}j?7%|YyVIi)9^e`sQ>&>COYGkOjo7WI@ zu%uTFDPZ7^1>&6Pf)Kr%rj-^=?8JIFq6b%Mi1F}T!qBQ?cK}@@UKr%RUtcC3$izyN z-u*2QO~V7w9Dt5_#K zJ${0L=u1ywkmnHV-ECZJ!N_^e|9dup2crIOBOFQfvdY|$e0KA{m-s?af|!V_HFV|w z-E{1hqqlhj|8I_O!eJ&-ExoYqxXVvEGXd7#Hm{9Q?g==`B)WoWDUg2AZ(h_i)unyJ zH{fAMk`LCZ!BOvyq@v!2H>~a|K>w{Yi)yMzNO$w*!P3gN_x|phOOJCQVodA6mfuXP zIdsW9%0jM`tE+zdW;*@4Gf!|QL&S8M>76Y6j#oC!B^vzfyNl+)vV}nu)&BmU#`*7P zjD#INg(#xSTE+k7chImy>W`0?9BjLtu6fa-zB2*r@}|4_x#a<`R7CZmBO|ujP9(`5 z-faCN9!YvWGrEsH;ac-_w{KlM3|*z1ck*Uv=Py|lWj%LFy%X&Au*@ocIR?H%{ zf5a#f%YUekCv1ZK-czw&`}vA;U(1_^{lERry&V$Eq%RWD>=ThyTN&1W0_iskheIZ^ z-x}Oxc3A3o_B??-Q)kYc>HdBAtFOf1VXTpHFiWJM@pf#DBfMtf{LsiFpC*#ko}+46 zADXk%3`QhWo$zs|D$Rc=jJH&a#Mu(s#H!_MMHA=9t~s;gJ!So&*rHP{kX5YovXK){ zCi|3bgC4AseT~yhR#ZC}Rfdn)zB(>b$x0o7+4?b+BsUpn^jzYcI_lv=KK{&&vFB9( z;tQynQq5+$)XvE2uw0*+&ZSb9_s{wHuEJw=Ti5Vm43n{~KyhU)qk}lPcHVzRaO)XX z$m(&2fxPKs2~2bZ`#4y8g$MG%Yi_eTRa@ypi=^J(q4Cg^;QN<5259E>p3njd6}>5) zy6}dY@b#-gp)$MsHco8_%0ZxKkg%C#fNiiwEP*qSiW$;RdL0tpmo*ibP^qBw7P?4= z8r#yN%y2n~pLlol{iWz-rMI9A7gnmR8*X_1E_D3hVO(M^-$|K40m^^)egMusN#)uK zMws`1gpp+nmAj1DknXhW$gBb5n1-LR*QE0YNi~W;&Dn`k$vBvW$o@>H)Or!Wui=+? z(GzY_Yp^3-%<^EghDG+|z-H<@ayxfRD5|s9VWMXJq(<{Q_ zlYF}GwBdpozQY#?gJqx2_xP&FPnedpJZtX*AV6zx36NN?Po8YeaI_!t(fpH%HQ+o`@(`giipd#;)YgR$SqzRU+#rCy}g zBhl1GqRVIb$Lh0^UBy=pA!3@d+YhBGy5obC+t_y7iVvrPuP)HLn`N9{#O!;dI*ix5 zsZS)yG?4_%j?dDr*4I~y%4lSw5MBD+ivgCSm&%BKlE^BZ!5*8l%#U} ze*XJpJjZrld~!*W;b#90=xN*{_cwledDa>!=`_8aPl6`kQl;myR^kIc=RYPp<=mL@ z^L=xm`^a0VvAVKCn^?KFL;yYOre_XZZU(u2xSLav#%ElpG5`W~XOg+0tlP}63>_?K z^)n}B9Mc^V^B=X}6UZ7Ej*e=DD&-&7Nir)ZvltLaQBVF(<(MF_( z&OO;v5&HnPnZusoiwV~JXEg!-4=x=|PEWpvTZ0Vmo4aKWk{|jQe04uMC1mWcu;_u6R;qb(ArK7XC*ZM5EE>M~>;7Pkx!)y{s`&B?> z0t<4e;JmWBN}kOz@KRvXrD*2%Q;D5X+>W}bL`8P{_xx>>>J|(Cg3Z$pG0F`T-$rGG^y(`XD^ z%Ks>|(55GAaTx{fYS;@3stXT9^n5%C4mb}@*IXHD0D^+xrHb#MjyaTVCkr?QyDI9X z*dGL}(tM@WWea=X%i2!UL5=7N>NkoM=Sftr6pgDxg1dxc~=Wm zSDM|){J|UZX3!L$2dQPH<+@DX+$F73!OAZ~4}=`d$K(AJ3?ko}o0e-JTI7|aiDiFb zbQasP%*W~9(p+N>v`i}%s?IK)I8p9?=glm1jt^;1)A2S3ZBi}chkMCZ)%FR-3z^dj zr;WqYw>>r66`!1Bs^7VwhPCNxi!Gzw>;)BjWdB}b&SwAb(jK+C**kc=MNelY|3A;NQ#sSvBiaW#jd@5lp)8@Mg9SVo{7_UrhPFBP1h%&MMi|h4o9D2j2?BXa_cFr$zZ-o} z!W-1V+K3^=PX3)TaY=Ez~S-)YKb%R;)`4Mmz6x+f=0*$By(9{t5l)OQ{b01w3$C|kQ0O6tO1s)ome;y%!lUiV9(q0rg#bPH6gAsFMwOpT z`*cSK>|NVIo!;k#J;B@1NAiyEQov(K2aV`gS}WzBVmz}I%V&FqsYAwA_P1J&`)fL^ z2v4t!6;hpL265qj?~Eb%`_BFS z6KGGw|JX=tW!eA#*r$O^vdw(7oI-D_ZT~8HFsYTr&mdTqAEm1|QR>CcC9i#^kJQp# zK^S=5nMGW%E=AP)@V!~V2g-Ur?N@~-mxc)oz{JXuC2#c2~gh`M-5V@ zS~2-a1OoH4)&bPY;<#fCHJ5@1ur^ct0JHg=Jk8jwRAJ34MU3G$;jh_QuSj8OQ#WZY zGyRg1Z&Hu%o==$2N1Q57Y>UxcgQ%4bPKI9-{c?Rj>zh(=TGP*)RG>aTQjriPzxFbw zs^iNtxyHa65Y<_rQvpv&w>Tg${Gj#MD4Fj|H(eszua$ZrqtgnZb@EMDyNqr%lL{i@ z*>hLQ?1YE9H$)Ke%Jap<#b8&iG&OH9q^^Gse`_n7`hGp=LG8&^ryT$iS~dE=<_v?w z@B05|)?j1E28qFv^g3LSkQ7z+t~#2ljfS;~_kRV34U`HP-f={-sl1$T>&iehtSeRQ z^0DNxm@I@!dX^lGog6+`!TW~3xnRA2P@ovNeaDT7ni%3`AC(6pMoQ+Bg#{{Yr+yImx zHx{och2sKQEip(5u9)f&W5ub!O^#_oSU~}|5YqqT1OAlKwA#-;jV?!mhC2En%FcIi%0*s2s7f8cl(S zE3$cE@2coPfBFv(1^*C3qep1A%w&;kQQv!`ks7KlP=D&S_Y{(Ek7fyi%g2%(9gRIs zP|Jn;si1VB23d%mId|D*!-+B+J?Xo|9@fFHOuM?FXj~?^zrcqev2_}OpdUCzMKnuC zEBM-AoNvga-MyZ_D=+%K@Op={!qE|$E(d#9Il;2 z#bT!;2pm$>-TO$Mdg;~}=s^s7^TvKzc%(DoK$_*J;)0W_vmO2}{}Ak=+;ZTQ8rl@c zw?Dt`Wk(95?9$lPv1Q8a1N;-}$JVc`!HVr^o zL;6IApOj^@7U}8NZIW~A2e};(sI=40FIp6b8foxf7O*$hP$P}C!-%rD4C~mi2p`1d z2g#1a*#=<*5VroyKLU@;PHg@c~A5&zr3X^)Gjf1rY=01%i>u!Jf*AvS!5^8WI69+@;7DS~X^!R1MNIlPUY&#J&HIYbakuXK`%bqWZpjmaGq zav~y`htKg~eOI`Q;o;1RUvYsFT`^TpHTwn`ixo3{(4ni(cMUN$W>an8|2;N`CGK=C z1$*t0vU2-219Eu_hHV+xH<1D((O6fZZOOMIpzu$reB~#3=Va9m_?_Jt({A=e>_VSF z;AVQ$U0QDLF^5_IasCg2aTm~M`YPZDa;Ss1uNN(tuYl!b;0|j5L7qj*0rFheR$>W z^);IQtoofvMEKnKCfJfw%9>1PK@6~I3^(vktT)CMxAvm+|D>@)pu?`@K|Ad=T~u{- z<%G$o*KPRLLvkkn=n@Co9gP`)3BenZj)K+V1CJchM0a{WzUjtpFs{lz8Y8t-iAzrN zMq%Q~r&Bn1E+sewtnjSw{0ZV#*DWrg+lWtV1kk3#6Q}dK?&p`Gb8dM9S@IQKkNqq^ za@~RmK>(oJs0U{ml?4BihGP?;JK07)72^sM%IQaFh*EheMSWsPwBz8CGwZCBw*!r0 zMQ<763lknxEE}~@Ga9&gCe!djT3mEP1OUXG6QG2XcbS&n8I5Uc3W(*QKQd9C`&s>? z!7O@`RQ|VM6khXZ^PG2T$5Y*&xX(Io6JQ3Eoi8c@`UMuJ+2(W#xN~D385{U0#hPQt zW-#7@`Mnb^MjBT*B42FH%!Ezw(fPFt4q-vEBuV^I=Eh$N4i9D15^g^maX9zI46~K` zpi`f+93hl;FwL$Qv?^t-R*u9u&X?0$g}(=hx`nj9VS$kiG1+KH=lLj4tG5^aZAI*J z!LGzh)0xw`V1GE15y5qF${J;$_uKEzEl<>g{6KwJ{9nM4po4WtXT%M|1 zImQm-ki?KJz|$$%BhkjU)ILU&8y0HQhW7_1e^gGZHU(+f;XtP?S zGhgWHXAwcbjB3_7VH`Ld!)DIq?{Cn{;%h2__Fl^3E-tJc=fUmSd7!9OPEFs%RY?B} zsBm}#F)AKb1Bdp!25^t**u*2RkGrJPSfK-f$K!QVUp1iO`4F{%9liO*Dq8Ml^o8^x-J+!e3izHc5+Tu?a!^CA=EvvET<$G z9s9!vBA230N{2>6k)Y&|si?{|c%}El&x8yEIbG+>Sm7pd`ia%tA%Pf>Ff#RVvV>^u zAN^HG}W{~;6zhTF=*9$!=;uxzCE9dLq5PMx4HN z%PO|Zh#C2SO!1~J$8BVDV1k5dLT#q4Kb_z~-bzB__dDO(9tKyZ@rhdd(~4%01%1}k z3=q?k{^mTbxdDJ13_~4#MHJ?d*I6yQv=A)xnYBmD9ggr!v#<14c{IBoRsJ_6L9Iuf@lT-T>-S!mti?4hMT&P; z{UAZZVPD2w=PO=y%2#nCylNSxEwPp{Ix;U}F1rF6bnyFK48n)(2XJuk??|pLzy+fn zgiRFADkKKKfv0lOgL>6?MNauYJg<48E-@t1KgqNS?2pbC_ct;I`7n|x2Wcj478e&M zw*C3t$bnF5(yrpne97jITko42H*^m36pKnm2CpiCcE@qvkjO zUwhw3&VBurIz_XN)vF9W-p|^?8kQplJF9aPY8{OJiOB|v0tG~|@@3O2!6Fla_@&?dBWgob?o1DuD3jbQZ-Pyw2au_%E z|Ja+*#R8$BfAV|~#(9VCn*Ii_!IlFrpgqlA*sOpEI2k5!_jblZwL0)~MCIv^Y!3S4 zP6&7oGn{y!H4tR^xb{hH5wWhN$qovOlYoF2!{BfLjQ%oLgP_AI}JzDq4}Yh{GeuG?ks5jDD?G4zi4*CemLNTZBW?CV~a!) zSvaO@%@5@_YxR*Xl$`0(F-x;yTE$0PQHqb}2ftI8<_8~rIkWB50G2^&c&IhniKgR2 zKJ1=v^NEX>ANH557J{jN-a4jZfFXg{vUzRhGRlo)a7s^n7y#|7g?fuaosdaE|C?$d zKy8IiOq=3kY*T|fViCwf8$UjPY}9|jC~z%)SbHp^9QvRC^P%J2YilFeUP+3Y+xWk+`!18f=I}jtz z5vi5=221v)MEP-JJdp6hg|Gd}`=^;PKGOZ14^27vUjg?#VKuFk0LIUha9%7Z00zSF`Y7Y9VX-b<1M~t^$HXFh*1O=z~*v zwh+|<09r_lgmz(l4g&P+q7h7*JM)w@1BPE3MDPMF2|y@Qe00_~p3P$+<4>&*D)di`V(m!{-6N2{f(=#csQM|Wy1@@0; z*x2$uWld8h>Y%OA;8Rkfq?GHtV$~vZ9(WxySRRVsI{wpZ6501!!)nq3-%id*CIo*j zC$2+Zz;5p5&6>o$oHyaV@a(T~X#!|V2pi0RoQg@N;Bg}OSa9%P5a4rxYqupmgqT_& zs-84Go;&O4HDE*a8{!mz%Db2{#h+7|$t*j|33!If*Zk31x}RU7ja^R#d4zu?BL1Ua ztn64gQiq13Q(ehM&7_(4_T}T$%?p~Os-?Nmy58@i(dZW#rEt7bY25zIw%F(hGeX6C zjW;rQr3a)$i)_VtZ9I5OJxTYHf0GVo)cq>ZTN0aWhx?T>$mc~`;Lzf+m4%#2WCM)4 z!{~EC&@P*YHf`ePthDjlK>DT(4=X-V8|ZG9_rCcll?dpz7W0Y%mV5D13w_}7jd)2>xM&ZwCyvkBqqbT5{_PMyNG zT}Iq*MfvUY+87KRKh|X?RQmbL(3Y-$!sV*co2|T*ZxXKgbyzd6&+OT?dVT0_5IvSX ziustA&c_O`Bmr11u%Oyji^LpzeI|`=e^n)WjaZAh_HkA@b@}ipyq=>q-rckAap#h1 z+(t!ltFT^b?}tHIKW$Zthw5_X>NkGbps^nG`k&U3`ES>M3kx)A3*0zJ48~5=ZdrKW z@rXBmTP%^SE*w4Kk@{Mn`F1}BCb&v8wTDLVZN9v1U3E29357z(Q&|6E13y!~|Ldmq z@=JiB?jr`w;Q4mf@H1EIxPf|%@YV6<9E~rOe{unqrf!u3MrtrOwX7da{*(Y(y zO}Au7=+;q^$A#QleP&z}qfJ1}lCDc-teGT&j z#&QbxCPeae_+HS74zLM6vzOA$t4)2nzGmd{Q;T8d`^yg4sB`V#cEhcl@()SeFBo%2 z^4!+8d7mJpYBr15o9O64o~tqGD$G{3iU?0)9T~>3Hxkg9Yd4`3-? z74Jx6y{06;!I-}gmwQYaWib<4k6~!O^h>`bA%@5jpP;5=n_!|Taj?PffgP{!c3;MqJ|yP&o;UmbEx%bz;fui`6t&hr8grqy4L zZaXJmuIZ_jMZ*8mE_#2ol8QEzhAV$q=?%M61#oy+ic8CV@PQ zXyUfKU^lp>e0<2_6Xy&3kl=2UDmk$T3)f$UnsA2-<>rQgDNhpPgBkkn?eK~)P9k_^ zRd=D40nb>EbIh9Xmiz14f`N1GB+ca~GSv1juIZGe2_d~11cIW55%xxhD)QBpjeJqc z;pu~Kyazv6VmsNS~Q1C_p=m%}Fv>7dG%aM|K#jBFoCpuhh$By99| zqsgK4rpm^B<{zSV?mlsx^1<1ZA7tUsM?bX01szF~MVyW;I^-V<_boi%xRiRK?3R>b zYKl>xK0lPzLc`Ei_sPlaUoc-z{!-0YQ%yDXW>Z25-^LQ9S7?WuL}sIaOT_${c1nO5 z=P@qPIWB~MH@J;_@#2%c@U5%WI*HtOC^cIt#PZ5+bg|=yY3RN->|=@Tf;FaapD-Nrf~Pbl_-{{LvntzT z*1&}&vxUs#Xlx1^aJNTL3Tt9AQkCZy=!^Swzab=Bn>r(V=M}+fn-(AE(ncS0M$Bwz1M>~)(@V`0=+f~{&PB}s*K#& zFiFty4CLlouN0^}{2Ao%lgp!h!B^_=s2x6E0}rS4nw}(goXFeE*4F5Rjw5 zKj!x*6XZXe7ZeDWJQ7|Uk`J0X>1N9BMF(T*l3rlw74GF}R683O$kVQmJ9tpE!Xx{$3zUu9Z`B|LGIGbx zH4)Qd)>m(cPBv~m^X)C~7=2Ctu*qs5>mdE96I$@X%CJOUoN{NHqpxF2d24DhalDH% z`Fm3{t)QuXGvBW`s7|L%cD%>w%A{?A!6D<%SdGBmnhd{jk4=k$M-z`~7H;>3FFQr- zstJ>Qt1N35*LZRdWEd2aTViJ>%sQGsnMARXsph(ahlAK;e#36Ax8!qiiHCo zH59}!!YFnYot}Y5xDCv=bfU0;gdw&$O|LuW0bktvM9m zn$+?4oP4uoKN-un)}3(vfo!*#TBL*;^jj+Tg-M5O(-pDmm9e;y(<_So!iS5#3ZE7P zqb7<2BmOCFvi(9kD5`k+I&!Ra9ETNNCsuV{-1tx4hsXfKFa}MXD?@qku`}tFkczp5 zI0EeDw93f>Y3pl55EqGr>Mv3@b0LN~cH$hmEs&)=02NL$C#3N~5cRu3E9lzgeTil+EZg(L_ zgNSgT#`4TTX0~Vk?}MkkJ=Mw9Amh`h-4KS^-*x&1+p8I2Ax-p?LpQYjN7B?4X>c7Xlc4iNRhA7!je00|%%Db60P{tqBcM(^kLZU$!F9G~1cE;^moVebGW2 zSzpah-RPu_Jqe-9uc8d{F37ZK?V>FYIo|_-@X+N`O16Spj)-M=Yp%lEBk6E6hv|&> z!?3}m4TJ90VcRd$uB`{_hi%~YC5k%F1wC_S{bKz&*&llG>q<5o4ztne7*rJ2FN75K z9X`gH8;)r=-)US-oXq5~!mr@?{auS}VbLQ;o$s7r*D{iGpwFZxg700#IE=qt`!Z3M zxun*1A@UNJnxkI%XX+_M+_cI5`r(@UCu5tBc9CrGBBbj?n?fV4vh@f2pJ}T0zhIAy zoaB866eW(^iMx3HpOUAVGh;zMO zoe|1~6+?}Oy?nWwK;Cv@j|V>e>D#-pX5ECv=ArsJetDH$^eew%A=z#6hFV_gQ|6Z5 zENgku2!X@4Kb0lyb{dDAhg$a7LOA_Bjw}})%fUP^hHT0f_V35WTGNjQe~ywX4d=?8 z(@K0=?NwF2IF6yCOJK(G8n}=AuBM+hyVT=sNF1Up2=VoMsulgudUJGw*f+X-@0Wsz zKij|Wky3h^J2BRjW_A0DYuoy3Wtq7POo%!1 z^nVY%V93V*D6;=KJmNwG{}y1B`Ji~o$7bxaF+J1zyC(~k; ziq}T_0eEasryQCUk0p@x=N};q^}@sU^URII5(YcLJU(}3%q#$vc7}!nRJ8(#etq(^ zq=?7l5IVE6;cZiE1q6r6rGMpcw`{u_gy#Ia2|@$4KG4FUOD;6|vMsF(p|zGAnUQI> zly9-)J`8=$xqK}a)zV+4qL{u{ZI@*7zLiG4H3Yo!SEz1_zaH~>nZRhV{)SfO^awWW z(D6$^!IS3^dZZts9{?!%UR}M(B}@wDSHX+=FSJ%uBbz%J1;n4{`WVN_w9p62uC*c* zg-EZ;+|V<6tf2zP*$Czzs@Oc2~FH20#Tbb9h6{0UJ}|m{OF84{aal64c1FvdgTFc z5P|4Fh5%5r(nJKDo^k&G;D<&qO2{Jdd@4FCcF0_9KE6#)&XuP`46u=2Zp>Kh2S&Zg zYT5^br9=q6SfnTYwUhG)$l6>_b#*sGSNpU3f)&e|V5gd7RD`dD>NS~8{J3yodqq9} zn2XQ2U`YmcrU1lS5oF;X!O0^LbQyJZ4oIQl9LR=l?pO-EhKlh~e|rmDLvHhTT7LNN zGo%1ve409FkzGIQ$rrVomNMaq`zP}#Gy;Mgu7#Agsj9}I4F!W$;$9E8OcU5Jk0#Qh zRciMp_qe8rU?w!xk%NaWrCb+axi;inIiU7I>MS}80MsNkAgl?H}yy}R(P)hiUpdzSyC zaUp1oX7&_KL&`@2E$A8tWVIUOG((zP`~@?Z?oY5$H=gWhIAZee&C~amBmjdyG6W`T!23!yX~do{zQ*>i_d_5C1cXwI z`LRO#To#C`e8%^sSy2-3*#;ns1`D~Pf-5x;M2ou~;6UQ$??EV@YxOfnVAw{V0bf@6 zctHbtRSrtaj(LlpYMQ<3>5rKkcV4Yt4hYX$61K}@BM%M6Ld9Eu3pS3Wu~?@(9z$V5 z=@Dbht!yuR8zU_@$4FtXbM3B0n~(fW9X8Ye5Zc~4PWnrc-|vAa7bcjAP9Pq{v01t# z2nwA9T!SD+q)?VuFFLbD>a`>@DMk7D98qc!REAU%xOIO)c*@vUrmoI}h%@4C7f}No z)1agB{h6%gf8admntf-(u(#@FQf630H5z6(7BI*@I1>ydo3&WKp?6SomiWM z9qRdDVd$U<0*S$Z8gCg=%2Fy;JZ4fsV%RXV7ZrJre%$Y~)$ujDjgAaKoD|=V&6;>}wjWU#RQr{<)bg)&>_$w|4D?Y|XQenrMO=!`VuB6Ryb%0F3ugFHM=*O2qkd zgTGclR`QTXEU1@+^vl;mMo|A?f(Hm>F6dWTBE{|y^GA^AfCBdBOZ_MsH8SSXSAdre zb(8kWTRU9Z*6^kWAWPL0pb1x!bQAUcc^sQ$+6K5oU|hB#`;tIL;EDlDUovhu_7Ve{ zBS{LqsMkmicc6AnPQDxr{{f?}ac|>pyU-A92?pN&3{ZZO0Vw{QGyp*g^##as zj?vIovGh>=#)d;kpQFK;OxN`FZsQmH#Gl)!++yw57YtE}lz?`{hJ~KM_ndTf^#$M| zU5je)zMarGRjr^l?Sma`IAuqdM!;h>0KqXrRZ>*jkqCaK0B8HRw#2DqVl?x#*;Ky; zFcmi74gy`~PHqd2A=XJ@7L1w5hNU034-T;N)%K#H6EG@OE8R1X`2~6g(&Gc0S=1v}qTF+g^63G=8X2q4QI zo~FP|Ca?3mwF5P{`esCX70hiplGt5x^Agmb?&bT7i*jWSB^(Uscgmz31Ct!s*fy`3 zKN*Z}hhK;Ytz6T6`i)kX)B)IR6CJpv1OvKr_c+iT>4w?0V-tN9ZfwE85G*jqdeS)# zWJPp&mZc2UCXK|VMAcbOLB|}%YV*&* z;E=`x0NC!F99lRZ6j*6(dF&%KG0=IJ3$(lfp+fWT-Xi-f7iH}-Qn6L#+6MvG#IS8O zqHK5i&4ZlbOx1M9-IjhLReZhzRs2aVGz}RLvSxVOGN>i;GL9B2ynPK5e5ErCd%c{J z$Q<>XUp)&kYWo&AA}I>yKHO1QzHFnPS6c1i>)Phu zn7Q3XX+oWsL#d%ROaP0jMSN9pJLpt{eE1bP)PO1qx4+FI4+)*54&>ycWUBUh`?+Qe z2jbG@Zn=&wpS_leVcDWzRD3rcWJ_}Xuk~BTzxTha3Nk3CP=rNpw-bROooJFBmCs)V zo?*R>S3k&Q##*_FL!922I7(+y&TY}*k6}RDCxUlPf2#~r_Df>xVshwGJc9}(=4@;s zPuY8i+_}7bu}s}4!(ZfP0IZxBmtGv$!y#c|in`ET`(RK%F;}N_2IWkFamUFX%aD5> za>~i8a?Ax?f5X$Qn9q~vUC8xMl~3%+zfq!!UXGApSVR5KNuhH-4$El1Q5jyW&$p`j zR#07^L4oH-MPsAtyTLrfL@GFZG8o~)%P*|KPtJetl0;0IyC-_F+RsNG7T7`meX86- zb5%A`I?shlz`C;$lu>8B_4-_)uGgf%q6LfH((eI`<@Ros7YIe`9CRlulxW+k(j6}_ zEkq9}&osXB2fE7uK8wq!zYU@;|H2hQ9*s$g?-Tn|atOAKX|lQRgI;LKq#$jXQ=)j< z#O1r<~Ct-Mt3kE=_hkeuda(AAZb|L>L~qM)E%A{}I@=jFZ;1 z`sK02^qP|Ca4w)4lLQC$lcD}~4!(?m$LIFX>1_f+;T4}|y!Aw_ej+qVu}Ua^tpBZf z`OY!`1Cr24VdP)88^humtUI?VLl>dd{EUsW!jMYpcsF~hG!kxE76Jm-dCf6gjDhNGMsz$er1tUPw2~Jhc@2qyk^1*WH6RVh(GeppcOcYnm$09s_ zRc*!I!$ebjlPP3h0JxN68~+z^>N^Mc)`GFSJTsE!>gbkI8a-TmwJV0Ec?x?h26uc8 z(?}KJMzO*hbga^+6&ct6{lJ05&uN7esX9D23|KL#hj_ghH)&xL`rg_IlTmzzhV{F* zmJjlhJ~4%_tG&|Z)P%41fGn!TjF8mZVm?}C17$g9Nv8R!k;xynj5_Ph^{=ivxy(@| zW0^DkT4G7!v5Mh;&WVu7*PQ2!k!f`?&g3ug~jq7*Ucu?thv_hHk@sg*YVU#e>6TUd_JR1#M z^Ra7I`H{bSw^^#TbR9cwP@-0nYqMO?c;CJfz{$tsxEu+$=kjZy!ZR2W7)?vUWKH=- zRFBVZWX%0eo4r{=!>Q9|FX_#AUd5V<8OwLj{pl-yK8+T+xVMoXeP0E8%yeaD&RQF( zrasGGPTVDKc|w-Y=`Xw8(oAU)+aqGaW)bdF@XfS9VlbaR)tE*lyDX1ts{3V)3$(F* zjFo}1ujl-owQomJQCM2NSZn&F5dt^w={O2^Djb8Kx-R!h( zg?Xo$0N26v;SF)Afv0WK(>KSwu);8}&TMk?n8DhV8cqRxu@lAB^h;#ywH}@Qi$u{b z!wm3DtM|p*>4+V+6#a?~S{+cU(3#es?fdTz)=K^kZ80O0;{=aV!4ul=MoC5Q?}QYE zgP?g(y^z%e9LIO{aQ!TaH3VGE>6G}$$LRao*; z(~}TIN{@gm-EzPx#5?CFCH-Z3a%xY{|K8&OH}Y8i8<1-60+4+_t0v<6lR?&l8^vSE zmkljjL`WFVag3~S3mdZV*}x26C(ken*%usA0u|?G=KZo$F4nC{#GvVSiw|M{WB1B= ziAH~(6-J3(Qtpn%Ac^sjBJHZFQ-^H**;i(aHk$e_u_yxoQV@{*q&e@Y>cV}m2XLfgXp8xaffWgAXk`6i@ z(>eX9Z09dzoPOtMM2%6RyX5JhGB4-&#A}r%whacG>yviw(SDc%%UpTrY3+ah{DCk} z{k>jRT2U4RLp47b@9Vcvx_H4b^Tn_c0?E6N=F3EDduH6Fe4~5Ohjwd$fS++{=nhbhaFu9{b4x# zWNsWTBt+r_fywe7K@SS&B8NxU<#T@yRSGG*T~~dNjuAub{iuf zU#fOsG-UHu+)raAS#Pw?c?h7^IvR@~4mr~w#^Wbrz_`!2!Xa3HX$LfTm!x|~P zLbv%IFfPMniSAy%fRhB@7qfBFy27};qodK>(C$pj&1B}x=1ahQC%H6X+r8qG2;X86 zDb*&D1NsFp1-J;j!q@ujxfHzFD6-_^eoX3T1ht_4WJRHUoeE>sz#19+-Etk{$!LJa zMcm&>VKpW2H(WEESd8{=jC?aZMrl_}VbZBfvo}8>P5blucSby{oF}1I+;hSvkvU47 zEckQNG7}G8VNvNgIdi#9a3duWNZWdjkNi1bLHJhm!k9W;N5yvmK3A1u1^g8%I+fGS0+h+ zD-$}oOP`vO#niEa7kD;;JVf(OUfbu73y(gh)hT?wt2tFYqN?1-Jp46a7SRj2@geCU z(7HExmFn#ANrs3kAV@16A@ZuDfRp1slnKsQXhgA=bwa)I7uU*iiow^!{=sw`B<#lk z$@9N8R+=bI=Gk;*XBD-3D=e-(a$#aTX3Pht1G(}>Q{kK^oe_4=9<-I}`cQZMcx)&y zKNHV-`@Sy{IACMY$W(XySs~KfpPaiqF+kDwJ)C;p{jM7g*2Z-H&gwqA`?4Ge>4s`o z&5~mTZ@&sqOX2EbUgc~rjSE+#2ngx=@EB}79kWvr7X+Gy2;q5rWJlWqGIrKCry1u3 zjLAqL`OEsYY$P8w0K37L*=qfgBW2J=AM_Rx{V$2OgYm z^UVKYNbZC;Joth(q(=QcVW&0Ey!stLwp>sKB>;vm$%CW*f<`9zqXx-W1(G6KlUPaO zwlBy4ZF(r_?&iJ3kE4C^_q)gCfppSI+M!f7+dFUl$PS68S1b3!Y%!=z z%8uuM{UjOgVJ$xYde)x#|jG{xK5v-eezN>!MhJCS(iL)r3}fByW}2Jf@QjGu<~tj)^G8@3}AipOjaB0x+$QOL*Z% z;(p~sF$-KrvI{S4H;r_Wc;=899ytDwxSN|RW8$7(8LZ35*7cWY ztTzbI<9u#BXy1v;Buf?&xA!(Czwf?+AzRne$C~wF#})Uih3Xf^)09akd2~m(0MZ0X z%!_Gx`S(YE#xptuXWc2n@=8i93{yqpd~l(Z6E`!DD!w#8zrnVV$)zndL>e<Peq?Vl4v6NZ6A83rOREPtAY* z_5*fvU?JAMU8x`N61~(QX)Rv*(BO8F0XarGxoWRaW!e^|cu?vNNWpg}F>tU8Uu^S- zsM|M%{W1;A@+&IWfZ|JOTkZ|c@Uhiy-T?}EmYbN7o}#{$O`IOh&>Cs$av4YupHcc@ zoP5j9Zp77U+qRG6rf;{?y^(dF7?ba2M8`YYhsA#$c->iz{?v_`AoVfD3K?g~!jF$n zXP++Ar#0JDC>*YPWHyvhz002X;hbiuv!UXA9juB62|$6Si4gOJp%0w=G@0q@MEuTZoRX7?eTmy_?DT! z&5U2u`#7$jTS9Z+J8^r0n^1m)7!uduLn;6Vy9`@)r39wfEQUw%_g*U(r&Ye6twAfj z*VPqx6W*dbAkq9Zg^DB7$#qV}YEK3w$XdxC_l+ybL9%zoioHxskSik4NfiH&4XmND z+Eg_1c5uxdp6rhS19*Rj+67ojCJk*>@9<=@P&`}dJZAx(PtPL!^b)g)vc9<6f706_ zJ(QGqQNvu}rXo%28>8>!ADdCLPx(#{SykuMLfS1-R_8kv(%yHec#@I0>q-F#NN(se zTYnSB?A5Jc!X+8_f4ut5Sc|LwSV08X7od+QI^kC~fh0?_4><%qe%gR!MsHxdO z4gs74U8L1=vQDIIUENeZRZQIx)(R&$&`O?WZF#hWDVS!C{YWc$%Iw(w1)37tQS!2a zjsk!tum2S6FiK;qg4K$?slOc6EoR|CHa<6DIL|Cr?j5=-pV%5G_;IPiN$(Q zZ0(33*ER{eyoN!i5yfJJhNH+;69N34$>vnLJEm?Wx!>O7Oevw?*(?9Hha66n`sc@} z`79;HETu<}dk(qs_f~4h{a|#;O!1G1$MRT~I{e9BE8g8p)E%mGj=ln6`)<&vyh^&W zPh#)Gs4D(u|BYqrR*n*`r?KUWAO2NXS-MKrrSXTwgcc`gCrm;%GBxr&+xndITtq&| zlWKGNmF-Z>Tef_|t46-fq&I!-%#Z8nG^iy>W$~`oHyEC2uA(MfG}~U*)hA3~_AeuB zmSeH!#(Rpw3QC)&kaFF3hIR-Gz?{u8@A&{agIRo#bQ8J;zFe@TEeQVq=Ol zNfYT;NJrI|zx$PDquxJgC)oIekjnWI`%1Z@MAPFn%B@KED*YMHzzJbv;(H~`%rk;W zfiK2Lw`Ac72M;+t0lKwaBN|5Mw`MpLJP+;_n{F%S!*w*LMxQ=%57|nTP0n?YjF*q$ zoOu~C)3{@;ul3Dk+eCX?u6SC*L;AxW?)sx2`~jnzQKj)w4E5~07Z(^5Ux(tz3ATaY zR+PWb4oE0sTD(86t~^(&D^zh;Mj(szo-fC|1HPJ?C#4s&OqL#V1BcfirjoJJhwpWLSItdOzxg~Ojq!Tr3F(|&=!d_3-cqxG4fL$OvoVmt%>%vWH zFip-d1!?DQw)D3lM|#2KF>%8Hv~ahXBr7${aF4a2gQlK$K~E!MqEYA0zV8+4dPUeI ze95g{MSnUabV`3ro&6%@#!H`BBdGO5$!V=v+@CYdsyanaqXKgC{k~!5X53}3#p72y zr$?L?MA+e>o_UmW-QCn9oHs?MhI*qTUKP}ZvU>X##yoSf*suUpT9Mz&>yvgV!^>!f z8$;ZXsWldN1izHDbZTJ|eLWS1wc3#ySY$LWPv=^nAE$wh`?N7ny2szHC+U4!{tg~X zv|H)cVwC+wFzHduomZO-iuW`K=hFff<_|7M#|j=xf;Qvdc@EMh znIZ-g6CYQ8)PvKG$I8&sewT-(n&&5Yl6{tm(u7D+ zv(Fr22Ty4Y5$0oV-I%d-l~t^m>O-Apm0Ci&24ZnPH{}d~-Hr&M_{)EDb892;>t2@f zgQm}H!inj}g}jY^XwT=8V^rD$wLt|Ybw7WXs!Rlv77P+KdymY1rB94$4{;rwdm4bc z>15F0WP#+_ABN;?@^KI7(N!&`jc@bcYX|7p9bd2E0|o)P5p7Gx990G?2(zJ6N%=Ra zVoAlMnDJ*Uxu+j+Fzc?+9NHz6PGG@Th4kCXWBS`GI^dm5xd11s;e%h5`j|{~H=JIuhEN|Fc$5Vz$K7vt-#cR<7+(GNS?fvlWitxuex1G5b(iYr`lk zp?txLtTwPqv7JQyPUUu^aM`SE+_ddn!P;yQE=>~UyU5U2>FdRj{NkLjkXs-6%0d=- zQqS>$lkkU|&)oA~S?-8OawdymLBXd(8lR9HSDBj3tfQT0i;I)s{_Bcz`moQhz!w4@ zqG6xuUiA*@ZFi`XA)>kBNS26tUU%7l@SKh0b0Vtp$?Vj4_4Bde^qnv_`8=bDH3qo9 zr4D_kc%y7)P^%R9bktFr;oO*?Z6Vg)*Tg6+(-FzPF&`ud)K?i(&ntRzwdZ{mAKtHV zc2r$@6r-aOqq(|kaYzYtJ>{(t#(gEgt?>K}Q^BOm-1}6`V@yR;Rb+8>$srHKJRM6! zdWn=qfKDn?;wy!iYHLLo_KpBaz0gZMK-9t+7fy(pd75XsJpAw_4*W5yWR&>Ptd*P#_dxInp;M*kCIhOeVRbA~N2e(N} zKE-*N^4l>Yz@l3E%6nuKP$OeQ5XP@Gr$J?svzW)1XBWgx_BlT!1%;qQ!HxRDR zZ`Xv?d!5O>`WQ&=3WeS}+31iGf0s%B$O|LQj!Yr|RB?6<1=!#(zF?lJd;ZuZ2gl-0 z#>=zrE7q!oC&)9o*(xj3Ecv=IVuRpKWG6QLjH(Z)z_?Z+TgDD3(r@CJpD_swNQ7hd zAaFhOU1Xep$~qh)jt#wF!u(K@%SLy>mc>%=v#o?J!@eU&15Za3$Uq-;Ql5MB>L{Ug z2{f|DyxHuA9eGHo?jkWx@O|U>LnGJqwx`qVMjL0xFFMN z=u>LfeAUROCGz9SHA9vd&=4Be5e17~0BBDvMQhAWw>pPZrM%1E7I!Xp+PH($b3C?$ zhatHZ4JHYof%W9%9B-l_qrz-^@slmq=mnRIOoK&kYWuX7IU8ksbl3Ji8&T z8rLfy2glwQW3slTGpU?aPy8-eC&M0Xk;dZ?$`;ysFy?p9pcsytsRw-~tNhCR99N3) zXz-+Hqt7|}OZ2z^J>|}agB2f?Z*#VXc3UUtI;6kg;DWQ2w@6#ey$4D6V-N@HK2Nx! z1`0AZqyzlF<>0`cTu7ib4pL>~Dr)P?VzL)q9G-Ap4oIwuLc7hsB^dTJ>hElH$;xKE zM9+Ml*k5ZU%zKb^Qpab?Hn6G0QNQzmm#B9C0d3;B-rk0uh25=N0qIyG6a%cP=N*}{ zOpQPV%iTS#1C9mC;{jH5UeculF$x^DyHik=Y}T^Z;tXXUP$Pe;L|S+yg6>L}X_I;M zUdc!PHR>+=*A#R;-CZ7Mfak)W*ZtokpB6I_!?*7t-4K^HDA$55n*wXa z)V^1EuXQt1U*NyidH2WF88QZywkN}zq9pP~s*kr*7*v>?w<`50yrQWc#>zH+kf~PW zkXbRLE+66>PzANrYB0S@MQgY93`$xBQKQ{Dusph(WdTNf@QNMz0@m}t3n!E)=L$I} z)ny%oL@K?Bdvwmz^Oecqy1(4n(fzN#v%fn8#%v$3Q}xY&S~i&OeUmOGUn8t%)fZx( zZ5h^wXqSeXcLXf?FxhUFE&cj#wU3?5EwA8t6$!qz4w^pV`hIq2Jj$`!6~gp<=_EMv zxdFT5PhX((#THG;3Ir}eL@-!4RrEWk0zKAbWM9Ky_n?HnwH-Npz68n3xv=h$(V|<-|5_&JR7a?C+zfZCb^lH z%S%E)tJy&}1Is1sOm!SmU72-2^Wig{UE%|wefXb|>jwcN51bzhQ3f&^HI&*f!erAP z&Gr!A9cvIC`F(JK4YzoLauY<>6DtCNB&w^C8xJB8+@jDBOJroycm;^OL z&5Va5ZATL?wK9DD1%dam2%sss4IUHXPL& zRf$zih&?fN8A%Yd(vqj%I=2S zUd8SaU8gDbjMqQxc z9l7U-V28V^OT3Nj?12%NQ#Wie=MhmN<+(Igh>*PY?V;8h>DyJ)jMno%ykY7Be&5?` zk#N*joI6WXiNGc{>IQAb{Zh;KZkVDI-BN+pJ!I6{sl(ZB$Ed`u8QD6mnbEGM9VlSP z)u7_+8?xXFm1!tUPu9i4Mw<-=Pt5V!H>bMh_#~lur+kyq4*L?-xd0F`Xf<=3&WNs2IKFSXBlkuEZskUY)jkCM zv~SL^^1nlja)V%qx`AYYj-iLx+Yr^b*XtS8^LH6~_j7!XsKAOlG}O!Ec~a{u_mOB^ zajfIz>(BK2b5BqpgC%~(G6%=|hJBY7PlVaIUuLH=YPseP>+N+CRP*TvT=@6TNhJ<1 zZUYs_*h+}oFILM8IOwnn99HaoOT-@ph#oHC-d(v3(pyP*H!7g z`nT2NoNBnk9s-OCk40h~w{CgFjGPXkP|dlY3uAZ;VX|Upb`HWZG|lc#jPw0-HB zS@h)=YoG?H^pR^!HwRfIvr7)io42+%RNyE+Ie%=#XY3_Ydr_% zxEJXma=r7#Iw?z#o+47obQ^F3AE4~FtPss%GB3lo{B0nU7DNbmOVkKNO@EDaUbyIs z2iM3!tK2Yfx`{K368@W~NrYw%+d{|dCgvC%^`A^JO}ySF>aB*#JQ_c*Kwh)3&~Bs^XAT2`u#jxF-Cklg+kGnTra65X)V-n>r_UnJoDrczdDtg4@j0TQ zT+ycLx32j6; zedJJFwwu?K(F-aL!B*GxRsH)(ps{R?hlc3joE+@om=RI)eDVB3S7uC9L4mTz7ux-J zn=a4nz5{OXLp5E&k&j==K~?40NmNqIqHo~Kwr;y?bLXE+5KPq?+vP{13L?qt3M8`j z1@}%~0n=tCJY6D!)= z_BUFuK*p&=-4EAzg5n6fY~?>bp?a&=7`br}Uuh4-7C;?OTqv=P=*6v=(teyWt@l zV1NrAJgG2xh#2tRqc&?D_8gw-Cj;A`Io~P{R4;rE176gdzs;dpD_DrHDi8fK3pMZI zjv8gU*MbAnj*wKKwydE67xxMAetjTI$Z5H4XIL5XexC}Q;r3VcHA+6}tveGoupYjo z3yw|i*U-t5r9)JaqPFx7H55K+zqd3dM4s4@-c?zUVSVMaimrT+Ul#c0!3z^PWZcL& zMTr2&qymt=?6rY~ZZ7pLbbh2Q&q zzI#)j5i3J=LSbagdCf&SJN_iVxHf9pMa)ik;D8omZ-Ze~4L(XPYfMOJC}^`_0Hd_%ArRQ;uok9^ zmjN4~`umbu%j$QF2x3QF?Z5>t#f79OiqD&c2KW2;q5$UrQqHrW22y8%^vFjRwFCh; zz&sr{!bnJ$QWUF=y|Qbt9IfELsO?JHAOZ@D&Kt^p!GP~bC~$m*y{j~VoCcS~z>8`H zlv0xTi7q7X{7qf_t8a1a=q^)tA=o0bLfkH4lnV}q$MAvaeH`$&R6$uTm!~AFejx)S zc;Jcpt2;I*R3!FvNa$BXplRmZv6d|^`TCPrcVRGcy6&$6vhAiXKBf098p1-(gDv86 zaNyEisF3KOTNoE|PLBq?ByHwoSmcSU$5;M&?EK0Wze8eMaFYC=S45E+)Xd=zVX*ar zoi4IxP3UDUzVX{gqAgt?me!I7o|+N(@iF~_Ply8PiR0B1E{VNJ)sZ-|>Ck%wL_d5{ zcEm-kc$fh;m&$@6(Lnjf477uwXgkGF_6-Tj&mIh#kiz_N*IHN;`#=P6$^Y4SnDC9p z-TUM+>aW20^Kf7E*yD;EHFJM6wi*Y9hD8lp{+kzam)*+WTkgVA39tT8Z*?^jv3TDC zI7`{G&1_zZ)c%2A zC(V#N2U%$+tERes&C2YN?K?`(*s2I4#?L1nN~0bqkc)AA+){pu1)K{HB%xnZ;AFeV z5#64+yO`uxM8O9d0l>FVWrpHzDN@@y$#XIG5*%tRO!N{C%#jWo9savD>}Aoyu0Bza zmjHqum&3u1kjXE#&D$vF6~e(Xg-gWp$@}x>2abI5c$Do2cA1$tr3a-?dM8_6d)=?` z5c#n5KK%~pH~IwY#hDrpdFaTVeI)er)?A~&vA&AZHuPgb-~0^?D!ihGJJExLzacq` zX*y1`hHB0+h36>hilo=trO4|x(5GhfrO^3~7uN@5Qx{j*p1#;Q04O{1rwXbL8589d z)OB$NP+A}wLZBNq)r&f^Eu!oWrL}2akWp-gc>4*wWHvCEBTvu1x?h#ZtAwLv7qo^Q ztFVBco8?m+X>G$mEeGq@7pc`}kB-}7Lec2hmjyD-!NJr>NmpK!AGC)MG(jK2FeEs@ z8aDo?*7tpzRw!zMnFvY{>8QfQ&lY%-dXfpPcC! z_UlCOTY5MaBP5ZZ7*hE;`}o!Orxv|l4LF$XHC{{p5LpF{+X@spmzX{!#6$RcZY^VjhSt0>JTeZ8mGZ^NsD&5%!UUHB~NOr?z6g% zmd4#zjLF^MciT1eSXZw$cQ`}9dW92c8XKxdDzG!UORR%b#tCrBihf#-0 zHd`r#sX4cR#Sci78Ow}bayk9@#Mjpl+$Z-{$~v?mS)ON@cQ7J5(c+d!MfmeUFke4%u;Zq8wcuqVJ{!5|>2sX@X~lqo29J2fdjR_5C_` zsGlnGSVYyj*AoBP+weSq12K=c`>nbn%{4HK`!p?I)P5H{FxCDNU?`;L z@aEAR+nTedU)L@3pNm~;*O}Zntj9Bap8EXipWN?C7E-8kQWm)K+mX13l`9aPrey60 z(^CT{6K8zXG{^lz+G*v!_I2z%c+`-Yu2KCdyz2uHHd|?o)lg>~+x5m9u z{#$BZ!j?-1alL_-gHwL*@J2R{nj2iRQcr9( zDF-?Z7HT><0w?%JQ%3Vs7s-(vtg`seDIJ_y*@~4TKIiMdAqnyEDZ=Mb@hb=#>B$?z5ZcEE^^F5MhjSk$>{Pj-Wit#$S z-woD`dT?CMjk0?8KYne@;}z$BrrG!K>BJM|wP??5X*aMToim>L=g6co&0gVPlU~}hDw85O>gStsMAM4X66m`n_*9U+m7zq`>00!w zZOdi}lcJbQbGM{_X2!C>EjYEttL!?sPgSCs$@dUL?IcpUiMCM{o960oZLN{ne?O5Z zxA8X;;$&`eIO-(6nOPsn;S5ETEOz;_VE$P8l&Uwj@b*oQOT&6=01wK~@5kve1ZEht^s&LF`sR<| z(zM#{t+QR!NAOY9RRSz?kYt`va-w4MElpY^RPmO^c;8dir+ryn$(nF$Twtv@YdO=Pm>M;ub16e z)yf@A${J%jdwu!glV!2->xSHZgWs7;IXZV}$a-K^$50!ApGiX9t^*?wRxR!JSXSMoF7;i*spoJ99Ou z5l@aOAuzpz9JOih953O{vp`=TYy^|gKqmy+thIK3=)9i@DKz4CC+m58Hk-Sg{w@cS zNY2g!(K=9o6XMw!*l6733y5hmjOW~3{oWK;*tDvU`ZewH z&;@Ha;l^}80A%3w{36j>{8T5ajuPD#EzdZ(W`<$fyGv(dF@-5Q#goKmNDk~LbH|5a zCK?u^0+o5sjv5mO0^*!VLbeMM?o1Vkmts$Nz2~F0+aVf|u%@WyhB;B#yFzs2!A(Lr zt!rw_1m4b#@kvqBz}3n!G#G31_7CO@0xwT}KCY?5phMxoC{b}3lnzf;4Y^DF-whb( z0Plx?cWo1ygHcF&s=Ii@0U!Xcz{d=wllE5Otfc)*0n7gVfa3Tm_-Gyk()cZ~(B6I* z*h;Er=!GjR5AollG~e98SjV7{yHx*8xRDU9NYsV}0ZRbO_wgtiZv~?`{=ZC_^`T!! zA<8~S76=h->pXX@442M%-^d@qaq3+b)*YZMQns<6l>&q1H7F<#$|KRJE~?-$Z~m7O z?B6<@EIHG-RGzzL4(ab~uQ@qKeP1YvSPQSiW{%@up&tNPCEi;M44< zeNsUW9y^AzItmboLa21%!i#|Hvm44R^0PP}l*hrhil_jnAmljSKPX zZECf*=dw7SBPZZJp3LB3?;_1U8=)MuK>&jer-WMv&8=R3_%W?sw>1Fh^cnh$##U;& zxJ2}4RdjIwL#e6q0JCG#{mVhu!MA?~j0+ERZqf_n3n_wl1@^cr{2iLw^9`G^v|mH( zJtMlqya+=s6XyopP!V>D6&8AmUtOjP#D~5KMVHk{W-*_NmB!hYpU&#Z1qSg>H)(Q z#*=^$KKe355GnjKy)Bjt3BaOw0Uz4Dk0dfH0(EE?mtTNLl?zq>h4X%N2b{d`yF=1tNwKg6XOcEtJI^);Oy%e76` zT@itf{Z@aEIzO_v5m(B^+vHzK3N`}xXMIAgydWDwvuLCCqcsn*a?c6O01wpkew zhWuXqE#?3(kO8}NKo`BxT;Y)A$Fy*BUB=BjAvJnP;H9}6Gr)&si5eGh{MqTt7Xo@J zq|~oNmk&xv?i)8CXqe#bN*joLYg_-^nC1Y2aJ%KY}!=&+w^N~c4PFv;Y zWI+}gN*s-TEQGHs5L=d}4~WdyZ~-Q}!B}s{<2`3e_*zNT6}}Sr zV(r0=@_}A@#@zR}uT30Re$gjdf2}puDPu>M+~3Ikd|`TwZDw~fUH-A`p;#UD(|#oI zkpAdTEUo4C=d(2X>9p2UEF!i4BXO4k575?UWJ05DA60rnH_igI?T<7ZEE;v%|FyEP z{2o!-&TQk)R@9-?hP;aY>E+rQc~Lp#r6-p4<{dLH1$?{`8AYod+3rQu1BnP!Rb79> z{C7Yo8hZtC<@sYlEIztt3HmQ(>|5ZKVezFQhtdD3+PqCHrugwND!Cp<=C{xt)0u8bkyR$Ucv{pMwpgzy8Y|SD< zniorT+}yGnQ_@R8EWA#|?_m$grG>AY{HK@7gw&McHjMzXA^@(r3a6}(L>ZjWLgO^R`nA&$&u$%Qx1HKPlswG`uPrSLm>0P$Sxr)kP`y=Lxx%0 zkP&5X_=ocS*Bu~`6CS{e{{;I30&)BiD`o?i!A1o@AYL2*9WPtS+>dfs$@#{BVFgW9 z{%PNXk(28*``gL&RU0F$kO;q{fVP{bL&;bD0|+=gFZ91H95UM724~tdH2^_OrzN(F zViA?1xBfODe++9A;dCLesAxYt%hg_qz)>KC;2$}9<{9W0A%lx$>T0)bhcECHN0q+x zIS=rBtNdhmy#;~IF_!j;IzJb;z@{eStoFBev16}+7)KMKJ|4v_(J17*cyLqD79R&m z?&WAGeR%PK4rJ!a4Xu~YYFJB)#a&4|aN6FXMiKGn#I#7v(AIa1KO6Qo9fpvQ;dZ~2 z(c=K_^l5P2at=czWpd| zuvt@!gad#;2w>PH`tR8Xs?bsFj{mlbGZgoQmV34k5{_2eqhx-7I(dv#=5%>GV zoUolQZ+<=)`0A!UPYgnF+<#;i7d+**u561|cpK;?|{2WTPUp1;c)?sYjLs`FiKy!!`G! zfJ3^NaAlGo0*b~?pBx^ac06-1jXFK{q5tMO{DXbt&t}@ECq%v9pA93lkL9AANqUL} zP%^)1af3s;XoqQ4Wf0~Lh|ywdOpdg7J_Vd0Bo;BX>|!Zd*`4*|cJK3zl^58$I&j_B z1o8Tw1>C&3A$sHaAnVI*U;Y8PMua8S4C~|RuchzZ{Ld_W`cH&1!w@@6cdmNh z(D{%G_Dsyj{9Ikn<^&#AbW^}BH~{17ZuKwTeO}_i6qpr;eI5c?PJEN(gQ74o(PXXN z$EqRE3X`U(G1fHOEDuq`J|B_S$7r3uXM%A=Jf=AaCkDJ?&nSnE z2^}4URV=_OWzI=ZHmh~9ARK4r99aO`B;)h^wLMr&A%6vh5Z%I9!4S;%2<8^1dUcj= zpwtd+UMeItj~Bq3gLmV#BUWBx*TT?x)!RlvF_QxEdknU}Ih=1u06bh-3eFMp0RF6Ep<^k$SN1xAy-XFf7|3^#U%A;;o(_LBOqB8wrO7yX^q*C?ZTC8S$n% z_E24JaM%DI%rHUnfh1@oK0}{Pmpy^F_)k z_on`OB=F$c>BydnL2Vq~6KCIwO1oYRca*fdxhZk~v%Cw-6DeQ>VqSXSi$+4%ay0&; zwg#?_$wJ@5d$@|3uwyFh(R-ZT*g=11Gko~1e}7>fWAARrdGQp)i}TyTW_y=1W5+s3 zxAgo_Z2wjFga@NPNR0f-M=gs3CP)zVujtb<5NzhU^G?C0qMxulY3w1ZmNFLO;y;MI z_()VlBUW%5a~1$w86d?D+A?p3)UdVRL!hv(zEF5V?0+y4hPV}g0ZI&7K}n$2G_8I^l2M(I83@j9G~lUCx;@Y^hMNs z^G5}i7;Ojtr({lAeUoQMc%D!ZBS77O>eAbl>B6<;&y_*ut~ep>I#kbI%W2ZzyITla z>&!2`Udhp{D?4g`TI;8lY<-=dkk_SDg(pjvtTUJ-QHi@@-h)!=9ZQopU|RKa{k%}s z!kKJsNyENZ-q%Y_Esk7Gzfa?bU^=J@tVaO(U*mU_l#AZpzXNq?lX&|>PWJsR{j5+BFhez@9HffB!V zZhc2blZz@?xI7HMeC^i@si}-p|F*!nScB4ceib`w?tjzht`(#HmC6Chp|c*FT72Eg zvD)l6pQ&zbZ;IPfN)1TBx^c9H^mIL*0O;%R};&-h>vd?{n> zN@h;*P}|=((uY1w9c-Iz1umYYS=)OJD~{hOxL0Ki@DEyQ-Ik_h%ZoT85@-u!9zD8z zpK@p*a(m9%G)r{kSl0_$AYraYluhS#gVxw*MoOkgfnXHPmrZlPTr?CgA0GBhzjn6n zU>x-{?l^lXnf;|R7Jca1+WY09XWpPYqG-rVD%NsLjZC7PQGpRVSzK#RK--Kyb`MdS zad9jf-2_GL*}|jEG5!|mFYcP^vA)sSmXr||h_M~Bb7sNJonGVwbn5CWVl5aV&>$t0 z=^jjWf*sN`StSBp8H&MDkVF9BV6L46P|zGccB7e{YQbY9jiTX^{dg?E-zJ%{=Gn6XZ#Nood@v$=(KC}8J7h9F8e*9dC3i+m6s=q9XkoY1yVSm&dN_NmS;)`nb@Vt*?d5tqa1FcI+ z4$(t|weA<@urblYWPwL_Uv>DCeB+hmC0C#6I>GikTaoE z+T5tYIZJC^lyLo2vfO2(orA+$MfmoD{9h>)N&#C{E>c8bUB%95>em(_Yeh^~n$reY zwjF0s7-mjnj*Aih5=`lqHS~uDe9`p`&l-ZS#NAU@)WSs&bi!nJ`_k#JsQ2NT!pJR{ zY(h#BEl!Q-m!xkv@Pw*`gH(t8dSHlyo|?hY42SB>s^aFV>m_vigEfdNX4R1I)&57rtjGN!AR5; zf_)QH-Z>ZszhS<7#mBF<=2vml8!Q5)qd$6g2WQMWWljncR?oaXZ#m(0M*^JR5|$s4 z&8z2#$UFqqi)T=QP3W3A1(Y9!2;)qMn>aTK?xuh5=39{Ggb%c!F=YUu*D@%#Oq~0< z4Jn0Kc3BvB7&kUz&G}q}hd}RFBu|&mOkl;QwTbRnYT?HTtAmgpsqaOlo_7ai)4v({ zOIHafetwR?#8uIR?e~3eQUYYoyd6?F^qH9&gsL6)%qa_1b2is#VghV#>md}aqKQ*v z308lHo7Lj3m_YJA)gr1&=;gN70TT)m6+2CIq@~&iL`%#eWcIJ+%m1 z&EVT17{sDc@me)M%je~f_T)9)msH?w#YP{-QNv925tN@`5x`y8;05~QCRCM>ixuC$ zf8wCDUgA|Q#R7l%XVnDMq$E(}kZe@#?GHfPkiG-bmb()uk=dc(Ub*_K@} zbuekzQ3UrQIKa-zzf)34g(4sjqJyXhfXtQqBXRN*cbc3|f8L;sHztq}s%-x>q=^J; zaF}wgb%FWSt603fAb4w{ z&+RA2Xr`q~Iq=6vma}{lVD1_4?c=UDy+Olq_`g1AyuienIQE&LW!7SE3~zhoZnl2} zTt9z}g#tu(B<1@OVzI+}>Ch?HA%OQ9Y4jKV{mH%W{Y(Jwt06F=@H-nIm~+-ZjKcu9 zNteIu6oXzpJGkRfUBH*PBZO*0yk3LR;TRIVuY7jYI*5g*+I0ZX?s9B@+KPo$8Op8I zs5QAS_qh7Cy$OIJoa*z@BY2E5dFUws8jdweg>d@>$O3nDS$~$$cpMPkbybb(q~_^b zavGd3%R}ySVCq%$aNcAN*Q7Alv>2(yiow`!Oo-v>zikmb_;5Qg_#1X;4kbcZnZNK; zbH*oH9wr7QMtA@oJf0p4{f{&3VIms@)ldE_;X3O5Qn6IQmr%IG6(d*DzA`xzyEPSz z8jsFF`ud+0Kyp9n_hYVhx9~8U%p1vmB){4u!iN5 zEtgMiy^(xEb+=EcIa5$8YV=CXT=#sg1qPS!hU*!kR z2E)a3LVXV05OLI*k6kw+pWx!D=K1)PdJ9KmxDh zo~HtD()QU0DAZKxe?v164{n5UjG`p5U~8%xt zFazLPDA9wa2jIi%O+q?Vf$Gc2;c1;jPCu?s23F$xfosxHM#C|KDp_mghx!tISAY_<$FAsf$+dds~CSq z{5w!Lrf%PrNW}!fWa(;*8u&bV;xnfVg*lGvcT+t;x8`qHBb9! zbi7(a+^1)*NjPlJJe!v@Zznrx2&I1ewWw--5o6o4Nxw{;Jvs}xd!{bsp6RLv@ZUP1 z-?usAb8!)BYKz8LE4j@W0O_wi=EXyD#UBKvY{`8PVol?28 zfy+N}d~+UF$6?!e{!x+`eeOr?t2UQjlaj-PzaIqkon3~JzU!l#@DD2n2UHazy2@_x zNo{=6bVN$8aThdLl6c+Gc|taZR{o6Ou->exLV;wf&(<1r&4gFrErxNZ)S}hu{IS`X) zkiZz>9E|ihP&uSyc;b@#UJ2e@gaETrjfG=*QlU?bi0(67wNGYG2T2e};{;iRFAqXdli4_nNnmG8$(}BP*O1S0>Wyhqzi{$#1KR8 zda^Z6BzxiSmViLc|8>a>tefQ)!2fJrcUaI%AS;1wnNVb<5UZb;f2^c!Vf~YUZiBAR zHTtrxJXyKSX645IltrK1$FEdE-E9r{pJ~Y-l~_uNHuQj1sNxmxRu;;cG<29K1lIis zFcZ>6o#;^Y5V};nd|3LOi9Tz%6#Y7$Q{m3*e79C;a1Yeh7Veng`Th4+Klv~b!>(zy zZuu*-c=ODorZA4rWqolV4dWdkB2XuTF8^~AnpqGLXYiE}mmy?CElGg>>P2;-n8s*+ zT5Rqgv2kKt=z{yXM%?1)t7sZY{N3*nQUBM$K*4~U=@_d`0=tj6SR?kp2oC%%{7g&C9pnh(yejrvkxf91zASMY)atrv(x(Fi(EC?CRHh zDg>(~es@n(s2-2M;X%bKc+A#su@DU`!(|j^n&xXmdc6!#UQ)gK25u z2eQ%{1kJ7_O|?A_%%!=Gjxrd4*-)I|+#XUHXDH}DThu5L4#gD1RN(-Z=#Us1aTcgL z5`_nHzzu#qOreg%qz(S>hVVjG^{Qhskp3_PtjdN+nhEMDjd_cJsjI`1*QsFx36)}N zPab9M2)<4$f#7{uSF( z@aEq|I&`88rsdzInsB>A+Qj66urMHNv#`kD`=YvF8E~$2IP>=1P!}nAmk>Dwj41vC zJrrOD<#N+tncq8jtO6`Su%l;|@cfRnue|Pm9(c!*6})#U81=6mZ1nLM7K{`lfMp5T z{OfkaSIAwBgT$ArTcL%|Ji&tY13;J@&22RD0zUZUe==oo1PoqG2MfUYue#d*U%`yZ z-?m|k6$&x)x*8(Ei+B?M`cq{>u=&1IZjYCKoJ{?q%4#kYb23^15a0(I_8&_^a^<2IsK9(1b$bYj1j z?*6nx0$}{aV0VNl>Mqx|3|siy%PiPF=W1Gl^iNLQctH(&AmaPGa6&WP-YhrB=Gx;q z$mX)7@}2bRQ>0#btv(EKQm^gs)Y)#zO04pEclo%<0=xy?P zj>a^Km14$!2qJfDOu>WwQK`vxhJzhis!fSpNQ#Q|*cueka^%QO`6TW{#DVMWPx0|8 zp@EVkTbtmw(t1}Fv|Cvy9fDMLp=MAfvH8h`mBe;IA1>xD=PS01#53xiqGofg{Xqa}y+=vQsdQ|f1~*|kLL=g2 z{q#|1)av`cu?%odPi7VOKnAzV3(M%u3gyE)p7_<^&MV$vz(jv)SZC$F0AWa{N}cqo za5)Xdz$oxX@Z54~G3<5Vdw6~(!5@#w?{`$eh{1q6nRk2A@nUrEpN9(TwB*F7CcgN# zgxy6AGIk^*+4??%g~*IQ?L7&QcVefMULO#QMN4(<-M|b`mlLEy#(Zoaym>u*;Z76$ zItt3!aJrjw(qXl zp~fi?bMprHh$r1$&t&}X5V*=bk>y`1wZW@{{D1^Zwnp6I33i|ws@hp+S-fDP)<7#9 zsg+Uaz=zS@_>Hd^Y<9?Fh4fX6Ys2Yk;vx=irw?nO_Q92g&K(iZIxmkygKMd`D3P7o z$f`4ORgIdt`&;|+$|Z3rC&%$!x~yRy__Y|)tz;NE>~|jZFBP7l_O?Gwa@aDiKS_wK zo%N?M5^nWd%uxn0W4vp`)Z{s5K;ZXYm7%{_;5iEz@S4GSi&Z1tj<=^#W=0en-rsc2@Yn4EzjM-|px;C`fYS~j-F zBNR7=yLZ4(kf_r-_Hyhg^Wrh{v*OE1W|ubWC2HPW+T_~gSd6$;S6c&aj60n+C76vy zf9(04c8}X3zolxeQLLDfGPLmU7v0KJgt_FckiYH1Q?@AdW;G?x%nK6oB#^+b50?+` zHn#D-RWCs}QM~w$RnXq72UbfwOQ96M$ zoVL#bK}hU@`-{DQf&b7sBrJG$?j%dPo&$Zu zCTHjO@R!1C|99XVPl|S|#riH2sge@-vF$9;xc=zW!5D+D=}$pQ1yaQg?Z!19Zk7U# zym~>n)(4!ock(kQj^f72ysCE&yI0~^U#D0PDWjSqU<*(#v7vOj$M|0<=#8RaSWj4Y zHG;;Jm47{7@Xaz-U}$gc{e>gA7dT!jC;on1fWzq8)9A|=Uf!w&yNfngbBVkyXvBu? zkpbjYh!7}<0nD1DcjU9^{SGRj6p}Mcrs1mo3Vp4 zryjCt97fNqv(IM>tJzFCt)m9jMh%g1TnJP{KZNgNGyCoveinS^=@fIi>7Y4bleOIx zs`g3WEknhqoo#CoCqBOqnisIF+G0Z$HX2L++`p2?yvmDrf~1+dasJ`gQyxA~&PRR- z)=<`oG?X86me?7;bB%0_m)Pd06wVhYMw?c~x6;i+AKNeUC*i}@JAr)k{k)&zr&GV@ zw&TE`j@Hc?@RNQd3;Fyc%wO(n#HZ6DPS1=anPZNHyS13flg_fr)f0xUxR2VeN-R&5 z!we@4ygc`;X0yxGj|sEmBeD(}G{Q&)Fr>uIV>p2|!lVte)Lp2FP6d}~@~y}VbqCWY zZhbQsvcf{7tiuN7=5f5X#gl59I1>2P;!i&GEq(oBcfzfYn*$z-lw$NM_q;6@TfR7X zMjf)8TD+`Nvz<%7C=hI{zwfX?I}J5z*XW;!ZIL>Ejb`|ve9>j$^hCk(pLt!o(xOWk zhSyVbrhlo~OQp~t@iHri_wmAmse7C^ffBp5X1Df}?FZpK4C6Ma+b```f3-7YG0=AD z7>hsuF*}yAZ@1%~jyF+?wUvf(pd8R?v2RJqmA!h-#K!q^sZ-iGb(;g151e9Yi+}I) zao$`^gNmU8?Ki*Y1u9H$)Dk98V&6Nl{s@&{8QMo6DXzmh7+=`2^)G?EpZ8jDkqgYX zd;u$b>dwc5J6Y-dV~|eO3w>TN^^Mm>zyzsAuP_f8w*Em95EH-)K(cv}L7#75P{8tk zes>g7bv7K-kFk0@d17tA*6*%vD1aLrG95rA=z4D!KE37-5jBa^Ux6^4Z%i-6=<^tR1-Rj%g}? za*~~P@45-1$ly&?pmDc#C(p24O1%=#D-~?lmc41R%;zz})e_x2N9cZYC$}OX|1!WU zY?$$diz9T<@`*yx@F5=wpljna$?ovAt7a5lJFzfz-AyD3ngoTIc^O$&74wnY0FIqJ zr8ejBx9yehV3OcA|MY^3QHO}_Z``d3ZQ6wlIqf_z8Ht(tMfqR)EjeIK7`@BH#J37; zQl(B@~?0Rrs-e$Pnckbplb{?Z+ z1cz*?agtd&Bia#Vy*?$+UZ{S^6a2=U$C+1P^fvi`FEIAsn>rZ<1CB~1ZynHEDJ@DDs|XkLB}+<3Q0ZXfLwk&b~h zt*ls2#W_-ydU}Xf_jGA8o2jsTn){H_*V|64M&;$e^`}Sy)(!#F-HagrC7!;tlB%${ zy+|Vlfw(q-S#$Qb#Ql=;RWX*;uDkqh>7)0dd-}^)y!e|3K^Y=n!oL~os(c>QC!`PH z=og9kt>$>MMi-&eaz!Ha+GRS{2oJxb!<>VeTT$wXEG->p?^TWLy`#xUbMhbdu;Ui% z%|9qORz!o~v!I$tSnq^>4+*;L3e6D)yh|YMeeJIhoczq*@oGJz!$_llCYPy*iSbcd zn;Cie*7TKl2r=P6VUoW0iZM$xQNX-W`V@(9gE;?3&ks}~ReuFyR9ob~{SmJmTd{0R z4+-0?6_u9ZyHSkReQ=BGaKG9H#h&g;grUN%S-yv7tog`t|LfBH9UGQi+nyEq}DA-u9|j`f)^<4B(^`f6?Eue}NP2iD7o86FJii z>$GLO`Ka2fF}e2hW8-JJ1%0OtmbkQjfMj!m)=#vuUoJ?BqhJ73`@@z}dLW5kl)Y_4 zdW0@vsjY)>wd38H)Q*tb8X3%OlpfT5yjGa#p`USl>{IZz`r?i!DI&#l*OEEvk8f2P z%zL`0v!1RgYq;lvoJkLEw!PB?bB^{aDPwf9)EW4aHucMhi@Ca&_ zrdhe)PCxignCOe~`WGf|DEaR){lC2a;k<33_!ELXruqY|ZdvX}9cfP#bWMgCDDG|en2)8HnCsSUz>@CBy3w^t&TDV5kq4??cK*;JK z&NV&&bbnjv0bA?t#PG4`Vko3AOf3Ir=ZX*A!+Zi^V7*SMn1jdo(0&Gru91NnyW}a; z{@%5>#)}%}RkUYULsjOEZI*YM%Ur}EBXR}BZ{9ggHrp>ekPZLw$j=vW>zZ9ASi^dD zk&q7 zs~y)D%V=US9ijZ%$8S1eDT2b>`}yo?PC`V!+6bp?B9GMXG@G=s0bDs`=pXSW9)L6@ zo)VQ!L&Kv4jMVlfzt$HmF@npRS6JZ9L-$r3aIdyM`5=8z3J#8WSd zxP1%Kk!M=8`(d93mr`xc<7*0I)2Cw(q{Oc-Q5zcvb%J zSsi#rPAA0|vp;($th*$`*6#AyaUn2NVEQ}7g^5yM-J&PG`ldVeZ?+#+{A0feU-WGq z+CgN?Ycv&RjW{P0)SS;ULd|db zqCb*83K;yC!lC~?)qI_v+g;;Xd8Ov=kh4a)iH|Yl`KMa>leN^kwZ&lYM?8+DWKNmz zQ;Upi5I7$jZ;>hs46h2_##{fC2h2@7+X#_S68zkJ(}1ax7ndh^y3{TYqedv={>a5z zeW5{;bTA|VMAmN#rz-9Kl<2`N(}2Eb3rn;Pt6T{?!l)SOIl1=Ee+&vP#ISS}?p=bgFfK3EC;GaPKkRUH71KU@ z4LO4Ki76Btd|ae6j=I4RH3j|rSpTQ4@7XT!4pn`L7zl5Omm-m&>gF5MF5^(CR zU{_jzv<3$Obj=jyMu*-Ni_BVyoiRKJ{>dKHIt=Yt(8;&l&&*MS&(Z8p zut`kVc_;Oga_b&QNMAuftQBU$pz-Zuh zy!(XKiwRqgiV8We0@LTHoFzwG>~6yuoD-`vYrL4xL@0#g*$kD#Ru+m!^)V@Z*&P)^ zkJPyL+OfjoA0G53NSyLKk`KVpj}Ie#a}+2_y}b?y?&&}$y!%l*yXyxGUhGLB>G9wK z;&3CVy&0q0 zg%LSw*Fr3eu*1bYY!Gh1?d2~ZX^Zle(w4Q9h{NO)_kGbe7DuU!kdrgO=8aNRM=$J} zWgXjA^5NQTPwHNf0=AYN8T~*9-xy(m!~ND6^Az>RF}gcEV=t6xMmWD|WHZ~=n%cMH zvPBOtS&YWX#1nCxJb@84YK6!iEhWYcI`O$OLPx*l5^whCAyZDIV=RrPI)LaTYM6@< zHdr_87su`%D3Bf|4M8spp(emTiS7?!n;0j6zIi776rtPqt1;e~u?RIg{LLym+^jrv&2kI^s}dG`e2a;R61idsya2g0{QyU2D43&F}jcz+`@du(!AbPaAp(j;TaK&f8XppJxG3OMR z#p2ev=bRDSjC~eGdS&_Aq-aCM=-s^&5G-`piFdKaP~!L5!4%NNhlvH)MMN3OUIB{$ zRHt|MVDuZ!wPOXag@cOg?bXpv%D<*v4B?Cz5cIIQ(q7ep?n>aLg6l=hH5dZHY@G5s zsoQ+QPR_}s7~Tkb>eXEk^baR|MEIqG?r1?GdpPx&#Ag=x%JE@yzb`z`6p-so1B<75DAeAx0q!XTMoWVsDSTC+=y>33 zv2?)>mnGEAj>KeTmqJ=V)CHKkZzy>%73+RP5(tb60)cjZ=+gmm4*(%KmfebP`@db2 z!H%RTVDclkuFL3-mwEplA$XFX^~1ZnEY2HHIRZj`cnhP-zu~*NT?V@KUdJM?5vHvE!dvG#(*6>_-uo9=6ME30(4eW^K2=i}GI{4uy z$ojXtm7gj^?xyBp!@?>6!3S@j52+N@>4G=K8}MO;CwR~+ z5I6#n)fMo&Fw%WsP+B<@Dm@0&j5wbE2yay4=|m~1S9-j~fkN1GWRLOT2z(PqVYDL8 zHy@N4(W(a&V?HpB)dLZfA?e>ek4lMm(h|ArT_q~Mme)TsFAxX18>#qP7b6b#%a;dk zR=;$G2PMSnj;!9D$4DYXlu3N>TkpV!y5VySYt#Tn)ec#ETYGcx;lejyFe!$d44MT^ z3~zJre}lZ|gexxTmw7^9Ja+Ol7s42Gv4*o-)%6mH{uK<1sGeWC#fP)*Mtumtf|eV| zuv+eB9H!HuF_=aNqV8(lRRi*a`nnxBIbuOTs`~*K=JK5ZKc2qGK7)z=IsDri2GYS* z0I(h}i#`W~*YP)Dlhvr~@=DYO*~+FNqF4l~f|FHz`7DCd|KHyntw;e=R24_h23BM2 zv+-KbKRy5knpLlWbnALbOx-HXq3DbB%m!M??+1*Iv?!KpVdPgy($V*Ki?!c#cA;GI&fYvcb#^=gB16Jux@e<;&6U zk$bq%(Ryc3908>xr|jz%GNIBt7u+_Np(< zfcG2xX*+=%*0yyb1t)PVPj`Rz=H>guw1BO+p76Fo(4$KU{d)}!sENwuun$3L%7}e# zY^d}tDaXn7vF9VBQ!)?O{i^faY>wEmzXxtTeTKZ*M^;Ocb6o_Yqk&0aNTk$wo&sTK zD4Ab=Ij~iRsxm}F_tUVQ%hpS9*jun|O0!Y$L36ww#>X2is7^L)S%woY$xc^0ybV}1 zojJO2C>8O9uZv5(L0o&`StwX-=snL;&OISq#fN^{@3ilPC2yN7{_jX_3KhJpR6qy% z)aoELRM3keWU^S*gB%YoZhOn~w_F#~Y3bG^5-X{*0Ps_0mb?tAGK)b!lbBuCNpb{F zq*UW=@ymD>*ZkV1fMFl+hO_yzj~F=rdUV=9|12JduC~YfwTSFf+lzTP z;d<*l#*TVNnM)A}jxJS;fD#MMnIwt7FzJwjajus^;*xB4*szS~;cDkGW{w&~41*XI zVImZ%En$knGbzUh%(4AC)seTvtU*SF|^KElJRfC zhh6yXr1N(RM#h8SHx>+`!I z6(SzQOk|C58-cK+)NGU)aMQXDALB5tsqr4PEQ55Ug^9(=t`zLl&iA~IBQNmf{v|5S zedUUceE}Nz$ogepC+d!3M{yd%G6_DgQpo!G2+`;_ekCBfa?CKojgNiC6jN*8e0(qx zb89I{{MqPir zjr4hGUm-=Kl-Np%MWd<4*lp5JJ!zEJ4>N%Q|WkL(3ELz=oM$h01ug-%8U2k)rq~tI_c%R=gT5$?B2@yz#%U z2j5ojW7~mb?X{iDGiw%Kb9$zEePT8G?xgCB_)W_J*|gBZe~(7BtM%AEr?`0B@Mlh% z273>nZ^*dQQY^EfBkQp+%jPOM=0EiGGckEQe>SP6d^ovg>b0o5kvB1M3rXH#z#R=i ziE}(?N%)U;&xS=4;k%?|;SjhtM!F1dzC3fKeyS0XTm3h6DBd-AByu)Bhd1m)VxL{~ zXM*szj}+JUSEK$8U`bEkA`t4|^HR#wU*jL7J6}#^q?OMbJl)s|oppZX5!{yVKr@0j zxFh7xOJXxxXgnV%IOpEBI5sT595{;n#kPT6^nP|``Fhy)X;ISLdvLPJzE(rK3jk`w zqs1)_U?{kb^;7B9Q-Ln0mkMPR&i|&_X8It28vHrQTK3~K9C^+;DsY`fA(abB(9ft8m4CfhG<#gGbplgHN zadscElKxX7Xite^39%r(rof0%vPG#k7!WPxoQnEZ#PX#&tO^F^35MlkR%nBZT(2xt z?9v@etvTL`|4G!Q{#GR&!l}F*ON6fbfV=K=qHJzG5Ep!>y15(!q@b$X34G8Lfnt!2 zQn0T%X`y`vyyqgO!;KN7w9=02=VM5p-8l0xV~W{mPH=I!w(sEslQk+csg5pCD(4QE zfcv&@Q2i8w&tD@3=*FdBOygfGcJXEK;6neVqw)d^@qv}bLBg(7)VR3B+88#_BIk;_rc6Wl6)Ub(zei zn00B|ll1(_^m!`wOdp2g=Q4(!J=w{oj5wGO*}XSvVnuNG`vSsjFv8UH4M&sh%H`)O ziop0_fP2c z&_Cuq-SwRRmPh?3Q+V^eIsTD!%~s9028O&==At5FKc&vFUe3b* zNY3aZb4Sy3DwEWZegHoG#J^%b_EeB@=9`YYw$URJBXy3X*XcRUHpY?izpgw+fG`Ak zFtu{s&*B&K-_71@I|XeWS>V?@*c~zHc z{{$T{$ILL>y<#i*ts}!OJ83*t>Tu-z`a-qb(OV9`W@>?_Ox?ol{U0N#(Yj9>t?JO$ zpCuoit))KYcB71|e?~dJ6~TM&X&zVA+HM=d(oyr}=#IH}`vnVg6(YZIcrKz9Cc8h; z*;SEQcJZlc@tq;os>`ae^CMxTaWYP(6DxJY;sgy|KudqHvsmi|nQGI~n#7~8kHe6m zJl~HZb(g&@p5qn0atypzHEV$YXTEFGkk^w$W^}xO6Ko6Ex-T-3Rnk<_`mzb>$eO-m znXJXW)*b$}6f5U0=99Ye)ka(qOauDk<*TIMyRRu&;*+wRQ}{%qlv_b@vnl(&%Lltj3YMdagbbUc*5W`Hf7sy@TTscmGV! zQ>WKqEV=UHCZ^2Dp6i{z6y!!5F7?kt0v=MZ(BrWQ_d{z*%6jZj;#Zde5d+5J~`727u17YH->R0ih(Wr4O zwmyq(Dh4UeHG29T7q1V~bebJi{RW1t`HI!w^ZJv5n#UT*fPK3KB}0(t;zA)V-Sc|# zgVTV%Z?89TM)=v$CrWwnuZ3Q8!A4&V>w*E`EkC4J#aYlIuZ1D2R&mLgq*CVB^Tzl2 z@7~2c1~a&N>n8*`@zak2f^2|x2V<8x`)vfo zksRPjnHB2))zjn6Sk8k-{OSS&ANFJg0!<9lAH0#Nd$lZYCgtGGHPE#_`&Cq43AqMqE1*4KO<9)iT8db6$VkqD_G~T{?AGzk3^8E6Ax!#wKw9 zsYi<5GUwGhNRZgsJv(Bm)vG~K5Mvc)PttrC5JbZFJDnDW8>`#;tnm()7;=^%xuT*Y zxM3_P5n?B&MT%-wg1;!-DKD?$C4MdjdOE#l<}7rTQiiVD&~8Pfu#q693>XC2ly6ms zm4pPVr@;9MA-OwFPmEz%veJqyU;o&QOyj4f?gaudeG3b$5Ir{T4KTa|& z9P3GQkSoiiGm$%3D^S;Vj1}sS`1RdYQ9i5wHwHb_I^Qk>%WA^j%pID?Aj{z}JWwt? z{tIq5A0!u3WsTM(KnEj^7@)%n(oCO&)d|VL(nvTt*ds&$x<&oq2Hvbvhe09l)nSU( z?l4xU00Id&3PA*oaAE)Vy#KfBX2ZrjPKARzo#Xy<)_=d~6D~*r=8&_h3F}>ICl(?C zvHW;cDH{0V&jF5y5Yp|{ddOd!mwM^$8B3w?3)u2t8>Fjp?=xxHWwR;y)7|zPube-b z`TC!U=80%<-XBN>o3%MEKk@WA;iTv6XtK0OP zCM0Pze7572(%n1?piI+V(d%Oyehfix;zFeg1K^k#Sq|i*#yTbJx2Sr6bC4HjLdCvR z_tnl2F$O~Kd^0w#Ba6`;z;|tSekDeT464_o(4W~(SGxSNurL6UR&KkVmi6Ke_1zu4 zS*1#2e;gThA%zVtK>MEMhF&@vdyV>>NMF2zz_630qLBZ3xUn}DTBKf_>{JtT?@SYG z5n!6gwgW~>Nt$Ylw8UA)%-&S&1kxwjq4kRN1G!|r`w3{=yCm!ECxs1fcQmAh3yw7a zfck3~SsPw8lDvcUGbIvB+jqC0ZJop37fYu|#7TayDOKtp@oWy@`&vht7xX6kp|a9q z;gac>)S-DzgSYQM+kSJJV!PZERhb(}RsP$CNfK4%TCd-P+|3+l$iK|0JsFrSF^MsR>2@tVsmXK|t!4f#A+ zen~TzJOk+@&$nzzuS{sokGbVxYU^n`l6WFhT}_7K=Dj8A8>BKD_+z|86wNpJ6_Vp- zlXTDcxHuHDITci0wmkAl(^@8?*k4w`J&3%-p_=37DD?hZ$;?#Q2qT2O1_BpIV#8>K zI{~8(#x-_?oYBqq=U+7v^zy9q6~x4bF4W%?Y)hPc(`kb_;iU*ZlYRD_j!>iKk2(xg z|9XZi9B*3nYsLpTM*nH(Zs8D4?#!VeJBR4&epoGY z%5`08u7kP?LO!5FS1D5i&Y=HYEuc3s+1l7MAzRWuHW0{1B*RMD!Q%37fel*+ETG6f>anY z1>c6o>gYSSD5qCDr4j!(jumyzNA zb0C%Tp5sHqNOk!$j2;=MQKSXoLB7Zh{0c!?W+9RmF}8xS{4Da5N7O11lZj z>)LEWMh7-I_`#HJXjaPt%1i{9OVPjL5{C!pxbNhhC1l$y3f3oy>(ASm zQ$)MJ%2br}<$E+`wc8>lSTr~A)cM&22&@i;pKRu@vtc$~^p$a4;sH|GK|fT{%^KJcPRq_R&?xSlZSv#X5R0&Eu6 z2?AZ6Ut};R8xrd7X0hi_n<(8F>eVelhC2S058Ho!J|3ofhXcz(MoFOq$l?5|mLumi zB1HnU64B!-S8M6kg7W)#s(qt0vApyHA1!}s*_b4E&OF`~xhT;dBN6Ydal5E}N0Pxm z92K*fEl5oktRG-F@JoHL#%Y?=a*P$5<$T6#;PrDiI;AOLwP}JBEBF~5x>LK_nHlB$hvoQu_@^t%ai;Mqy7UJk$;1u_6EAl9$7CtTp z#F#ij=-`0LpbJRrr+?q+DpPD_*K&-X#LaAZPYGELi zbXWO7`cNm@aLD&94hBbaI`b5tPb=D#+}-Zgp8DN&l_nHKxr4f8em7@CnfY4(f- z-r5D~l<$&Y<4?%sW`M72HJn^F7O0If3vO9O%|um7>1X zf2K8JHhSUUDvs%4G|Md{mh!H4&)>0Ww_YdWMQ~MIzveP+=RbEhzPc5+7~(@rj>Oa$ z=7TW%6>qb&qi;kE=!PPMcYUvwxc@JG#ont>2JDU$W);d7fgz ze~(Z+aiRSdA^&|)BTn?{GD&`pv&cr%U*we zHJRJC)~y3`868bcC{ut?vhQhyVsN^ji&_T}(%eP9wLM|VK2!W-3(mKDEEQkj_1nw# z8Pu;(UZ37-ngYxlnEfkGC2QLBqn_y4-6DJE9cX-GCNY_fdiU-JA^6WNv-=*2Kazx2 zyk-D+FV;Owz5!Oq% zp7GIv5b!`}JyA%K&C(`S*?30hqB{j>NhCy7t9QEb7v!>oi>c(#{SKCz{}$Q6uLW?h z1GJvyH}us8x@q;>+|;^@thB8ba&;c58gL)r#=G`mI@k9)AS3wzA09xfPR7JH@><9i zDYGJ|?6S&RYo)W_wyN8LHnV9l1av>QFdqK`nTS~?)ysc6y!=#RnIKY+06oEz5nc8* zJc0P_>?se%tGwaDtlg!OL@{yk|Kn#W0MO@{!Y$UFFTmV=O7GnE{p+FJjDCgCKya!Tfx*{!$v4oB}xy1(HXhVKC|Q@=zudG;(f?~eG#tF>uI5ei5tpa zP%^OBGI-&DA0Tk`i&S2d*!`O;FuHH2{`sBnU?@ZsLezvMZ3+NZ3R)eqgb@`c^#Eb4 z33b%dnW2s2QPtZY5fpRwuk4$hlS8p+Gh}pM86QfB`HXkgR+ffJYr`Z4D zz=uEmwD`8|V5!8yM0JIspx5U19X412J2Us-DQ!W=lz2*dnA(yWm!5xa$64j2gE_JCLDr?z8p4<2L{Gb2xy06#F z+&kxV-kVN#)fz*NNJL&9TxscuSF7&PD!inbh$hM%RKh#|ad?-43@w+$ z&D3rRl#24cylKxzQ5W;E9Ymu@qLaG1F>WOd|2xzU> z7gis0fGEA8*0Z8{S#My7#0(NO*C4EHwzAt))tz_Wuh?a?d8_u?F}+C zhCxQdzHa4Tj9rGau6jhm3zj^DEzVj0l0Zf5e4^Um79wmb$neX#p6+-k1x>O6+vY3Q zT(B+W$X-``_6Q3O#Tc}~{7lNLrM`t5v~0Rz{v&v1?3Q#$)tUWnHVn{E92d@Mr&Bi( zWMyB=aa=E8+h=+khMLuOd7aC>JF|{=PN;O_DZ;3Q1?`AZ@|Vikf({2e#f*ys@^m7a zFI{hZEUQw=h`3M2RXBmqyK}w|aw#QG!w9H-boI#eY9?9XBx*eeNI{y{s@q!-s1%4_j9ibs zC0bI+{{y;BZ^7gI12yN*_=KH_Ot-FE1wb~|{(M^&YbARV<5nTU7lR55gq8m4fS(IK-KysPoZ2;ZEHj z!=bSk@_Xj)V`@2~ZMR>X8D{0y&IOp@jMc*F0Unu<3C_C+q2 z2ywS4P)hYVEM7oeKBB>yM5^)oi8X7(`L|8rz-dYA7miza*F*`)O+PC^fIb|`B0+2R zYUGEHTFhUhNGwHt6)g?q*RKDzMu~GWF@D71XPM7hC0bCFS~u{$U`Zd`IM6Vs<=Zhv zULtL#ycGhlQ|tD~fxG&uhF=<1@;*ydU&>#Ycy~YYwL}EjIqJ*PYqaf32U*_d0fBi{ zhqvA3%8~0=J}4oH9$P$rqYkLi+Y=G(%MHS^mtRL!k180Yo+$fAEIm`kg~>d~6HuKW znBg}}GRo$$E!uOEAWmC-6;(P5V&ir$O$N>abGOQ4S?ybSuZ0>GjnF@5AW`WK<@s@i z@BtmMu2fXZ1JrFs^iz-5c3+Q3?&k4m>c5eoZ+qofO~&5D4@5qS*E%EvT;AAE*v79wF7k)5xlC|ZP)j}I zy@eus)gQL40l0nZP4U=xl$6EPlx?j@?<+2{F{1bd{;w?e8qg*K5P$w@DG5^nIB_qZ zA3?LuGH*-eY%E-MLd}S)9~u{n&ZwOSsxbGaKk$fbQ=gTw)%j&J*5Jg6nxiYJJg>v3 zM!PxM{}c<}j6Qer4or{;Fz@$GJuaW*wbZwT(M0PPjJ3ZBOD*J_1FZWyH_iv_4;= zB6YA_Z+-2L^GlGJQX|M6$OO?me$Kp7wg1R;c8M(|2Mk={8SEc)uPUvL{`B~JI(yVoA|N7b-|FfjY zvAD zQT+8wXB{6e$1kU4pZ7?yPa{3^?_(dW`C;pMV8O7lcW2~+0JrQd9~$M&RKsq2)~}Dd zP5K+;KUvGAnhW2-g^u%k^GhKO>ebdUHruX9mSUsEEpM(@?kKtGXNMpAS@Ow#teWWyp zQeT_lIIhdLPHvX$!X+@{zU!N$dJQx!ZWN#Ss2U^3d|i=G!s|nZsbKb-Ue$prM^+mN zX*qTZvQM_If>$bo=u^*i(dSdfmK=XTp^PT?G?iU)E57_;cYQk_bi}jWbTLquZ z)Ji-hQKwdnBqPSenu0r%!eY|xtf=Wgqt)mMi>z?GX4=A!G>XgY-_J3t?$}(8*FURC zxJEx+>!9_5d55CrHt~$lFXC`u`}M|E4Hrg%Qb&S_VuyYK7%Tw8UW@z)WQ8xP!k7py ztW%v=sdcDkK;jxhB0n*O9zeo6VE?OF$|c@F5sYLn-?-rixB zU!Rh>gFC2^h)~AWKl#@yEqzCe`0KWW*5g@7)&scYxIv1^FVm4jP%q#X?CHGO$2Yx$!K=ct7$X9St73ott&AS*H z4xzUdQG7Y|xg-4*nAFI#k$2X6Q!hnO+~wEJq7fbUu@^c`gj|ux%4w#BnDpfU874BX z7u>bh5#zjfSl%z`aXGLMD{hKZ)^g^Y_gRn-S>p133zAF%L%2Gwi%&J15nWAyj< zk^=D3qQ!%9DF%4%+H{NTx|htv_Ms_XETm5Lh?hn5{7psi304wQ{wPklM|hW@oFGc0 zx8kKzdHoiryt}{jLApnOoD|3;79{}Asze8x3F*^=*H>~mdu)ORGSzCQQ!*&N_$j^U zWmpnzj+c)JXx8Pnupbfn+G(688AdL7r4?)yUCQ5AEv#~j_*tbVGd$3?#^oLn$vuww z)+)N@aFbv6x{d;S=TBeCw(aq+%d{HO0-3Qsea17;cW z(afVKY6V3ezjExA-miC(+09uc#8)_al0r{{W&Tj`lDai5&RRQPi%EIl zhLxDwK3upLt%KWQBDtj~i`*oDMrZi|F%N zJNlb+q1m#X*$IMh%*8AZemK z3gX@je#mSzKNK}KphjM~dIoG&a=CQ7vT$!vmuQX2BpRqF*)*!S#J*QTF>@>no64R| zwoo&EQU8=60F>bBNyM$7Pky_nLNsj}?OQyz1HGD!K#m>`A;)HjHl7-J->8ucW>Fq& zk^V=U3^NNbql6q8QSYnJH-=a!a)rFcxf+^7O@6;c_uxMKv~^lSiOzrN%y|%!194IR z3wpwl&tI&hPyC&t%VY1qDYq|a;)G+JS12<>m6C zb^1}mpKLqch|{C&N4ke;+O8&+8G(!PncZvth^X|=VW3UC&P$3tH7ZsJDM+P%KSJJ-XZOaE5&IWgldN~EQ}q@wQ-4=bK@BwiqOw6 zI_CM3%XOCd<=!^|{H-xV@Xuc}{le=ymt8>poUvNl#mh8eAEQQ~lK3BcQnpgdISb-! zsP$4qHt)RtM=*&#{R0%vla^@hPkZygG!jbtA-}>#iYdbx2%X=)2de2}vUU}j&Z))Z zt}4dNJ?N-i5~p<1FQ;ssM#n-Xp~~0(M_8YMg2`l^!d9V{_nGpZ+@#mx{}TrIj|Bdd zm6R3AMj8rcaFKQh$v8*!Fsx7FhdFEBz>M^BKA3-c4X~nv4N;m3H~7E&CS%AOGw$B-~gZc~Ux;KH~I(Ox0PSWy4WKmK(H@E}3M z1n4D>ABY7U5_K^Ll(9qz%O^+?!_0u9g8*2m;~%Ad*w_2sJ8D##s47yf8A#cTlkQD} z(d`9rAc0d_n=5#eQ{S7kd{Y7lp`l9?=u+`t5kYHk*NkY{1Wc>qLh zBj<45|Bt4BhZ=dEmg6NeNm4VEv+;RZ>yaPF<6j|YO7(&p@_wY>UYkuLBi~i~f7&(j zx+~mr4E?1>OeMqJa}n=ei`fr8mDl|23(63!nNY2NJ?7QdE%^Xv^Zb3q*YP;8pK&|9 ziU!|YB>=?SrxiXzWwLDul?UiL3NK3n#u%7kT4kpH?gKS{l?>HPx2$z0W+iEdx0m|9 zbY-QLDBzdN+xn@6f!=?=%@M5~i@g6B{CUxdGY% zh{UvuIN^@(@G5*ft>7_VeTrzHo(_f-3!#}wAnLr(i^bc6f`Ap!rb(RrxKDL-22X|? ziQoP*|fr=|e#cU)+8LqUB2K3MdF=6|f)SNiw!^sC_f#e8;*Pjr7 zWxWJhqBRl)Q}EZap!#j4VQQmt>2@J8eC(?xu7vgw2ZWSuZ{W1p33#Ias}S2t{&(~V;gP|G0+5e~mO5YrIIQ^o+u$sx)7Tbt0c zTqr~sHVlhEEYhQ%hbNE`5C{+eVPbm%d>|Afm$aa2O$dQNAyG)gY%?}$j1_Q`Dv*4^ zPDogM6L7G*B9|`zI70T93t|ZbKFK3;O(Q#1k~ zz=2EnqbwMdWNQek2_brXSTq09Xd``Q7ecP9DaD9%4lz~txe8-+Qm<0r$ zHgwGN-r7ihHLCt#x?Rn z7r~oXmzhGUT;ydxsj%_(U&}b65BfUzV&d>Kb6Q4)Q0MwH3VcLxIW|XIISZNltv|l7 zWf(xg#X8Z@7Ga%gxAR4$I82ITG*6^1}6JD^6TB0FI)RC!1jQT~`Y5{)8ab3PQ4A>jP~+;hyJ zZjlPSU!5roQdpzK`%t+bCVw2M|HzQfOq>tP7w?~ZetbyJ{((!KXlF_M`SzB3CqsIez_{4!oh#UgAe+KeI8mMx^~mf0_|@{Hz5 zk`ag2L&O>nP>{8p!Vk9jW59?ZkpkO_!0fKS+ev>cAp~a`PIzfw;TCvy^(*#@LckWI zQGus8k_@P(Ct{;!zhV`idrv6b5G@+6MUug)=_nKu0>^~Ft_Yo_6faf)n}FTA&<*SO z;HAJ~K``tJVU`9D^RE*SV^m~>FnOS*kYIqqXTwDE5aQl@qJjJJ2-Fw+Vla7Pnf`8b zquVxym*sH-McZ*&H+F1gT5i*Su=%uvI@oLa4dg0E8FhEbk%ETg@n#Z{N9;in zT{IR@)Pw+1AhakB=C~`P+EQ6r2W)63`tdElQ9`0P7?Y=!+bT;aC9=X$+oF@?n# zmyoJX^}5A`T|2dr#})K#^yfhkP@k>oN5kFu&p3j0ELPDU^0A;R8vNX?LfN*SaEJv? z8)J?Xsd&BVNoE^*2g$Nq-iX=>=LTki#~KFN`tM%I1iDRu5Bq=5S|a6b=#a1vf|~*E zyuXUN(3r3Y^516^^8J`{`-Ys2Fl|1aKYvC7)&SXgHHm!X0H*fgFEg)Gx^Q8XqG=$~ z`R<+66p)dY+BZoPBZuA+0JWEsJ-oq+4Y4t0DXbeCemE~|0pPnIzkHg^(PGO2# zTLbVJQC+1oj98wM7D(OZUUyX`vxmv?X#MFIi%Sa!9%J3w#@gC`@0>cM?!m55Ni-*QUcGk@TX>yatJnIQIyabt#N56&h!z88!w5PAG%_ib@cGjpDBu@tfV z+c2?L6yHYXh=G9L@eY_f?5rY^%wFoagrsfB-w8VMpB$1kUst+jY#3zgo4tnbn0Y=i zs;T+jT2DnR`niUCI1zpnhPOQGZXCO%HU`)b1mxl#d)7v&)LcN2T=U$95R2TS<7R(_ z01RLM#pF}|gkqI@6EX!1UYuE-@U5W_x#~>!swQOZ*=gFo;BZ*D4N-?6uBURQ6sO2o z;(d5pfTyZFtr)nC+p_R`XE8C}YEjH2-y-dy&+iahvyvpzdFZLNQ2$dPRp(m_)Y#~H zp&%)%s?vys?{2kD?MJEaAMG+^M$86GD;1wLA*bsyhn5EHELnNHRXevZn?irW&@--N z`IJ*JTH+X4;1yzl8K z9H3lZHsSv1GB{qVqr7_etS(ThoFQEB*i+L5;pn9!IB>GB&o<6%R5qm3$Pq8;)x}|h z_%oe;aYB6d{oNlE*32Z2y4JZk4rWpjZ`Qu3QD3FhfCsC_mC0X5y;<+3J^RyERl9ng zAU)zX#q3H-hZcUIEV=)z#;IQ$YTS~DBvaY*8F%MckW?pkDG;ybRzbKWUFvc3U?nF) zydBlhoo_2t%p|S(I3iTAYB6-<$tqb3x;ZtKs-c;tExJbHcDWxj=~Ln0#vj!kOYmc< z$A)+K+x`hdo)T5TBcK5mbs<9D)Yn0GRuM|i*b{e5mHD6ZvVSH89n8RK-+%@YVCYRO z=vIme*eDs|$T4;d8+ugGhdw3ABStQenws~*3OgOukj<$kqZ2tikc87>^5Jh)NJB`B*!&gYR+u*?i zCK6r*;mCzGcAG)X4ezgu#mp~wGQ&%j=`G3VV*U5jZPu!YlRzBcQJ^|L3y(E+3zaH6 zmDA?lg-%q%GmOxd;r1;I4roi}!KGRHpR&xR_SYxPPo4ud-x-*>Q1M)kszjmpcS$Ah zRf*+CL8#nWt_SB2oCOlpd@iJt2g&6_PSc$gHTjT%j`|tAAR_iAwV^XJ zsz6*ZTh_1t4bh;Uhm;Vl@JybYf{?uHUYDZ2LbUrgA5d$9TNeR7=_*Q_3{rLY`SaS$ z?Bp8vbeV%$QJx7S$a=i80`176wr$pWt5Vose&BWnPps0J8SJV=>EqSFp^wH~d2Ekiqi|xJZR$B8eY0K;j$NHS(^>v}Hv>Uy?&%k$7B)u8GUcKQZ*r_43-Dc^b|Iz=}$1bzRqU-3Ukk3p zIP+_9L=Vn?4rG}z4JsKG2Kc%sO~Eu!?|PwJG}^_hHEmOeN#vpMkLU!=#lu%+4`(aQ z-gLei{B!aB=Mi5`?Sg*C8LS*>&`IxOmDZd{#6-?N*YrF`A7f32ADhgjw4qGt@(jv; z3S$$xlpIOYXDMN*NvE{%lykKe8JCaaZ@Rz*t&O(d{#Ks5aWVz-NsyI0%J42gWkM^t8-t^n4cxgNNvU zYbOwxwWNic!7?22Z>5MNJNTjt`SX%sft&xkdIW*`DcLQqc4Np#53ULfuBJ8x9OR>>3Hj`+Qv^Yp>uPesgP88FFCR*d zPEWnstf9|C!$U%U)Se%Ebeqo!)30A{C8wB^iM28+A@UFr9`N==dXt6Rdl`A?2;lSK zjX%6)G0~$`usa>Tn#}>U_0l_5Ks^Oi=pwW;ewUg2-MZZR zKCk)=hA}an?(;2at$E>M8R@%)?~PZROZ+IU1hA-Z>QH>Q5D5hLR_L%%eo**?8tu8 z?EVgl@X`#8yqWMitgc@D=r8>k``U@tkdWJ3H`Q;K&}mBKHcJO{8&tD?@5)A(`hKd~ z*IAB6sRxP5lL zJt^)StB_y?Ci-_e-9j#1!Wk1=okZrgkTfPwdh)Apnb*z>zozx4h|d=Zj+abP)@@A2 z8>HB0sG)eQwHuap4bN%EUK5H-OX1Hqh<28>lk0dzWmo%&%(@qq($d$ELPOw2R16k$ zqe8DhjkTSJ$3q>VYs>qC#}uzAgDpEpRn?Jm@{{4Axmau0Gewx#C-n~#zDzzwn5}kb z?%b$0H==NQm%$pbH2hIXK8nHU%SAi9U4QPguHE1Pmho!h-(;BAq0%YrSByla_?7lU zuOVp)BcbtTGPKLtc4@zSBVHH0pV7zICn!kSFWtK!ZK)H{!gB_htd*(-9e>)N4gLNNW>}FLo{5XJN)}_TpGq27zYpS+;U2v)nkS`Y9^d59{zjPIWp20UHceg|_=BG4Nm(Z{~?~WwgXmEEFjLdqZysR?4 z!Z$Vn`9x$!40zD@E|sMCv9VXh2zXYX>&Q#m@3yIVTG^mn#KhFbu%Yeds=Ec-*!2Ii zu?9eOJrg4!;GHtXuu`M3J(Ep4Khou$udfeof_w zbJN%dd+L`LC(rn)KZ$mrCp&s(7{zL)KVr-KGyQ>nA$8vV3VYj+$;vgU8pCIlEoK^l zuBC*u(yv4}&JmTbwL7d??MYg9qDI~~9;lx69({Z}bA4%P>+E1FFL39mzV|8UtXe9< z5G;~15Cknno_Z2Cr81v~P|eZJIo>)yxBu=q zQ0|S_Gv16_&CVg}dfN|BZC01I0g@J^f}DGvAMj-Rc(C!e@%H{1(&=MiZz$RZ zcrZbfJRO`>->&dp>|`;FmURI{p7fFsFcFGTnpwQXs&Ca%Mipk{URI=Y%yMIP=1LIH zQ%kii-~5d3+N3Y+uHp)GMfrZg4Q6Km8GAQpK|2S)+^yfA+W$}h%mdp&qR8j*ZE>a- zKB#0{pu*`b`qmf=(P5}bx!2Ilq|5;6u5sZ4?{^CL>PI?{G>geN?#|SWZ{MP&=TSMpHAbI{_^)~X z{XG1)W?N^X|2sX8YTL-bN{<||RJ?C=&8#`{6t;go5Ww0sv0C|#K4hYQd*?Nn>wY)U zh|P|n=FcaOjrKx@IYElsey=D4srth!y3 zvT!A^u?j8mI4C2?WWh}AE4-khA=&?hK!M?6(=Vf*-*Z!~*PVSv4cZK&8JAwrA1d6L z-hcVYla`!!w>cC9f_dwdU!VClA(Xw{dv)wSmtMI5sQ*SQVEeHSdxmg4W=fpnllx@x7bhh}BjEDlt65(2Ib}rK)YlvcHS5f-mN^iz=)%^>~?%`NO?YVFY zL&|Lz;6jh;-U)BHtiY=K>-AjcfF_7 z>-%*uE5D#FmWp+{oSfEw9-W(hVAVs^^pZ5CE;7g^U5r=%~mt0hzQ5~+r=a_k@kqHn= z>HVi=J@Dnh>_u^(DG}g;pjy$*pIC1!?n-frCo{Rz2{Ve1QT3rwWhfH~RLib1jCh7@ z3^YuBK~>~S(`IFBuC&8kw)u)&QUFnJ)|9;hb0#|Q2z)ec3GJ0?e zA#qm7PM*`x)^E7>A}56{mY5APRhW|du|sZ;Z&@MSoM2~%MIb! zGS6IYln6OB5!;uI;^RsQd1+ia)DWKY0~_#|o+7sheKGs&Z)X^-uFgsd<-597H?y3t z26SB8!$F9j7FPqBEbHq=(nbst$0;-sT^ATpU44$hT-ENPB6g@Kyc{lVWvZ5C`l+fq ziVqiC%8b*wg8R+dEtBi>BIVdLTv|${%U9mioO6MSh?NI=;GNc+N0P$&gjP%&%t=t_ zYcW(sImb+5ZXSV5UI(dAkj?s0lzLMtsb1tYRHP<)Z7`#h=3c%ebHBHQP%9PVG0N0S znd8LG>U_%+l3r#;^(-38KF`}nOf>aHy;TlVlPPg`&>MafrhlD-bfZ}D-K~|gH^_6Yzof^9E06wxW?SfIyQT}!PYLaFx2GgT z2+YZtf@)356?58jXN(h!*I++RgW&}#XG8BX$UWWRbt zi>yqqF|bnuuwOk=x>@xl4K!FJ-l69<3zSn7hdY$Uc> z1sWpga;4a&(b1KwVW09B;>Pf1>XK+%qM7{jyIeMH^O_XB;+GJo2TJ7hI6$#cCQD0k z9N>BCc)qh!1ni#GyvA*QP`x)jXduUdnkAm_KwqX;gdF+56~BJ=#w0BgkZh z-oKKAd6bTi{6#s-OLoQYFKf8ftqao48B`d{j$sG43oEhOWO3p6Ew^a7%oGo^tVoYE z&BiVCP;2D*TO#HH5~vZpb!zv}bs-XG%-=(gzPAx?1{N#JECUd4Gx-=h>{~Gh{3xC) zxm%6ed(0ZJ$!*gOr9E`*){2-~l7MoW1b0D-<<5y!<@=f`8z~(d8#9@h<`|5hEjKd! zl*{$r1PiJyYw2EJI&w}%MIS=k^|8L)A$*xRkgJ$yHp_d8A<*SZq*#?t@d}v_UBvDS z<36K>#h;fHvwt!ip&zW>{!X!s^j059Y2Ch)=pkCAIWUeeW(>=dHx^}a0Z#mtTF|w3 zgEn~OFOVkOZJI75#tq$LFn~6IYp(t3Ys*ozwbK%l(_q1shl|)7QdSTN8I36&gOk@ zuF~CR8uL(_j6fQafObnL3*g;#O%3O7#v=y0zI|gDo0f`AFmf&}!~A7R*LD;wEhT!i zM79cjzTMfs7+3bvE9k*@_mS~U&4`8^CM8|bj7xdB3VBNrw7MBhF*(v=4^7|Cw|Cs@ zrL1z>;Z7E!rQgj&`t_Fqk_9a*is47g;aRA&+ny|-IF(qUk(nG<5wv8iYll1k~QF; z{+koF+z1j70c$VQz(FnTq}#@D$RI*&>l+1eEl|Yo4JKjzS5$hj^7_$U%A#1r|49u| z;Quw6q$v2CrJdyf7MzW#7TV89?Vk!&8Iqi#~aIaVDR=kda3Olti1skVgmoO|K36FtK(YrOz80B zZfk&2_!e%H!6r zF;8aSJOV{P&C$bLWo>`?J6*gnw0h?(9hs=qfX}DH;S5S__ZUyv^0w~3IZM7mMSGxP zwz&04wId)#^~b~dn7iqyxz8&XMKy?9H7G)bmLjS70XsA47DfSRyZ><~*iixz`t9OU zpUIJE;a#j_R)RaP-a6K0dVj#)>)A^Cef(Jsu?}Na^a1EF5;7k`c_)N(X#cj0!e|~l z<>&qPr!O0TwrfutjK$U7=$>|-r!eEiPb#Rx+BgvIVBG!+7*Y!PGot<-^SHxYXizZX z%_l7@+xq^=>Fw2yETjH0GmhiV3!;MBl-kje25D-GG`ta)&rC)`A zRBU5$x9)*N7drQ!Sdr<;)+5gEjN~|Qr&5TB%KgIww<``506HRR@Ds$ZMrOSng*E$) zyjjT|?&C7dvuC{zBBpJ%CF*L14uiF|CR+pe(b``N%Yv{)sj zR6mPg%n!$msNH)at*qB*ue5Nps{`T8_2lPUjfYNdPXz_L3#oP1X}5oUZ%I{q*K3KO zKO0Q+9^CUsl{0#lY;fuZJO80RQk!LG0mDxQ8s-_grYd%aUBu^Ned0B z>5)^3lukXdZl7^oNUO?(G>1#JBHZ>+k9&h_%IyvG}^X{f(bt+0G;;=8i zPEg*K%GVq|bam^u;cDvsx#H~!*vIaF77$UId2^?_^d%TpjYeFUlPc&_drX;mwQ82$ zQh{aOP5P(D;6)*d^0<$Vu3;7)PW-Z)RBK_wmd^)Tkt+iVm(k?paXuD=318o{E|Ce| z92l)1f1KSuB)xT?_~`!25VsIp!M-XLbKkvo;brMlXRZeSI=(@66cGxEIP#y8rEWXX zAi@(A5es${QMXAqUITf)dm^oBX=&Q%?WRQlrF*@_Df?4PvF}E{q&2f?MLHxbICL3a zu8Lde(f6E;MoEi})+kHg(`dQ(G;v#ociXv0oo9*FfI7h>$#l#9s^i@xP;a%aRJR!? zvX$hQX4>L6aAY{^PZ@skDkG@sm=@#g808_-GTKQBFM+x6MsI)JNah?T8=79ai=RXYTt!NoZR8s#l*@Nx+PrE04xPx;CAFjfl z!m6N;il$9&N#?bCCgqU?ZY(xY=F;MG;%xTh4dAYM>MlPZWx?1+S#U!qtxlX4vEhd+ zmm*rn)*Rgrp5a=%40bieMjP#&&{N>%c>`HUwKe9IBPCy2IdLAhhcRkr+Kww_FHLad z<8ORSGkIWyvJqduL8J7Z71f2Pj`cf^psM{TRuZ%HN`Zu`dhil6h%O&6GN-^+8Q6qefp9G~5o)8z0kBs^eEL z#+BK;p_nT_LwuU{c9VWR26}PlYI@e|pWKe%i|XJ|Zk!~y)n`K!29&=9koq<59ppL@ z6vRL()V}+^Znn}NuT+=qDOX@M(r}BIgI4Cs8WoX<+4w66EC|X# zyi;H8!E-U(pQLDgpv2Mtu0PMhoXfwr;F` z(K?jEZDDNHZwCJ?*Z45v+i*<=5rAPyc&0D{n6?gfWh5PdZ!(Q6aV>CU`# zpDe3A;Lq7s|NTR%=R1BN?%i#a4X^fh%0&zMbb){F+^@Mqrkn8Wg+E(R3bYe^uQaxH zHP%q(_AShvJ64@JzA!!hp3Bj|Yjn$puO#bI(G{j>C>BQ0#l6~QMEcm6#@ZO~S4P`Z z^fX74Irt=bo!Cyow1)Pv-WRWkymlT4RcvG>U2lRpJVxdGPp{$pA7r>`7d9&Tt4q=} zj*Rj3kI0MAg_sT-gJlbv=P%lQ$m*7T(Emd~FUjj!K@wc7WQ<_#-DpEc`f|yPbz+O6 zLp9>ai+)oz?y*9jV=4XDcR9wiDBQ8y*d$$hW-`U-06dtv|L2W~klJN1VyWkg* zK0K+T(OcWOuzVBO0%i%b59zpN8ntAPOyAdXU$0%iwM?WzR~#jxV1uwolsh{^k}cD^_x` zTMqCKVf0Dyl|Yea8jZi`cUvv38QedL6Fr@k%^Ajy?k|;sp6wCrLZ1=B;To;gJX}|l zs68LGdXjhO@x~Wj9}}>{`}99O0X4d~E(i+<#xk`Xg^^Dg@Lpm=)+c|rz47t)hmCj8 z^5l*phY0<>zueP4s1!ei&fAihxZ)z0O$n(^pLNapj`@aB~{)ye7~^oHJ@U4rv55jU7K7k)PW{qM80$UGPJoJYj`$1 zNCHS3dkq*UKn45lL)2U3Z|dvNS<u2YJZ& zWkQV+sJQ_~QlZV+#`|TgZc0)flUCi|$*iBPzGa}Z3p3yF0Byb8XEIE~w^YyCbf3#? z0N*fr*HL+QNiPglbkRo^&v7%C-<V>Gizn&R|lHaHx0eg^rft9KThG(jou$cO%OqJZ8RnUrohu8%e3(@NNnZ;=?<{=j(hABs{IPFB=-3t5HTV0LB~^FFWH zv*vb$=FN-Cd*aupY&GMaH#rXFUYz%ju*1-oc)0riNTTY$EU(LCr^Sf+qkQ1fHwn*q zzgrIt6-3Uq?=qFz)}L4NNVmgroqoLneBM88+7&H(<368JZ1=$8iOz>uyHW1H7L@9Z z1-dIU2PYomGwuHJDIqHbJVs6Qv=63CqFkQ-b$hz9DaluddyR(o0CbHkBpd&~xJ_Gs z0W}kFTG#7n@xWnKZ{hFqhhG`d_f21+jXhEQnXC6_SWt_Z!~_IJXaqhIk0O)kRA;D518qJf?6_lq&q7hQgWq_sGE zg?@C>SKGt1`CA*3oYy_>^ityw6PN5N(@ZJqOUCaB#Vw@O=sxL;UHCPLGxfL^ZTm51 zuU21|0QEge7j{b*WzBc@>oc!TyGOHid@<&(i`Vo~zb_8=Jy3t|7|FZ~-N+ep>lA-* z2ad6G$yOXE?AhW+Om(+RWmnlnNl@c=8ob|vErqZJE=|}3*Aq$dSDc7G{7cV35`7+? z`Jj}EDQKix5}1Z0d9F(WH6w?ec5Ai>4>;T#{p=?-{yM$mFq(*%#j6r$F9Hn0t2k`_ zLAekd7W{W$5x6Tq-a;K_QNf?mTr(HmqjHO{i@{MuY*!VEkAd{l_Mh-(m@$ek61o(} z2S5N~2g1@LcOV1=pOnKHOOc`GoM&1`d3n{S@h~76PEG+odC<#7G?4gLL<;hHV_LPZ(%Zn=6dsIY(+%+y-Pgz~o znk~>%stUAwu%hHXn1Zma!kf4&cQcb2aZbbuiaAv`eM@}C9j^1Zv2~TV2%d9*A4abA zF^mCrJ$q7K6)^}|q<-0QQRJb<*Or-Gyrg>_1Hk@ zZiY$5LiaSMA_XF&9Aj+0TYoSmcCTd$Z@{5wBm^+t!*_h|f?EuQdrQjecBN=tTe5dn zw;H8eIxI-p{jj{kR(&>ryQ2 znC6E&@V?kE!^fA}`(yPC3Xo5UHZ*%ywjPdjcmxcQL?F7?Z9TL=A`vdVMH!XLzM>z^ z$P5o1#GZw%$D7+Zf7x*W4G~UgMBF+AQHj2iHcz`~v&`e6G+*`Vn)nawcI>uKRWO+1 zi231Sj_9hmA6738%)rUaQ5Xnt5rshTu83-_nTRKd@YsJVLhVT4;HDG>`1+Px&0!V;MyIJ4?(3``&i;MA)&` zq!q*+o&*SK)+x>$cDE9U1~mG6M@(BF=|*(6n2@2TJ5lP`hIF&lRWhE7Ml2Gj?VyAgSFwUXBB)ju9kUv_q|?K;5#nLC9hAX-+)TcdNi#V8Sz;bB+= zbO9`OCwQ;`WH5f57a$c+@Dc`}!q)}CI4uIG0U;79Z9xDBe~Tn2$G~5RfydE&Fudh| zr9C1T*moveh7emiPzp9J8BPF!lP}UV;VZi=8mf&{HnG3f&OGQsUu&lNzHZlb$xgC2 zVD)sl{3NZtsf^0;{eCo=nA=IVWf>BlLP%;6Nhf)yH$3fWFPhv4voZe1q4pUStzjFi#30SD*Y`<@pKz0KK5&IqYf6v-Nd1_SJ z`G$l0?3J$VJnT8ia^&%j=Jq!8_K1ihsL&&Gb)X%d|L`kFvNmKZY#j{WAd_-o1n&Mb z8wM|9A3**FMo2=-wZQu+Z4m7xLuP^Z5BEnmLJKT#xipFCzq06P3k)UGgu}2{2=Nb4 zgIKFjL)yr}4emxrIA#Ov=pz(3wk_ZAzwc5;$BHOB49;iy(s{&Tof`Tmh?)7AhuySh zSyg3Z{UQuNHy`*G?%Vvo`gVWHGE8Bh&AHq3qz)bug(&fyuHxS(8tHem6)2&&d#r~v!<%7pQ5CPij8`Hf(d5)#n|+z!R3#v*>|h_ z;_LU<5CdfQgY%Z3Qc^Dz&Q2}RU4sCZFu>fSqImAQ7Q$ZQ8%6sqXvfNtJm}+A-w(i@V zQC2hZG1aTC8+zw@H}rky2iFJJxq*sZlRMjYF_`tSm#ezLIq!lCj9jC=5ysTI*0$U< zm8~{UrPUio2qEOZBgi}~BlEBonTLu|v3IE0x1(Y=7R$2LP13@Jn7--nVryi#L1YVo z>$k-?yA5AVF;2y(*jrTW+kIRN=D>?$2zP+&?$?65xKeEP4Fj)BI23pAu-Agmit#+n z3ihh_LNVm*4meKdN4r+>X)&pq_>nar$0!#}WEk-@1SVAMZ84SUf`L}L(MmN+W22N- zQS*zPbJ23!1OlkUV@3>6o&sY#dp140|Nn~}dDAA%#_h^=+O*w~jU)uu=AIRxn3L*x z&#_!JhGs&LQi5D2s+=b$l143)+}}#7lIt&GOBGJEkX)3YjU~jgi^ULJre!g5k>9}> zR;wpzeX+&YTCJk(m`bs=SPt^?sX^UOg1bbL|&9PYTkI9$&8`~0`7SM>GB`CEhP-0dZ)eJKVrNq^{QXU@o z_u<1O;P7zfL*$>>c{?1AC$Y;|J%p93G4bJWJ{{!4`QgAker?Hzwuc2DUg|OFpzm*N zN9eDN(fM>fAJ+AFIIeU$u(3X^htv6Ril@Ut?=mJ%>v}$~>*@62aCAp?+&JrLJ)RkS zJoG&*Jh}6UWBZvO?rm(AJ3@VBYz9?JHHL_ctls!4CPM}+fw6XPKUQYXb;)}2P$hGW zN6T<43VGbal170w)iTU3cQ!VuJt5!6%DwS!?A_SkZEWFF-d@G>>-*J(MP6y;XBHpH zWx4wJ@j_Ux8N2RHzB+i}{jJ6-p#{5ulAsj;A%dAFz*+$+X|x(ZO9Kdk3rIw=w#q9# z|0fnw-AWm*gG5pw3I+oTngnNpwJ4Ya6Mvfm%%n1e%shQg6kvjYxLf;iq^W&MrUMn z$@VbH9ET&z)EJm%X*dg!zPqtGIOkdrvDlP{8zH!yJgwa7l3Xq{T8yHm)MH?wy2TuF zqHnjdF)K6?dvL8WDYWFGuQ~HU2y62hQ`!h6G{-88Zv(D8KjgroQgmg+95b$^#>6z| z_}oJZj>PIpE$i?(gps@r4r#4#GbVl#f>ZGZWAZCwQq2TpBxy;40MNiZE5*k+YoX^$ zu9R9Mp#qvT&It3j?@Gz_jA548HUuQCiPUzkXCwk6q@J~5fF>j$$;4}SC6Feq0F(hH zNtzWy$dgBdC>5b(u9A!afh1-n2^U3gyel7{A3r`leS8#8Pfy~qJwHEf@q35j`PJVT zrwo7HjV?EhZ;M=BPUN?L!CH2()Mv;mn&h12GS0m2_}^qq{QUFBpPxT+`m%lGLr!`3 z_@%F>$H&*klqaYLlmvki@Y1^iC-Mek%34b(z!wi32#cCI1ze0^X7OZDypbh~w`2s$ z8KDwzn?V?%7X*!9Es!NLiv>hMR_-yafBd48d;ZA6UKzvKTX)XV zXx@xGwoW^a5#bhNE=JeF;Pld?b>S1Iu0S7c1G^ZUdK*hew{69w(1#msVCOXJ9CM3q z^vG8uI@cJy)@7%$C%y@vh4}dV{P_Iz^5)m$vv_>^@}}gI*T!^f-i_gFXh1oKT!QPV zgu2{tSB5ac@OcFCTet71IHox=y_FP77(F?9>)%+;AvOXgjFfwa==^YUxW?Hz_SU)+ zK5ZjRwDV`i_n%!Lsu^isXqF41Q3?^Za-C6Qptn0?I70D7=Px^h`#amXY;4!-6y2iN1^lt1e z#%$bTgcwtPH})1|puu`FKqTPZ*!NsNrtUH(O?PL>yTtUv&MxlH()VHgk7ls%bN>IB zF}28zf5}-sNt}7RrIkW|%GCPR4|g}Sg!m4|1Wv!TS}wbcY3F?A>m3`OM3qPA9Rg_u>Ujhr@AKE6s=X0$As5_0z-Qg0r_7!%wW^ z)ZJzE*@L^IvFb{5F*(*Ya`(H8VexIv!{@^xGNQ!ni%P8omwZ^$zPmC_CGr+zcg3wC zxHUz%&6rA)kDghRt;cv;OQd`DV+It>YDR=vA>KAtR`}<~w<|1?_#=&#Khlig-!Z%R zky`mLF}D0Tt^8LQTdYMeAP@j5;AG?sV2F@35NR?>8Ka2|SP&qA6GVAs4n;r+dbSn_ zrV4J~Wf%b;0|J>>EU~fFXBPa*f)^IoHy4(8VZp`{7uI(* zh9Qg=vdcL*=Zfmqwj4Nz^F|?;Ed`f?+b*{}r|ep2aAii2{K&3QA52go=M(~r>;>25 z92W{RoJs*x@i~l~^T>E9=lrX2_rfyWM!a>@hV=dkF5rfCkJmnbH$fmkWGI2@Q#g!Ss*7Tl4D>?Kz zvGDiJ^>!TSo*qPBeMtLHP2R8jGjU=3d;oCH)uS646* zaWSrhKvn{Az`__6d^Nc7EKyx5NivogR6&3)w1xN(y=zOJez)337L`dpcP$+@<2OV3Y& zKvyz`ED?~zN=Rg8Bv~y1s+mtTDHMWiMXJ?^YsQE4@miEo#7J&9Y~ zYANyrpfnK9q_hxJE5EIQzxr<%Qm94GxAbEnxslq|n~fGmAn)>UA(!H)xEzS`b`s_^ zT8}M{He6b+w6eLaH}u&lW;N||2qhH3^weBvYKO7erQCA2-lbt7ZC_&O;zIA2C0j1Y ze`d%Y8{w8?I6-F;Z!72xx=snZbgu9!M)eMk0w|Nm$1VA|box4oP8 z-gdoPNkV{uM}1GLXx^i{I9|6WO0V@=M* z7#-ZM*dTvNn)l3l^EyA4($3zOb90C9;s)P^b(A8wael1m!r`B3b* zH`I<4+v(o=SFv{MhWZ_67sm|t-E;nVJ`{VGVv5K}HLA%d`EJFo`MkCKS)b*Hw(@cD zud(L$vU+~SaW0>LPngb=A6E?4S|MU|A&l=;OfujFi>6P$* zXDvQN5Furg0jI@|Hc%F~u_V?BLexAKt%;DO0%?8B7ACO69IUNLeOR$pN-O7ruITqF zX7AZVQ`iK>ek0C9K3$4Gm&^Hl@|SUVr?=<+d|}pwSxksG2A)B6?~*b z)J;D%j=GQM*o&?FSC)Ln(vu#=H2+93GyThYZKPh)JmL!#Qz^kyN|0w77cLP2WX{F} zlO?bKQ7VG1Ksi$%^KvV%EP7&5o?)7vv6%D365KCP6hAsvK4$^^leHMIIrgxdxLqwU ze~n_MV)Ix%I`qvuQ;1q!Tf>GPgKbFQaV_W-VBvlZu{y``_{!{JUAQ;oKIu19QT2}OR5Vv<4#L@q&y zDU!f!BS2i^njB@7LY_hjE(ArXQny@%L=Y>z@;AmTj>ExRhcD$!T(5q}U*|6jBPsZzbelq|{OxYpfL9 zO=-y=C4?S~;+WVflB4RiIn>5dYVIuc3TsxnzXoOc>&Npkb7e^(-e56kTOnbDWTgJO z`zW$ZLJB$eiZ+t#y-Tlg%qXe18kgr$alapI<*d{&Dke|E%=z0$!?maGZ?NRdo%u^n za_hVCrS7>=+e%y6b)Aglb_)v+;MW)I`6H#&-AXYifS6oNae^3P9w5{dyRno%bMF_p zu-dm3lhH+RE{1@q`!+)mz$h0fS_r`bBVFi7#~&#}biKQLgB23x5JIde(6lD1Jk*KI z#28Z46{Ab0HWxTGL`L$0)aNVKd~~58R&OdcM2FtS;#*QF7WBR~EIz2wqg#VV1ogIJ z%xblG^c71h=={P+-;p_%=ED`)up^@~t-0Ra&abe9^KFgjS^X7Tg~AZ;*43=5Z%i(F zt!t^WwYE05dQ8qU*H2&IPS0S+NH&86fNYo4%mS2vU?OPODTO8K{U+{b<(rBzE60T) zu-M++g3Qdc3;AgR3LOdb9GN4R##hx}IssrBt<9JeEg2Y_A6GJ>w9L=LmQRdRQwC1z z(-pg(f9r15=C_>ZK}|iTl<93tZ{oMn+r<&V`H!xf_8m>{&c*(cT)859i+DtGOJ{2R z)csY~(ECWal_s(6%KGPVCH2Jd{^rDh-fvr${KjWVlG7mfc$TzN#bgNmpaX|ux3a`q zBVr8ZQ0zw5ob#CTWW}M_t*pLd-{;kyy(+ff>tlP`%DpSL+xzy2y*gC}^~jcg|9)3h zsrkxMFQYwsdkI_lCGu~qzcTrODzgs9-3}ag-yj`|9g4k3dsj^Uk~Hj@wa3|okUM34 zh7Wdkv(CNW*ZP?AABug6VslC$Z5|B#{8lRG)UQ%ZWU#dIN~N$f#m3XQ;OTrWCy%AX zpHu97@uhk1%h?rVX07t0tnqTXTuyhAF6R|{Uo zsmC|6EhF+dId&au`{N{~+v71@)Wx4wi6qoe; zl|I-EoO%|(&Sb7W*i04(mIRX_vP3-!psrSt$nQQvzP#%#anSaj11y5eG;j#8PB!CWnbd_d@DN(D*9H?qxPGVtoc?EG1%2NU)P2m zjc8MI&1Npv73CYxTXm&iEzM!UvTi=uwffeO`5`+7BGZib=);g=i@Y5P0TAhveXuEn zxcOjXOpXj@>$r*$xdtMHynssuDWn)+2vNioBB$Umzw*pUiqTO_s?riDhRAp#(5Z%y z0s+?`HDMX2EN;tw<2UgXlG}W+(Q!c|Sgyu)U9XYbnA_TrHs>`}7lI2Grzu7I={{H` zq|{1kq4efAHC$cv!ZpQFIdcTe)&DwZF>tdHLJMu=Kc5H4L$%&GYSzkhIMd}Ij8H;v zrdFx!AbS_RaL~v%pDn05O)&7;dfDrG%|>xVWjiP(8E7V>JKoMS6F*ro{c;MWUOVu- z|M)h`Yj{u}oDIJ?LP+uYd{2G#^TZ7jfVP?P0@(l%IRZ+F%m>Z|6F}}`rL`TBlTI-Z zS(ShwWnz5b&vFAxC9;&A zAbM=2s;^W`W+FSRkRXr}M392V1Y8&IR!k!$NJK$FN(43(Yaw%+DK1ejKmg9I{CAdQ zP@rI3AWo3721}IlVk__x@&E)JzXHcEoxyHi)A7-BI?w!NjqG0^@O){;Y#_;L9)#t# z%@=Ex+zLI)`E5_y?rG3+WA@=pGA%(G39ZVwC}T|J#FRg72Dlb?)}%dLHv?5 z@0oS@E)J*4i*zXVpQl5yL$McW=Zei*{DP$Au;aAf`Pg35e&_sqO^0GTP)sT%tr*5b zv0D|({holaoLwb%u7_g(u9&)icsOs~rHA`_JQTZOKXxv6D_h(8BV|iF6uUt&BSvMG zu@p0DeJFPOu~LX*&wtD=4#f_|{+WIU#Uv#&PLnaqGQlW@MXG7C*=j2$YfavsDyz6* zfzd3|nC*$M3v5q>{g#SNE_&b4phsU@B(tt$Q;UyJl}!WUR0FH)+!|B(mWn;Pj)ai<+1>Z1Jm%g! zsLc&EM$0ko(T7 zW=VFm1dICiiV2U{is!tn0t>d*kgEo{c}$!KbfS4*IK;t{zP(~{T_fUJD|#9N28S$I z4I_m$3<;xK5r(>QbB!ErY5eYrbt9=(JqgL}QVE-k>P{mL+llLH9@I7UsGPsOVy{Gx zIsYH++OY}%AP7a#9yR#?UmOM!xU>WY-8{Md7)7{{cKet4Vp w3q1|S-UVVg#L7bqu@qwEA%<8AvGPJ}0*t0kHETW|ga7~l07*qoM6N<$f{h@Kp#T5? literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-tnail@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpcode/screenshot-tnail@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3d5ef1f3306a851ee081e2c92a6eaa0e8c77d323 GIT binary patch literal 22485 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/logo.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/logo.svg new file mode 100644 index 00000000..3b84931b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/logo.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-full@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..68c0d7d2d70822dd9a600ea221ebc423b74d1f15 GIT binary patch literal 32511 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-tnail.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpconsent/screenshot-tnail.png new file mode 100644 index 0000000000000000000000000000000000000000..69b356bab66c167c7bc0fd0b15274d5ac2533c5c GIT binary patch literal 5028 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/logo.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/logo.svg new file mode 100644 index 00000000..c2fdf603 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/logo.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-full@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-full@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4de47ca7c0c5d737540c3288cff436675bd03a43 GIT binary patch literal 101377 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-tnail@2x.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/recommendations/plugins/wpvibe/screenshot-tnail@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..adf917c85aac6e1580561183a900a3c46cb1de38 GIT binary patch literal 24336 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 \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-arrow-down.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..f9bec5cb0bde4fb751899f26cb0b798fc44c4daa GIT binary patch literal 271 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoM!3-oVvX^}UQkDTeA+A8WprD|-y1Kl)ytK5m zs;a82tgN!KvZA5_$N&PMI5L2X{MNZ`0Msm0666=mARw<`U=R?WP*C46fB*UW@8AEL zDzFGBTkPrL7@~3h?gekLW(5J4i$*_kJuYnjf7HrBpf~4$)Hhqx7n8h>@rv21GCX9x zwA0>GT>PW?M;CQXr$tI~9doNJG)^-ea5rLDrew^ZHg`&j&B6eNLmNU1F8pHN7k*-` u%zP`A)xV*f) zw6wIUs;UeOfb5Ej3ZNj62P7c?nca82k`<_5xFpCgm_a~5Ucta1AU+`>zrJDq{`2?W z|9Kv44wNqOba4#PIDhrxX~6~s9+nHPANjQ|)vsr3Vw=Er$64qZuO)-R#(Rq-U)RoN zGU;6C$Y0~JV37=0SAlHExz!5$4Bv))GgNPr4BT+asg2jOr9_4&&eLQ;F=t%k!h?a= w`y5+Nyu1+F@3kQ0Bj3!BpUZW=xNnxp_i5*EcmdKI;Vst04Y#s%K!iX literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-check-gray.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-check-gray.png new file mode 100644 index 0000000000000000000000000000000000000000..45179aa9f847d2a3191a96bb2f8aa9bd7c685bbe GIT binary patch literal 886 zcmeAS@N?(olHy`uVBq!ia0vp^jzH|d!3-peN?%<9QdTO#j5qoR;*sOV(s$PK(v18%GFC( zu3f%z_41W#m#tW{V&&QutAPsEEL*i^`6{3|m|U|KWDZaS$OVfm14;qW3Wx|;8PJf` zYd~hM0;ycKY8{XPG!twHn1pBnlMlT2tpK{)x+KUim_a~5K*7Mkz`-FPK0YBKA-|xY zzPE(f!tfFom|;6+Vg@0Y*#PcM3F8vAQv{f4JHXFdHcX)x*dSv{W9 zS^v1w%tL>X!Rjr>i4yZ;AO3wk?f1PK9S{D@Y~XDSJlk^rmg@TY8hx(bz<8#a2O^|d zJXkLkDM*_$<@U_se9*?hRN%No=|WNS)h>>0``8>LuH~!U_X%m?wEEUD=ibv(maiOt zW`9hc?7<@DcHre16B8D{o;~Rc6=j0O=gaUps(3Wq``Epyk>7C-8`FyRdlwxG7=qK^ z-g)$S#_@Bv5??rOs}p7}s`0NaE_pXtn7vN#+E;n`bP0l+XkKq~pI4 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-check.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-check.png new file mode 100644 index 0000000000000000000000000000000000000000..1eaa6751e164996f0dc627bfbcf6ac95d10b06a3 GIT binary patch literal 876 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!Rs{HjxB}^d`3(j0`!nXZXUy+U zo!^x;zbj*YclN@*%=w*}^Lw%v0@*zoVDYSlJz&A^tc6`bcGmpPtohxU3%Y??vKMq` zFX#m_vgY?>E$GTx(49HI8%zQX0jlZBT+jm{fwDl;fVw~$K`OzD!ECSsh#*KW7y(TK zF>21N`wMidc}b98FoS@Af`Wm8gMUCke0)MeenCNfeM3V(5KNdbZ{Ga*3l^+fw|@Ql z4g2<=JAeM(z577$?*03J%KvQkFfcILc)B=-L>yiko|`<`K!8t0)ZEp5X`WH(YQI>s z7|Grz|Nk3DYh=dhPyhB);*-qxI&mka1*xAelohqFdwP~t*;8ZI#8r#;Yj1c{^V7fT z;{NpK7u*F;ujuG!sJ+Cr#8_|fgEwF9`wN6C{_Ax~dih;$dd8ina&_9m7j_9v`e7~W zx2^tI`sDwfYKJ>moiF#z6RWZ-iKz2^;davMU_)B{?`wukxd%R%{QtC|Wk&K&w@>je z8$Y;8PFd3MNc)f@>k_7KGBRgK8tmHU`B3*i-| zcvb!A+!lSdSIhc19X{!4EjXXc8O7jSd%NHav!NeKBEDWCbJQ4Gzdr`LvjJF_SAL;LR%PQNub8y-CRFfHKq zk=@;`)yK}AJyf*%c*lnam3M3Rmfv|K!QlUsNoM!NZF|iv#J@2lytw)>bD9zS!tJa6Kcht)4mZaVaF^@qis zd5`{x|6r5~PTGA+iQkp literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-click.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-click.png new file mode 100644 index 0000000000000000000000000000000000000000..737017592450ebfb0ceedd5db5e761dfd46f5973 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX3?zBp#Z3TG)d4;su0VSJ{Q2wGuU`%V>zA)z zzjW!UrK=!d{jyc-maksDV#VrZ%T_L1xdte{eEHgCKsG8@2UG);1quQcfEYlLRcjA5(*0H>l+&S`+-i|zkmPv^XKp1zyJRI`=xJ-rvUYA^mK6y(Kvtg z@@=6e1(DW7U+Woj*Y;d`)9`Wsf8jF4l>#RBwxuVu{nU%7He)$>!0UYFi|Y~sb#ct+ z>z{qp?^8A6S+=sBb%N}|qR*lNTr=KGnX^MOf%S^n;xAcdx+i00rOdoHvrMZCbC_FM z`QP%okOrfkEnnWO-A^LzE0@H?Y>CZ{nNu1&U&iKr#g~l5p)YFwmd~!)8Y-cD^VXc% u%G$bdGw#IPzoxxOO|Em3wxRT=kN3{;S$5u9_oxNvcm_{bKbLh*2~7ZuYs)JD literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-email.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-email.png new file mode 100644 index 0000000000000000000000000000000000000000..e12acc1cd6799703123d472df2f752cca80aaf12 GIT binary patch literal 585 zcmeAS@N?(olHy`uVBq!ia0vp^u0ZU-!3-p;=XtFFQc(duAt1qq`Rf~|uLqL#v(`5N z5s*6@OwL)~IB#Ra-1Uv~H`dPuih$S+^EWiiTi-Bu1A;LRC<5Vv84&Kg4NHu2E&xrJ zEeY}qW)M(NFfcH1a0rM`$S$w`7JwMnL|zv&E4w&nwR1V3M(#vaEmhv8k_apFR*E zn>Xnk&^vcD)-rhAS(C-C-66H$zMmuOx?8c2+-Ba(J-vJB3t@&3%?WOdPs$!03Tlfz zf3)mO<&WmXDd7yQWl^ce%cms8^r@Elylg9P6g~N-eIR77k|8c zm7rwfX2~0$OcI2O{xM1m* z!`JVW{pM!oi^<&wExDqtHZJDoxMu2e_A#Tyu^e)0732k&_#aCL^mLq?g`QkJ`tMC1-EP3sTdBXwEKZ{Y_9zr|`xKB(<~ z?8U{Ykp0l5OZ!~rzq$Kd4q9C}^it%?&6wDwB3Djt=VEkS(ELeUql&%j*2`tP4zK;m zW*t*8Kl0L1Bc{sD&!_!a{p9%d$@402daZo?`cwT&@eo<#={~%FJb`=q6m9?j literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-error.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-error.png new file mode 100644 index 0000000000000000000000000000000000000000..22911743de0a49394b0cd413a64eeccaab87148a GIT binary patch literal 815 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!W(N3#xB}_(1_tL19L_s9oHwvI zZ((uW!s5KC)fH3I%O)0=Ei7)Bm|rqByJlv2-OS>Osl_#O^DE|-KoZEk3KRjdP0g=> zkR_0P)!gE`xy4nWGBXeZq}~jO%&(eR0!;v_zXlWm30ht?1!)8_zzTp`%t6QuC;~(v zT@SX0XaF5&R1)MD%pf2jprD{&VBp~39}p0qkY7+x-_YMbVZwxY^XAW=zhJ?-_3Jn6 z+kfu-`SbVgzkC1w{lBVfYp*ddFbR9QIEF+VUON45(k%m?R({p5)3!yMt=YO^qvLMV z#R*(5{>rZt*PLCiay+l}c;`Oh=#8zs8ihB{*5}I_Z@&HQvp?^uM_KPIxVGnbzvTXX zCTYj{v(*J@Y3c5|`%*q6ibU>tS!wd?+1vH*F8YaHqH@3a_W|H2gVrX~jRQmZ@`|r)K?n&AdEMNT_oCGk1m^Cv%KXeLj;L;dD{) z_-CakmnP?F>FhNcSz=u0b1oG2I-e5JDsMjI*JSM&1sJt5(%zwkT$ug{sc!{V}*?WybXdv|RKTo$@( z&iePg@i*5yF1z&K$=0f9eHQ<$kBesKy!!DwW&SnGKXV_&^7RLAjJzZIX!(3m-{42- zRfmo8=G@;JYOCL8>+7!9mo``WFMn`BxoLK#((;en|98*6@wntxeSUW#!-3;@7xUX* j|9|%{x_sr+y$lo19Gc_Ps5J$cxEMTL{an^LB{Ts50)u@c literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-open.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/icon-open.png new file mode 100644 index 0000000000000000000000000000000000000000..9f68a8a9f2c8bebff74697c6979d106338c10e86 GIT binary patch literal 427 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/wp-mail-smtp-logo.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/email/wp-mail-smtp-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2e716dad1a29ac329659b82a57b315917d24e548 GIT binary patch literal 7831 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 literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/icon-note.png b/web/app/plugins/wp-mail-smtp-pro/assets/images/reports/icon-note.png new file mode 100644 index 0000000000000000000000000000000000000000..f3f324276bcfb4f7e6d69f4924c38fdbcda00552 GIT binary patch literal 1233 zcmV;?1TOoDP)~oQGLWjezs}e4&$745(8AHz-RkD5$-lD3%=P&B`uqFq@bcuJY~bnZ*5Tya z<>%4e;Fqwy@AUQV^zzKv-OSkCtIEIe&$j8Wh3c<=z0K6AzsT_P^`5oC=I!mr)!O#m z$%&@6`|swIuDjdh=*-{Q@5iYzKG>cB001F$QchC~WdtsK000AcNklc@GIIJjp5bo|c71IU)+pIJ#m$L%lI~9o2=+p|b zccom9IOLB%x@E(|n3nO#vzw5PdC2l(#46*T1q=LGLheVX+R75n{>&p!G9R99hFJOZ zua$f<@Z!ujA{6nTjZ{rolJb&x2#&M4FZi2AdsmqtIx7qBkQB=RyCtpg}M zus|^OvbmHsst^WK>W3bNX-FJX!&G`ofQ$4ysyH|Xq!v_VYdt(;N`*vqe2>BvEU`KM z_s_Kv{WOL;4{iNgfzX=ip0^|c!#L`Jpe0eYU{O}CfnX@mePxgC0>StgF zmdrewUoD~zsyu@n{s>bbE@z+WR~@dU`dwIZMfR%#wGri4i|yuEaHILJe+c3e|8(Uy()_R_c=kU*{nh;V z<;_q1G4p?xz2F?R{po&jjl=98vS00=CSre1a{0$%f0Fk9^)JD-x8U!0!KJt0-%mlj zy)N$kMW>H?!CM;z>VHBZt2tEV&ul;#t?tD=6c?NKhbr#=GhX?RP@Jpj5!)Xzp?>5M z3FO-`3vSB`9E~BU<%8*PNW4G$>iD<^2uS`3=J=8}tQga)<1iF;ApVf`L(=cqHQ^7Z zSiKK&_K~zz072hmXdD?Z!09Db`sa-i>hl0Sz%W5D>!;8C1U7Nm0X4uMrsxMg+{1p; zdxsCmN*DwX<7BZ;_wgUdR6<{`%s7A~eQy^Vz${P%qd}zHQz6&&B7oFog&$na7N`%P znqCGR5I$s~x$WJtg%2>CRA~d&90@f!fEgVVl4*4eX2tZ9w~T`8n7Z8Q^1BM2|Guw( z!5NX-UZ>Jps~b++WotUR{uUaX<-u5@EyK9}1OO3$X8Wb4N4(4Mywdc=25fI>`r-qW zUSSmC+cD~!FT z2{1|yFbfQ@o&$0PGK<_U7Uu0TURu0`}3tAP6vjzVFSSU~@<>ay|00000NkvXXu0mjfiJ*=n literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/test-success/illustration-lite.svg b/web/app/plugins/wp-mail-smtp-pro/assets/images/test-success/illustration-lite.svg new file mode 100644 index 00000000..0122b5e8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/images/test-success/illustration-lite.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/images/wp-spinner.gif b/web/app/plugins/wp-mail-smtp-pro/assets/images/wp-spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..f4087c969ae0ca682bb03f748667b5e86d47bb78 GIT binary patch literal 3656 zcmbuCdr(t%7RT?sx#1zN5K>+OB_Rm`BNJY$u+RyRK!AWjbc<~0MvaIRvjJH_m+k}- zk*9>`@~V~~C_$7&aY2;jEfH{4tm1<%4@D@|h0^X|+Zp$UTI)C$%?QyN;9CHgn3y?_~}<3yHRh177BphxiB}yvS|He!f9o5WaP3#lB^EF>J8KG#Vrn(}IqK#b%uaJJM z37Hp06hU>y)r!$fv)b@F)$ZIQk8^Q*)kQ68xH=8D`cc%xX0d41RWe0Eb)Eu~lk5r<+k*x3PZ(JBUo&gsLP`Tdubug-ESPhCtP#{e!brFu_i8wd(lzoINC( z0TLZ*%aS#It~Djp{iKN=<==**pT){5uSRocRq6J)&=onV4phYSS^wKje5<4NV$PG27ObQVusCXYd-%Q1)hulJXrB34a*SH(2v zFCmSh9ywNjQ6vpyj?0RSNXRRw%D8J&CF$6J3)6d45|nW`F6|K5dJpe*DeB0Eutf6ImXgt zXz!lT|5AvSaq0HmI8bOR!WHEFD-AzQhI^T%O8?5?sdz*Pzi+%D)S^|$FF%l3hX~Po z;=V>>p&O#D)@#TG=|(U>*kl_Ja0~uJUy!?cVK+%SkwALN(56*{PJo$fA8*@kgcH_;j;7+G_^uWp zz7zItsF6@QQ^G6}z%DPxqFf4yM$k>djQ^?Azp)WhFID#L`}C=0uoOSwWM_Z_!z~M7 zbt9JAft@Fw1k5As=Ro7TM9fdX+(0Q07@hyB9T(krKw1LKR$O zb5~ZsFMT=;k!GZ8i~boLnDR8f2AB%0w~pAn^BWS9lWPXydc{D&#yP{0r5MY?FYcpH zb)EcpfpnG3)8;O7vhT_+9>a`3HQUHU&8dNk9VpC2@b;w@CcV~h}Kf;y1^>_F|p8{#`?imFM6TT z9BW{u2opGcR5lEzdR3*+BKCbV-Dw!3J|C8TL*!S%o^c_5hUInk*mu2yzT7EOH4 zdvIjV_PB|DLDDA!WJW{5lj>~4)caXK@WR0S!5!sv>(8&N6LQFnY}BQM8tz-3DZObz zFd0~|9H~B%^QY2H@K-lfAmHI$$j~IIk?Z!5R>jc#b11IOl6dAtnf(#EAS7{(YYH`d zJU~`Y%?P35xR2~5olh>`P9g+0KAZGA>UKKi0p2}M`YfLGB`F+{>TpMSk%Ap&GqTaMJJ>NqUpcqV^s?m#NVtgf>n3R{X zP)%eZIj@H2Q>O5gXOZ^i%a?^cWdDlArC+W>?CMKx?Kh1OX1a@!t4&ddKlq*fgMSas ufMTbBT0biW$7^dI+b*S^pIFVwE!~0`(sn?AWsj!D6((l79DTE5fBPRr)lDP- literal 0 HcmV?d00001 diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/connect.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/connect.js new file mode 100644 index 00000000..938c2782 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/connect.js @@ -0,0 +1,178 @@ +/* globals wp_mail_smtp_connect */ + +/** + * Connect functionality - Upgrade plugin from Lite to Pro version. + * + * @since 2.6.0 + */ + +'use strict'; + +var WPMailSMTPConnect = window.WPMailSMTPConnect || ( function( document, window, $ ) { + + /** + * Elements reference. + * + * @since 2.6.0 + * + * @type {object} + */ + var el = { + $connectBtn: $( '#wp-mail-smtp-setting-upgrade-license-button' ), + $connectKey: $( '#wp-mail-smtp-setting-upgrade-license-key' ) + }; + + /** + * Public functions and properties. + * + * @since 2.6.0 + * + * @type {object} + */ + var app = { + + /** + * Start the engine. + * + * @since 2.6.0 + */ + init: function() { + + $( app.ready ); + }, + + /** + * Document ready. + * + * @since 2.6.0 + */ + ready: function() { + + app.events(); + }, + + /** + * Register JS events. + * + * @since 2.6.0 + */ + events: function() { + + app.connectBtnClick(); + }, + + /** + * Register connect button event. + * + * @since 2.6.0 + */ + connectBtnClick: function() { + + el.$connectBtn.on( 'click', function() { + app.gotoUpgradeUrl(); + } ); + }, + + /** + * Get the alert arguments in case of Pro already installed. + * + * @since 2.6.0 + * + * @param {object} res Ajax query result object. + * + * @returns {object} Alert arguments. + */ + proAlreadyInstalled: function( res ) { + + return { + title: wp_mail_smtp_connect.text.almost_done, + content: res.data.message, + icon: '">
    "+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( '

    ' ); + } else { + + // Generic error. + $plugin.find( '.actions' ).append( '
    ' + res.data + '
    ' ); + } + + $btn.html( wp_mail_smtp_about.plugin_download_btn ); + } + + if ( ! isInstallSuccessful ) { + $btn.removeClass( 'disabled' ); + } + $btn.removeClass( 'loading' ); + + // Automatically clear plugin messages after 3 seconds. + setTimeout( function() { + $( '.plugin-item .msg' ).remove(); + }, 3000 ); + + } ).fail( function( xhr ) { + console.log( xhr.responseText ); + } ); + } ); + } + }; + + // Provide access to public functions/properties. + return app; +}( document, window, jQuery ) ); + +// Initialize. +WPMailSMTP.Admin.About.init(); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-about.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-about.min.js new file mode 100644 index 00000000..f62dfff7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-about.min.js @@ -0,0 +1 @@ +var WPMailSMTP=window.WPMailSMTP||{};WPMailSMTP.Admin=WPMailSMTP.Admin||{},WPMailSMTP.Admin.About=WPMailSMTP.Admin.About||((a,t,u)=>{var i={init:function(){u(i.ready)},ready:function(){i.pageHolder=u(".wp-mail-smtp-page-about"),i.bindActions(),u(".wp-mail-smtp-page").trigger("WPMailSMTP.Admin.About.ready")},bindActions:function(){jQuery(".wp-mail-smtp-admin-about-plugins .plugin-item .details").matchHeight(),u(a).on("click",".wp-mail-smtp-admin-about-plugins .plugin-item .action-button .button",function(a){a.preventDefault();var i=u(this);if(i.hasClass("disabled")||i.hasClass("loading"))return!1;var s,n,l,e=i.closest(".plugin-item"),a=i.attr("data-plugin");if(i.addClass("loading disabled"),i.text(wp_mail_smtp_about.plugin_processing),i.hasClass("status-inactive"))s="about_plugin_activate",n="status-active button button-secondary disabled";else{if(!i.hasClass("status-download"))return i.hasClass("status-open")?(t.open(i.attr("href"),"_blank").focus(),i.removeClass("loading disabled"),void i.text(wp_mail_smtp_about.plugin_visit)):void 0;s="about_plugin_install",n="status-active button disabled"}var o=wp_mail_smtp_about.plugin_active,d=wp_mail_smtp_about.plugin_activated,a={action:"wp_mail_smtp_ajax",task:s,nonce:wp_mail_smtp_about.nonce,plugin:a};u.post(wp_mail_smtp_about.ajax_url,a,function(a){var t;a.success?(t=!0,"about_plugin_install"===s?(i.attr("data-plugin",a.data.basename),l=a.data.msg,a.data.is_activated||(n="button",o=wp_mail_smtp_about.plugin_inactive,d=wp_mail_smtp_about.plugin_activate)):l=a.data,e.find(".actions").append('
    '+l+"
    "),e.find("span.status-label").removeClass("status-active status-inactive status-download").addClass(n).removeClass("button button-primary button-secondary disabled").text(o),i.removeClass("status-active status-inactive status-download").removeClass("button button-primary button-secondary disabled").addClass(n).html(d)):(t=!1,a.hasOwnProperty("data")&&a.data.hasOwnProperty(0)&&a.data[0].hasOwnProperty("code")?e.find(".actions").append('
    '+wp_mail_smtp_about.plugin_install_error+"
    "):e.find(".actions").append('
    '+a.data+"
    "),i.html(wp_mail_smtp_about.plugin_download_btn)),t||i.removeClass("disabled"),i.removeClass("loading"),setTimeout(function(){u(".plugin-item .msg").remove()},3e3)}).fail(function(a){console.log(a.responseText)})})}};return i})(document,window,jQuery),WPMailSMTP.Admin.About.init(); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.js new file mode 100644 index 00000000..1d6227f1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.js @@ -0,0 +1,174 @@ +/* global wp_mail_smtp_activelayer_wc */ + +'use strict'; + +( function( $ ) { + + const l10n = wp_mail_smtp_activelayer_wc; + + const WPMailSMTPActiveLayerWC = { + + /** + * Bind the section controls. + * + * @since 4.9.0 + */ + init: function() { + + $( document ) + .on( 'click', '.wp-mail-smtp-activelayer-button', WPMailSMTPActiveLayerWC.onClick ) + .on( 'click', '.wp-mail-smtp-activelayer-wc-dismiss', WPMailSMTPActiveLayerWC.onDismiss ); + }, + + /** + * Handle a click on the section button based on its data-action. + * + * @since 4.9.0 + * + * @param {Event} e Click event. + */ + onClick: function( e ) { + + e.preventDefault(); + + const $btn = $( this ), + action = $btn.attr( 'data-action' ); + + if ( $btn.hasClass( 'disabled' ) ) { + return; + } + + if ( action === 'goto-url' ) { + window.open( $btn.attr( 'data-url' ), '_blank', 'noopener' ); + return; + } + + if ( action === 'goto-settings' ) { + window.location.href = $btn.attr( 'data-url' ); + return; + } + + if ( action === 'install' ) { + WPMailSMTPActiveLayerWC.run( $btn, 'about_plugin_install', l10n.download_url, l10n.installing ); + } else if ( action === 'activate' ) { + WPMailSMTPActiveLayerWC.run( $btn, 'about_plugin_activate', l10n.plugin, l10n.activating ); + } + }, + + /** + * Run an install or activate task through the shared plugin dispatcher. + * + * @since 4.9.0 + * + * @param {jQuery} $btn The section button. + * @param {string} task The dispatcher task name. + * @param {string} plugin The download URL (install) or basename (activate). + * @param {string} processingText Button text while the request runs. + */ + run: function( $btn, task, plugin, processingText ) { + + $btn.addClass( 'disabled' ).text( processingText ); + + $.post( + l10n.ajax_url, + { + action: 'wp_mail_smtp_ajax', + task: task, + plugin: plugin, + nonce: l10n.nonce + } + ) + .done( function( res ) { + + if ( task === 'about_plugin_install' ) { + + if ( res && res.success && res.data && res.data.is_activated ) { + WPMailSMTPActiveLayerWC.toSettings( $btn ); + return; + } + + // Installed but not auto-activated: try activating. + if ( res && res.success ) { + WPMailSMTPActiveLayerWC.run( $btn, 'about_plugin_activate', l10n.plugin, l10n.activating ); + return; + } + + WPMailSMTPActiveLayerWC.fail( $btn, task ); + return; + } + + if ( res && res.success ) { + WPMailSMTPActiveLayerWC.toSettings( $btn ); + return; + } + + WPMailSMTPActiveLayerWC.fail( $btn, task ); + } ) + .fail( function() { + + WPMailSMTPActiveLayerWC.fail( $btn, task ); + } ); + }, + + /** + * Turn the button into the "Connect Your Free Account" CTA. + * + * @since 4.9.0 + * + * @param {jQuery} $btn The section button. + */ + toSettings: function( $btn ) { + + $btn + .removeClass( 'disabled' ) + .attr( 'data-action', 'goto-settings' ) + .attr( 'data-url', l10n.settings_url ) + .text( l10n.goto_settings ); + }, + + /** + * Show an inline error and fall back to the WordPress.org link. + * + * @since 4.9.0 + * + * @param {jQuery} $btn The section button. + * @param {string} task The dispatcher task that failed. + */ + fail: function( $btn, task ) { + + const msg = task === 'about_plugin_install' ? l10n.error_install : l10n.error_activate; + + $btn + .removeClass( 'disabled' ) + .attr( 'data-action', 'goto-url' ) + .attr( 'data-url', l10n.wporg_url ) + .text( l10n.get_activelayer ); + + if ( ! $btn.siblings( '.wp-mail-smtp-activelayer-error' ).length ) { + $btn.after( '

    ' + msg + '

    ' ); + } + }, + + /** + * Dismiss the section for the current user. + * + * @since 4.9.0 + */ + onDismiss: function() { + + $.post( + l10n.ajax_url, + { + action: 'wp_mail_smtp_activelayer_wc_dismiss', + nonce: l10n.nonce + } + ); + + // Remove optimistically; persistence failures only resurface the section on reload. + $( this ).closest( '.wpms-activelayer-wc' ).remove(); + } + }; + + $( WPMailSMTPActiveLayerWC.init ); + +}( jQuery ) ); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.min.js new file mode 100644 index 00000000..4e00d7d9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-activelayer-wc.min.js @@ -0,0 +1 @@ +(l=>{let s=wp_mail_smtp_activelayer_wc,e={init:function(){l(document).on("click",".wp-mail-smtp-activelayer-button",e.onClick).on("click",".wp-mail-smtp-activelayer-wc-dismiss",e.onDismiss)},onClick:function(t){t.preventDefault();var t=l(this),a=t.attr("data-action");t.hasClass("disabled")||("goto-url"===a?window.open(t.attr("data-url"),"_blank","noopener"):"goto-settings"===a?window.location.href=t.attr("data-url"):"install"===a?e.run(t,"about_plugin_install",s.download_url,s.installing):"activate"===a&&e.run(t,"about_plugin_activate",s.plugin,s.activating))},run:function(a,i,t,n){a.addClass("disabled").text(n),l.post(s.ajax_url,{action:"wp_mail_smtp_ajax",task:i,plugin:t,nonce:s.nonce}).done(function(t){if("about_plugin_install"===i)return t&&t.success&&t.data&&t.data.is_activated?void e.toSettings(a):t&&t.success?void e.run(a,"about_plugin_activate",s.plugin,s.activating):void e.fail(a,i);t&&t.success?e.toSettings(a):e.fail(a,i)}).fail(function(){e.fail(a,i)})},toSettings:function(t){t.removeClass("disabled").attr("data-action","goto-settings").attr("data-url",s.settings_url).text(s.goto_settings)},fail:function(t,a){a="about_plugin_install"===a?s.error_install:s.error_activate;t.removeClass("disabled").attr("data-action","goto-url").attr("data-url",s.wporg_url).text(s.get_activelayer),t.siblings(".wp-mail-smtp-activelayer-error").length||t.after('

    '+a+"

    ")},onDismiss:function(){l.post(s.ajax_url,{action:"wp_mail_smtp_activelayer_wc_dismiss",nonce:s.nonce}),l(this).closest(".wpms-activelayer-wc").remove()}};l(e.init)})(jQuery); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.js new file mode 100644 index 00000000..02a746e5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.js @@ -0,0 +1,118 @@ +/* global wp_mail_smtp_admin_notices, ajaxurl */ + +/** + * WP Mail SMTP Admin Notices. + * + * @since 4.4.0 + */ + +'use strict'; + +var WPMailSMTPAdminNotices = window.WPMailSMTPAdminNotices || ( function( document, window, $ ) { + + /** + * Public functions and properties. + * + * @since 4.4.0 + * + * @type {object} + */ + var app = { + + /** + * Start the engine. + * + * @since 4.4.0 + */ + init: function() { + + $( app.ready ); + }, + + /** + * Document ready. + * + * @since 4.4.0 + */ + ready: function() { + + app.events(); + }, + + /** + * Register JS events. + * + * @since 4.4.0 + */ + events: function() { + + $( '.wp-mail-smtp-notice.is-dismissible' ) + .on( 'click', '.notice-dismiss', app.dismiss ); + + $( '.wp-mail-smtp-notice__copy-btn' ).on( 'click', app.copyErrorCode ); + }, + + /** + * Copy error code to clipboard. + * + * @since 4.8.0 + */ + copyErrorCode: function() { + + var $btn = $( this ); + var code = $btn.siblings( 'code' ).text(); + + if ( navigator.clipboard ) { + navigator.clipboard.writeText( code ); + } + + $btn.find( '.wp-mail-smtp-notice__icon-copy' ).hide(); + $btn.find( '.wp-mail-smtp-notice__icon-check' ).show(); + + setTimeout( function() { + $btn.find( '.wp-mail-smtp-notice__icon-check' ).hide(); + $btn.find( '.wp-mail-smtp-notice__icon-copy' ).show(); + }, 2000 ); + }, + + /** + * Click on the dismiss notice button. + * + * @since 4.4.0 + * + * @param {object} event Event object. + */ + dismiss: function( event ) { + + var $notice = $( this ).closest( '.wp-mail-smtp-notice' ); + + // If notice key is not defined, we can't dismiss it permanently. + if ( $notice.data( 'notice' ) === undefined ) { + return; + } + + var $button = $( this ); + + $.ajax( { + url: ajaxurl, + dataType: 'json', + type: 'POST', + data: { + action: 'wp_mail_smtp_ajax', + nonce: wp_mail_smtp_admin_notices.nonce, + task: 'notice_dismiss', + notice: $notice.data( 'notice' ), + }, + beforeSend: function() { + $button.prop( 'disabled', true ); + }, + } ); + }, + }; + + return app; + +}( document, window, jQuery ) ); + +// Initialize. +WPMailSMTPAdminNotices.init(); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.min.js new file mode 100644 index 00000000..9daa2401 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin-notices.min.js @@ -0,0 +1 @@ +var WPMailSMTPAdminNotices=window.WPMailSMTPAdminNotices||(o=>{var i={init:function(){o(i.ready)},ready:function(){i.events()},events:function(){o(".wp-mail-smtp-notice.is-dismissible").on("click",".notice-dismiss",i.dismiss),o(".wp-mail-smtp-notice__copy-btn").on("click",i.copyErrorCode)},copyErrorCode:function(){var i=o(this),n=i.siblings("code").text();navigator.clipboard&&navigator.clipboard.writeText(n),i.find(".wp-mail-smtp-notice__icon-copy").hide(),i.find(".wp-mail-smtp-notice__icon-check").show(),setTimeout(function(){i.find(".wp-mail-smtp-notice__icon-check").hide(),i.find(".wp-mail-smtp-notice__icon-copy").show()},2e3)},dismiss:function(i){var n,t=o(this).closest(".wp-mail-smtp-notice");void 0!==t.data("notice")&&(n=o(this),o.ajax({url:ajaxurl,dataType:"json",type:"POST",data:{action:"wp_mail_smtp_ajax",nonce:wp_mail_smtp_admin_notices.nonce,task:"notice_dismiss",notice:t.data("notice")},beforeSend:function(){n.prop("disabled",!0)}}))}};return i})((document,window,jQuery));WPMailSMTPAdminNotices.init(); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin.js new file mode 100644 index 00000000..025ea5db --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-admin.js @@ -0,0 +1,1396 @@ +/* globals wp_mail_smtp, jconfirm, ajaxurl */ +'use strict'; + +var WPMailSMTP = window.WPMailSMTP || {}; +WPMailSMTP.Admin = WPMailSMTP.Admin || {}; + +/** + * WP Mail SMTP Admin area module. + * + * @since 1.6.0 + */ +WPMailSMTP.Admin.Settings = WPMailSMTP.Admin.Settings || ( function( document, window, $ ) { + + /** + * Public functions and properties. + * + * @since 1.6.0 + * + * @type {object} + */ + var app = { + + /** + * State attribute showing if one of the plugin settings + * changed and was not yet saved. + * + * @since 1.9.0 + * + * @type {boolean} + */ + pluginSettingsChanged: false, + + /** + * Start the engine. DOM is not ready yet, use only to init something. + * + * @since 1.6.0 + */ + init: function() { + + // Do that when DOM is ready. + $( app.ready ); + }, + + /** + * DOM is fully loaded. + * + * @since 1.6.0 + */ + ready: function() { + + app.pageHolder = $( '.wp-mail-smtp-tab-settings' ); + + app.settingsForm = $( '.wp-mail-smtp-connection-settings-form' ); + + // If there are screen options we have to move them. + $( '#screen-meta-links, #screen-meta' ).prependTo( '#wp-mail-smtp-header-temp' ).show(); + + app.bindActions(); + var removableQueryParams = [ 'sendlayer_quick_connect_result', 'sendlayer_quick_connect_disconnect_result' ]; + + if ( ! $( '.wp-mail-smtp-tab-tools-debug-events' ).length ) { + removableQueryParams.push( 'debug_event_id' ); + } + + app.cleanQueryParams( removableQueryParams ); + + app.setJQueryConfirmDefaults(); + + // Flyout Menu. + app.initFlyoutMenu(); + }, + + /** + * Process all generic actions/events, mostly custom that were fired by our API. + * + * @since 1.6.0 + */ + bindActions: function() { + + // Mailer selection. + $( '.wp-mail-smtp-mailer-image', app.settingsForm ).on( 'click', function() { + $( this ).parents( '.wp-mail-smtp-mailer' ).find( 'input' ).trigger( 'click' ); + } ); + + $( '.wp-mail-smtp-mailer input', app.settingsForm ).on( 'click', function() { + var $input = $( this ); + + if ( $input.prop( 'disabled' ) ) { + + // Educational Popup. + if ( $input.hasClass( 'educate' ) ) { + app.education.upgradeMailer( $input ); + } + + return false; + } + + // Deselect the current mailer. + $( '.wp-mail-smtp-mailer', app.settingsForm ).removeClass( 'active' ); + + // Select the correct one. + $( this ).parents( '.wp-mail-smtp-mailer' ).addClass( 'active' ); + + // Hide all mailers options and display for a currently clicked one. + $( '.wp-mail-smtp-mailer-option', app.settingsForm ).addClass( 'hidden' ).removeClass( 'active' ); + $( '.wp-mail-smtp-mailer-option-' + $( this ).val(), app.settingsForm ).addClass( 'active' ).removeClass( 'hidden' ); + } ); + + app.mailers.sendlayer.bindActions(); + app.mailers.smtp.bindActions(); + + // Dismiss Pro banner at the bottom of the page. + $( '#wp-mail-smtp-pro-banner-dismiss', app.pageHolder ).on( 'click', function() { + $.ajax( { + url: ajaxurl, + dataType: 'json', + type: 'POST', + data: { + action: 'wp_mail_smtp_ajax', + task: 'pro_banner_dismiss', + nonce: wp_mail_smtp.nonce + } + } ) + .always( function() { + $( '#wp-mail-smtp-pro-banner', app.pageHolder ).fadeOut( 'fast' ); + } ); + } ); + + // Dissmis educational notices for certain mailers. + $( '.js-wp-mail-smtp-mailer-notice-dismiss', app.settingsForm ).on( 'click', function( e ) { + e.preventDefault(); + + var $btn = $( this ), + $notice = $btn.parents( '.inline-notice' ); + + if ( $btn.hasClass( 'disabled' ) ) { + return false; + } + + $.ajax( { + url: ajaxurl, + dataType: 'json', + type: 'POST', + data: { + action: 'wp_mail_smtp_ajax', + nonce: wp_mail_smtp.nonce, + task: 'notice_dismiss', + notice: $notice.data( 'notice' ), + mailer: $notice.data( 'mailer' ) + }, + beforeSend: function() { + $btn.addClass( 'disabled' ); + } + } ) + .always( function() { + $notice.fadeOut( 'fast', function() { + $btn.removeClass( 'disabled' ); + } ); + } ); + } ); + + // Show/hide debug output. + $( '#wp-mail-smtp-debug .error-log-toggle' ).on( 'click', function( e ) { + e.preventDefault(); + + $( '#wp-mail-smtp-debug .error-log' ).slideToggle(); + } ); + + // Copy debug output to clipboard. + $( '#wp-mail-smtp-debug .error-log-copy' ).on( 'click', function( e ) { + e.preventDefault(); + + var $self = $( this ); + + // Get error log. + var $content = $( '#wp-mail-smtp-debug .error-log' ); + + // Copy to clipboard. + if ( ! $content.is( ':visible' ) ) { + $content.addClass( 'error-log-selection' ); + } + var range = document.createRange(); + range.selectNode( $content[0] ); + window.getSelection().removeAllRanges(); + window.getSelection().addRange( range ); + document.execCommand( 'Copy' ); + window.getSelection().removeAllRanges(); + $content.removeClass( 'error-log-selection' ); + + $self.addClass( 'error-log-copy-copied' ); + + setTimeout( + function() { + $self.removeClass( 'error-log-copy-copied' ); + }, + 1500 + ); + } ); + + // Remove mailer connection. + $( '.js-wp-mail-smtp-provider-remove', app.settingsForm ).on( 'click', function() { + return confirm( wp_mail_smtp.text_provider_remove ); + } ); + + // Copy input text to clipboard. + $( '.wp-mail-smtp-setting-copy', app.settingsForm ).on( 'click', function( e ) { + e.preventDefault(); + + var target = $( '#' + $( this ).data( 'source_id' ) ).get( 0 ); + + target.select(); + document.execCommand( 'Copy' ); + + var $buttonIcon = $( this ).find( '.dashicons' ); + + $buttonIcon + .removeClass( 'dashicons-admin-page' ) + .addClass( 'wp-mail-smtp-dashicons-yes-alt-green wp-mail-smtp-success wp-mail-smtp-animate' ); + + setTimeout( + function() { + $buttonIcon + .removeClass( 'wp-mail-smtp-dashicons-yes-alt-green wp-mail-smtp-success wp-mail-smtp-animate' ) + .addClass( 'dashicons-admin-page' ); + }, + 1000 + ); + } ); + + // Notice bar: click on the dissmiss button. + $( '#wp-mail-smtp-notice-bar' ).on( 'click', '.dismiss', function() { + var $notice = $( this ).closest( '#wp-mail-smtp-notice-bar' ); + + $notice.addClass( 'out' ); + setTimeout( + function() { + $notice.remove(); + }, + 300 + ); + + $.post( + ajaxurl, + { + action: 'wp_mail_smtp_notice_bar_dismiss', + nonce: wp_mail_smtp.nonce, + } + ); + } ); + + app.triggerExitNotice(); + app.beforeSaveChecks(); + + // Register change event to show/hide plugin supported settings for currently selected mailer. + $( '.js-wp-mail-smtp-setting-mailer-radio-input', app.settingsForm ).on( 'change', this.processMailerSettingsOnChange ); + + // Disable multiple click on the Email Test tab submit button and display a loader icon. + $( '.wp-mail-smtp-tab-tools-test #email-test-form' ).on( 'submit', function() { + var $button = $( this ).find( '.wp-mail-smtp-btn' ); + + $button.attr( 'disabled', true ); + $button.find( 'span' ).hide(); + $button.find( '.wp-mail-smtp-loading' ).show(); + } ); + + $( '#wp-mail-smtp-setting-gmail-one_click_setup_enabled-lite' ).on( 'click', function( e ) { + e.preventDefault(); + + app.education.gmailOneClickSetupUpgrade(); + } ); + + $( '#wp-mail-smtp-setting-misc-rate_limit-lite' ).on( 'click', function( e ) { + e.preventDefault(); + + app.education.rateLimitUpgrade(); + } ); + + // Confirm before enabling the "Hide Email Delivery Errors" option. + $( '#wp-mail-smtp-setting-email_delivery_errors_hidden' ).on( 'change', app.confirmHideDeliveryErrors ); + + // Obfuscated fields + $( '.wp-mail-smtp-btn[data-clear-field]' ).on( 'click', function( e ) { + var $button = $( this ); + var fieldId = $button.attr( 'data-clear-field' ); + var $field = $( `#${fieldId}` ); + + $field.prop( 'disabled', false ); + $field.attr( 'name', $field.attr( 'data-name' ) ); + $field.removeAttr( 'value' ); + $field.focus(); + $button.remove(); + } ); + + $( '.email_test_tab_removal_notice' ).on( 'click', '.notice-dismiss', function() { + var $button = $( this ); + + $.ajax( { + url: ajaxurl, + dataType: 'json', + type: 'POST', + data: { + action: 'wp_mail_smtp_ajax', + nonce: wp_mail_smtp.nonce, + task: 'email_test_tab_removal_notice_dismiss', + }, + beforeSend: function() { + $button.prop( 'disabled', true ); + }, + } ); + } ); + + // Banner: toggle the inline error-log panel. + $( document ).on( + 'click', + '.wpms-email-sending-errors-banner__error-log-toggle', + function( e ) { + e.preventDefault(); + + var $btn = $( this ); + var $panel = $btn.closest( '.wpms-email-sending-errors-banner' ) + .find( '.wpms-email-sending-errors-banner__error-log' ); + + if ( ! $panel.length ) { + return; + } + + var showLabel = $btn.data( 'show-label' ); + var hideLabel = $btn.data( 'hide-label' ); + var isHidden = $panel.prop( 'hidden' ) || ! $panel.is( ':visible' ); + + if ( isHidden ) { + $panel.prop( 'hidden', false ).hide().slideDown( 200 ); + $btn.text( hideLabel ); + } else { + $panel.slideUp( 200, function() { + $panel.prop( 'hidden', true ); + } ); + $btn.text( showLabel ); + } + } + ); + + // Banner: copy the inline error-log panel contents to clipboard. + $( document ).on( + 'click', + '.wpms-email-sending-errors-error-log__copy-icon', + function( e ) { + e.preventDefault(); + + var $btn = $( this ); + var $panel = $btn.closest( '.wpms-email-sending-errors-banner__error-log' ); + var $default = $btn.find( '.wpms-email-sending-errors-error-log__copy-icon-default' ); + var $done = $btn.find( '.wpms-email-sending-errors-error-log__copy-icon-done' ); + var $tooltip = $panel.find( '.wpms-email-sending-errors-error-log__copy-tooltip' ); + + if ( ! $panel.length ) { + return; + } + + var $content = $panel.clone(); + $content.find( '.wpms-email-sending-errors-error-log__copy-icon, .wpms-email-sending-errors-error-log__copy-tooltip' ).remove(); + $content.find( 'br' ).replaceWith( '\n' ); + var text = $content.text().trim(); + + var afterCopy = function() { + $default.prop( 'hidden', true ); + $done.prop( 'hidden', false ); + $tooltip.prop( 'hidden', false ); + + setTimeout( + function() { + $default.prop( 'hidden', false ); + $done.prop( 'hidden', true ); + $tooltip.prop( 'hidden', true ); + }, + 2000 + ); + }; + + var copyViaExecCommand = function() { + var $tmp = $( '' ).val( response.data.code ); + var $footer = $( '' ); + var $installing = $( '' ); + var $action = $( '
    ' ).text( l10n.install_text ); + + $codeWrap.append( $textarea ); + $footer.append( $installing ).append( $action ); + $content.append( $codeWrap ).append( $footer ); + + if ( response.data.note ) { + $content.prepend( $( '
    ' ).text( response.data.note ) ); + } + + dialog.setContent( $content ); + app.initEditor( $textarea[ 0 ], response.data.code_type ); + app.bindModalInstall( dialog, libraryId, installUrl, plugin, $action, $installing ); + } ) + .fail( function() { + + dialog.setContent( error ); + } ); + }, + + /** + * Wire the preview modal's "Install Snippet" button to the right action for + * the current WPCode state (active, installed-inactive, or absent). + * + * @since 4.9.0 + * + * @param {object} dialog The jconfirm dialog instance. + * @param {number} libraryId The snippet library id. + * @param {string} installUrl WPCode install URL (active state). + * @param {string} plugin WPCode download URL (WPCode-absent branch). + * @param {object} $action The modal action button. + * @param {object} $installing The modal "installing" text element. + */ + bindModalInstall: function( dialog, libraryId, installUrl, plugin, $action, $installing ) { + + var l10n = window.wp_mail_smtp_code_snippets; + + $action.on( 'click', function( clickEvent ) { + + clickEvent.preventDefault(); + + if ( l10n.wpcode_active ) { + if ( installUrl ) { + app.setInstalling( $installing, $action ); + window.location.href = installUrl; + } + return; + } + + // WPCode installed but inactive: activate, then install (mirrors the card button). + if ( l10n.wpcode_path ) { + app.setInstalling( $installing, $action ); + app.activatePluginThenInstallSnippet( libraryId, l10n.wpcode_path, function() { + + app.clearInstalling( $installing, $action ); + dialog.close(); + app.showManualActivationPluginPopup(); + } ); + return; + } + + dialog.close(); + app.showInstallPluginPopup( plugin, libraryId ); + } ); + }, + + /** + * Initialize CodeMirror on a textarea, with a plain-text fallback. + * + * @since 4.9.0 + * + * @param {HTMLElement} textarea The textarea element. + * @param {string} codeType Snippet code type. + */ + initEditor: function( textarea, codeType ) { + + var l10n = window.wp_mail_smtp_code_snippets; + + if ( ! window.wp || ! window.wp.codeEditor || ! l10n.code_editor ) { + + // Syntax highlighting unavailable (user profile setting): leave the + // textarea as a plain read-only code box. + $( textarea ).prop( 'readonly', true ); + return; + } + + var settings = $.extend( true, {}, l10n.code_editor ); + + settings.codemirror = $.extend( {}, settings.codemirror, { + mode: app.mapMode( codeType ), + readOnly: true, + lineNumbers: true, + } ); + + window.wp.codeEditor.initialize( textarea, settings ); + }, + + /** + * Card "Install Snippet" (WPCode absent): open the install-plugin popup. + * + * @since 4.9.0 + * + * @param {object} event The click event. + */ + openInstallPluginPopup: function( event ) { + + event.preventDefault(); + app.showInstallPluginPopup( $( this ).data( 'plugin' ), $( this ).data( 'library-id' ) ); + }, + + /** + * Show the install-plugin popup. Its button installs + activates WPCode, + * then navigates to the chosen snippet's install URL. + * + * @since 4.9.0 + * + * @param {string} plugin WPCode download URL. + * @param {number} libraryId The snippet library id. + */ + showInstallPluginPopup: function( plugin, libraryId ) { + + var l10n = window.wp_mail_smtp_code_snippets; + + // $.dialog (no jconfirm button row) so the icon row, title, description, + // CTA, and link render as one centered content block per the design. + $.dialog( { + title: '', + container: '#wp-mail-smtp', + boxWidth: '550px', + useBootstrap: false, + closeIcon: true, + draggable: false, + content: app.installPopupHtml( l10n ), + + // The scoped modal styles key off these classes. jQuery-Confirm v3.3.4 + // has no containerClass option, so add them in onOpenBefore: it is the + // earliest hook where $el exists and it fires before the open animation + // reveals the box, so the modal paints styled on first render. + onOpenBefore: function() { + + this.$el.addClass( 'wp-mail-smtp-wpcode-dialog wp-mail-smtp-wpcode-install-dialog' ); + }, + onContentReady: function() { + + var dialog = this; + + dialog.$content.find( '.wp-mail-smtp-wpcode-install-modal-btn' ).on( 'click', function( event ) { + + event.preventDefault(); + + var $btn = $( this ); + + if ( $btn.hasClass( 'wp-mail-smtp-btn-loading' ) ) { + return; + } + + $btn.addClass( 'wp-mail-smtp-btn-loading' ); + dialog.$content.find( '.wp-mail-smtp-wpcode-modal-error' ).remove(); + + app.installAndActivatePlugin( + plugin, + function() { + + app.navigateToSnippetInstall( libraryId ); + }, + function( reason ) { + + $btn.removeClass( 'wp-mail-smtp-btn-loading' ); + dialog.$content.append( app.modalErrorHtml( reason ) ); + } + ); + } ); + }, + } ); + }, + + /** + * Build the install-WPCode popup body: an icon row, a two-line title and + * description, the install CTA, and a "learn more" link. + * + * @since 4.9.0 + * + * @param {object} l10n Localized strings and URLs. + * + * @returns {string} The popup body markup. + */ + installPopupHtml: function( l10n ) { + + return '
    ' + + '' + + '
    ' + l10n.install_popup_title + '
    ' + + '
    ' + l10n.install_popup_desc + '
    ' + + '' + + '' + l10n.learn_more_text + '' + + '
    '; + }, + + /** + * Build the inline error shown in the install/activate confirm dialog on + * failure: a message plus a manual-fallback link (activation URL for + * activation failures). + * + * @since 4.9.0 + * + * @param {string} reason 'install' or 'activate'. + * + * @returns {object} jQuery element for the error paragraph. + */ + modalErrorHtml: function( reason ) { + + var l10n = window.wp_mail_smtp_code_snippets; + var $error = $( '

    ' ); + + // Install failure: message only. A plugin-directory search link is + // fragile (it can surface the wrong plugin), so we don't offer one. + if ( reason !== 'activate' ) { + return $error.text( l10n.install_error_text ); + } + + // Activation failure: WPCode is installed, so link to its exact + // activation URL. + return $error + .text( l10n.activate_error_text + ' ' ) + .append( + $( '' ) + .attr( 'href', l10n.lite_activate_url ) + .text( l10n.activate_manual_link ) + ); + }, + + /** + * Fallback overlay button: install (or activate) WPCode directly, then + * reload so the tab re-renders with live snippet data. + * + * @since 4.9.0 + * + * @param {object} event The click event. + */ + onFallbackInstallClick: function( event ) { + + if ( event && typeof event.preventDefault === 'function' ) { + event.preventDefault(); + } + + var $btn = $( this ); + + if ( $btn.hasClass( 'disabled' ) ) { + return; + } + + // Lock the width, hide the label, and disable while the request runs. + $btn.width( $btn.width() ).text( '' ).addClass( 'disabled' ); + + var task = $btn.data( 'action' ) === 'activate' ? 'about_plugin_activate' : 'about_plugin_install'; + + app.post( 'wp_mail_smtp_ajax', { task: task, plugin: $btn.data( 'plugin' ) } ) + .done( function() { + + window.location.reload(); + } ); + }, + }; + + return app; +}( document, window, jQuery ) ); + +WPMailSMTP.Admin.CodeSnippets.init(); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-code-snippets.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-code-snippets.min.js new file mode 100644 index 00000000..f411c488 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-code-snippets.min.js @@ -0,0 +1 @@ +var WPMailSMTP=window.WPMailSMTP||{};WPMailSMTP.Admin=WPMailSMTP.Admin||{},WPMailSMTP.Admin.CodeSnippets=WPMailSMTP.Admin.CodeSnippets||((u,w)=>{var _={snippetSearch:null,init:function(){w(_.ready)},ready:function(){_.snippetSearch=new List("wp-mail-smtp-wpcode-snippets-list",{valueNames:["wp-mail-smtp-wpcode-snippet-title"]}),_.events()},events:function(){w(".wp-mail-smtp-wpcode-fallback-install-plugin").on("click",_.onFallbackInstallClick),w(".wp-mail-smtp-wpcode-install-plugin").on("click",_.openInstallPluginPopup),w(".wp-mail-smtp-wpcode-activate-plugin").on("click",_.onActivatePluginClick),w(".wp-mail-smtp-wpcode-view-snippet").on("click",_.openViewSnippetModal),w(".wp-mail-smtp-wpcode-install-snippet").on("click",_.installSnippet),w("#wp-mail-smtp-wpcode-snippet-search").on("keyup search",function(){_.searchSnippet(this)})},post:function(t,a){t=w.extend({action:t,nonce:u.wp_mail_smtp.nonce},a||{});return w.post(u.wp_mail_smtp.ajax_url,t)},setInstalling:function(t,a){t.addClass("wp-mail-smtp-wpcode-installing-in-progress").text(u.wp_mail_smtp_code_snippets.installing_text),a.addClass("wp-mail-smtp-btn-loading")},clearInstalling:function(t,a){t.removeClass("wp-mail-smtp-wpcode-installing-in-progress").text(""),a.removeClass("wp-mail-smtp-btn-loading")},searchSnippet:function(t){var t=w(t).val(),t=_.snippetSearch.search(t),a=w("#wp-mail-smtp-wpcode-no-results");0===t.length?a.show():a.hide()},installSnippet:function(){var t=w(this);"edit"!==t.data("action")&&_.setInstalling(t.closest(".wp-mail-smtp-wpcode-snippet").find(".wp-mail-smtp-wpcode-snippet-badge"),t)},onActivatePluginClick:function(t){t.preventDefault();var a=w(this),n=a.closest(".wp-mail-smtp-wpcode-snippet").find(".wp-mail-smtp-wpcode-snippet-badge");_.setInstalling(n,a),_.activatePluginThenInstallSnippet(a.data("library-id"),a.data("plugin"),function(){_.clearInstalling(n,a),_.showManualActivationPluginPopup()})},activatePluginThenInstallSnippet:function(a,t,n){_.post("wp_mail_smtp_ajax",{task:"about_plugin_activate",plugin:t}).done(function(t){t&&t.success?_.navigateToSnippetInstall(a):n()}).fail(function(){n()})},navigateToSnippetInstall:function(t){var a={};a.library_id=t,_.post("wp_mail_smtp_get_wpcode_snippet_install_url",a).done(function(t){t&&t.success&&t.data&&t.data.install_url?u.location.href=t.data.install_url:u.location.reload()}).fail(function(){u.location.reload()})},installAndActivatePlugin:function(t,a,n){_.post("wp_mail_smtp_ajax",{task:"about_plugin_install",plugin:t}).done(function(t){t&&t.success?t.data&&!1===t.data.is_activated?n("activate"):a():n("install")}).fail(function(){n("install")})},showManualActivationPluginPopup:function(){var a=u.wp_mail_smtp_code_snippets;w.confirm({title:a.activate_popup_title,container:"#wp-mail-smtp",boxWidth:"600px",useBootstrap:!1,closeIcon:!0,draggable:!1,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:'
    '+t.loading_text+"
    ",onContentReady:function(){this.$el.addClass("wp-mail-smtp-wpcode-dialog"),_.loadSnippetCode(this,a,n,i)}})},loadSnippetCode:function(s,o,c,d){var m=u.wp_mail_smtp_code_snippets,r='
    '+m.error_text+"
    ",t={};t.library_id=o,_.post("wp_mail_smtp_get_wpcode_snippet_code",t).done(function(t){var a,n,i,l,p,e;t&&t.success&&t.data?(s.setTitle(t.data.title),a=w('
    '),n=w('
    '),i=w("").val(t.data.code),l=w(''),p=w(''),e=w('').text(m.install_text),n.append(i),l.append(p).append(e),a.append(n).append(l),t.data.note&&a.prepend(w('
    ').text(t.data.note)),s.setContent(a),_.initEditor(i[0],t.data.code_type),_.bindModalInstall(s,o,c,d,e,p)):s.setContent(r)}).fail(function(){s.setContent(r)})},bindModalInstall:function(a,n,i,l,p,e){var s=u.wp_mail_smtp_code_snippets;p.on("click",function(t){t.preventDefault(),s.wpcode_active?i&&(_.setInstalling(e,p),u.location.href=i):s.wpcode_path?(_.setInstalling(e,p),_.activatePluginThenInstallSnippet(n,s.wpcode_path,function(){_.clearInstalling(e,p),a.close(),_.showManualActivationPluginPopup()})):(a.close(),_.showInstallPluginPopup(l,n))})},initEditor:function(t,a){var n=u.wp_mail_smtp_code_snippets;u.wp&&u.wp.codeEditor&&n.code_editor?((n=w.extend(!0,{},n.code_editor)).codemirror=w.extend({},n.codemirror,{mode:_.mapMode(a),readOnly:!0,lineNumbers:!0}),u.wp.codeEditor.initialize(t,n)):w(t).prop("readonly",!0)},openInstallPluginPopup:function(t){t.preventDefault(),_.showInstallPluginPopup(w(this).data("plugin"),w(this).data("library-id"))},showInstallPluginPopup:function(i,l){var t=u.wp_mail_smtp_code_snippets;w.dialog({title:"",container:"#wp-mail-smtp",boxWidth:"550px",useBootstrap:!1,closeIcon:!0,draggable:!1,content:_.installPopupHtml(t),onOpenBefore:function(){this.$el.addClass("wp-mail-smtp-wpcode-dialog wp-mail-smtp-wpcode-install-dialog")},onContentReady:function(){var n=this;n.$content.find(".wp-mail-smtp-wpcode-install-modal-btn").on("click",function(t){t.preventDefault();var a=w(this);a.hasClass("wp-mail-smtp-btn-loading")||(a.addClass("wp-mail-smtp-btn-loading"),n.$content.find(".wp-mail-smtp-wpcode-modal-error").remove(),_.installAndActivatePlugin(i,function(){_.navigateToSnippetInstall(l)},function(t){a.removeClass("wp-mail-smtp-btn-loading"),n.$content.append(_.modalErrorHtml(t))}))})}})},installPopupHtml:function(t){return'
    '+t.install_popup_title+'
    '+t.install_popup_desc+'
    '+t.learn_more_text+"
    "},modalErrorHtml:function(t){var a=u.wp_mail_smtp_code_snippets,n=w('

    ');return"activate"!==t?n.text(a.install_error_text):n.text(a.activate_error_text+" ").append(w("").attr("href",a.lite_activate_url).text(a.activate_manual_link))},onFallbackInstallClick:function(t){t&&"function"==typeof t.preventDefault&&t.preventDefault();var a,t=w(this);t.hasClass("disabled")||(t.width(t.width()).text("").addClass("disabled"),a="activate"===t.data("action")?"about_plugin_activate":"about_plugin_install",_.post("wp_mail_smtp_ajax",{task:a,plugin:t.data("plugin")}).done(function(){u.location.reload()}))}};return _})((document,window),jQuery),WPMailSMTP.Admin.CodeSnippets.init(); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.js new file mode 100644 index 00000000..061a689b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.js @@ -0,0 +1,305 @@ +/* global wp_mail_smtp_dashboard_widget, ajaxurl, moment, WPMailSMTPChart */ +/** + * WP Mail SMTP Dashboard Widget function. + * + * @since 2.9.0 + */ + +'use strict'; + +var WPMailSMTPDashboardWidget = window.WPMailSMTPDashboardWidget || ( function( document, window, $ ) { + + /** + * Elements reference. + * + * @since 2.9.0 + * + * @type {object} + */ + var el = { + $canvas : $( '#wp-mail-smtp-dash-widget-chart' ), + $settingsBtn : $( '#wp-mail-smtp-dash-widget-settings-button' ), + $dismissBtn : $( '.wp-mail-smtp-dash-widget-dismiss-chart-upgrade' ), + $summaryReportEmailBlock : $( '.wp-mail-smtp-dash-widget-summary-report-email-block' ), + $summaryReportEmailDismissBtn : $( '.wp-mail-smtp-dash-widget-summary-report-email-dismiss' ), + $summaryReportEmailEnableInput: $( '#wp-mail-smtp-dash-widget-summary-report-email-enable' ), + $emailAlertsDismissBtn : $( '#wp-mail-smtp-dash-widget-dismiss-email-alert-block' ), + }; + + /** + * Chart.js functions and properties. + * + * @since 2.9.0 + * + * @type {object} + */ + var chart = { + + /** + * Chart.js instance. + * + * @since 2.9.0 + */ + instance: null, + + /** + * Chart.js settings. + * + * @since 2.9.0 + */ + settings: { + type: 'line', + data: { + labels: [], + datasets: [ + { + label: '', + data: [], + backgroundColor: 'rgba(34, 113, 177, 0.15)', + borderColor: 'rgba(34, 113, 177, 1)', + borderWidth: 2, + pointRadius: 4, + pointBorderWidth: 1, + pointBackgroundColor: 'rgba(255, 255, 255, 1)', + } + ], + }, + options: { + maintainAspectRatio: false, + scales: { + x: { + type: 'timeseries', + time: { + tooltipFormat: 'MMM D', + }, + ticks: { + beginAtZero: true, + source: 'labels', + padding: 0, + minRotation: 25, + maxRotation: 25, + callback: function( value, index, values ) { + const gap = Math.floor( values.length / 7 ); + + if ( gap < 1 ) { + return moment( value ).format( 'MMM D' ); + } + if ( ( values.length - index - 1 ) % gap === 0 ) { + return moment( value ).format( 'MMM D' ); + } + }, + }, + }, + y: { + ticks: { + beginAtZero: true, + maxTicksLimit: 6, + padding: 0, + callback: function( value ) { + + // Make sure the tick value has no decimals. + if ( Math.floor( value ) === value ) { + return value; + } + }, + }, + }, + }, + elements: { + line: { + tension: 0, + fill: true, + }, + }, + animation: false, + plugins: { + legend: { + display: false, + }, + tooltip: { + displayColors: false, + }, + }, + }, + }, + + /** + * Init Chart.js. + * + * @since 2.9.0 + */ + init: function() { + + var ctx; + + if ( ! el.$canvas.length ) { + return; + } + + ctx = el.$canvas[ 0 ].getContext( '2d' ); + + chart.instance = new WPMailSMTPChart( ctx, chart.settings ); + + chart.updateWithDummyData(); + + chart.instance.update(); + }, + + /** + * Update Chart.js settings with dummy data. + * + * @since 2.9.0 + */ + updateWithDummyData: function() { + + var end = moment().startOf( 'day' ), + days = 7, + data = [ 55, 45, 34, 45, 32, 55, 65 ], + date, + i; + + for ( i = 1; i <= days; i++ ) { + + date = end.clone().subtract( i, 'days' ); + + chart.settings.data.labels.push( date ); + chart.settings.data.datasets[ 0 ].data.push( { + x: date, + y: data[ i - 1 ], + } ); + } + }, + }; + + /** + * Public functions and properties. + * + * @since 2.9.0 + * + * @type {object} + */ + var app = { + + /** + * Publicly accessible Chart.js functions and properties. + * + * @since 2.9.0 + */ + chart: chart, + + /** + * Start the engine. + * + * @since 2.9.0 + */ + init: function() { + $( app.ready ); + }, + + /** + * Document ready. + * + * @since 2.9.0 + */ + ready: function() { + + el.$settingsBtn.on( 'click', function( e ) { + $( this ).toggleClass( 'open' ); + $( this ).siblings( '.wp-mail-smtp-dash-widget-settings-menu' ).fadeToggle( 200 ); + } ); + + el.$dismissBtn.on( 'click', function( event ) { + event.preventDefault(); + + app.saveWidgetMeta( 'hide_graph', 1 ); + $( this ).closest( '.wp-mail-smtp-dash-widget-chart-block-container' ).remove(); + $( '#wp-mail-smtp-dash-widget-upgrade-footer' ).show(); + } ); + + // Hide summary report email block on dismiss icon click. + el.$summaryReportEmailDismissBtn.on( 'click', function( event ) { + event.preventDefault(); + + app.saveWidgetMeta( 'hide_summary_report_email_block', 1 ); + el.$summaryReportEmailBlock.slideUp(); + } ); + + // Enable summary report email on checkbox enable. + el.$summaryReportEmailEnableInput.on( 'change', function( event ) { + event.preventDefault(); + + var $self = $( this ), + $loader = $self.next( 'i' ); + + $self.hide(); + $loader.show(); + + var data = { + _wpnonce: wp_mail_smtp_dashboard_widget.nonce, + action : 'wp_mail_smtp_' + wp_mail_smtp_dashboard_widget.slug + '_enable_summary_report_email' + }; + + $.post( ajaxurl, data ) + .done( function() { + el.$summaryReportEmailBlock.find( '.wp-mail-smtp-dash-widget-summary-report-email-block-setting' ) + .addClass( 'hidden' ); + el.$summaryReportEmailBlock.find( '.wp-mail-smtp-dash-widget-summary-report-email-block-applied' ) + .removeClass( 'hidden' ); + } ) + .fail( function() { + $self.show(); + $loader.hide(); + } ); + } ); + + // Hide email alerts banner on dismiss icon click. + el.$emailAlertsDismissBtn.on( 'click', function( event ) { + event.preventDefault(); + + $( '#wp-mail-smtp-dash-widget-email-alerts-education' ).remove(); + app.saveWidgetMeta( 'hide_email_alerts_banner', 1 ); + } ); + + chart.init(); + app.removeOverlay( el.$canvas ); + }, + + /** + * Save dashboard widget meta in backend. + * + * @since 2.9.0 + * + * @param {string} meta Meta name to save. + * @param {number} value Value to save. + */ + saveWidgetMeta: function( meta, value ) { + + var data = { + _wpnonce: wp_mail_smtp_dashboard_widget.nonce, + action : 'wp_mail_smtp_' + wp_mail_smtp_dashboard_widget.slug + '_save_widget_meta', + meta : meta, + value : value, + }; + + $.post( ajaxurl, data ); + }, + + /** + * Remove an overlay from a widget block containing $el. + * + * @since 2.9.0 + * + * @param {object} $el jQuery element inside a widget block. + */ + removeOverlay: function( $el ) { + $el.siblings( '.wp-mail-smtp-dash-widget-overlay' ).remove(); + }, + }; + + // Provide access to public functions/properties. + return app; + +}( document, window, jQuery ) ); + +// Initialize. +WPMailSMTPDashboardWidget.init(); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.min.js new file mode 100644 index 00000000..f280a6e2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-dashboard-widget.min.js @@ -0,0 +1 @@ +var WPMailSMTPDashboardWidget=window.WPMailSMTPDashboardWidget||(e=>{var s={$canvas:e("#wp-mail-smtp-dash-widget-chart"),$settingsBtn:e("#wp-mail-smtp-dash-widget-settings-button"),$dismissBtn:e(".wp-mail-smtp-dash-widget-dismiss-chart-upgrade"),$summaryReportEmailBlock:e(".wp-mail-smtp-dash-widget-summary-report-email-block"),$summaryReportEmailDismissBtn:e(".wp-mail-smtp-dash-widget-summary-report-email-dismiss"),$summaryReportEmailEnableInput:e("#wp-mail-smtp-dash-widget-summary-report-email-enable"),$emailAlertsDismissBtn:e("#wp-mail-smtp-dash-widget-dismiss-email-alert-block")},n={instance:null,settings:{type:"line",data:{labels:[],datasets:[{label:"",data:[],backgroundColor:"rgba(34, 113, 177, 0.15)",borderColor:"rgba(34, 113, 177, 1)",borderWidth:2,pointRadius:4,pointBorderWidth:1,pointBackgroundColor:"rgba(255, 255, 255, 1)"}]},options:{maintainAspectRatio:!1,scales:{x:{type:"timeseries",time:{tooltipFormat:"MMM D"},ticks:{beginAtZero:!0,source:"labels",padding:0,minRotation:25,maxRotation:25,callback:function(t,a,i){var e=Math.floor(i.length/7);return e<1||(i.length-a-1)%e==0?moment(t).format("MMM D"):void 0}}},y:{ticks:{beginAtZero:!0,maxTicksLimit:6,padding:0,callback:function(t){if(Math.floor(t)===t)return t}}}},elements:{line:{tension:0,fill:!0}},animation:!1,plugins:{legend:{display:!1},tooltip:{displayColors:!1}}}},init:function(){var t;s.$canvas.length&&(t=s.$canvas[0].getContext("2d"),n.instance=new WPMailSMTPChart(t,n.settings),n.updateWithDummyData(),n.instance.update())},updateWithDummyData:function(){for(var t,a=moment().startOf("day"),i=[55,45,34,45,32,55,65],e=1;e<=7;e++)t=a.clone().subtract(e,"days"),n.settings.data.labels.push(t),n.settings.data.datasets[0].data.push({x:t,y:i[e-1]})}},a={chart:n,init:function(){e(a.ready)},ready:function(){s.$settingsBtn.on("click",function(t){e(this).toggleClass("open"),e(this).siblings(".wp-mail-smtp-dash-widget-settings-menu").fadeToggle(200)}),s.$dismissBtn.on("click",function(t){t.preventDefault(),a.saveWidgetMeta("hide_graph",1),e(this).closest(".wp-mail-smtp-dash-widget-chart-block-container").remove(),e("#wp-mail-smtp-dash-widget-upgrade-footer").show()}),s.$summaryReportEmailDismissBtn.on("click",function(t){t.preventDefault(),a.saveWidgetMeta("hide_summary_report_email_block",1),s.$summaryReportEmailBlock.slideUp()}),s.$summaryReportEmailEnableInput.on("change",function(t){t.preventDefault();var a=e(this),i=a.next("i"),t=(a.hide(),i.show(),{_wpnonce:wp_mail_smtp_dashboard_widget.nonce,action:"wp_mail_smtp_"+wp_mail_smtp_dashboard_widget.slug+"_enable_summary_report_email"});e.post(ajaxurl,t).done(function(){s.$summaryReportEmailBlock.find(".wp-mail-smtp-dash-widget-summary-report-email-block-setting").addClass("hidden"),s.$summaryReportEmailBlock.find(".wp-mail-smtp-dash-widget-summary-report-email-block-applied").removeClass("hidden")}).fail(function(){a.show(),i.hide()})}),s.$emailAlertsDismissBtn.on("click",function(t){t.preventDefault(),e("#wp-mail-smtp-dash-widget-email-alerts-education").remove(),a.saveWidgetMeta("hide_email_alerts_banner",1)}),n.init(),a.removeOverlay(s.$canvas)},saveWidgetMeta:function(t,a){t={_wpnonce:wp_mail_smtp_dashboard_widget.nonce,action:"wp_mail_smtp_"+wp_mail_smtp_dashboard_widget.slug+"_save_widget_meta",meta:t,value:a};e.post(ajaxurl,t)},removeOverlay:function(t){t.siblings(".wp-mail-smtp-dash-widget-overlay").remove()}};return a})((document,window,jQuery));WPMailSMTPDashboardWidget.init(); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.js new file mode 100644 index 00000000..094a0764 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.js @@ -0,0 +1,185 @@ +/* global wp_mail_smtp, ajaxurl */ + +/** + * WP Mail SMTP Admin Notifications. + * + * @since 2.3.0 + */ + +'use strict'; + +var WPMailSMTPAdminNotifications = window.WPMailSMTPAdminNotifications || ( function( document, window, $ ) { + + /** + * Elements holder. + * + * @since 2.3.0 + * + * @type {object} + */ + var el = { + $notifications: $( '#wp-mail-smtp-notifications' ), + $nextButton: $( '#wp-mail-smtp-notifications .navigation .next' ), + $prevButton: $( '#wp-mail-smtp-notifications .navigation .prev' ), + $adminBarCounter: $( '#wp-admin-bar-wp-mail-smtp-menu .wp-mail-smtp-admin-bar-menu-notification-counter' ), + }; + + /** + * Public functions and properties. + * + * @since 2.3.0 + * + * @type {object} + */ + var app = { + + /** + * Start the engine. + * + * @since 2.3.0 + */ + init: function() { + + $( app.ready ); + }, + + /** + * Document ready. + * + * @since 2.3.0 + */ + ready: function() { + + app.updateNavigation(); + app.events(); + }, + + /** + * Register JS events. + * + * @since 2.3.0 + */ + events: function() { + + el.$notifications + .on( 'click', '.dismiss', app.dismiss ) + .on( 'click', '.next', app.navNext ) + .on( 'click', '.prev', app.navPrev ); + }, + + /** + * Click on the Dismiss notification button. + * + * @since 2.3.0 + * + * @param {object} event Event object. + */ + dismiss: function( event ) { + + if ( el.$currentMessage.length === 0 ) { + return; + } + + // AJAX call - update option. + var data = { + action: 'wp_mail_smtp_notification_dismiss', + nonce: wp_mail_smtp.nonce, + id: el.$currentMessage.data( 'message-id' ), + }; + + $.post( ajaxurl, data, function( response ) { + if ( ! response.success ) { + return; + } + + // Update counter. + var count = parseInt( el.$adminBarCounter.text(), 10 ); + if ( count > 1 ) { + --count; + el.$adminBarCounter.html( '' + count + '' ); + } else { + el.$adminBarCounter.remove(); + } + + // Remove notification. + var $nextMessage = el.$nextMessage.length < 1 ? el.$prevMessage : el.$nextMessage; + + if ( $nextMessage.length === 0 ) { + el.$notifications.remove(); + } else { + el.$currentMessage.remove(); + $nextMessage.addClass( 'current' ); + app.updateNavigation(); + } + } ); + }, + + /** + * Click on the Next notification button. + * + * @since 2.3.0 + * + * @param {object} event Event object. + */ + navNext: function( event ) { + + if ( el.$nextButton.hasClass( 'disabled' ) ) { + return; + } + + el.$currentMessage.removeClass( 'current' ); + el.$nextMessage.addClass( 'current' ); + + app.updateNavigation(); + }, + + /** + * Click on the Previous notification button. + * + * @since 2.3.0 + * + * @param {object} event Event object. + */ + navPrev: function( event ) { + + if ( el.$prevButton.hasClass( 'disabled' ) ) { + return; + } + + el.$currentMessage.removeClass( 'current' ); + el.$prevMessage.addClass( 'current' ); + + app.updateNavigation(); + }, + + /** + * Update navigation buttons. + * + * @since 2.3.0 + */ + updateNavigation: function() { + + el.$currentMessage = el.$notifications.find( '.wp-mail-smtp-notifications-message.current' ); + el.$nextMessage = el.$currentMessage.next( '.wp-mail-smtp-notifications-message' ); + el.$prevMessage = el.$currentMessage.prev( '.wp-mail-smtp-notifications-message' ); + + if ( el.$nextMessage.length === 0 ) { + el.$nextButton.addClass( 'disabled' ); + } else { + el.$nextButton.removeClass( 'disabled' ); + } + + if ( el.$prevMessage.length === 0 ) { + el.$prevButton.addClass( 'disabled' ); + } else { + el.$prevButton.removeClass( 'disabled' ); + } + }, + }; + + return app; + +}( document, window, jQuery ) ); + +// Initialize. +WPMailSMTPAdminNotifications.init(); diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.min.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.min.js new file mode 100644 index 00000000..3c4b3f00 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-notifications.min.js @@ -0,0 +1 @@ +var WPMailSMTPAdminNotifications=window.WPMailSMTPAdminNotifications||(t=>{var a={$notifications:t("#wp-mail-smtp-notifications"),$nextButton:t("#wp-mail-smtp-notifications .navigation .next"),$prevButton:t("#wp-mail-smtp-notifications .navigation .prev"),$adminBarCounter:t("#wp-admin-bar-wp-mail-smtp-menu .wp-mail-smtp-admin-bar-menu-notification-counter")},i={init:function(){t(i.ready)},ready:function(){i.updateNavigation(),i.events()},events:function(){a.$notifications.on("click",".dismiss",i.dismiss).on("click",".next",i.navNext).on("click",".prev",i.navPrev)},dismiss:function(e){var n;0!==a.$currentMessage.length&&(n={action:"wp_mail_smtp_notification_dismiss",nonce:wp_mail_smtp.nonce,id:a.$currentMessage.data("message-id")},t.post(ajaxurl,n,function(e){e.success&&(1<(e=parseInt(a.$adminBarCounter.text(),10))?a.$adminBarCounter.html(""+--e+""):a.$adminBarCounter.remove(),0===(e=a.$nextMessage.length<1?a.$prevMessage:a.$nextMessage).length?a.$notifications.remove():(a.$currentMessage.remove(),e.addClass("current"),i.updateNavigation()))}))},navNext:function(e){a.$nextButton.hasClass("disabled")||(a.$currentMessage.removeClass("current"),a.$nextMessage.addClass("current"),i.updateNavigation())},navPrev:function(e){a.$prevButton.hasClass("disabled")||(a.$currentMessage.removeClass("current"),a.$prevMessage.addClass("current"),i.updateNavigation())},updateNavigation:function(){a.$currentMessage=a.$notifications.find(".wp-mail-smtp-notifications-message.current"),a.$nextMessage=a.$currentMessage.next(".wp-mail-smtp-notifications-message"),a.$prevMessage=a.$currentMessage.prev(".wp-mail-smtp-notifications-message"),0===a.$nextMessage.length?a.$nextButton.addClass("disabled"):a.$nextButton.removeClass("disabled"),0===a.$prevMessage.length?a.$prevButton.addClass("disabled"):a.$prevButton.removeClass("disabled")}};return i})((document,window,jQuery));WPMailSMTPAdminNotifications.init(); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.js new file mode 100644 index 00000000..0339fa05 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-recommendations.js @@ -0,0 +1,282 @@ +/* global wp_mail_smtp_recommendations, wp_mail_smtp */ + +/** + * @param wp_mail_smtp_recommendations.plugin_page.activated + * @param wp_mail_smtp_recommendations.plugin_page.activated_pro + * @param wp_mail_smtp_recommendations.plugin_page.download_now + * @param wp_mail_smtp_recommendations.plugin_page.error_could_not_activate + * @param wp_mail_smtp_recommendations.plugin_page.error_could_not_install + * @param wp_mail_smtp_recommendations.plugin_page.is_activated + * @param wp_mail_smtp_recommendations.plugin_page.license_level + * @param wp_mail_smtp_recommendations.plugin_page.result_status + * @param wp_mail_smtp_recommendations.plugin_page.plugins_page + * @param wp_mail_smtp_recommendations.plugin_page.setup_status + * @param wp_mail_smtp_recommendations.plugin_page.step3_button_url + * @param wp_mail_smtp_recommendations.plugin_page.manual_activate_url + * @param wp_mail_smtp_recommendations.plugin_page.manual_install_url + */ + +'use strict'; + +const WPMailSMTPRecommendations = {}; + +/** + * Plugin recommendation page. + * + * @since 4.9.0 + */ +WPMailSMTPRecommendations.plugin_page = ( function( document, window, $ ) { + + /** + * Elements. + * + * @since 4.9.0 + * + * @type {object} + */ + let el = {}; + + /** + * Public functions and properties. + * + * @since 4.9.0 + * + * @type {object} + */ + const app = { + + /** + * Start the engine. + * + * @since 4.9.0 + */ + init: () => { + $( app.ready ); + }, + + /** + * Document ready. + * + * @since 4.9.0 + */ + ready: () => { + app.initVars(); + app.events(); + }, + + /** + * Init variables. + * + * @since 4.9.0 + */ + initVars: () => { + el = { + $stepInstall: $( 'section.step-install' ), + $stepInstallNum: $( 'section.step-install .num img' ), + $stepSetup: $( 'section.step-setup' ), + $stepSetupNum: $( 'section.step-setup .num img' ), + $stepResult: $( 'section.step-result' ), + $stepResultNum: $( 'section.step-result .num img' ), + }; + }, + + /** + * Register JS events. + * + * @since 4.9.0 + */ + events: () => { + + // Step the 'Install' button click. + el.$stepInstall.on( 'click', 'button', app.stepInstallClick ); + + // Step 'Setup' button click. + el.$stepSetup.on( 'click', 'button', app.gotoURL ); + + // Step the 'Addon' button click. + el.$stepResult.on( 'click', 'button', app.gotoURL ); + }, + + /** + * Step the 'Install' button click. + * + * @since 4.9.0 + * + * @param {Event} e Event object. + */ + stepInstallClick: ( e ) => { + const $btn = $( e.currentTarget ); + + if ( $btn.hasClass( 'disabled' ) ) { + return; + } + + const action = $btn.attr( 'data-action' ); + + let task = ''; + + switch ( action ) { + case 'activate': + task = 'about_plugin_activate'; + $btn.html( wp_mail_smtp_recommendations.plugin_page.activating ); + break; + + case 'install': + task = 'about_plugin_install'; + $btn.html( wp_mail_smtp_recommendations.plugin_page.installing ); + break; + + case 'goto-url': + window.location.href = $btn.attr( 'data-url' ); + return; + + default: + return; + } + + $btn.addClass( 'disabled' ); + app.showSpinner( el.$stepInstallNum ); + + const plugin = $btn.attr( 'data-plugin' ); + + const data = { + action: 'wp_mail_smtp_ajax', + task, + nonce : wp_mail_smtp.nonce, + plugin, + }; + $.post( wp_mail_smtp.ajax_url, data ) + .done( function( res ) { + app.stepInstallDone( res, $btn, action ); + } ) + .always( function() { + app.hideSpinner( el.$stepInstallNum ); + } ); + }, + + /** + * Done part of the step 'Install'. + * + * @since 4.9.0 + * + * @param {object} res Result of $.post() query. + * @param {jQuery} $btn Button. + * @param {string} action Action (for more info look at the app.stepInstallClick() function). + */ + stepInstallDone: ( res, $btn, action ) => { // eslint-disable-line complexity + const success = 'install' === action ? res.success && res.data.is_activated : res.success, + provider = $btn.data( 'provider' ); + + if ( success ) { + el.$stepInstallNum.attr( 'src', el.$stepInstallNum.attr( 'src' ).replace( 'step-1.', 'complete.' ) ); + $btn.addClass( 'grey' ).removeClass( 'button-primary' ).html( wp_mail_smtp_recommendations.plugin_page.activated ); + app.stepInstallPluginStatus( provider ); + } else { + const activationFail = ( 'install' === action && res.success && ! res.data.is_activated ) || 'activate' === action, + installUrl = wp_mail_smtp_recommendations.plugin_page[ provider + '_manual_install_url' ] || '', + activateUrl = wp_mail_smtp_recommendations.plugin_page[ provider + '_manual_activate_url' ] || '', + url = ! activationFail ? installUrl : activateUrl, + msg = ! activationFail ? wp_mail_smtp_recommendations.plugin_page.error_could_not_install : wp_mail_smtp_recommendations.plugin_page.error_could_not_activate, + btn = ! activationFail ? wp_mail_smtp_recommendations.plugin_page.download_now : wp_mail_smtp_recommendations.plugin_page.plugins_page; + + $btn.removeClass( 'grey disabled' ).html( btn ).attr( 'data-action', 'goto-url' ).attr( 'data-url', url ); + $btn.after( '

    ' + 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{var t=n(t.currentTarget),e=t.attr("data-url");!t.hasClass("disabled")&&e&&l.open(e,"_blank")},showSpinner:t=>{t.siblings("i.loader").removeClass("hidden")},hideSpinner:t=>{t.show(),t.siblings("i.loader").addClass("hidden")}};return i})((document,window),jQuery),WPMailSMTPRecommendations.plugin_page.init(); \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-tools-debug-events.js b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-tools-debug-events.js new file mode 100644 index 00000000..9b4ae8d3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/assets/js/smtp-tools-debug-events.js @@ -0,0 +1,360 @@ +/* global wp_mail_smtp_tools_debug_events, ajaxurl, flatpickr */ +/** + * WPMailSMTP Debug Events functionality. + * + * @since 3.0.0 + */ + +'use strict'; + +var WPMailSmtpDebugEvents = window.WPMailSmtpDebugEvents || ( function( document, window, $ ) { + + /** + * Elements. + * + * @since 3.0.0 + * + * @type {object} + */ + var el = { + $debugEventsPage: $( '.wp-mail-smtp-tab-tools-debug-events' ), + $dateFlatpickr: $( '.wp-mail-smtp-filter-date-selector' ), + }; + + /** + * Public functions and properties. + * + * @since 3.0.0 + * + * @type {object} + */ + var app = { + + /** + * Start the engine. + * + * @since 3.0.0 + */ + init: function() { + + $( app.ready ); + }, + + /** + * Document ready. + * + * @since 3.0.0 + */ + ready: function() { + + app.initDateRange(); + app.events(); + + // Open debug event popup from the query string. + var searchParams = new URLSearchParams( location.search ); + + if ( searchParams.has( 'debug_event_id' ) ) { + app.openDebugEventPopup( searchParams.get( 'debug_event_id' ) ); + } + }, + + /** + * Register JS events. + * + * @since 3.0.0 + */ + events: function() { + + el.$debugEventsPage.on( 'click', '#wp-mail-smtp-reset-filter .reset', app.resetFilter ); + el.$debugEventsPage.on( 'click', '#wp-mail-smtp-delete-all-debug-events-button', app.deleteAllDebugEvents ); + el.$debugEventsPage.on( 'click', '.js-wp-mail-smtp-debug-event-preview', app.eventClicked ); + }, + + /** + * Init Flatpickr at Date Range field. + * + * @since 3.0.0 + */ + initDateRange: function() { + + var langCode = wp_mail_smtp_tools_debug_events.lang_code, + flatpickrLocale = { + rangeSeparator: ' - ', + }; + + if ( + flatpickr !== 'undefined' && + Object.prototype.hasOwnProperty.call( flatpickr, 'l10ns' ) && + Object.prototype.hasOwnProperty.call( flatpickr.l10ns, langCode ) + ) { + flatpickrLocale = flatpickr.l10ns[ langCode ]; + flatpickrLocale.rangeSeparator = ' - '; + } + + el.$dateFlatpickr.flatpickr( { + altInput : true, + altFormat : 'M j, Y', + dateFormat: 'Y-m-d', + locale : flatpickrLocale, + mode : 'range' + } ); + }, + + /** + * Reset filter handler. + * + * @since 3.0.0 + */ + resetFilter: function() { + + var $form = $( this ).parents( 'form' ); + + $form.find( $( this ).data( 'scope' ) ).find( 'input,select' ).each( function() { + + var $this = $( this ); + if ( app.isIgnoredForResetInput( $this ) ) { + return; + } + app.resetInput( $this ); + } ); + + // Submit the form. + $form.submit(); + }, + + /** + * Reset input. + * + * @since 3.0.0 + * + * @param {object} $input Input element. + */ + resetInput: function( $input ) { + + switch ( $input.prop( 'tagName' ).toLowerCase() ) { + case 'input': + $input.val( '' ); + break; + case 'select': + $input.val( $input.find( 'option' ).first().val() ); + break; + } + }, + + /** + * Input is ignored for reset. + * + * @since 3.0.0 + * + * @param {object} $input Input element. + * + * @returns {boolean} Is ignored. + */ + isIgnoredForResetInput: function( $input ) { + + return [ 'submit', 'hidden' ].indexOf( ( $input.attr( 'type' ) || '' ).toLowerCase() ) !== -1 && + ! $input.hasClass( 'flatpickr-input' ); + }, + + /** + * Process the click on the delete all debug events button. + * + * @since 3.0.0 + * + * @param {object} event jQuery event. + */ + deleteAllDebugEvents: function( event ) { + + event.preventDefault(); + + var $btn = $( event.target ); + + $.confirm( { + backgroundDismiss: false, + escapeKey: true, + animationBounce: 1, + closeIcon: true, + type: 'orange', + icon: app.getModalIcon( 'exclamation-circle-solid-orange' ), + title: wp_mail_smtp_tools_debug_events.texts.notice_title, + content: wp_mail_smtp_tools_debug_events.texts.delete_all_notice, + buttons: { + confirm: { + text: wp_mail_smtp_tools_debug_events.texts.yes, + btnClass: 'btn-confirm', + keys: [ 'enter' ], + action: function() { + app.executeAllDebugEventsDeletion( $btn ); + } + }, + cancel: { + text: wp_mail_smtp_tools_debug_events.texts.cancel, + btnClass: 'btn-cancel', + } + } + } ); + }, + + /** + * Process the click on the event item. + * + * @since 3.0.0 + * + * @param {object} event jQuery event. + */ + eventClicked: function( event ) { + + event.preventDefault(); + + app.openDebugEventPopup( $( this ).data( 'event-id' ) ); + }, + + /** + * Open debug event popup. + * + * @since 3.5.0 + * + * @param {int} eventId Debug event ID. + */ + openDebugEventPopup: function( eventId ) { + + var data = { + action: 'wp_mail_smtp_debug_event_preview', + id: eventId, + nonce: $( '#wp-mail-smtp-debug-events-nonce', el.$debugEventsPage ).val() + }; + + var popup = $.alert( { + backgroundDismiss: true, + escapeKey: true, + animationBounce: 1, + type: 'blue', + icon: app.getModalIcon( 'info-circle-blue' ), + title: false, + content: wp_mail_smtp_tools_debug_events.loader, + boxWidth: '550px', + buttons: { + confirm: { + text: wp_mail_smtp_tools_debug_events.texts.close, + btnClass: 'btn-confirm', + keys: [ 'enter' ] + } + }, + onOpenBefore: function() { + this.$contentPane.addClass( 'no-scroll' ); + } + } ); + + $.post( ajaxurl, data, function( response ) { + if ( response.success ) { + popup.setTitle( response.data.title ); + popup.setContent( response.data.content ); + } else { + popup.setIcon( app.getModalIcon( 'exclamation-circle-regular-red' ) ); + popup.setType( 'red' ); + popup.setContent( response.data ); + } + } ).fail( function() { + popup.setContent( wp_mail_smtp_tools_debug_events.texts.error_occurred ); + } ); + }, + + /** + * AJAX call for deleting all debug events. + * + * @since 3.0.0 + * + * @param {object} $btn jQuery object of the clicked button. + */ + executeAllDebugEventsDeletion: function( $btn ) { + + $btn.prop( 'disabled', true ); + + var data = { + action: 'wp_mail_smtp_delete_all_debug_events', + nonce: $( '#wp-mail-smtp-debug-events-nonce', el.$debugEventsPage ).val() + }; + + $.post( ajaxurl, data, function( response ) { + var message = response.data, + icon, + type, + callback; + + if ( response.success ) { + icon = 'check-circle-solid-green'; + type = 'green'; + callback = function() { + location.reload(); + return false; + }; + } else { + icon = 'exclamation-circle-regular-red'; + type = 'red'; + } + + app.displayModal( message, icon, type, callback ); + $btn.prop( 'disabled', false ); + } ).fail( function() { + app.displayModal( wp_mail_smtp_tools_debug_events.texts.error_occurred, 'exclamation-circle-regular-red', 'red' ); + $btn.prop( 'disabled', false ); + } ); + }, + + /** + * Display the modal with provided text and icon. + * + * @since 3.0.0 + * + * @param {string} message The message to be displayed in the modal. + * @param {string} icon The icon name from /assets/images/font-awesome/ to be used in modal. + * @param {string} type The type of the message (red, green, orange, blue, purple, dark). + * @param {Function} actionCallback The action callback function. + */ + displayModal: function( message, icon, type, actionCallback ) { + + type = type || 'default'; + actionCallback = actionCallback || function() {}; + + $.alert( { + backgroundDismiss: true, + escapeKey: true, + animationBounce: 1, + type: type, + closeIcon: true, + title: false, + icon: icon ? app.getModalIcon( icon ) : '', + content: message, + buttons: { + confirm: { + text: wp_mail_smtp_tools_debug_events.texts.ok, + btnClass: 'wp-mail-smtp-btn wp-mail-smtp-btn-md', + keys: [ 'enter' ], + action: actionCallback + } + } + } ); + }, + + /** + * Returns prepared modal icon. + * + * @since 3.0.0 + * + * @param {string} icon The icon name from /assets/images/font-awesome/ to be used in modal. + * + * @returns {string} Modal icon HTML. + */ + getModalIcon: function( icon ) { + + return '">
    ' ); + }, + + /** + * Insert remove connection buttons to all providers if connections count more than one per provider. + * + * @since 3.5.0 + */ + insertRemoveConnectionButtons: function() { + + $( '.wp-mail-smtp-setting-row-alert-options' ).each( function() { + var $connectionsOptions = $( this ).find( '.wp-mail-smtp-setting-row-alert-connection-options' ); + + if ( $connectionsOptions.length > 1 ) { + $connectionsOptions.each( function() { + app.insertRemoveConnectionButton( $( this ) ); + } ); + } + } ); + }, + + /** + * Enable the test alerts button when at least one + * alert channel is enabled, or disable it otherwise. + * + * @since 3.9.0 + */ + toggleTestAlertsButton: function() { + + var enabledChannelsCount = $( '.wp-mail-smtp-tab-alerts .js-wp-mail-smtp-setting-alert-enabled:checked' ).length; + var $button = $( '#js-wp-mail-smtp-btn-test-alerts' ); + + if ( enabledChannelsCount > 0 ) { + $button.prop( 'disabled', false ); + } else { + $button.prop( 'disabled', true ); + } + }, + + /** + * Submit test alerts action. + * + * @since 3.9.0 + */ + submitTestAlerts: function() { + + // If settings have been changed, display an alert + // and bail early. + if ( WPMailSMTP.Admin.Settings.pluginSettingsChanged ) { + $.confirm( { + backgroundDismiss: true, + escapeKey: true, + animationBounce: 1, + closeIcon: true, + type: 'blue', + icon: app.getModalIcon( 'info-circle-blue' ), + title: wp_mail_smtp_alerts.texts.alert_title, + content: wp_mail_smtp_alerts.texts.alert_content, + buttons: { + confirm: { + text: wp_mail_smtp_alerts.texts.ok, + btnClass: 'btn-confirm', + keys: [ 'enter' ], + }, + } + } ); + + return; + } + + $.post( ajaxurl, { + action: 'wp_mail_smtp_ajax', + nonce: wp_mail_smtp.nonce, + task: 'test_alerts', + } ).then( function() { + window.location.replace( window.location.href ); + } ); + }, + + /** + * Returns prepared modal icon. + * + * @since 3.9.0 + * + * @param {string} icon The icon name from /assets/images/font-awesome/ to be used in modal. + * + * @returns {string} Modal icon HTML. + */ + getModalIcon: function( icon ) { + + return '">
    ')},insertRemoveConnectionButtons:function(){o(".wp-mail-smtp-setting-row-alert-options").each(function(){var t=o(this).find(".wp-mail-smtp-setting-row-alert-connection-options");1
    ").attr({name:p,class:"wp-mail-smtp-conditional__value"}),e.property.choices&&l.each(e.property.choices,function(i,t){o.append(l(" + widget_meta( 'get', 'hide_graph' ); + ?> + + + + + esc_html__( 'Confirmed Emails', 'wp-mail-smtp' ), + 'sent' => esc_html__( 'Unconfirmed Emails', 'wp-mail-smtp' ), + 'unsent' => esc_html__( 'Failed Emails', 'wp-mail-smtp' ), + ]; + + if ( Helpers::mailer_without_send_confirmation() ) { + unset( $options['sent'] ); + $options['delivered'] = esc_html__( 'Sent Emails', 'wp-mail-smtp' ); + } + + ?> + + + +
    + +
    +
    +

    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +

    +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +
    + get_email_stats_data(); + ?> + + + + '; + } + + $count++; + ?> + + + +
    +
    + <?php esc_attr_e( 'Table cell icon', 'wp-mail-smtp' ); ?> + + + +
    +
    + + get_total_emails_sent(); + + $output_data = [ + 'all' => [ + 'type' => 'all', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/total.svg', + /* translators: %d number of total emails sent. */ + 'title' => esc_html( sprintf( esc_html__( '%d total', 'wp-mail-smtp' ), $total_sent ) ), + ], + 'delivered' => [ + 'type' => 'delivered', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/delivered.svg', + /* translators: %s fixed string of 'N/A'. */ + 'title' => esc_html( sprintf( esc_html__( 'Confirmed %s', 'wp-mail-smtp' ), 'N/A' ) ), + ], + 'sent' => [ + 'type' => 'sent', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/sent.svg', + /* translators: %s fixed string of 'N/A'. */ + 'title' => esc_html( sprintf( esc_html__( 'Unconfirmed %s', 'wp-mail-smtp' ), 'N/A' ) ), + ], + 'unsent' => [ + 'type' => 'unsent', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/wp/unsent.svg', + /* translators: %s fixed string of 'N/A'. */ + 'title' => esc_html( sprintf( esc_html__( 'Failed %s', 'wp-mail-smtp' ), 'N/A' ) ), + ], + ]; + + if ( Helpers::mailer_without_send_confirmation() ) { + + // Skip the 'unconfirmed sent' section. + unset( $output_data['sent'] ); + + // Change the 'confirmed sent' section into a general 'sent' section. + $output_data['delivered']['title'] = esc_html( /* translators: %s fixed string of 'N/A'. */ + sprintf( esc_html__( 'Sent %s', 'wp-mail-smtp' ), 'N/A' ) + ); + } + + return $output_data; + } + + /** + * Get/set a widget meta. + * + * @since 2.9.0 + * + * @param string $action Possible value: 'get' or 'set'. + * @param string $meta Meta name. + * @param int $value Value to set. + * + * @return mixed + */ + protected function widget_meta( $action, $meta, $value = 0 ) { + + $allowed_actions = [ 'get', 'set' ]; + + if ( ! in_array( $action, $allowed_actions, true ) ) { + return false; + } + + if ( $action === 'get' ) { + return $this->get_widget_meta( $meta ); + } + + $meta_key = $this->get_widget_meta_key( $meta ); + $value = sanitize_key( $value ); + + if ( 'set' === $action && ! empty( $value ) ) { + return update_user_meta( get_current_user_id(), $meta_key, $value ); + } + + if ( 'set' === $action && empty( $value ) ) { + return delete_user_meta( get_current_user_id(), $meta_key ); + } + + return false; + } + + /** + * Get the widget meta value. + * + * @since 3.9.0 + * + * @param string $meta Meta name. + * + * @return mixed + */ + private function get_widget_meta( $meta ) { + + $defaults = [ + 'hide_graph' => 0, + 'hide_summary_report_email_block' => 0, + 'hide_email_alerts_banner' => 0, + ]; + + $meta_value = get_user_meta( get_current_user_id(), $this->get_widget_meta_key( $meta ), true ); + + if ( ! empty( $meta_value ) ) { + return $meta_value; + } + + if ( isset( $defaults[ $meta ] ) ) { + return $defaults[ $meta ]; + } + + return null; + } + + /** + * Retrieve the meta key. + * + * @since 3.9.0 + * + * @param string $meta Meta name. + * + * @return string + */ + private function get_widget_meta_key( $meta ) { + + return 'wp_mail_smtp_' . static::SLUG . '_' . $meta; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/DebugEvents.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/DebugEvents.php new file mode 100644 index 00000000..eddc64a8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/DebugEvents.php @@ -0,0 +1,473 @@ +is_const_defined( 'debug_events', 'retention_period' ) ) { + add_action( 'admin_init', [ $this, 'detect_debug_events_retention_period_constant_change' ] ); + } + } + + /** + * Detect debug events retention period constant change. + * + * @since 3.6.0 + */ + public function detect_debug_events_retention_period_constant_change() { + + if ( ! WP::in_wp_admin() ) { + return; + } + + if ( Options::init()->is_const_changed( 'debug_events', 'retention_period' ) ) { + ( new DebugEventsCleanupTask() )->cancel(); + } + } + + /** + * Cancel previous debug events cleanup task if retention period option was changed. + * + * @since 3.6.0 + * + * @param array $options Currently processed options passed to a filter hook. + * + * @return array + */ + public function maybe_cancel_debug_events_cleanup_task( $options ) { + + if ( isset( $options['debug_events']['retention_period'] ) ) { + // If this option has changed, cancel the recurring cleanup task and init again. + if ( Options::init()->is_option_changed( $options['debug_events']['retention_period'], 'debug_events', 'retention_period' ) ) { + ( new DebugEventsCleanupTask() )->cancel(); + } + } + + return $options; + } + + /** + * Process AJAX request for deleting all debug event entries. + * + * @since 3.0.0 + */ + public function process_ajax_delete_all_debug_events() { + + if ( + empty( $_POST['nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' ) + ) { + wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) ); + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); + } + + if ( ! self::is_valid_db() ) { + wp_send_json_error( esc_html__( 'For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue.', 'wp-mail-smtp' ) ); + } + + global $wpdb; + + $table = self::get_table_name(); + + $sql = "TRUNCATE TABLE `$table`;"; + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $result = $wpdb->query( $sql ); + + if ( $result !== false ) { + wp_send_json_success( esc_html__( 'All debug event entries were deleted successfully.', 'wp-mail-smtp' ) ); + } + + wp_send_json_error( + sprintf( /* translators: %s - WPDB error message. */ + esc_html__( 'There was an issue while trying to delete all debug event entries. Error message: %s', 'wp-mail-smtp' ), + $wpdb->last_error + ) + ); + } + + /** + * Process AJAX request for debug event preview. + * + * @since 3.0.0 + */ + public function process_ajax_debug_event_preview() { + + if ( + empty( $_POST['nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' ) + ) { + wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) ); + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); + } + + if ( ! self::is_valid_db() ) { + wp_send_json_error( esc_html__( 'For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue.', 'wp-mail-smtp' ) ); + } + + $event_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : false; + + if ( empty( $event_id ) ) { + wp_send_json_error( esc_html__( 'No Debug Event ID provided!', 'wp-mail-smtp' ) ); + } + + $event = new Event( $event_id ); + + wp_send_json_success( + [ + 'title' => $event->get_title(), + 'content' => $event->get_details_html(), + ] + ); + } + + /** + * Add the debug event to the DB. + * + * @since 3.0.0 + * + * @param string $message The event's message. + * @param int $type The event's type. + * + * @return bool|int + */ + public static function add( $message = '', $type = 0 ) { + + if ( ! self::is_valid_db() ) { + return false; + } + + if ( ! in_array( $type, array_keys( Event::get_types() ), true ) ) { + return false; + } + + if ( $type === Event::TYPE_DEBUG && ! self::is_debug_enabled() ) { + return false; + } + + try { + $event = new Event(); + $event->set_type( $type ); + $event->set_content( $message ); + $event->set_initiator(); + + return $event->save()->get_id(); + } catch ( \Exception $exception ) { + return false; + } + } + + /** + * Save the debug message. + * + * @since 3.0.0 + * @since 3.5.0 Returns Event ID. + * + * @param string $message The debug message. + * + * @return bool|int + */ + public static function add_debug( $message = '' ) { + + return self::add( $message, Event::TYPE_DEBUG ); + } + + /** + * Add a debug event subject to a throttle window — only logs if no other event + * with the same throttle key has been logged within the TTL. + * + * Useful for background errors that can fire on every page load (e.g. expired + * OAuth token refresh failures) to avoid flooding the events table. + * + * @since 4.9.0 + * + * @param string $message The event's message. + * @param string $throttle_key Unique key identifying this event's throttle bucket. + * Automatically prefixed with `wp_mail_smtp_` before + * being used as a transient key. + * @param int $ttl Throttle window in seconds. Default 5 minutes. + * @param int $type The event's type. Default Event::TYPE_ERROR (0). + * + * @return bool|int Event ID on success, false if throttled or save failed. + */ + public static function add_throttled( $message, $throttle_key, $ttl = 5 * MINUTE_IN_SECONDS, $type = 0 ) { + + $transient_key = 'wp_mail_smtp_' . $throttle_key; + + if ( get_transient( $transient_key ) ) { + return false; + } + + $event_id = self::add( $message, $type ); + + if ( $event_id !== false ) { + set_transient( $transient_key, time(), $ttl ); + } + + return $event_id; + } + + /** + * Get the debug message from the provided debug event IDs. + * + * @since 3.0.0 + * + * @param array|string|int $ids A single or a list of debug event IDs. + * + * @return array + */ + public static function get_debug_messages( $ids ) { + + global $wpdb; + + if ( empty( $ids ) ) { + return []; + } + + if ( ! self::is_valid_db() ) { + return []; + } + + // Convert to a string. + if ( is_array( $ids ) ) { + $ids = implode( ',', $ids ); + } + + $ids = explode( ',', (string) $ids ); + $ids = array_map( 'intval', $ids ); + $placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) ); + + $table = self::get_table_name(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $events_data = $wpdb->get_results( + $wpdb->prepare( "SELECT id, content, initiator, event_type, created_at FROM {$table} WHERE id IN ( {$placeholders} )", $ids ) + ); + // phpcs:enable + + if ( empty( $events_data ) ) { + return []; + } + + return array_map( + function ( $event_item ) { + $event = new Event( $event_item ); + + return $event->get_short_details(); + }, + $events_data + ); + } + + /** + * Returns the number of error debug events in a given time span. + * + * By default it returns the number of error debug events in the last 30 days. + * + * @since 3.9.0 + * + * @param string $span_of_time The time span to count the events for. Default '-30 days'. + * + * @return int|WP_Error The number of error debug events or WP_Error on failure. + */ + public static function get_error_debug_events_count( $span_of_time = '-30 days' ) { + + $timestamp = strtotime( $span_of_time ); + + if ( ! $timestamp || $timestamp > time() ) { + return new WP_Error( 'wp_mail_smtp_admin_debug_events_get_error_debug_events_count_invalid_time', 'Invalid time span.' ); + } + + if ( ! self::is_valid_db() ) { + return 0; + } + + $transient_key = self::ERROR_DEBUG_EVENTS_TRANSIENT . '_' . sanitize_title_with_dashes( $span_of_time ); + $cached_error_events_count = get_transient( $transient_key ); + + if ( $cached_error_events_count !== false ) { + return (int) $cached_error_events_count; + } + + global $wpdb; + + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + $sql = $wpdb->prepare( + 'SELECT COUNT(*) FROM `%1$s` WHERE event_type = %2$d AND created_at >= "%3$s"', + self::get_table_name(), + Event::TYPE_ERROR, + gmdate( WP::datetime_mysql_format(), $timestamp ) + ); + // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $error_events_count = (int) $wpdb->get_var( $sql ); + + set_transient( $transient_key, $error_events_count, HOUR_IN_SECONDS ); + + return $error_events_count; + } + + /** + * Register the screen options for the debug events page. + * + * @since 3.0.0 + */ + public function screen_options() { + + $screen = get_current_screen(); + + if ( + ! is_object( $screen ) || + strpos( $screen->id, 'wp-mail-smtp_page_wp-mail-smtp-tools' ) === false || + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ! isset( $_GET['tab'] ) || $_GET['tab'] !== 'debug-events' + ) { + return; + } + + add_screen_option( + 'per_page', + [ + 'label' => esc_html__( 'Number of events per page:', 'wp-mail-smtp' ), + 'option' => 'wp_mail_smtp_debug_events_per_page', + 'default' => EventsCollection::PER_PAGE, + ] + ); + } + + /** + * Set the screen options for the debug events page. + * + * @since 3.0.0 + * + * @param bool $keep Whether to save or skip saving the screen option value. + * @param string $option The option name. + * @param int $value The number of items to use. + * + * @return bool|int + */ + public function set_screen_options( $keep, $option, $value ) { + + if ( 'wp_mail_smtp_debug_events_per_page' === $option ) { + return (int) $value; + } + + return $keep; + } + + /** + * Whether the email debug for debug events is enabled or not. + * + * @since 3.0.0 + * + * @return bool + */ + public static function is_debug_enabled() { + + return (bool) Options::init()->get( 'debug_events', 'email_debug' ); + } + + /** + * Get the debug events page URL. + * + * @since 3.0.0 + * + * @return string + */ + public static function get_page_url() { + + return add_query_arg( + [ + 'tab' => 'debug-events', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) + ); + } + + /** + * Get the DB table name. + * + * @since 3.0.0 + * + * @return string Table name, prefixed. + */ + public static function get_table_name() { + + global $wpdb; + + return $wpdb->prefix . 'wpmailsmtp_debug_events'; + } + + /** + * Whether the DB table exists. + * + * @since 3.0.0 + * + * @return bool + */ + public static 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; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Event.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Event.php new file mode 100644 index 00000000..c187380f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Event.php @@ -0,0 +1,597 @@ +populate_event( $id_or_row ); + } + + /** + * Get and prepare the event data. + * + * @since 3.0.0 + * + * @param int|object $id_or_row The event ID or object with event attributes. + */ + private function populate_event( $id_or_row ) { + + $event = null; + + if ( is_numeric( $id_or_row ) ) { + // Get by ID. + $collection = new EventsCollection( [ 'id' => (int) $id_or_row ] ); + $events = $collection->get(); + + if ( $events->valid() ) { + $event = $events->current(); + } + } elseif ( + is_object( $id_or_row ) && + isset( + $id_or_row->id, + $id_or_row->content, + $id_or_row->initiator, + $id_or_row->event_type, + $id_or_row->created_at + ) + ) { + $event = $id_or_row; + } + + if ( $event !== null ) { + foreach ( get_object_vars( $event ) as $key => $value ) { + $this->{$key} = $value; + } + } + } + + /** + * Event ID as per our DB table. + * + * @since 3.0.0 + * + * @return int + */ + public function get_id() { + + return (int) $this->id; + } + + /** + * Get the event title. + * + * @since 3.0.0 + * + * @return string + */ + public function get_title() { + + /* translators: %d the event ID. */ + return sprintf( esc_html__( 'Event #%d', 'wp-mail-smtp' ), $this->get_id() ); + } + + /** + * Get the content of the event. + * + * @since 3.0.0 + * + * @return string + */ + public function get_content() { + + return $this->content; + } + + /** + * Get the event's type. + * + * @since 3.0.0 + * + * @return int + */ + public function get_type() { + + return (int) $this->event_type; + } + + /** + * Get the list of all event types. + * + * @since 3.0.0 + * + * @return array + */ + public static function get_types() { + + return [ + self::TYPE_ERROR => esc_html__( 'Error', 'wp-mail-smtp' ), + self::TYPE_DEBUG => esc_html__( 'Debug', 'wp-mail-smtp' ), + ]; + } + + /** + * Get human readable type name. + * + * @since 3.0.0 + * + * @return string + */ + public function get_type_name() { + + $types = self::get_types(); + + return isset( $types[ $this->get_type() ] ) ? $types[ $this->get_type() ] : ''; + } + + /** + * Get the date/time when this event was created. + * + * @since 3.0.0 + * + * @throws \Exception Emits exception on incorrect date. + * + * @return \DateTime + */ + public function get_created_at() { + + $timezone = new \DateTimeZone( 'UTC' ); + $date = false; + + if ( ! empty( $this->created_at ) ) { + $date = \DateTime::createFromFormat( WP::datetime_mysql_format(), $this->created_at, $timezone ); + } + + if ( $date === false ) { + $date = new \DateTime( 'now', $timezone ); + } + + return $date; + } + + /** + * Get the date/time when this event was created in a nicely formatted string. + * + * @since 3.0.0 + * + * @return string + */ + public function get_created_at_formatted() { + + try { + $date = $this->get_created_at(); + } catch ( \Exception $e ) { + $date = null; + } + + if ( empty( $date ) ) { + return esc_html__( 'N/A', 'wp-mail-smtp' ); + } + + return esc_html( + date_i18n( + WP::datetime_format(), + strtotime( get_date_from_gmt( $date->format( WP::datetime_mysql_format() ) ) ) + ) + ); + } + + /** + * Get the event's initiator raw data. + * Who called the `wp_mail` function? + * + * @since 3.0.0 + * + * @return array + */ + public function get_initiator_raw() { + + return json_decode( $this->initiator, true ); + } + + /** + * Get the event's initiator name. + * Which plugin/theme (or WP core) called the `wp_mail` function? + * + * @since 3.0.0 + * + * @return string + */ + public function get_initiator() { + + $initiator = (array) $this->get_initiator_raw(); + + if ( empty( $initiator['file'] ) ) { + return ''; + } + + return WP::get_initiator_name( $initiator['file'] ); + } + + /** + * Get the event's initiator file path. + * + * @since 3.0.0 + * + * @return string + */ + public function get_initiator_file_path() { + + $initiator = (array) $this->get_initiator_raw(); + + if ( empty( $initiator['file'] ) ) { + return ''; + } + + return $initiator['file']; + } + + /** + * Get the event's initiator file line. + * + * @since 3.0.0 + * + * @return string + */ + public function get_initiator_file_line() { + + $initiator = (array) $this->get_initiator_raw(); + + if ( empty( $initiator['line'] ) ) { + return ''; + } + + return $initiator['line']; + } + + /** + * Get the event's initiator backtrace. + * + * @since 3.6.0 + * + * @return array + */ + private function get_initiator_backtrace() { + + $initiator = (array) $this->get_initiator_raw(); + + if ( empty( $initiator['backtrace'] ) ) { + return []; + } + + return $initiator['backtrace']; + } + + /** + * Get the event preview HTML. + * + * @since 3.0.0 + * + * @return string + */ + public function get_details_html() { + + $initiator = $this->get_initiator(); + $initiator_backtrace = $this->get_initiator_backtrace(); + + ob_start(); + ?> +
    +
    + +
    +
    + + get_type_name() ); ?> +
    +
    + + get_created_at_formatted() ); ?> +
    +
    + +
    + ', $this->get_content() ), [ 'br' => [] ] ); ?> +
    +
    + +
    + +
    + +

    + get_initiator_file_path() ), + esc_html( $this->get_initiator_file_line() ) + ); + ?> + + +

    + +
    + $item ) { + printf( + /* translators: %1$d - index number; %2$s - function name; %3$s - file path; %4$s - line number. */ + esc_html__( '[%1$d] %2$s called at [%3$s:%4$s]', 'wp-mail-smtp' ), + $i, + isset( $item['class'] ) ? esc_html( $item['class'] . $item['type'] . $item['function'] ) : esc_html( $item['function'] ), + isset( $item['file'] ) ? esc_html( $item['file'] ) : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + isset( $item['line'] ) ? esc_html( $item['line'] ) : '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + echo '
    '; + } + ?> + +

    +
    +
    + +
    + get_initiator() ) ) { + $result[] = sprintf( + /* Translators: %s - Email initiator/source name. */ + esc_html__( 'Email Source: %s', 'wp-mail-smtp' ), + esc_html( $this->get_initiator() ) + ); + } + + $result[] = esc_html( $this->get_content() ); + + return implode( WP::EOL, $result ); + } + + /** + * Save a new or modified event in DB. + * + * @since 3.0.0 + * + * @throws \Exception When event init fails. + * + * @return Event New or updated event class instance. + */ + public function save() { + + global $wpdb; + + $table = DebugEvents::get_table_name(); + + if ( (bool) $this->get_id() ) { + // Update the existing DB table record. + $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $table, + [ + 'content' => $this->content, + 'initiator' => $this->initiator, + 'event_type' => $this->event_type, + 'created_at' => $this->get_created_at()->format( WP::datetime_mysql_format() ), + ], + [ + 'id' => $this->get_id(), + ], + [ + '%s', // content. + '%s', // initiator. + '%s', // type. + '%s', // created_at. + ], + [ + '%d', + ] + ); + + $event_id = $this->get_id(); + } else { + // Create a new DB table record. + $wpdb->insert( + $table, + [ + 'content' => $this->content, + 'initiator' => $this->initiator, + 'event_type' => $this->event_type, + 'created_at' => $this->get_created_at()->format( WP::datetime_mysql_format() ), + ], + [ + '%s', // content. + '%s', // initiator. + '%s', // type. + '%s', // created_at. + ] + ); + + $event_id = $wpdb->insert_id; + } + + try { + $event = new Event( $event_id ); + } catch ( \Exception $e ) { + $event = new Event(); + } + + return $event; + } + + /** + * Set the content of this event. + * + * @since 3.0.0 + * + * @param string|array $content The event's content. + */ + public function set_content( $content ) { + + if ( ! is_string( $content ) ) { + $this->content = wp_json_encode( $content ); + } else { + $this->content = wp_strip_all_tags( str_replace( '
    ', "\r\n", $content ), false ); + } + } + + /** + * Set the initiator by checking the backtrace for the wp_mail function call. + * + * @since 3.0.0 + */ + public function set_initiator() { + + $initiator = wp_mail_smtp()->get_wp_mail_initiator(); + + if ( empty( $initiator->get_file() ) ) { + return; + } + + $data['file'] = $initiator->get_file(); + + if ( ! empty( $initiator->get_line() ) ) { + $data['line'] = $initiator->get_line(); + } + + if ( DebugEvents::is_debug_enabled() ) { + $data['backtrace'] = $initiator->get_backtrace(); + } + + $this->initiator = wp_json_encode( $data ); + } + + /** + * Set the type of this event. + * + * @since 3.0.0 + * + * @param int $type The event's type. + */ + public function set_type( $type ) { + + $this->event_type = (int) $type; + } + + /** + * Whether the event instance is a valid entity to work with. + * + * @since 3.0.0 + */ + public function is_valid() { + + return ! ( empty( $this->id ) || empty( $this->created_at ) ); + } + + /** + * Whether this is an error event. + * + * @since 3.0.0 + * + * @return bool + */ + public function is_error() { + + return self::TYPE_ERROR === $this->get_type(); + } + + /** + * Whether this is a debug event. + * + * @since 3.0.0 + * + * @return bool + */ + public function is_debug() { + + return self::TYPE_DEBUG === $this->get_type(); + } +} + diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/EventsCollection.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/EventsCollection.php new file mode 100644 index 00000000..07b55b4c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/EventsCollection.php @@ -0,0 +1,421 @@ + 0 ] ); + * + * @since 3.0.0 + * + * @param array $params The events collection parameters. + */ + public function __construct( array $params = [] ) { + + $this->set_per_page(); + $this->params = $this->process_params( $params ); + } + + /** + * Set the per page attribute to the screen options value. + * + * @since 3.0.0 + */ + protected function set_per_page() { + + $per_page = (int) get_user_meta( + get_current_user_id(), + 'wp_mail_smtp_debug_events_per_page', + true + ); + + if ( $per_page < 1 ) { + $per_page = self::PER_PAGE; + } + + self::$per_page = $per_page; + } + + /** + * Verify, sanitize, and populate with default values + * all the passed parameters, which participate in DB queries. + * + * @since 3.0.0 + * + * @param array $params The events collection parameters. + * + * @return array + */ + public function process_params( $params ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $params = (array) $params; + $processed = []; + + /* + * WHERE. + */ + // Single ID. + if ( ! empty( $params['id'] ) ) { + $processed['id'] = (int) $params['id']; + } + + // Multiple IDs. + if ( + ! empty( $params['ids'] ) && + is_array( $params['ids'] ) + ) { + $processed['ids'] = array_unique( array_filter( array_map( 'intval', array_values( $params['ids'] ) ) ) ); + } + + // Type. + if ( + isset( $params['type'] ) && + in_array( $params['type'], array_keys( Event::get_types() ), true ) + ) { + $processed['type'] = (int) $params['type']; + } + + // Search. + if ( ! empty( $params['search'] ) ) { + $processed['search'] = sanitize_text_field( $params['search'] ); + } + + /* + * LIMIT. + */ + if ( ! empty( $params['offset'] ) ) { + $processed['offset'] = (int) $params['offset']; + } + + if ( ! empty( $params['per_page'] ) ) { + $processed['per_page'] = (int) $params['per_page']; + } + + /* + * Sent date. + */ + if ( ! empty( $params['date'] ) ) { + if ( is_string( $params['date'] ) ) { + $params['date'] = array_fill( 0, 2, $params['date'] ); + } elseif ( is_array( $params['date'] ) && count( $params['date'] ) === 1 ) { + $params['date'] = array_fill( 0, 2, $params['date'][0] ); + } + + // We pass array and treat it as a range from:to. + if ( is_array( $params['date'] ) && count( $params['date'] ) === 2 ) { + $date_start = WP::get_day_period_date( 'start_of_day', strtotime( $params['date'][0] ), 'Y-m-d H:i:s', true ); + $date_end = WP::get_day_period_date( 'end_of_day', strtotime( $params['date'][1] ), 'Y-m-d H:i:s', true ); + + if ( ! empty( $date_start ) && ! empty( $date_end ) ) { + $processed['date'] = [ $date_start, $date_end ]; + } + } + } + + // Merge missing values with defaults. + return wp_parse_args( + $processed, + $this->get_default_params() + ); + } + + /** + * Get the list of default params for a usual query. + * + * @since 3.0.0 + * + * @return array + */ + protected function get_default_params() { + + return [ + 'offset' => 0, + 'per_page' => self::$per_page, + 'order' => 'DESC', + 'orderby' => 'id', + 'search' => '', + ]; + } + + /** + * Get the SQL-ready string of WHERE part for a query. + * + * @since 3.0.0 + * + * @return string + */ + private function build_where() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + global $wpdb; + + $where = [ '1=1' ]; + + // Shortcut single ID or multiple IDs. + if ( ! empty( $this->params['id'] ) || ! empty( $this->params['ids'] ) ) { + if ( ! empty( $this->params['id'] ) ) { + $where[] = $wpdb->prepare( 'id = %d', $this->params['id'] ); + } elseif ( ! empty( $this->params['ids'] ) ) { + $where[] = 'id IN (' . implode( ',', $this->params['ids'] ) . ')'; + } + + // When some ID(s) defined - we should ignore all other possible filtering options. + return implode( ' AND ', $where ); + } + + // Type. + if ( isset( $this->params['type'] ) ) { + $where[] = $wpdb->prepare( 'event_type = %d', $this->params['type'] ); + } + + // Search. + if ( ! empty( $this->params['search'] ) ) { + $where[] = '(' . + $wpdb->prepare( + 'content LIKE %s', + '%' . $wpdb->esc_like( $this->params['search'] ) . '%' + ) + . ' OR ' . + $wpdb->prepare( + 'initiator LIKE %s', + '%' . $wpdb->esc_like( $this->params['search'] ) . '%' + ) + . ')'; + } + + // Sent date. + if ( + ! empty( $this->params['date'] ) && + is_array( $this->params['date'] ) && + count( $this->params['date'] ) === 2 + ) { + $where[] = $wpdb->prepare( + '( created_at >= %s AND created_at <= %s )', + $this->params['date'][0], + $this->params['date'][1] + ); + } + + return implode( ' AND ', $where ); + } + + /** + * Get the SQL-ready string of ORDER part for a query. + * Order is always in the params, as per our defaults. + * + * @since 3.0.0 + * + * @return string + */ + private function build_order() { + + return 'ORDER BY ' . $this->params['orderby'] . ' ' . $this->params['order']; + } + + /** + * Get the SQL-ready string of LIMIT part for a query. + * Limit is always in the params, as per our defaults. + * + * @since 3.0.0 + * + * @return string + */ + private function build_limit() { + + return 'LIMIT ' . $this->params['offset'] . ', ' . $this->params['per_page']; + } + + /** + * Count the number of DB records according to filters. + * Do not retrieve actual records. + * + * @since 3.0.0 + * + * @return int + */ + public function get_count() { + + $table = DebugEvents::get_table_name(); + + $where = $this->build_where(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + return (int) WP::wpdb()->get_var( + "SELECT COUNT(id) FROM $table + WHERE {$where}" + ); + // phpcs:enable + } + + /** + * Get the list of DB records. + * You can either use array returned there OR iterate over the whole object, + * as it implements Iterator interface. + * + * @since 3.0.0 + * + * @return EventsCollection + */ + public function get() { + + $table = DebugEvents::get_table_name(); + + $where = $this->build_where(); + $limit = $this->build_limit(); + $order = $this->build_order(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $data = WP::wpdb()->get_results( + "SELECT * FROM $table + WHERE {$where} + {$order} + {$limit}" + ); + // phpcs:enable + + if ( ! empty( $data ) ) { + // As we got raw data we need to convert each row to Event. + foreach ( $data as $row ) { + $this->list[] = new Event( $row ); + } + } + + return $this; + } + + /********************************************************************************************* + * ****************************** \Counter interface method. ********************************* + *********************************************************************************************/ + + /** + * Count number of Record in a Queue. + * + * @since 3.0.0 + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() { + + return count( $this->list ); + } + + /********************************************************************************************* + * ****************************** \Iterator interface methods. ******************************* + *********************************************************************************************/ + + /** + * Rewind the Iterator to the first element. + * + * @since 3.0.0 + */ + #[\ReturnTypeWillChange] + public function rewind() { + + $this->iterator_position = 0; + } + + /** + * Return the current element. + * + * @since 3.0.0 + * + * @return Event|null Return null when no items in collection. + */ + #[\ReturnTypeWillChange] + public function current() { + + return $this->valid() ? $this->list[ $this->iterator_position ] : null; + } + + /** + * Return the key of the current element. + * + * @since 3.0.0 + * + * @return int + */ + #[\ReturnTypeWillChange] + public function key() { + + return $this->iterator_position; + } + + /** + * Move forward to next element. + * + * @since 3.0.0 + */ + #[\ReturnTypeWillChange] + public function next() { + + ++ $this->iterator_position; + } + + /** + * Checks if current position is valid. + * + * @since 3.0.0 + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() { + + return isset( $this->list[ $this->iterator_position ] ); + } + +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Migration.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Migration.php new file mode 100644 index 00000000..0c778a91 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Migration.php @@ -0,0 +1,69 @@ +get_charset_collate(); + + $sql = "CREATE TABLE IF NOT EXISTS `$table` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `content` TEXT DEFAULT NULL, + `initiator` TEXT DEFAULT NULL, + `event_type` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ) + ENGINE='InnoDB' + {$charset_collate};"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + + 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/Admin/DebugEvents/Table.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Table.php new file mode 100644 index 00000000..49a01c1f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DebugEvents/Table.php @@ -0,0 +1,582 @@ + 'event', + 'plural' => 'events', + 'ajax' => false, + ] + ); + + // Include polyfill if mbstring PHP extension is not enabled. + if ( ! function_exists( 'mb_substr' ) || ! function_exists( 'mb_strlen' ) ) { + Helpers::include_mbstring_polyfill(); + } + } + + /** + * Get the debug event types for filtering purpose. + * + * @since 3.0.0 + * + * @return array Associative array of debug event types StatusCode=>Name. + */ + public function get_types() { + + return Event::get_types(); + } + + /** + * Get the items counts for various types of debug logs. + * + * @since 3.0.0 + */ + public function get_counts() { + + $this->counts = []; + + // Base params with applied filters. + $base_params = $this->get_filters_query_params(); + + $total_params = $base_params; + unset( $total_params['type'] ); + $this->counts['total'] = ( new EventsCollection( $total_params ) )->get_count(); + + foreach ( $this->get_types() as $type => $name ) { + $collection = new EventsCollection( array_merge( $base_params, [ 'type' => $type ] ) ); + + $this->counts[ 'type_' . $type ] = $collection->get_count(); + } + + /** + * Filters items counts by various types of debug events. + * + * @since 3.0.0 + * + * @param array $counts { + * Items counts by types. + * + * @type integer $total Total items count. + * @type integer $status_{$type_key} Items count by type. + * } + */ + $this->counts = apply_filters( 'wp_mail_smtp_admin_debug_events_table_get_counts', $this->counts ); + } + + /** + * Retrieve the view types. + * + * @since 3.0.0 + */ + public function get_views() { + + $base_url = $this->get_filters_base_url(); + $current_type = $this->get_filtered_types(); + + $views = []; + + $views['all'] = sprintf( + '%3$s (%4$d)', + esc_url( remove_query_arg( 'type', $base_url ) ), + $current_type === false ? 'class="current"' : '', + esc_html__( 'All', 'wp-mail-smtp' ), + intval( $this->counts['total'] ) + ); + + foreach ( $this->get_types() as $type => $type_label ) { + + $count = intval( $this->counts[ 'type_' . $type ] ); + + // Skipping types with no events. + if ( $count === 0 && $current_type !== $type ) { + continue; + } + + $views[ $type ] = sprintf( + '%3$s (%4$d)', + esc_url( add_query_arg( 'type', $type, $base_url ) ), + $current_type === $type ? 'class="current"' : '', + esc_html( $type_label ), + $count + ); + + } + + /** + * Filters debug event item views. + * + * @since 3.0.0 + * + * @param array $views { + * Debug event items views by types. + * + * @type string $all Total items view. + * @type integer $status_key Items views by type. + * } + * @param array $counts { + * Items counts by types. + * + * @type integer $total Total items count. + * @type integer $status_{$status_key} Items count by types. + * } + */ + return apply_filters( 'wp_mail_smtp_admin_debug_events_table_get_views', $views, $this->counts ); + } + + /** + * Define the table columns. + * + * @since 3.0.0 + * + * @return array Associative array of slug=>Name columns data. + */ + public function get_columns() { + + return [ + 'event' => esc_html__( 'Event', 'wp-mail-smtp' ), + 'type' => esc_html__( 'Type', 'wp-mail-smtp' ), + 'content' => esc_html__( 'Content', 'wp-mail-smtp' ), + 'initiator' => esc_html__( 'Source', 'wp-mail-smtp' ), + 'created_at' => esc_html__( 'Date', 'wp-mail-smtp' ), + ]; + } + + /** + * Display the main event title with a link to open event details. + * + * @since 3.0.0 + * + * @param Event $item Event object. + * + * @return string + */ + public function column_event( $item ) { + + return '' . + '' . + esc_html( $item->get_title() ) . + '' . + ''; + } + + /** + * Display event's type. + * + * @since 3.0.0 + * + * @param Event $item Event object. + * + * @return string + */ + public function column_type( $item ) { + + return esc_html( $item->get_type_name() ); + } + + /** + * Display event's content. + * + * @since 3.0.0 + * + * @param Event $item Event object. + * + * @return string + */ + public function column_content( $item ) { + + $content = $item->get_content(); + + if ( mb_strlen( $content ) > 100 ) { + $content = mb_substr( $content, 0, 100 ) . '...'; + } + + return wp_kses_post( $content ); + } + + /** + * Display event's wp_mail initiator. + * + * @since 3.0.0 + * + * @param Event $item Event object. + * + * @return string + */ + public function column_initiator( $item ) { + + return esc_html( $item->get_initiator() ); + } + + /** + * Display event's created date. + * + * @since 3.0.0 + * + * @param Event $item Event object. + * + * @return string + */ + public function column_created_at( $item ) { + + return $item->get_created_at_formatted(); + } + + /** + * Return type filter value or FALSE. + * + * @since 3.0.0 + * + * @return bool|integer + */ + public function get_filtered_types() { + + if ( ! isset( $_REQUEST['type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + return intval( $_REQUEST['type'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + /** + * Return date filter value or FALSE. + * + * @since 3.0.0 + * + * @return bool|array + */ + public function get_filtered_dates() { + + if ( empty( $_REQUEST['date'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + $dates = (array) explode( ' - ', sanitize_text_field( wp_unslash( $_REQUEST['date'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + return array_map( 'sanitize_text_field', $dates ); + } + + /** + * Return search filter values or FALSE. + * + * @since 3.0.0 + * + * @return bool|array + */ + public function get_filtered_search() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( empty( $_REQUEST['search'] ) ) { + return false; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return sanitize_text_field( wp_unslash( $_REQUEST['search'] ) ); + } + + /** + * Whether the event log is filtered or not. + * + * @since 3.0.0 + * + * @return bool + */ + public function is_filtered() { + + $is_filtered = false; + + if ( + $this->get_filtered_search() !== false || + $this->get_filtered_dates() !== false || + $this->get_filtered_types() !== false + ) { + $is_filtered = true; + } + + return $is_filtered; + } + + /** + * Get current filters query parameters. + * + * @since 3.0.0 + * + * @return array + */ + public function get_filters_query_params() { + + $params = [ + 'search' => $this->get_filtered_search(), + 'type' => $this->get_filtered_types(), + 'date' => $this->get_filtered_dates(), + ]; + + return array_filter( + $params, + function ( $v ) { + return $v !== false; + } + ); + } + + /** + * Get current filters base url. + * + * @since 3.0.0 + * + * @return string + */ + public function get_filters_base_url() { + + $base_url = DebugEvents::get_page_url(); + $filters_params = $this->get_filters_query_params(); + + if ( isset( $filters_params['search'] ) ) { + $base_url = add_query_arg( 'search', $filters_params['search'], $base_url ); + } + + if ( isset( $filters_params['type'] ) ) { + $base_url = add_query_arg( 'type', $filters_params['type'], $base_url ); + } + + if ( isset( $filters_params['date'] ) ) { + $base_url = add_query_arg( 'date', implode( ' - ', $filters_params['date'] ), $base_url ); + } + + return $base_url; + } + + /** + * Get the data, prepare pagination, process bulk actions. + * Prepare columns for display. + * + * @since 3.0.0 + */ + public function prepare_items() { + + // Retrieve count. + $this->get_counts(); + + // Prepare all the params to pass to our Collection. All sanitization is done in that class. + $params = $this->get_filters_query_params(); + + // Total amount for pagination with WHERE clause - super quick count DB request. + $total_items = ( new EventsCollection( $params ) )->get_count(); + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'event', 'type', 'content', 'initiator', 'created_at' ], true ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $params['orderby'] = sanitize_key( $_REQUEST['orderby'] ); + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_REQUEST['order'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $params['order'] = strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) === 'DESC' ? 'DESC' : 'ASC'; + } + + $params['offset'] = ( $this->get_pagenum() - 1 ) * EventsCollection::$per_page; + + // Get the data from the DB using parameters defined above. + $collection = new EventsCollection( $params ); + $this->items = $collection->get(); + + /* + * Register our pagination options & calculations. + */ + $this->set_pagination_args( + [ + 'total_items' => $total_items, + 'per_page' => EventsCollection::$per_page, + ] + ); + } + + /** + * Display the search box. + * + * @since 1.7.0 + * + * @param string $text The 'submit' button label. + * @param string $input_id ID attribute value for the search input field. + */ + public function search_box( $text, $input_id ) { + + if ( ! $this->is_filtered() && ! $this->has_items() ) { + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $search = ! empty( $_REQUEST['search'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['search'] ) ) : ''; + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'event', 'type', 'content', 'initiator', 'created_at' ], true ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $order_by = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) ); + echo ''; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_REQUEST['order'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $order = strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) === 'DESC' ? 'DESC' : 'ASC'; + echo ''; + } + ?> + + + + items ) > 0; + } + + /** + * Message to be displayed when there are no items. + * + * @since 3.0.0 + */ + public function no_items() { + + if ( $this->is_filtered() ) { + esc_html_e( 'No events found.', 'wp-mail-smtp' ); + } else { + esc_html_e( 'No events have been logged for now.', 'wp-mail-smtp' ); + } + } + + /** + * Displays the table. + * + * @since 3.0.0 + */ + public function display() { + + $this->_column_headers = [ $this->get_columns(), [], [] ]; + + parent::display(); + } + + /** + * Hide the tablenav if there are no items in the table. + * And remove the bulk action nonce and code. + * + * @since 3.0.0 + * + * @param string $which Which tablenav: top or bottom. + */ + protected function display_tablenav( $which ) { + + if ( ! $this->has_items() ) { + return; + } + ?> + +
    + + extra_tablenav( $which ); + $this->pagination( $which ); + ?> + +
    +
    + has_items() ) { + return; + } + + $date = $this->get_filtered_dates() !== false ? implode( ' - ', $this->get_filtered_dates() ) : ''; + ?> +
    + + + + + +
    + get_capability_manage_options() ) ) { + wp_nonce_field( 'wp_mail_smtp_debug_events', 'wp-mail-smtp-debug-events-nonce', false ); + printf( + '', + esc_html__( 'Delete All Events', 'wp-mail-smtp' ) + ); + } + } + + /** + * Get the name of the primary column. + * Important for the mobile view. + * + * @since 3.0.0 + * + * @return string The name of the primary column. + */ + protected function get_primary_column_name() { + + return 'event'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/DomainChecker.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DomainChecker.php new file mode 100644 index 00000000..b6d7d712 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/DomainChecker.php @@ -0,0 +1,203 @@ +mailer = $mailer; + + $params = [ + 'mailer' => $mailer, + 'email' => base64_encode( $email ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'domain' => $sending_domain, + ]; + + $response = wp_remote_get( + add_query_arg( $params, self::ENDPOINT ), + [ + 'user-agent' => Helpers::get_default_user_agent(), + ] + ); + + if ( is_wp_error( $response ) ) { + $this->results = [ + 'success' => false, + 'message' => method_exists( $response, 'get_error_message' ) ? + $response->get_error_message() : + esc_html__( 'Something went wrong. Please try again later.', 'wp-mail-smtp' ), + 'checks' => [], + ]; + } else { + $this->results = json_decode( wp_remote_retrieve_body( $response ), true ); + } + } + + /** + * Simple getter for the API results. + * + * @since 2.6.0 + * + * @return array + */ + public function get_results() { + return $this->results; + } + + /** + * Check if the domain checker has found any errors. + * + * @since 2.6.0 + * + * @return bool + */ + public function has_errors() { + + if ( empty( $this->results['success'] ) ) { + return true; + } + + if ( empty( $this->results['checks'] ) ) { + return false; + } + + $has_error = false; + + foreach ( $this->results['checks'] as $check ) { + if ( $check['state'] === 'error' ) { + $has_error = true; + break; + } + } + + return $has_error; + } + + /** + * Check if the domain checker has not found any errors or warnings. + * + * @since 2.6.0 + * + * @return bool + */ + public function no_issues() { + + if ( empty( $this->results['success'] ) ) { + return false; + } + + $no_issues = true; + + foreach ( $this->results['checks'] as $check ) { + if ( in_array( $check['state'], [ 'error', 'warning' ], true ) ) { + $no_issues = false; + break; + } + } + + return $no_issues; + } + + /** + * Check if the domain checker support mailer. + * + * @since 2.7.0 + * + * @return bool + */ + public function is_supported_mailer() { + + return ! in_array( $this->mailer, [ 'mail', 'pepipostapi' ], true ); + } + + /** + * Get the domain checker results html. + * + * @since 2.8.0 + * + * @return string + */ + public function get_results_html() { + + $results = $this->get_results(); + $allowed_html = [ + 'b' => [], + 'i' => [], + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ]; + + ob_start(); + ?> +
    +

    + + +
    +

    +
    + + + +
    + +
    + <?php printf( /* translators: %s - item state name. */ esc_attr__( '%s icon', 'wp-mail-smtp' ), esc_attr( $check['state'] ) ); ?> +
    +

    +

    +
    +
    + +
    + +
    + get_admin()->is_admin_page() ) { + return; + } + + $dismissed = get_user_meta( get_current_user_id(), self::DISMISS_NOTICE_BAR_KEY, true ); + + if ( ! empty( $dismissed ) ) { + return; + } + + $current_screen = get_current_screen(); + $upgrade_utm_content = $current_screen === null ? 'Upgrade to Pro' : 'Upgrade to Pro - ' . $current_screen->base; + $upgrade_utm_content = empty( $_GET['tab'] ) ? $upgrade_utm_content : $upgrade_utm_content . ' -- ' . sanitize_key( $_GET['tab'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + printf( + '
    +
    + %s +
    +
    ', + wp_kses( + sprintf( /* translators: %s - WPMailSMTP.com Upgrade page URL. */ + __( 'You’re using WP Mail SMTP Lite. To unlock more features, consider upgrading to Pro for %2$s OFF.', 'wp-mail-smtp' ), + wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'notice-bar', 'content' => $upgrade_utm_content ] ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound, + '$50' + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + 'strong' => [], + ] + ), + esc_attr__( 'Dismiss this message.', 'wp-mail-smtp' ), + ); + } + + /** + * Ajax handler for dismissing notices. + * + * @since 2.3.0 + */ + public function notice_bar_ajax_dismiss() { + + // Run a security check. + check_ajax_referer( 'wp-mail-smtp-admin', 'nonce' ); + + // Check for permissions. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error(); + } + + update_user_meta( get_current_user_id(), self::DISMISS_NOTICE_BAR_KEY, time() ); + wp_send_json_success(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/EmailSendingErrors.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/EmailSendingErrors.php new file mode 100644 index 00000000..157d5c63 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/EmailSendingErrors.php @@ -0,0 +1,1661 @@ +registry = $registry instanceof Registry ? $registry : new Registry(); + } + + /** + * Register hooks. + * + * @since 4.9.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_pages_before_content', [ $this, 'render_error_banner' ] ); + add_action( 'admin_notices', [ $this, 'render_global_error_notice' ] ); + + add_action( 'wp_ajax_wp_mail_smtp_email_sending_errors_dismiss', [ $this, 'ajax_dismiss' ] ); + } + + /** + * Render the in-plugin banner on every WP Mail SMTP admin page. + * + * Renders the primary connection's record as a full banner (when set) and + * each additional connection's record as a stacked one-liner. Suppressed + * when no records exist or the current user lacks the manage-options cap. + * + * @since 4.9.0 + */ + public function render_error_banner() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded -- Linear gate chain over context (capability, multisite, test-tab, AC-edit). Splitting hurts readability. + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + return; + } + + // Read-only sticky-banner gate during admin render. Submission nonce is verified by TestTab::process_post() before any side effects. + // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended + $is_test_submission = $this->is_test_tab() && isset( $_POST['wp-mail-smtp'] ); + + // Respect the "hide delivery errors" option, except on a fresh test + // email submission — the user just ran a test and expects to see its + // result regardless of the global suppression. + if ( ! $is_test_submission && ! wp_mail_smtp()->get_admin()->is_error_delivery_notice_enabled() ) { + return; + } + + // Multisite network admin shows an aggregated view in a dedicated handler — + // this notice is per-site only. + if ( is_network_admin() ) { + return; + } + + $all = EmailSendingDebug::get(); + + if ( empty( $all ) ) { + return; + } + + // Test Email tab always starts fresh on initial load — the banner only + // surfaces after the user actually submits a test send. Read-only + // presence check; submission nonce is verified by TestTab::process_post(). + // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended + if ( $this->is_test_tab() && ! isset( $_POST['wp-mail-smtp'] ) ) { + return; + } + + // Which connection's record gets the primary (full banner) slot on this + // page: the AC edit-view's `connection_id`, the just-tested connection on + // the Test Email tab, or 'primary' everywhere else. + $connection_id = $this->get_primary_banner_connection_id(); + + // Render the full banner — except on the Connections list page, where + // primary failures route to the one-liner notice and the full banner is + // reserved for the per-connection edit view. + if ( + isset( $all[ $connection_id ] ) && + ! ( $this->is_connections_tab() && $connection_id === 'primary' ) + ) { + $this->print_primary_banner( + $connection_id === 'primary' ? 'primary' : 'additional', + $all[ $connection_id ], + $this->get_connection_name( $connection_id ), + $connection_id + ); + + unset( $all[ $connection_id ] ); + } + + // AC edit page and Test Email tab are connection-focused surfaces: + // they show only the active connection's banner. Skip stacking the + // one-liner notices for every other failing connection. Read-only + // presence check; the connection_id GET param is a navigation key. + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( + ( $this->is_connections_tab() && isset( $_GET['connection_id'] ) ) || + $this->is_test_tab() + ) { + return; + } + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + // Primary was either rendered above (and removed from $all) or is + // suppressed on the Connections list page. Either way, the loop below + // only handles additional connections. + unset( $all['primary'] ); + + foreach ( $all as $connection_id => $record ) { + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); + + if ( $connection !== false ) { + $this->print_additional_connection_note( $record, $connection ); + } + } + } + + /** + * Render the cross-admin one-liner notice on non–WP Mail SMTP admin pages. + * + * Outputs plain WP core `
    ` markup + * so it inherits standard admin-notice styling. Only renders when at least + * one record has `status: failed` AND `context: regular` — test-context + * failures stay scoped to the in-plugin banner. + * + * @since 4.9.0 + */ + public function render_global_error_notice() { + + // Multisite network admin shows an aggregated view in a dedicated handler; + // this notice is per-site only. + if ( + ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) || + ! wp_mail_smtp()->get_admin()->is_error_delivery_notice_enabled() || + wp_mail_smtp()->get_admin()->is_admin_page() || + is_network_admin() + ) { + return; + } + + $all = EmailSendingDebug::get(); + $live = array_filter( + $all, + static function ( $record ) { + + return isset( $record['status'], $record['context'] ) + && $record['status'] === 'failed' + && $record['context'] === 'regular'; + } + ); + + if ( empty( $live ) ) { + return; + } + + $settings_url = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $connection_id = array_keys( $live )[0]; + + if ( count( $live ) === 1 && $connection_id !== 'primary' ) { + $settings_url = add_query_arg( + [ + 'page' => 'wp-mail-smtp', + 'tab' => 'connections', + 'mode' => 'edit', + 'connection_id' => $connection_id, + ], + admin_url( 'admin.php' ) + ); + } + + printf( + '

    %1$s %3$s

    ', + esc_html__( 'WP Mail SMTP: One or more emails recently failed to send.', 'wp-mail-smtp' ), + esc_url( $settings_url ), + esc_html__( 'View details', 'wp-mail-smtp' ) + ); + } + + /** + * AJAX endpoint: dismiss a single connection's failure record. + * + * @since 4.9.0 + */ + public function ajax_dismiss() { + + if ( ! check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) ) { + wp_send_json_error( esc_html__( 'Security check failed. Please reload the page and try again.', 'wp-mail-smtp' ) ); + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have permission to perform this action.', 'wp-mail-smtp' ) ); + } + + $connection_id = isset( $_POST['connection_id'] ) ? sanitize_key( wp_unslash( $_POST['connection_id'] ) ) : ''; + + if ( empty( $connection_id ) ) { + wp_send_json_error( esc_html__( 'Missing connection identifier.', 'wp-mail-smtp' ) ); + } + + EmailSendingDebug::clear( $connection_id ); + + wp_send_json_success(); + } + + /** + * Resolve the connection_id when viewing an Additional Connection's edit page. + * + * Returns an empty string when not on the AC edit view. Read-only access to + * request superglobals — no state changes are made here. + * + * @since 4.9.0 + * + * @return string + */ + private function get_primary_banner_connection_id() { + + $connection_id = 'primary'; + + // Read-only banner-target resolution during admin render. Submission + // nonce is verified by TestTab::process_post() before any side effects, + // and the AC edit view's connection_id is a navigation parameter (not + // processed input). + // phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended + if ( $this->is_connections_tab() && isset( $_GET['connection_id'] ) ) { + $connection_id = sanitize_key( wp_unslash( $_GET['connection_id'] ) ); + } elseif ( $this->is_test_tab() && isset( $_POST['wp-mail-smtp']['test']['connection'] ) ) { + $connection_id = sanitize_key( wp_unslash( $_POST['wp-mail-smtp']['test']['connection'] ) ); + } + // phpcs:enable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended + + return $connection_id; + } + + /** + * Look up a connection's display name (primary or additional). + * + * @since 4.9.0 + * + * @param string $connection_id Connection ID. + * + * @return string + */ + private function get_connection_name( $connection_id ) { + + if ( $connection_id === 'primary' ) { + return esc_html__( 'Primary Connection', 'wp-mail-smtp' ); + } + + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); + + if ( $connection ) { + return $connection->get_name(); + } + + return esc_html__( 'an additional connection', 'wp-mail-smtp' ); + } + + /** + * Render the full banner for a connection (primary scope, or General Settings + * banner shell). + * + * @since 4.9.0 + * + * @param string $role Role: 'primary' or 'additional'. + * @param array $record Failure record. + * @param string $connection_name Display name. + * @param string $connection_id Connection id this banner is rendering ('primary' or an additional id). + */ + private function print_primary_banner( $role, $record, $connection_name, $connection_id ) { + + $is_warning = ( isset( $record['status'] ) && $record['status'] === 'sent' ); + + $severity_class = $is_warning + ? 'wpms-email-sending-errors-banner--warning-severity' + : 'wpms-email-sending-errors-banner--failed-severity'; + + $severity_label = $is_warning + ? esc_html__( 'Warning', 'wp-mail-smtp' ) + : esc_html__( 'Error', 'wp-mail-smtp' ); + + $title = $this->get_title( $record, $role, $connection_name ); + + $doc_state = $this->resolve_doc_state( $record ); + + ob_start(); + $this->print_banner_body( $record, $doc_state, $connection_id ); + $body = ob_get_clean(); + + printf( + '
    ' . + '' . + '
    ' . + '' . + '

    %5$s

    ' . + '
    ' . + '
    %6$s
    ' . + '
    ', + esc_attr( $severity_class ), + esc_attr( $connection_id ), + esc_attr__( 'Dismiss this notice', 'wp-mail-smtp' ), + esc_attr( $severity_label ), + $title, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $body // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Build the banner title. + * + * The generic "last email was unsuccessful" string is the unconditional + * fallback; specific framings override for primary-rescued, additional-failed, + * and additional-rescued. The test-context variants substitute "test email" + * (bolded) for "email". + * + * @since 4.9.0 + * + * @param array $record Failure record. + * @param string $role Role: 'primary' or 'additional'. + * @param string $connection_name Display name for the failing connection. + * + * @return string Title HTML (with bolded interpolations). + */ + private function get_title( $record, $role, $connection_name ) { + + $is_test = ( isset( $record['context'] ) && $record['context'] === 'test' ); + $rescued = ( isset( $record['status'] ) && $record['status'] === 'sent' ); + $is_primary = ( $role === 'primary' ); + $name_html = '' . esc_html( $connection_name ) . ''; + + if ( $is_primary && $rescued ) { + return $is_test + ? esc_html__( 'Heads up! Your primary connection failed to send the last test email. Your backup connection sent it successfully', 'wp-mail-smtp' ) + : esc_html__( 'Heads up! Your primary connection failed to send the last email. Your backup connection sent it successfully', 'wp-mail-smtp' ); + } + + if ( ! $is_primary && $rescued ) { + return $is_test + ? sprintf( /* translators: %s: the connection name, bolded. */ + __( 'Heads up! %s failed to send the last test email. Your backup connection sent it successfully', 'wp-mail-smtp' ), + $name_html + ) + : sprintf( /* translators: %s: the connection name, bolded. */ + __( 'Heads up! %s failed to send the last email. Your backup connection sent it successfully', 'wp-mail-smtp' ), + $name_html + ); + } + + if ( ! $is_primary && ! $rescued ) { + return $is_test + ? sprintf( /* translators: %s: connection name, bolded. */ + __( 'Heads up! The last test email your site attempted to send via %s was unsuccessful', 'wp-mail-smtp' ), + $name_html + ) + : sprintf( /* translators: %s: the additional connection name, bolded. */ + __( 'Heads up! The last email your site attempted to send via %s was unsuccessful', 'wp-mail-smtp' ), + $name_html + ); + } + + if ( $is_test ) { + return esc_html__( 'Heads up! The last test email your site attempted to send was unsuccessful', 'wp-mail-smtp' ); + } + + return esc_html__( 'Heads up! The last email your site attempted to send was unsuccessful', 'wp-mail-smtp' ); + } + + /** + * Classify the banner body branch from the registry lookup. + * + * Returns one of three states: + * - `doc_present` — Registry has a doc URL for the (mailer, error_key) pair. + * - `doc_not_present` — Registry has no doc URL (mailer/error_key recognized but not yet documented). + * - `unmatched` — No structured error_key recorded. + * + * @since 4.9.0 + * + * @param array $record Failure record. + * + * @return array Shape: [ 'state' => string, 'doc_url' => string|null ] + */ + private function resolve_doc_state( $record ) { + + $mailer = isset( $record['mailer'] ) ? (string) $record['mailer'] : ''; + $error_key = isset( $record['error_key'] ) ? (string) $record['error_key'] : ''; + + if ( $error_key === '' ) { + return [ + 'state' => 'unmatched', + 'doc_url' => null, + ]; + } + + $url = $this->registry->doc_url_for( $mailer, $error_key ); + + if ( $url === null ) { + return [ + 'state' => 'doc_not_present', + 'doc_url' => null, + ]; + } + + return [ + 'state' => 'doc_present', + 'doc_url' => $url, + ]; + } + + /** + * Print the body branch for the full banner. + * + * Three branches: + * - doc_present — known-issue copy + "View Troubleshoot Guide" CTA. + * - doc_not_present — fallback copy + "Send Test Email" CTA (Lite) / + * "Need hands-on help?" upsell (Pro). + * - unmatched — inline diagnostic from $record['error_message']. + * + * @since 4.9.0 + * + * @param array $record Failure record. + * @param array $doc_state Output of {@see resolve_doc_state()}. + * @param string $connection_id Connection id this banner belongs to. When not + * 'primary', the "Send Test Email" CTA carries + * `connection_id=` so the Test tab pre-selects + * that connection. + */ + private function print_banner_body( $record, $doc_state, $connection_id ) { + + $test_tab_url_args = [ + 'tab' => 'test', + 'auto-start' => 1, + ]; + + if ( $connection_id !== 'primary' ) { + $test_tab_url_args['connection_id'] = $connection_id; + } + + $test_tab_url = add_query_arg( + $test_tab_url_args, + wp_mail_smtp()->get_admin()->get_admin_page_url( 'wp-mail-smtp-tools' ) + ); + + switch ( $doc_state['state'] ) { + case 'doc_present': + printf( + '

    %s

    ', + esc_html__( "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.", 'wp-mail-smtp' ) + ); + + $actions = [ + sprintf( + '%2$s', + esc_url( wp_mail_smtp()->get_utm_url( $doc_state['doc_url'], [ 'medium' => 'email-sending-errors-banner', 'content' => 'Troubleshoot Guide' ] ) ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_html__( 'Open Troubleshoot Guide', 'wp-mail-smtp' ) + ), + $this->build_error_log_toggle_button( $record ), + sprintf( + '%2$s', + esc_url( $test_tab_url ), + esc_html__( 'Send Test Email', 'wp-mail-smtp' ) + ), + ]; + + $this->print_actions_row( $actions ); + $this->print_error_log_panel( $record ); + $this->print_need_help( $record, $doc_state ); + break; + + case 'doc_not_present': + $this->print_failure_cause( $record ); + + $actions = [ + $this->build_error_log_toggle_button( $record ), + sprintf( + '%2$s', + esc_url( $test_tab_url ), + esc_html__( 'Send Test Email', 'wp-mail-smtp' ) + ), + ]; + + $this->print_actions_row( $actions ); + $this->print_error_log_panel( $record ); + $this->print_need_help( $record, $doc_state ); + break; + + case 'unmatched': + default: + $diagnostic = isset( $record['error_message'] ) ? (string) $record['error_message'] : ''; + + printf( + '

    %1$s

    ', + esc_html__( 'No structured error code was recorded for this failure. The diagnostic from the mailer is below.', 'wp-mail-smtp' ) + ); + + if ( $diagnostic !== '' ) { + printf( + '
    %s
    ', + esc_html( $diagnostic ) + ); + } + break; + } + } + + /** + * Render the actions row inside the banner body. + * + * Wraps the supplied markup chunks in a flex container so they sit on one + * row per the design. Empty strings are skipped so callers can pass + * conditional actions without extra branching. + * + * @since 4.9.0 + * + * @param string[] $actions Pre-rendered action markup (buttons, links). + */ + private function print_actions_row( $actions ) { + + $actions = array_filter( $actions ); + + if ( empty( $actions ) ) { + return; + } + + $allowed_tags = [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + 'class' => [], + ], + 'button' => [ + 'type' => [], + 'class' => [], + 'data-show-label' => [], + 'data-hide-label' => [], + ], + 'span' => [ + 'class' => [], + ], + ]; + + echo '
    '; + foreach ( $actions as $action ) { + echo wp_kses( $action, $allowed_tags ); + } + echo '
    '; + } + + /** + * Build the "View Error Log" toggle-button markup for inclusion in the + * banner's actions row. Returns an empty string when the record has no + * error message to display. + * + * @since 4.9.0 + * + * @param array $record Failure record. + * + * @return string + */ + private function build_error_log_toggle_button( $record ) { + + if ( empty( $record['error_message'] ) ) { + return ''; + } + + return sprintf( + '', + esc_attr__( 'View Error Log', 'wp-mail-smtp' ), + esc_attr__( 'Hide Error Log', 'wp-mail-smtp' ) + ); + } + + /** + * Render the inline collapsible error-log panel (initially hidden). + * + * Paired with the toggle button produced by + * {@see build_error_log_toggle_button()}. + * + * @since 4.9.0 + * + * @param array $record Failure record. + */ + private function print_error_log_panel( $record ) { + + if ( empty( $record['error_message'] ) ) { + return; + } + + $allowed_tags = [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + 'p' => [], + 'strong' => [], + 'b' => [], + 'i' => [], + 'br' => [], + 'code' => [], + 'ul' => [], + 'ol' => [], + 'li' => [], + 'pre' => [], + ]; + + printf( + '', + esc_attr__( 'Copy error log', 'wp-mail-smtp' ), + esc_html__( 'Copied!', 'wp-mail-smtp' ), + wp_kses( $this->build_error_log( $record ), $allowed_tags ) + ); + } + + /** + * Render the full error log from a stored failure record. + * + * Reads the snapshot data captured at write-time (see + * {@see \WPMailSMTP\MailCatcherTrait::record_send_failure()}) instead of consulting + * live request state, so the bundle reflects the moment the failure occurred even + * if the admin views it later. + * + * @since 1.0.0 + * + * @param array $record EmailSendingDebug record for a single connection. + * + * @return string Pre-formatted HTML block. + */ + private function build_error_log( $record ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded -- Linear assembly of optional debug sections; splitting would scatter the bundle layout. + + $mailer_slug = isset( $record['mailer'] ) ? $record['mailer'] : ''; + $is_smtp = ( $mailer_slug === 'smtp' ); + + /* + * Versions Debug. + */ + $versions_text = 'Versions:
    '; + $versions_text .= 'WordPress: ' . esc_html( isset( $record['wp_version'] ) ? $record['wp_version'] : '' ) . '
    '; + $versions_text .= 'WordPress MS: ' . ( ! empty( $record['is_multisite'] ) ? 'Yes' : 'No' ) . '
    '; + $versions_text .= 'PHP: ' . esc_html( isset( $record['php_version'] ) ? $record['php_version'] : '' ) . '
    '; + $versions_text .= 'WP Mail SMTP: ' . esc_html( isset( $record['plugin_version'] ) ? $record['plugin_version'] : '' ) . '
    '; + + /* + * Mailer Debug. + */ + $mailer_text = 'Params:
    '; + $mailer_text .= 'Mailer: ' . esc_html( $mailer_slug ) . '
    '; + + if ( $record['context'] !== 'test' ) { + $mailer_text .= 'Source: ' . esc_html( isset( $record['initiator_name'] ) ? $record['initiator_name'] : '' ) . ' - ' . esc_html( isset( $record['initiator_file'] ) ? $record['initiator_file'] : '' ) . '
    '; + } + + $mailer_text .= 'Constants: ' . ( ! empty( $record['constants_enabled'] ) ? 'Yes' : 'No' ) . '
    '; + + if ( ! empty( $record['conflicts'] ) ) { + $mailer_text .= 'Conflicts: ' . esc_html( implode( ', ', (array) $record['conflicts'] ) ) . '
    '; + } + + if ( ! empty( $record['mailer_debug_info'] ) ) { + $mailer_text .= $record['mailer_debug_info']; + } + + if ( ! empty( $record['error_message'] ) ) { + $mailer_text .= '

    Error:
    ' . + wp_strip_all_tags( $record['error_message'] ) . + '
    '; + } + + /* + * SMTP Debug. + */ + $smtp_text = ''; + + if ( $is_smtp ) { + $smtp_text = 'SMTP Debug:
    '; + $smtp_text .= ! empty( $record['smtp_debug_info'] ) + ? '
    ' . $record['smtp_debug_info'] . '
    ' + : '[empty]'; + } + + /** + * Filters the sections rendered in the failure-record error log block. + * + * Each section is an already-formatted HTML string; the final block is + * `
    +
    +
    + Enable Weekly Email Summaries', 'wp-mail-smtp' ), + [ + 'b' => [], + ] + ); + ?> + + + + + + +
    + +
    +
    ` + sections joined by `
    ` (empty sections dropped). + * + * @since 1.0.0 + * + * @param string[] $sections { + * Indexed array of section HTML strings. + * + * @type string $0 Versions section. + * @type string $1 Mailer params + provider debug + (non-SMTP) error. + * @type string $2 Debug section. Reserved for backward-compat with the + * historical signature; no longer populated here. + * @type string $3 SMTP debug section (empty for non-SMTP mailers). + * } + */ + $errors = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName,WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity -- Public hook name + 1-arg signature preserved for backward compatibility after relocation from TestTab. + 'wp_mail_smtp_admin_test_get_debug_messages', + [ + $versions_text, + $mailer_text, + // Reserved slot to preserve the historical filter signature. + '', + $smtp_text, + ] + ); + + return '
    ' . implode( '
    ', array_filter( $errors ) ) . '
    '; + } + + /** + * Render the `description` block from `get_local_failure_info()` as the cause + * statement at the top of a Doc Not Present banner body. + * + * @since 4.9.0 + * + * @param array $record Failure record. + */ + private function print_failure_cause( $record ) { + + $info = $this->get_local_failure_info( $record ); + $description = isset( $info['description'] ) && is_array( $info['description'] ) ? $info['description'] : []; + + if ( empty( $description ) ) { + return; + } + + $allowed_tags = [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + 'strong' => [], + 'b' => [], + 'em' => [], + 'i' => [], + 'code' => [], + ]; + + echo '
    '; + printf( + '

    %s

    ', + esc_html__( 'Typically this error is returned for one of the following reasons:', 'wp-mail-smtp' ) + ); + + echo '
      '; + foreach ( $description as $cause ) { + echo '
    • ' . wp_kses( $cause, $allowed_tags ) . '
    • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo '
    '; + echo '
    '; + } + + /** + * Returns debug information for detection, processing, and display. + * + * Pattern-matches the failure record's error text against a registry of known + * issue signatures and returns the matched (title, description, steps) bundle, + * or a generic default when nothing matches. + * + * @since 4.9.0 + * + * @param array $record EmailSendingDebug record for a single connection. + * + * @return array + */ + private function get_local_failure_info( $record ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded,Generic.Metrics.NestingLevel.MaxExceeded -- Pattern-matching switch over a static registry of failure signatures; flattening would scatter the registry. + + $details = [ + // [any] - cURL error 60/77. + [ + 'mailer' => 'any', + 'errors' => [ + [ 'cURL error 60' ], + [ 'cURL error 77' ], + ], + 'title' => esc_html__( 'SSL certificate issue.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your server cannot establish secure HTTPS connections.', 'wp-mail-smtp' ), + esc_html__( 'Your server\'s CA certificate bundle is outdated.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Contact your web hosting provider and inform them your site has an issue with SSL certificates.', 'wp-mail-smtp' ), + esc_html__( 'Share the Error Log below with them so they can resolve the issue.', 'wp-mail-smtp' ), + ], + ], + // [any] - cURL error 6/7. + [ + 'mailer' => 'any', + 'errors' => [ + [ 'cURL error 6' ], + [ 'cURL error 7' ], + ], + 'title' => esc_html__( 'Could not connect to host.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your web server is blocking outbound connections.', 'wp-mail-smtp' ), + esc_html__( 'Your SMTP host is rejecting the connection.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( '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.', 'wp-mail-smtp' ), + ], + ], + // [sendgrid] - cURL error 18 - potential incorrect API key. + [ + 'mailer' => 'sendgrid', + 'errors' => [ + [ 'cURL error 18' ], + ], + 'title' => esc_html__( 'Invalid SendGrid API key.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SendGrid API key is invalid or has been revoked.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Go to the WP Mail SMTP plugin Settings page.', 'wp-mail-smtp' ), + esc_html__( 'Make sure your API Key in the SendGrid mailer settings is correct and valid.', 'wp-mail-smtp' ), + esc_html__( 'Save the plugin settings.', 'wp-mail-smtp' ), + esc_html__( 'If updating the API Key doesn\'t resolve this issue, please contact our support.', 'wp-mail-smtp' ), + ], + ], + // [any] - cURL error XX (other). + [ + 'mailer' => 'any', + 'errors' => [ + [ 'cURL error' ], + ], + 'title' => esc_html__( 'Could not connect to your host.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your server is unable to make outbound HTTP connections.', 'wp-mail-smtp' ), + esc_html__( 'Your hosting provider is blocking the request.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Contact your web hosting provider and inform them you are having issues making outbound connections.', 'wp-mail-smtp' ), + esc_html__( 'Share the Error Log below with them so they can resolve the issue.', 'wp-mail-smtp' ), + ], + ], + // [smtp] - SMTP Error: Count not authenticate. + [ + 'mailer' => 'smtp', + 'errors' => [ + [ 'SMTP Error: Could not authenticate.' ], + ], + 'title' => esc_html__( 'Could not authenticate your SMTP account.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SMTP username or password is incorrect.', 'wp-mail-smtp' ), + esc_html__( 'Your SMTP host requires a different authentication method.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'Contact your SMTP host to confirm you are using the correct username and password.', 'wp-mail-smtp' ), + esc_html__( 'Verify with your SMTP host that your account has permissions to send emails using outside connections.', 'wp-mail-smtp' ), + sprintf( + wp_kses( /* translators: %s - URL to the wpmailsmtp.com doc page. */ + __( 'Visit our documentation for additional tips on how to resolve this error.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-other-smtp-mailer-in-wp-mail-smtp/#auth-type', [ 'medium' => 'email-test', 'content' => 'Other SMTP auth debug - our documentation' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + ), + ], + ], + // [smtp] - Sending bulk email, hitting rate limit. + [ + 'mailer' => 'smtp', + 'errors' => [ + [ 'We do not authorize the use of this system to transport unsolicited' ], + ], + 'title' => esc_html__( 'Error due to unsolicited and/or bulk e-mail.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'You are sending more emails than your SMTP host allows.', 'wp-mail-smtp' ), + esc_html__( 'Your message was flagged as spam by the SMTP host.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Reduce the recipient count per email: keep single emails to under 10 recipients.', 'wp-mail-smtp' ), + esc_html__( 'Install an email-logging plugin to inspect your TO, CC, and BCC recipients.', 'wp-mail-smtp' ), + esc_html__( 'Contact your SMTP host to ask about sending and rate limits.', 'wp-mail-smtp' ), + esc_html__( 'Verify with them that your SMTP account is in good standing and has not been flagged.', 'wp-mail-smtp' ), + ], + ], + // [smtp] - Unauthenticated senders not allowed. + [ + 'mailer' => 'smtp', + 'errors' => [ + [ 'Unauthenticated senders not allowed' ], + ], + 'title' => esc_html__( 'Unauthenticated senders are not allowed.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SMTP host requires authentication, but it is disabled in the plugin settings.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Go to the WP Mail SMTP plugin Settings page.', 'wp-mail-smtp' ), + esc_html__( 'Enable Authentication.', 'wp-mail-smtp' ), + esc_html__( 'Enter the correct SMTP Username (usually an email address) and Password in the appropriate fields.', 'wp-mail-smtp' ), + ], + ], + // [smtp] - certificate verify failed. + // Has to be defined before "SMTP connect() failed" error, since this is a more specific error, + // which contains the "SMTP connect() failed" error message as well. + [ + 'mailer' => 'smtp', + 'errors' => [ + [ 'certificate verify failed' ], + ], + 'title' => esc_html__( 'Misconfigured server certificate.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'The SMTP host\'s SSL certificate is misconfigured or expired.', 'wp-mail-smtp' ), + esc_html__( 'Your server\'s OpenSSL CA bundle is outdated.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Verify that the host\'s SSL certificate is valid.', 'wp-mail-smtp' ), + sprintf( + wp_kses( /* translators: %s - URL to the PHP openssl manual. */ + __( 'Contact your hosting support and share the Error Log below along with this link.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + 'https://www.php.net/manual/en/migration56.openssl.php' + ), + ], + ], + // [smtp] - SMTP connect() failed. + [ + 'mailer' => 'smtp', + 'errors' => [ + [ 'SMTP connect() failed' ], + ], + 'title' => esc_html__( 'Could not connect to the SMTP host.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SMTP settings are incorrect (wrong port, security setting, or host).', 'wp-mail-smtp' ), + esc_html__( 'Your web server is blocking the outbound connection.', 'wp-mail-smtp' ), + esc_html__( 'Your SMTP host is rejecting the connection.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Triple-check your SMTP settings: host, email, password, port, and security.', 'wp-mail-smtp' ), + wp_kses( + __( '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.', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ), + esc_html__( 'Contact your SMTP host to confirm you are using the correct username and password.', 'wp-mail-smtp' ), + esc_html__( 'Verify with your SMTP host that your account has permissions to send emails using outside connections.', 'wp-mail-smtp' ), + ], + ], + // [mailgun] - Please activate your Mailgun account. + [ + 'mailer' => 'mailgun', + 'errors' => [ + [ 'Please activate your Mailgun account' ], + ], + 'title' => esc_html__( 'Mailgun failed.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your Mailgun account has not been activated.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Check the inbox you used to create your Mailgun account and click the activation link in the email from Mailgun.', 'wp-mail-smtp' ), + esc_html__( 'If you do not see the activation email, go to your Mailgun control panel and resend it.', 'wp-mail-smtp' ), + ], + ], + // [mailgun] - Forbidden. + [ + 'mailer' => 'mailgun', + 'errors' => [ + [ 'Forbidden' ], + ], + 'title' => esc_html__( 'Mailgun failed.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your Mailgun API key is incorrect.', 'wp-mail-smtp' ), + esc_html__( 'Your Mailgun domain name is incorrect.', 'wp-mail-smtp' ), + esc_html__( 'Your Mailgun region is incorrect.', 'wp-mail-smtp' ), + ], + 'steps' => [ + sprintf( + wp_kses( /* translators: %1$s - Mailgun API Key area URL. */ + __( 'Go to your Mailgun account and verify that your Mailgun API Key is correct.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://app.mailgun.com/settings/api_security' + ), + sprintf( + wp_kses( /* translators: %1$s - Mailgun domains area URL. */ + __( 'Verify your Domain Name is correct.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://app.mailgun.com/mg/sending/domains' + ), + esc_html__( 'Verify your domain Region is correct.', 'wp-mail-smtp' ), + ], + ], + // [mailgun] - Free accounts are for test purposes only. + [ + 'mailer' => 'mailgun', + 'errors' => [ + [ 'Free accounts are for test purposes only' ], + ], + 'title' => esc_html__( 'Mailgun failed.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your Mailgun domain has not been verified.', 'wp-mail-smtp' ), + esc_html__( 'Your Mailgun account is still on the free trial plan.', 'wp-mail-smtp' ), + ], + 'steps' => [ + sprintf( + wp_kses( /* translators: %s - Mailgun documentation URL. */ + __( 'Go to our how-to guide for setting up Mailgun with WP Mail SMTP.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-the-mailgun-mailer-in-wp-mail-smtp/', [ 'medium' => 'email-test', 'content' => 'Mailgun with WP Mail SMTP' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + ), + esc_html__( 'Complete the steps in section "2. Verify Your Domain".', 'wp-mail-smtp' ), + ], + ], + // [gmail] - 401: Login Required. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ '401', 'Login Required' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Gmail authorization has not been completed.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Go to the WP Mail SMTP plugin Settings page.', 'wp-mail-smtp' ), + esc_html__( 'Click the "Allow plugin to send emails using your Google account" button.', 'wp-mail-smtp' ), + esc_html__( 'On the Google authorization screen, click "Allow" to grant the plugin permission to send emails on your behalf.', 'wp-mail-smtp' ), + ], + ], + // [gmail] - 400: Recipient address required. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ '400', 'Recipient address required' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'The recipient email address is empty or invalid.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Check the "Send To" email address and confirm it is valid and not empty.', 'wp-mail-smtp' ), + sprintf( /* translators: 1 - correct email address example. 2 - incorrect email address example. */ + esc_html__( 'It should look like %1$s. These are invalid: %2$s.', 'wp-mail-smtp' ), + 'info@example.com', + 'info@localhost, info@192.168.1.1' + ), + esc_html__( 'If you are generating the email yourself, make sure it includes a TO header.', 'wp-mail-smtp' ), + ], + ], + // [gmail] - Token has been expired or revoked. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ 'invalid_grant', 'Token has been expired or revoked' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your Gmail OAuth refresh token has expired.', 'wp-mail-smtp' ), + esc_html__( 'Access was revoked from your Google account.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Go to the WP Mail SMTP plugin Settings page and click the "Remove OAuth Connection" button.', 'wp-mail-smtp' ), + esc_html__( 'Then click the "Allow plugin to send emails using your Google account" button and re-enable access.', 'wp-mail-smtp' ), + ], + ], + // [gmail] - Code was already redeemed. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ 'invalid_grant', 'Code was already redeemed' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'The Google authorization code has already been used.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Do not manually clean up the plugin options to retry the "Allow..." step.', 'wp-mail-smtp' ), + esc_html__( 'Reinstall the plugin with clean plugin data enabled on the Misc page. This will remove all plugin options so you can safely retry.', 'wp-mail-smtp' ), + esc_html__( 'Make sure there is no aggressive caching on admin pages, or clear the cache between attempts.', 'wp-mail-smtp' ), + ], + ], + // [gmail] - 400: Mail service not enabled. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ '400', 'Mail service not enabled' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your Google Workspace trial period has expired.', 'wp-mail-smtp' ), + esc_html__( 'Gmail is not enabled in your Google Workspace account.', 'wp-mail-smtp' ), + esc_html__( 'The Gmail API is not enabled in your Google Cloud project.', 'wp-mail-smtp' ), + ], + 'steps' => [ + sprintf( + wp_kses( /* translators: %s - Google Workspace Admin area URL. */ + __( 'Verify in Google Workspace Admin that your trial period has not expired.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://admin.google.com' + ), + sprintf( + wp_kses( /* translators: %s - Google Workspace Admin area URL. */ + __( 'In the Apps list of Google Workspace Admin, confirm that Gmail is enabled.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://admin.google.com' + ), + sprintf( + wp_kses( /* translators: %s - Google Cloud Console URL. */ + __( 'Enable the Gmail API in your project from Google Cloud Console.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://console.cloud.google.com/' + ), + ], + ], + // [gmail] - 403: Project X is not found and cannot be used for API calls. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ '403', 'is not found and cannot be used for API calls' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'The Google Cloud project linked to your Client ID/Secret no longer exists.', 'wp-mail-smtp' ), + esc_html__( 'The Gmail API is not enabled on the project.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Make sure the Client ID/Secret in your settings corresponds to a project that has the Gmail API enabled.', 'wp-mail-smtp' ), + sprintf( + wp_kses( /* translators: %s - Gmail documentation URL. */ + esc_html__( 'Follow our Gmail tutorial to ensure your project and credentials are configured correctly.', 'wp-mail-smtp' ), + [ + '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/', [ 'medium' => 'email-test', 'content' => 'Gmail tutorial' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + ), + ], + ], + // [gmail] - The OAuth client was disabled. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ 'disabled_client', 'The OAuth client was disabled' ], + ], + 'title' => esc_html__( 'Google API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your Google OAuth client has been disabled.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Make sure your Client ID/Secret corresponds to a project that has the Gmail API enabled.', 'wp-mail-smtp' ), + esc_html__( '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.', 'wp-mail-smtp' ), + ], + ], + // [SMTP.com] - The "channel - not found" issue. + [ + 'mailer' => 'smtpcom', + 'errors' => [ + [ 'channel - not found' ], + ], + 'title' => esc_html__( 'SMTP.com API Error.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SMTP.com Sender Name is incorrect.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Please make sure you entered an accurate Sender Name in WP Mail SMTP plugin settings.', 'wp-mail-smtp' ), + ], + ], + // [gmail] - GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler. + [ + 'mailer' => 'gmail', + 'errors' => [ + [ 'GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler' ], + ], + 'title' => esc_html__( 'GuzzleHttp requirements.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'The cURL PHP extension is disabled on your server.', 'wp-mail-smtp' ), + esc_html__( 'The allow_url_fopen PHP setting is disabled on your server.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Edit your php.ini file on your hosting server.', 'wp-mail-smtp' ), + esc_html__( '(Recommended) Enable the cURL PHP extension by adding extension=curl to php.ini.', 'wp-mail-smtp' ), + esc_html__( '(Or) Enable allow_url_fopen by adding allow_url_fopen = On to php.ini.', 'wp-mail-smtp' ), + esc_html__( 'If you cannot edit php.ini yourself, share the Error Log below with your hosting support and ask them to make this change.', 'wp-mail-smtp' ), + ], + ], + // [sparkpost] - Forbidden. + [ + 'mailer' => 'sparkpost', + 'errors' => [ + [ 'Forbidden' ], + ], + 'title' => esc_html__( 'SparkPost API failed.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SparkPost API key is incorrect.', 'wp-mail-smtp' ), + esc_html__( 'Your SparkPost API key is missing the "Transmissions: Read/Write" permission.', 'wp-mail-smtp' ), + ], + 'steps' => [ + sprintf( + wp_kses( /* translators: %1$s - SparkPost API Keys area URL, %2$s - SparkPost EU API Keys area URL. */ + __( 'Go to your SparkPost account or SparkPost EU account and verify that your API key is correct.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://app.sparkpost.com/account/api-keys', + 'https://app.eu.sparkpost.com/account/api-keys' + ), + esc_html__( 'Verify that your API key has "Transmissions: Read/Write" permission.', 'wp-mail-smtp' ), + ], + ], + // [sparkpost] - Unauthorized. + [ + 'mailer' => 'sparkpost', + 'errors' => [ + [ 'Unauthorized' ], + ], + 'title' => esc_html__( 'SparkPost API failed.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your SparkPost account region is incorrect.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Verify that your SparkPost account region is selected in WP Mail SMTP settings.', 'wp-mail-smtp' ), + ], + ], + ]; + + /** + * [any] - PHP 7.4.x and PCRE library issues. + * + * @see https://wordpress.org/support/topic/cant-send-emails-using-php-7-4/ + */ + if ( + version_compare( phpversion(), '7.4', '>=' ) && + defined( 'PCRE_VERSION' ) && + version_compare( PCRE_VERSION, '10.0', '>' ) && + version_compare( PCRE_VERSION, '10.32', '<=' ) + ) { + $details[] = [ + 'mailer' => 'any', + 'errors' => [ + [ 'Invalid address: (setFrom)' ], + ], + 'title' => esc_html__( 'PCRE library issue.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your server is running PHP 7.4.x with an outdated libpcre2 library (version 10.32 or earlier) that fails email address validation.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'As a temporary workaround, you can downgrade to PHP 7.3 on your server.', 'wp-mail-smtp' ), + ], + ]; + } + + $mailer_slug = isset( $record['mailer'] ) ? $record['mailer'] : ''; + $haystack = implode( + "\n", + array_filter( + [ + isset( $record['error_message'] ) ? $record['error_message'] : '', + isset( $record['mailer_debug_info'] ) ? $record['mailer_debug_info'] : '', + isset( $record['smtp_debug_info'] ) ? $record['smtp_debug_info'] : '', + ] + ) + ); + + // Error detection logic. + foreach ( $details as $data ) { + + // Check for appropriate mailer. + if ( $data['mailer'] !== 'any' && $mailer_slug !== $data['mailer'] ) { + continue; + } + + $match = false; + + // Attempt to detect errors. + foreach ( $data['errors'] as $error_group ) { + foreach ( $error_group as $error_message ) { + $match = strpos( $haystack, $error_message ) !== false; + + if ( ! $match ) { + break; + } + } + if ( $match ) { + break; + } + } + + if ( $match ) { + return $data; + } + } + + // Return defaults. + return [ + 'title' => esc_html__( 'An issue was detected.', 'wp-mail-smtp' ), + 'description' => [ + esc_html__( 'Your plugin settings are incorrect (wrong SMTP settings, invalid Mailer configuration, etc.).', 'wp-mail-smtp' ), + esc_html__( 'Your web server is blocking the connection.', 'wp-mail-smtp' ), + esc_html__( 'Your host is rejecting the connection.', 'wp-mail-smtp' ), + ], + 'steps' => [ + esc_html__( 'Triple-check your plugin settings, reconfigure if needed to make sure everything is correct.', 'wp-mail-smtp' ), + wp_kses( + __( '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.', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ), + ], + ]; + } + + /** + * Render the "Need Help?" sub-section inside the banner body. + * + * Renders the matched issue's `steps` (or the default bundle's steps when + * nothing matched), followed by the SendLayer Quick Connect upsell (Lite + * only), the White Glove Setup bullet, and an edition-specific support + * line. Steps are suppressed when a troubleshoot doc URL is available, + * since the doc covers the same ground. + * + * @since 4.9.0 + * + * @param array $record Failure record. + * @param array $doc_state Output of {@see resolve_doc_state()}. + */ + private function print_need_help( $record, $doc_state ) { + + $bullets = []; + + $allowed_tags = [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + 'class' => [], + ], + 'strong' => [], + 'b' => [], + 'em' => [], + 'i' => [], + 'br' => [], + 'code' => [], + 'ul' => [], + 'li' => [], + 'span' => [ + 'class' => [], + ], + ]; + + if ( $doc_state['state'] !== 'doc_present' ) { + $info = $this->get_local_failure_info( $record ); + $steps = isset( $info['steps'] ) && is_array( $info['steps'] ) ? $info['steps'] : []; + + foreach ( $steps as $step ) { + $bullets[] = wp_kses( $step, $allowed_tags ); + } + } + + $primary_mailer = (string) Options::init()->get( 'mail', 'mailer' ); + $record_mailer = isset( $record['mailer'] ) ? (string) $record['mailer'] : ''; + + if ( $primary_mailer !== 'sendlayer' && $record_mailer !== 'sendlayer' ) { + $bullets[] = wp_kses( + __( 'Resolve your email sending issues with SendLayer, and send your first 200 emails for free with just a few clicks!', 'wp-mail-smtp' ), + $allowed_tags + ); + } + + $bullets[] = sprintf( + wp_kses( /* translators: %s - White Glove Setup URL. */ + __( 'Need hands-on help? Our White Glove Setup handles everything for you, just share your site details and we’ll do the rest.', 'wp-mail-smtp' ), + $allowed_tags + ), + // TODO: Verify that this White Glove Setup URL is correct and up to date before release. + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/white-glove-setup/', [ 'medium' => 'email-sending-errors-banner', 'content' => 'White Glove Setup' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + ); + + if ( $doc_state['state'] !== 'doc_present' ) { + if ( wp_mail_smtp()->is_pro() ) { + $bullets[] = sprintf( + wp_kses( /* translators: %s - WPMailSMTP.com support URL. */ + __( 'Still stuck? Please log in to your WP Mail SMTP account and submit a support ticket with the error log.', 'wp-mail-smtp' ), + $allowed_tags + ), + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/support/', [ 'medium' => 'email-sending-errors-banner', 'content' => 'submit a support ticket' ] ) ) // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + ); + } else { + $bullets[] = sprintf( + wp_kses( /* translators: %1$s - wordpress.org support forum URL, %2$s - upgrade-to-Pro URL. */ + __( 'Still stuck? Get priority support by Upgrading to Pro or create a support thread along with the error log on WordPress.org forums. ', 'wp-mail-smtp' ), + $allowed_tags + ), + esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'email-sending-errors-banner', 'content' => 'Upgrading to Pro' ] ) ), // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + 'https://wordpress.org/support/plugin/wp-mail-smtp/', + ); + } + } + + echo '
    '; + printf( + '

    %s

    ', + esc_html__( 'Need Help?', 'wp-mail-smtp' ) + ); + echo '
      '; + foreach ( $bullets as $bullet ) { + echo '
    • ' . $bullet . '
    • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo '
    '; + echo '
    '; + } + + /** + * Render the verbose one-liner notice for an additional connection. On the + * General Settings page these stack vertically under the primary banner. + * + * @since 4.9.0 + * + * @param array $record Failure record. + * @param ConnectionInterface $connection Additional connection object. + */ + private function print_additional_connection_note( $record, $connection ) { + + $connection_id = $connection->get_id(); + $connection_name = $connection->get_name(); + + if ( $connection_name === '' ) { + $connection_name = esc_html__( 'an additional connection', 'wp-mail-smtp' ); + } + + $severity_class = ( isset( $record['status'] ) && $record['status'] === 'sent' ) + ? 'notice-warning' + : 'notice-error'; + + $edit_url = add_query_arg( + [ + 'page' => 'wp-mail-smtp', + 'tab' => 'connections', + 'mode' => 'edit', + 'connection_id' => $connection_id, + ], + admin_url( 'admin.php' ) + ); + + $is_backup = $this->is_backup_connection( $connection_id ); + $name_html = '' . esc_html( $connection_name ) . ''; + $rescued = ( isset( $record['status'] ) && $record['status'] === 'sent' ); + $is_test = ( isset( $record['context'] ) && $record['context'] === 'test' ); + $test_word = '' . esc_html__( 'test', 'wp-mail-smtp' ) . ''; + $backup_prefix = $is_backup + ? sprintf( /* translators: %s: connection name, bolded. */ + __( 'your backup connection %s', 'wp-mail-smtp' ), + $name_html + ) + : $name_html; + + if ( $rescued ) { + $copy = sprintf( /* translators: %s: connection display name. */ + __( '%s failed to send the last email. Your backup connection sent it successfully.', 'wp-mail-smtp' ), + $backup_prefix + ); + } elseif ( $is_test ) { + $copy = sprintf( /* translators: %1$s: literal word "test", bolded. %2$s: connection display name. */ + __( 'The last %1$s email your site attempted to send via %2$s was unsuccessful.', 'wp-mail-smtp' ), + $test_word, + $name_html + ); + } else { + $copy = sprintf( /* translators: %s: connection display name. */ + __( 'The last email your site attempted to send via %s was unsuccessful.', 'wp-mail-smtp' ), + $backup_prefix + ); + } + + $manage_html = wp_kses( + sprintf( + /* translators: %s - link to the Additional Connection Settings page. */ + __( 'Manage it on the %s page.', 'wp-mail-smtp' ), + sprintf( + '%s', + esc_url( $edit_url ), + esc_html__( 'Additional Connection Settings', 'wp-mail-smtp' ) + ) + ), + [ 'a' => [ 'href' => [] ] ] + ); + + printf( + '
    ' . + '

    %3$s %4$s

    ' . + '
    ', + esc_attr( $severity_class ), + esc_attr( $connection_id ), + wp_kses( $copy, [ 'strong' => [] ] ), + $manage_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + /** + * Whether a given additional-connection id is currently configured as the + * backup connection. Returns false when Pro is inactive or no backup is set. + * + * @since 4.9.0 + * + * @param string $connection_id Connection ID. + * + * @return bool + */ + private function is_backup_connection( $connection_id ) { + + $backup_id = Options::init()->get( 'backup_connection', 'connection_id' ); + + return ! empty( $backup_id ) && $backup_id === $connection_id; + } + + /** + * Whether we're on Tools > Email Test tab. + * + * Handles the implicit-default case: {@see \WPMailSMTP\Admin\Pages\Tools::$default_tab} + * is `'test'`, so a missing `?tab=…` param on the tools page still means we're + * on the test tab. `Area::is_admin_page( 'test' )` can't detect this because + * `'test'` isn't a top-level registered page, and `Area::get_current_tab()` + * only resolves the tab for the general page. + * + * @since 4.9.0 + * + * @return bool + */ + private function is_test_tab() { + + if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'tools' ) ) { + return false; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; + + return $tab === '' || $tab === 'test'; + } + + /** + * Whether we're currently viewing the Additional Connections tab on Settings. + * + * Settings → Connections is `page=wp-mail-smtp&tab=connections`; the AC tab is + * NOT a registered WP admin page slug, so `is_admin_page('connections')` falls + * back to matching the Settings page (any tab). This helper does the + * tab-aware check. + * + * @since 4.9.0 + * + * @return bool + */ + private function is_connections_tab() { + + if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'general' ) ) { + return false; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; + + return $tab === 'connections'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/Registry.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/Registry.php new file mode 100644 index 00000000..94b3e407 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/EmailSendingErrors/Registry.php @@ -0,0 +1,241 @@ +cache_is_stale( $cache, $mailer ) ) { + $cache = $this->refresh( $cache, $mailer, $code ); + } elseif ( ! isset( $cache[ $mailer ]['codes'] ) || ! array_key_exists( $code, $cache[ $mailer ]['codes'] ) ) { + // Per-code miss: this code has never been asked of the API for this mailer. + // Fetch fresh; if the API still doesn't return it, refresh() will plant an + // explicit null marker so we don't keep refetching until the next full refresh. + $cache = $this->refresh( $cache, $mailer, $code ); + } + + if ( empty( $cache[ $mailer ]['codes'][ $code ] ) ) { + return null; + } + + return (string) $cache[ $mailer ]['codes'][ $code ]; + } + + /** + * Fetch the mailer's error map and merge it into the cache. When `$code` is + * given and the freshly-fetched map doesn't contain it, stores an explicit + * `null` under that code so subsequent lookups skip the network until the + * next full refresh. + * + * @since 4.9.0 + * + * @param array $cache Current transient payload (passed by value, returned mutated). + * @param string $mailer Mailer slug to refresh. + * @param string $code Specific code that triggered the refresh, if any. + * + * @return array Updated cache payload. + */ + private function refresh( $cache, $mailer, $code = '' ) { + + $fresh = $this->fetch( $mailer ); + + // Transport failures return null; leave the existing cache untouched so the + // caller will retry on the next render. + if ( ! is_array( $fresh ) ) { + return $cache; + } + + // Negative-cache the requested code when the API came back without it. This + // is what lets unknown codes stop refetching after one round-trip — they sit + // as null until the whole mailer entry ages out of cache. + if ( $code !== '' && ! array_key_exists( $code, $fresh['codes'] ) ) { + $fresh['codes'][ $code ] = null; + } + + $cache[ $mailer ] = $fresh; + + set_transient( self::TRANSIENT_KEY, $cache, self::TTL ); + + return $cache; + } + + /** + * Whether the cached entry for the given mailer is missing or older than the TTL. + * + * @since 4.9.0 + * + * @param array $cache Full transient payload. + * @param string $mailer Mailer slug. + * + * @return bool + */ + private function cache_is_stale( $cache, $mailer ) { + + if ( ! isset( $cache[ $mailer ] ) || ! isset( $cache[ $mailer ]['populated_at'] ) ) { + return true; + } + + return ( time() - (int) $cache[ $mailer ]['populated_at'] ) >= self::TTL; + } + + /** + * Fetch a single mailer's error map from the registry endpoint. + * + * @since 4.9.0 + * + * @param string $mailer Mailer slug. + * + * @return array|null Array (`populated_at` + `codes` map) on a successful response, + * including an empty codes map when the API has no entry for this + * mailer (negative cache). Null on transport / server failure so + * the caller can retry on the next render. + */ + private function fetch( $mailer ) { + + $backoff_key = self::FETCH_BACKOFF_TRANSIENT_PREFIX . $mailer; + + // A prior fetch failed within the backoff window; skip the HTTP request so a + // degraded API or flaky network doesn't translate into a stream of outbound + // requests on every banner render. + if ( get_transient( $backoff_key ) ) { + return null; + } + + $base_url = defined( 'WPMS_PRODUCT_API_BASE_URL' ) ? WPMS_PRODUCT_API_BASE_URL : self::BASE_URL; + $endpoint = trailingslashit( $base_url ) . self::ENDPOINT_PATH; + + $response = wp_remote_get( + add_query_arg( 'mailer', $mailer, $endpoint ), + [ + 'timeout' => 5, + 'user-agent' => Helpers::get_default_user_agent(), + ] + ); + + if ( is_wp_error( $response ) ) { + set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); + + return null; + } + + $response_code = (int) wp_remote_retrieve_response_code( $response ); + + if ( $response_code !== 200 ) { + set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); + + return null; + } + + $body = wp_remote_retrieve_body( $response ); + + if ( empty( $body ) ) { + set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); + + return null; + } + + $decoded = json_decode( $body, true ); + + if ( ! is_array( $decoded ) ) { + set_transient( $backoff_key, time(), self::FETCH_BACKOFF_TTL ); + + return null; + } + + // Successful response — clear any stale backoff marker so future failures + // start a fresh window instead of inheriting a partially-elapsed one. + delete_transient( $backoff_key ); + + if ( ! isset( $decoded['errors'] ) ) { + return [ + 'populated_at' => time(), + 'codes' => [], + ]; + } + + $errors = is_array( $decoded['errors'] ) ? $decoded['errors'] : (array) $decoded['errors']; + + return [ + 'populated_at' => time(), + 'codes' => array_map( 'strval', $errors ), + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/FlyoutMenu.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/FlyoutMenu.php new file mode 100644 index 00000000..ce198d8d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/FlyoutMenu.php @@ -0,0 +1,146 @@ +get_admin()->is_admin_page() ) { + return; + } + + printf( + '
    +
    %1$s
    + +
    %2$s
    +
    %2$s
    +
    +
    ', + $this->get_items_html(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + esc_html__( 'See Quick Links', 'wp-mail-smtp' ), + esc_url( wp_mail_smtp()->assets_url . '/images/flyout-menu/mascot.svg' ) + ); + } + + /** + * Generate menu items HTML. + * + * @since 3.0.0 + * + * @return string Menu items HTML. + */ + private function get_items_html() { + + $items = array_reverse( $this->menu_items() ); + $items_html = ''; + + foreach ( $items as $item_key => $item ) { + $items_html .= sprintf( + ' +
    %3$s
    + %3$s +
    ', + esc_url( $item['url'] ), + (int) $item_key, + esc_html( $item['title'] ), + esc_url( $item['icon'] ), + ! empty( $item['bgcolor'] ) ? ' style="background-color: ' . esc_attr( $item['bgcolor'] ) . '"' : '', + ! empty( $item['hover_bgcolor'] ) ? ' onMouseOver="this.style.backgroundColor=\'' . esc_attr( $item['hover_bgcolor'] ) . '\'" onMouseOut="this.style.backgroundColor=\'' . esc_attr( $item['bgcolor'] ) . '\'"' : '' + ); + } + + return $items_html; + } + + /** + * Menu items data. + * + * @since 3.0.0 + * + * @return array Menu items data. + */ + private function menu_items() { + + $icons_url = wp_mail_smtp()->assets_url . '/images/flyout-menu'; + + $items = [ + [ + 'title' => esc_html__( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ), + 'url' => wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'quick-link-menu' ] ), + 'icon' => $icons_url . '/star.svg', + 'bgcolor' => '#E27730', + 'hover_bgcolor' => '#B85A1B', + ], + [ + 'title' => esc_html__( 'Support & Docs', 'wp-mail-smtp' ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + 'url' => esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/', [ 'medium' => 'quick-link-menu', 'content' => 'Support' ] ) ), + 'icon' => $icons_url . '/life-ring.svg', + ], + [ + 'title' => esc_html__( 'Follow on Facebook', 'wp-mail-smtp' ), + 'url' => 'https://www.facebook.com/wpmailsmtp', + 'icon' => $icons_url . '/facebook.svg', + ], + [ + 'title' => esc_html__( 'Suggest a Feature', 'wp-mail-smtp' ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + 'url' => esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/suggest-a-feature/', [ 'medium' => 'quick-link-menu', 'content' => 'Feature' ] ) ), + 'icon' => $icons_url . '/lightbulb.svg', + ], + ]; + + if ( wp_mail_smtp()->is_pro() ) { + array_shift( $items ); + } + + /** + * Filters quick links items. + * + * @since 3.0.0 + * + * @param array $items { + * Quick links items. + * + * @type string $title Item title. + * @type string $url Item link. + * @type string $icon Item icon url. + * @type string $bgcolor Item background color (optional). + * @type string $hover_bgcolor Item background color on hover (optional). + * } + */ + return apply_filters( 'wp_mail_smtp_admin_flyout_menu_menu_items', $items ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Notifications.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Notifications.php new file mode 100644 index 00000000..a25258ec --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Notifications.php @@ -0,0 +1,544 @@ +hooks(); + } + + /** + * Register hooks. + * + * @since 2.3.0 + */ + public function hooks() { + + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); + add_action( 'wp_mail_smtp_admin_pages_before_content', [ $this, 'output' ] ); + add_action( 'wp_ajax_wp_mail_smtp_notification_dismiss', [ $this, 'dismiss' ] ); + } + + /** + * Check if notifications are enabled. + * + * @since 4.3.0 + * + * @return bool + */ + public function is_enabled() { + + return ! Options::init()->get( 'general', 'am_notifications_hidden' ); + } + + /** + * Check if user has access and is enabled. + * + * @since 2.3.0 + * + * @return bool + */ + public function has_access() { + + $access = false; + + if ( + current_user_can( wp_mail_smtp()->get_capability_manage_options() ) && + $this->is_enabled() + ) { + $access = true; + } + + return apply_filters( 'wp_mail_smtp_admin_notifications_has_access', $access ); + } + + /** + * Get option value. + * + * @since 2.3.0 + * + * @param bool $cache Reference property cache if available. + * + * @return array + */ + public function get_option( $cache = true ) { + + if ( $this->option && $cache ) { + return $this->option; + } + + $option = get_option( self::OPTION_KEY, [] ); + + $this->option = [ + 'update' => ! empty( $option['update'] ) ? $option['update'] : 0, + 'events' => ! empty( $option['events'] ) ? $option['events'] : [], + 'feed' => ! empty( $option['feed'] ) ? $option['feed'] : [], + 'dismissed' => ! empty( $option['dismissed'] ) ? $option['dismissed'] : [], + ]; + + return $this->option; + } + + /** + * Fetch notifications from feed. + * + * @since 2.3.0 + * + * @return array + */ + protected function fetch_feed() { + + $feed_url = self::SOURCE_URL . '/' . wp_mail_smtp()->get_license_type(); + + $response = wp_remote_get( + $feed_url, + [ + 'user-agent' => Helpers::get_default_user_agent(), + ] + ); + + if ( is_wp_error( $response ) ) { + return []; + } + + $body = wp_remote_retrieve_body( $response ); + + if ( empty( $body ) ) { + return []; + } + + return $this->verify( json_decode( $body, true ) ); + } + + /** + * Verify notification data before it is saved. + * + * @since 2.3.0 + * + * @param array $notifications Array of notification items to verify. + * + * @return array + */ + protected function verify( $notifications ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $data = []; + + if ( ! is_array( $notifications ) || empty( $notifications ) ) { + return $data; + } + + $option = $this->get_option(); + + foreach ( $notifications as $notification ) { + + // The message and license should never be empty, if they are, ignore. + if ( empty( $notification['content'] ) || empty( $notification['type'] ) ) { + continue; + } + + // Ignore if license type does not match. + if ( ! in_array( wp_mail_smtp()->get_license_type(), $notification['type'], true ) ) { + continue; + } + + // Ignore if expired. + if ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) { + continue; + } + + // Ignore if notification has already been dismissed. + if ( ! empty( $option['dismissed'] ) && in_array( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict + continue; + } + + // Ignore if notification existed before installing WPForms. + // Prevents bombarding the user with notifications after activation. + $activated = get_option( 'wp_mail_smtp_activated_time' ); + + if ( + ! empty( $activated ) && + ! empty( $notification['start'] ) && + $activated > strtotime( $notification['start'] ) + ) { + continue; + } + + $data[] = $notification; + } + + return $data; + } + + /** + * Verify saved notification data for active notifications. + * + * @since 2.3.0 + * + * @param array $notifications Array of notification items to verify. + * + * @return array + */ + protected function verify_active( $notifications ) { + + if ( ! is_array( $notifications ) || empty( $notifications ) ) { + return []; + } + + // Remove notifications that are not active. + foreach ( $notifications as $key => $notification ) { + if ( + ( ! empty( $notification['start'] ) && time() < strtotime( $notification['start'] ) ) || + ( ! empty( $notification['end'] ) && time() > strtotime( $notification['end'] ) ) + ) { + unset( $notifications[ $key ] ); + } + } + + return $notifications; + } + + /** + * Get notification data. + * + * @since 2.3.0 + * @since 3.9.0 Make the AS a recurring task. + * + * @return array + */ + public function get() { + + if ( ! $this->has_access() ) { + return []; + } + + $option = $this->get_option(); + $events = ! empty( $option['events'] ) ? $this->verify_active( $option['events'] ) : []; + $feed = ! empty( $option['feed'] ) ? $this->verify_active( $option['feed'] ) : []; + + return array_merge( $events, $feed ); + } + + /** + * Get the update notifications interval. + * + * @since 3.9.0 + * + * @return int + */ + public function get_notification_update_task_interval() { + + /** + * Filters the interval for the notifications update task. + * + * @since 3.9.0 + * + * @param int $interval The interval in seconds. Default to a day (in seconds). + */ + return (int) apply_filters( 'wp_mail_smtp_admin_notifications_get_notification_update_task_interval', DAY_IN_SECONDS ); + } + + /** + * Get notification count. + * + * @since 2.3.0 + * + * @return int + */ + public function get_count() { + + return count( $this->get() ); + } + + /** + * Add a manual notification event. + * + * @since 2.3.0 + * + * @param array $notification Notification data. + */ + public function add( $notification ) { + + if ( empty( $notification['id'] ) ) { + return; + } + + $option = $this->get_option(); + + if ( in_array( $notification['id'], $option['dismissed'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict + return; + } + + foreach ( $option['events'] as $item ) { + if ( $item['id'] === $notification['id'] ) { + return; + } + } + + $notification = $this->verify( [ $notification ] ); + + update_option( + self::OPTION_KEY, + [ + 'update' => $option['update'], + 'feed' => $option['feed'], + 'events' => array_merge( $notification, $option['events'] ), + 'dismissed' => $option['dismissed'], + ] + ); + } + + /** + * Update notification data from feed. + * + * @since 2.3.0 + */ + public function update() { + + $option = $this->get_option(); + + // Bail if feed was updated less than an interval ago. + if ( time() - (int) $option['update'] < $this->get_notification_update_task_interval() ) { + return; + } + + $feed = $this->fetch_feed(); + + update_option( + self::OPTION_KEY, + [ + 'update' => time(), + 'feed' => $feed, + 'events' => $option['events'], + 'dismissed' => $option['dismissed'], + ] + ); + } + + /** + * Admin area assets. + * + * @since 2.3.0 + * + * @param string $hook Hook suffix for the current admin page. + */ + public function enqueue_assets( $hook ) { + + if ( strpos( $hook, Area::SLUG ) === false ) { + return; + } + + if ( ! $this->has_access() ) { + return; + } + + $notifications = $this->get(); + + if ( empty( $notifications ) ) { + return; + } + + wp_enqueue_style( + 'wp-mail-smtp-admin-notifications', + wp_mail_smtp()->assets_url . '/css/admin-notifications.min.css', + [], + WPMS_PLUGIN_VER + ); + + wp_enqueue_script( + 'wp-mail-smtp-admin-notifications', + wp_mail_smtp()->assets_url . '/js/smtp-notifications' . WP::asset_min() . '.js', + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + } + + /** + * Output notifications. + * + * @since 2.3.0 + */ + public function output() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded + + $notifications = $this->get(); + + if ( empty( $notifications ) ) { + return; + } + + $notifications_html = ''; + $current_class = ' current'; + $content_allowed_tags = [ + 'em' => [], + 'i' => [], + 'strong' => [], + 'span' => [ + 'style' => [], + ], + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + 'br' => [], + 'p' => [ + 'id' => [], + 'class' => [], + ], + ]; + + foreach ( $notifications as $notification ) { + + // Buttons HTML. + $buttons_html = ''; + if ( ! empty( $notification['btns'] ) && is_array( $notification['btns'] ) ) { + foreach ( $notification['btns'] as $btn_type => $btn ) { + if ( empty( $btn['text'] ) ) { + continue; + } + $buttons_html .= sprintf( + '%4$s', + ! empty( $btn['url'] ) ? esc_url( $btn['url'] ) : '', + $btn_type === 'main' ? 'primary' : 'secondary', + ! empty( $btn['target'] ) && $btn['target'] === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : '', + sanitize_text_field( $btn['text'] ) + ); + } + $buttons_html = ! empty( $buttons_html ) ? '
    ' . $buttons_html . '
    ' : ''; + } + + // Notification HTML. + $notifications_html .= sprintf( + '
    +

    %1$s

    +
    %2$s
    + %3$s +
    ', + ! empty( $notification['title'] ) ? sanitize_text_field( $notification['title'] ) : '', + ! empty( $notification['content'] ) ? wp_kses( wpautop( $notification['content'] ), $content_allowed_tags ) : '', + $buttons_html, + ! empty( $notification['id'] ) ? esc_attr( sanitize_text_field( $notification['id'] ) ) : 0, + $current_class + ); + + // Only first notification is current. + $current_class = ''; + } + ?> + +
    + +
    +
    + + + + +
    +
    +
    + +
    + + + 1 ) : ?> + + + +
    + +
    +
    +
    + get_capability_manage_options() ) || empty( $_POST['id'] ) ) { + wp_send_json_error(); + } + + $id = sanitize_text_field( wp_unslash( $_POST['id'] ) ); + $option = $this->get_option(); + $type = is_numeric( $id ) ? 'feed' : 'events'; + + $option['dismissed'][] = $id; + $option['dismissed'] = array_unique( $option['dismissed'] ); + + // Remove notification. + if ( is_array( $option[ $type ] ) && ! empty( $option[ $type ] ) ) { + foreach ( $option[ $type ] as $key => $notification ) { + if ( $notification['id'] == $id ) { // phpcs:ignore WordPress.PHP.StrictComparisons + unset( $option[ $type ][ $key ] ); + break; + } + } + } + + update_option( self::OPTION_KEY, $option ); + + wp_send_json_success(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/PageAbstract.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/PageAbstract.php new file mode 100644 index 00000000..56610da4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/PageAbstract.php @@ -0,0 +1,208 @@ +parent_page = $parent_page; + } + + /** + * @inheritdoc + */ + public function get_link() { + + $page = Area::SLUG; + + if ( $this->parent_page !== null ) { + $page .= '-' . $this->parent_page->get_slug(); + } + + return add_query_arg( + 'tab', + $this->slug, + WP::admin_url( 'admin.php?page=' . $page ) + ); + } + + /** + * Title of a tab. + * + * @since 3.7.0 + * + * @return string + */ + public function get_title() { + + return $this->get_label(); + } + + /** + * Get tab slug. + * + * @since 2.8.0 + * + * @return string + */ + public function get_slug() { + + return $this->slug; + } + + /** + * Get tab priority. + * + * @since 2.8.0 + * + * @return int + */ + public function get_priority() { + + return $this->priority; + } + + /** + * Get parent page. + * + * @since 2.8.0 + * + * @return ParentPageAbstract + */ + public function get_parent_page() { + + return $this->parent_page; + } + + /** + * Get parent page slug. + * + * @since 3.0.0 + * + * @return string + */ + public function get_parent_slug() { + + if ( is_null( $this->parent_page ) ) { + return ''; + } + + return $this->parent_page->get_slug(); + } + + /** + * Register tab related hooks. + * + * @since 2.8.0 + */ + public function hooks() {} + + /** + * Register tab related ajax hooks. + * + * @since 3.0.0 + */ + public function ajax() {} + + /** + * Process tab form submission ($_POST ). + * + * @since 1.0.0 + * + * @param array $data $_POST data specific for the plugin. + */ + public function process_post( $data ) {} + + /** + * Process tab & mailer specific Auth actions. + * + * @since 1.0.0 + */ + public function process_auth() {} + + /** + * Print the nonce field for a specific tab. + * + * @since 1.0.0 + */ + public function wp_nonce_field() { + + wp_nonce_field( Area::SLUG . '-' . $this->slug ); + } + + /** + * Make sure that a user was referred from plugin admin page. + * To avoid security problems. + * + * @since 1.0.0 + */ + public function check_admin_referer() { + + check_admin_referer( Area::SLUG . '-' . $this->slug ); + } + + /** + * Save button to be reused on other tabs. + * + * @since 1.5.0 + */ + public function display_save_btn() { + + ?> +

    + +

    + post_form_hidden_field(); + } + + /** + * Form hidden field for identifying plugin POST requests. + * + * @since 2.9.0 + */ + public function post_form_hidden_field() { + + echo ''; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/PageInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/PageInterface.php new file mode 100644 index 00000000..86f53724 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/PageInterface.php @@ -0,0 +1,45 @@ +get_tab_label( $tab ); + } + + return esc_html__( 'About Us', 'wp-mail-smtp' ); + } + + /** + * Title of a page. + * + * @since 1.5.0 + * + * @return string + */ + public function get_title() { + + return $this->get_label(); + } + + /** + * Active the given plugin. + * + * @deprecated 2.9.0 + * + * @since 1.5.0 + */ + public static function ajax_plugin_activate() { + + _deprecated_function( __METHOD__, '2.9.0', '\WPMailSMTP\Admin\Pages\AboutTab::ajax_plugin_activate' ); + + AboutTab::ajax_plugin_activate(); + } + + /** + * Install & activate the given plugin. + * + * @deprecated 2.9.0 + * + * @since 1.5.0 + */ + public static function ajax_plugin_install() { + + _deprecated_function( __METHOD__, '2.9.0', '\WPMailSMTP\Admin\Pages\AboutTab::ajax_plugin_install' ); + + AboutTab::ajax_plugin_install(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AboutTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AboutTab.php new file mode 100644 index 00000000..ac5b0290 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AboutTab.php @@ -0,0 +1,698 @@ +get_label(); + } + + /** + * Tab content. + * + * @since 2.9.0 + */ + public function display() { + + ?> +
    + +
    +

    + +

    + +

    + +

    +

    + +

    +

    + WPForms, the largest WordPress resource site, WPBeginner, the most popular lead-generation software, OptinMonster, the best WordPress analytics plugin, MonsterInsights, and more.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://wpforms.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', + 'https://www.wpbeginner.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', + 'https://optinmonster.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', + 'https://www.monsterinsights.com/?utm_source=wpmailsmtpplugin&utm_medium=pluginaboutpage&utm_campaign=aboutwpmailsmtp', + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://awesomemotive.com/', [ 'medium' => 'pluginaboutpage', 'content' => 'aboutwpmailsmtp' ] ) ) + ); + ?> +

    +

    + +

    +
    + +
    +
    + <?php esc_attr_e( 'The WPForms Team photo', 'wp-mail-smtp' ); ?> +
    + +
    +
    +
    + +
    + + display_plugins(); + } + + /** + * Display the plugins section. + * + * @since 2.9.0 + */ + protected function display_plugins() { + + ?> +
    +
    + $plugin ) : + $is_url_external = false; + + $data = $this->get_about_plugins_data( $plugin ); + + if ( isset( $plugin['pro'] ) && array_key_exists( $plugin['pro']['path'], get_plugins() ) ) { + $is_url_external = true; + $plugin = $plugin['pro']; + + $data = array_merge( $data, $this->get_about_plugins_data( $plugin, true ) ); + } + + // Do not display a plugin which has to be installed and the user can't install it. + if ( ! current_user_can( 'install_plugins' ) && $data['status_class'] === 'status-download' ) { + continue; + } + + ?> +
    +
    +
    + <?php esc_attr_e( 'Plugin icon', 'wp-mail-smtp' ); ?> +
    + +
    +

    + +

    +
    +
    +
    + + ' . esc_html( $data['status_text'] ) . '' + ); + ?> + +
    +
    + + + + +
    +
    +
    +
    + +
    +
    + + [ + 'path' => 'optinmonster/optin-monster-wp-api.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-om.png', + 'name' => esc_html__( 'OptinMonster', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/optinmonster.zip', + ], + 'wpforms' => [ + 'path' => 'wpforms-lite/wpforms.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpf.png', + 'name' => esc_html__( 'WPForms', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/wpforms-lite.zip', + 'pro' => [ + 'path' => 'wpforms/wpforms.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpf.png', + 'name' => esc_html__( 'WPForms Pro', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://wpforms.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'mi' => [ + 'path' => 'google-analytics-for-wordpress/googleanalytics.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-mi.png', + 'name' => esc_html__( 'MonsterInsights', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/google-analytics-for-wordpress.zip', + 'pro' => [ + 'path' => 'google-analytics-premium/googleanalytics-premium.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-mi.png', + 'name' => esc_html__( 'MonsterInsights Pro', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://www.monsterinsights.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'aioseo' => [ + 'path' => 'all-in-one-seo-pack/all_in_one_seo_pack.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-aioseo.png', + 'name' => esc_html__( 'AIOSEO', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/all-in-one-seo-pack.zip', + 'pro' => [ + 'path' => 'all-in-one-seo-pack-pro/all_in_one_seo_pack.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-aioseo.png', + 'name' => esc_html__( 'AIOSEO', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://aioseo.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'seedprod' => [ + 'path' => 'coming-soon/coming-soon.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-seedprod.png', + 'name' => esc_html__( 'SeedProd', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/coming-soon.zip', + 'pro' => [ + 'path' => 'seedprod-coming-soon-pro-5/seedprod-coming-soon-pro-5.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-seedprod.png', + 'name' => esc_html__( 'SeedProd', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://www.seedprod.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'rafflepress' => [ + 'path' => 'rafflepress/rafflepress.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-rp.png', + 'name' => esc_html__( 'RafflePress', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/rafflepress.zip', + 'pro' => [ + 'path' => 'rafflepress-pro/rafflepress-pro.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-rp.png', + 'name' => esc_html__( 'RafflePress Pro', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://rafflepress.com/pricing/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'pushengage' => [ + 'path' => 'pushengage/main.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-pushengage.png', + 'name' => esc_html__( 'PushEngage', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/pushengage.zip', + ], + 'smash-balloon-instagram-feeds' => [ + 'path' => 'instagram-feed/instagram-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-instagram-feeds.png', + 'name' => esc_html__( 'Smash Balloon Instagram Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/instagram-feed.zip', + 'pro' => [ + 'path' => 'instagram-feed-pro/instagram-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-instagram-feeds.png', + 'name' => esc_html__( 'Smash Balloon Instagram Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://smashballoon.com/instagram-feed/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'smash-balloon-facebook-feeds' => [ + 'path' => 'custom-facebook-feed/custom-facebook-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-facebook-feeds.png', + 'name' => esc_html__( 'Smash Balloon Facebook Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/custom-facebook-feed.zip', + 'pro' => [ + 'path' => 'custom-facebook-feed-pro/custom-facebook-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-facebook-feeds.png', + 'name' => esc_html__( 'Smash Balloon Facebook Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://smashballoon.com/custom-facebook-feed/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'smash-balloon-youtube-feeds' => [ + 'path' => 'feeds-for-youtube/youtube-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-youtube-feeds.png', + 'name' => esc_html__( 'Smash Balloon YouTube Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/feeds-for-youtube.zip', + 'pro' => [ + 'path' => 'youtube-feed-pro/youtube-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-youtube-feeds.png', + 'name' => esc_html__( 'Smash Balloon YouTube Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://smashballoon.com/youtube-feed/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'smash-balloon-twitter-feeds' => [ + 'path' => 'custom-twitter-feeds/custom-twitter-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-twitter-feeds.png', + 'name' => esc_html__( 'Smash Balloon Twitter Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/custom-twitter-feeds.zip', + 'pro' => [ + 'path' => 'custom-twitter-feeds-pro/custom-twitter-feed.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-smash-balloon-twitter-feeds.png', + 'name' => esc_html__( 'Smash Balloon Twitter Feeds', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://smashballoon.com/custom-twitter-feeds/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'trustpulse' => [ + 'path' => 'trustpulse-api/trustpulse.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-trustpulse.png', + 'name' => esc_html__( 'TrustPulse', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/trustpulse-api.zip', + ], + 'searchwp' => [ + 'path' => '', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/searchwp.png', + 'name' => esc_html__( 'SearchWP', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://searchwp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + 'pro' => [ + 'path' => 'searchwp/index.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/searchwp.png', + 'name' => esc_html__( 'SearchWP', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://searchwp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'affiliatewp' => [ + 'path' => '', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/affiliatewp.png', + 'name' => esc_html__( 'AffiliateWP', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://affiliatewp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + 'pro' => [ + 'path' => 'affiliate-wp/affiliate-wp.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/affiliatewp.png', + 'name' => esc_html__( 'AffiliateWP', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://affiliatewp.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'wp-simple-pay' => [ + 'path' => 'stripe/stripe-checkout.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/wp-simple-pay.png', + 'name' => esc_html__( 'WP Simple Pay', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/stripe.zip', + 'pro' => [ + 'path' => 'wp-simple-pay-pro-3/simple-pay.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/wp-simple-pay.png', + 'name' => esc_html__( 'WP Simple Pay Pro', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://wpsimplepay.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'easy-digital-downloads' => [ + 'path' => 'easy-digital-downloads/easy-digital-downloads.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/edd.png', + 'name' => esc_html__( 'Easy Digital Downloads', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/easy-digital-downloads.zip', + ], + 'sugar-calendar' => [ + 'path' => 'sugar-calendar-lite/sugar-calendar-lite.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/sugar-calendar.png', + 'name' => esc_html__( 'Sugar Calendar Lite', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/sugar-calendar-lite.zip', + 'pro' => [ + 'path' => 'sugar-calendar/sugar-calendar.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/sugar-calendar.png', + 'name' => esc_html__( 'Sugar Calendar', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://sugarcalendar.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'wp-charitable' => [ + 'path' => 'charitable/charitable.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-charitable.png', + 'name' => esc_html__( 'Charitable', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/charitable.zip', + ], + 'wpcode' => [ + 'path' => 'insert-headers-and-footers/ihaf.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpcode.png', + 'name' => esc_html__( 'WPCode Lite', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip', + 'pro' => [ + 'path' => 'wpcode-premium/wpcode.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-wpcode.png', + 'name' => esc_html__( 'WPCode Pro', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://wpcode.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'duplicator' => [ + 'path' => 'duplicator/duplicator.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/duplicator-icon-large.png', + 'name' => esc_html__( 'Duplicator', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', + 'pro' => [ + 'path' => 'duplicator-pro/duplicator-pro.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/duplicator-icon-large.png', + 'name' => esc_html__( 'Duplicator Pro', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + 'url' => 'https://duplicator.com/?utm_source=WordPress&utm_medium=about&utm_campaign=smtp', + ], + ], + 'activelayer' => [ + 'path' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/icon-activelayer.svg', + 'name' => esc_html__( 'ActiveLayer', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Smarter spam protection for WordPress. Catch spam in milliseconds with AI, invisible to your real visitors.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip', + ], + 'wpconsent' => [ + 'path' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/icon-wpconsent.svg', + 'name' => esc_html__( 'WPConsent', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Stay GDPR & privacy compliant. Add a cookie consent banner to your site and meet privacy laws in minutes.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', + ], + 'wpvibe' => [ + 'path' => 'vibe-ai/vibe-ai.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/plugin-vibe-ai.png', + 'name' => esc_html__( 'Vibe AI', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'AI-powered tools for WordPress. Work faster and smarter with automation built for your WordPress workflow.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/vibe-ai.zip', + ], + 'universally' => [ + 'path' => 'universally-language-translation-multilingual-tool/universally.php', + 'icon' => wp_mail_smtp()->assets_url . '/images/about/icon-universally.svg', + 'name' => esc_html__( 'Universally', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Make your WordPress site accessible to everyone. Automatic accessibility fixes and a visitor widget, no coding required.', 'wp-mail-smtp' ), + 'url' => 'https://downloads.wordpress.org/plugin/universally-language-translation-multilingual-tool.zip', + ], + ]; + + return $data; + } + + /** + * Active the given plugin. + * + * @since 2.9.0 + */ + public static function ajax_plugin_activate() { + + $error = esc_html__( 'Could not activate the plugin. Please activate it from the Plugins page.', 'wp-mail-smtp' ); + + // Check for permissions. + if ( ! current_user_can( 'activate_plugins' ) ) { + wp_send_json_error( $error ); + } + + if ( empty( $_POST['plugin'] ) ) { + wp_send_json_error( $error ); + } + + $plugin_slug = sanitize_text_field( wp_unslash( $_POST['plugin'] ) ); + + $whitelisted_plugins = []; + + foreach ( self::get_am_plugins() as $item ) { + if ( ! empty( $item['path'] ) ) { + $whitelisted_plugins[] = $item['path']; + } + + if ( ! empty( $item['pro']['path'] ) ) { + $whitelisted_plugins[] = $item['pro']['path']; + } + } + + if ( ! in_array( $plugin_slug, $whitelisted_plugins, true ) ) { + wp_send_json_error( esc_html__( 'Could not activate the plugin. Plugin is not whitelisted.', 'wp-mail-smtp' ) ); + } + + $activate = activate_plugins( $plugin_slug ); + + if ( ! is_wp_error( $activate ) ) { + wp_send_json_success( esc_html__( 'Plugin activated.', 'wp-mail-smtp' ) ); + } + + wp_send_json_error( $error ); + } + + /** + * Install & activate the given plugin. + * + * @since 2.9.0 + */ + public static function ajax_plugin_install() { // phpcs:ignore:Generic.Metrics.CyclomaticComplexity.TooHigh + + $error = esc_html__( 'Could not install the plugin.', 'wp-mail-smtp' ); + + // Check for permissions. + if ( ! current_user_can( 'install_plugins' ) ) { + wp_send_json_error( $error ); + } + + if ( empty( $_POST['plugin'] ) ) { + wp_send_json_error(); + } + + $plugin_url = esc_url_raw( wp_unslash( $_POST['plugin'] ) ); + + if ( ! in_array( $plugin_url, wp_list_pluck( array_values( self::get_am_plugins() ), 'url' ) , true ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. Plugin is not whitelisted.', 'wp-mail-smtp' ) ); + } + + // Set the current screen to avoid undefined notices. + set_current_screen( 'wp-mail-smtp_page_wp-mail-smtp-about' ); + + // Prepare variables. + $url = esc_url_raw( + add_query_arg( + [ + 'page' => 'wp-mail-smtp-about', + ], + admin_url( 'admin.php' ) + ) + ); + + /* + * The `request_filesystem_credentials` function will output a credentials form in case of failure. + * We don't want that, since it will break AJAX response. So just hide output with a buffer. + */ + ob_start(); + // phpcs:ignore WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine + $creds = request_filesystem_credentials( $url, '', false, false, null ); + ob_end_clean(); + + // Check for file system permissions. + if ( false === $creds ) { + wp_send_json_error( $error ); + } + + if ( ! WP_Filesystem( $creds ) ) { + wp_send_json_error( $error ); + } + + // Do not allow WordPress to search/download translations, as this will break JS output. + remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); + + // Import the plugin upgrader. + Helpers::include_plugin_upgrader(); + + // Create the plugin upgrader with our custom skin. + $installer = new Plugin_Upgrader( new PluginsInstallSkin() ); + + // Error check. + if ( ! method_exists( $installer, 'install' ) ) { + wp_send_json_error( $error ); + } + + $installer->install( $plugin_url ); + + // Flush the cache and return the newly installed plugin basename. + wp_cache_flush(); + + if ( $installer->plugin_info() ) { + + $plugin_basename = $installer->plugin_info(); + + if ( $plugin_basename === 'wpforms-lite/wpforms.php' ) { + add_option( 'wpforms_installation_source', 'wp-mail-smtp-about-us' ); + } + + // Activate the plugin silently. + $activated = activate_plugin( $plugin_basename ); + + if ( ! is_wp_error( $activated ) ) { + wp_send_json_success( + [ + 'msg' => esc_html__( 'Plugin installed & activated.', 'wp-mail-smtp' ), + 'is_activated' => true, + 'basename' => $plugin_basename, + ] + ); + } else { + wp_send_json_success( + [ + 'msg' => esc_html__( 'Plugin installed.', 'wp-mail-smtp' ), + 'is_activated' => false, + 'basename' => $plugin_basename, + ] + ); + } + } + + wp_send_json_error( $error ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/ActionSchedulerTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/ActionSchedulerTab.php new file mode 100644 index 00000000..2a5a0ebd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/ActionSchedulerTab.php @@ -0,0 +1,157 @@ +get_label(); + } + + /** + * URL to a tab. + * + * @since 2.9.0 + * + * @return string + */ + public function get_link() { + + return add_query_arg( [ 's' => 'wp_mail_smtp' ], parent::get_link() ); + } + + /** + * Register hooks. + * + * @since 2.9.0 + */ + public function hooks() { + + add_action( 'current_screen', [ $this, 'init' ], 20 ); + } + + /** + * Init. + * + * @since 2.9.0 + */ + public function init() { + + if ( $this->is_applicable() ) { + \ActionScheduler_AdminView::instance()->process_admin_ui(); + } + } + + /** + * Display scheduled actions table. + * + * @since 2.9.0 + */ + public function display() { + + if ( ! $this->is_applicable() ) { + return; + } + ?> +

    + +

    + 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.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'https://actionscheduler.org/' + ); + ?> +

    + +

    + +

    + + +
    + %s', 'wp-mail-smtp' ), + sanitize_text_field( wp_unslash( $_GET['s'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ), + [ 'strong' => [] ] + ); + ?> + + + +
    + + + render_admin_ui(); + } + + /** + * Check if ActionScheduler_AdminView class exists. + * + * @since 2.9.0 + * + * @return bool + */ + private function is_applicable() { + + return class_exists( 'ActionScheduler_AdminView' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AdditionalConnectionsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AdditionalConnectionsTab.php new file mode 100644 index 00000000..3a5613dc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/AdditionalConnectionsTab.php @@ -0,0 +1,193 @@ +get_admin()->get_current_tab() === $this->slug && ! wp_mail_smtp()->is_pro() ) { + $this->hooks(); + } + } + + /** + * Link label of a tab. + * + * @since 3.7.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Additional Connections', 'wp-mail-smtp' ); + } + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.7.0 + */ + public function enqueue_assets() { + + wp_enqueue_style( + 'wp-mail-smtp-admin-lity', + wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', + [], + '2.4.1' + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-lity', + wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', + [], + '2.4.1' + ); + } + + /** + * Output HTML of additional connections' education. + * + * @since 3.7.0 + */ + public function display() { + + $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Additional Connections Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', + ] + ); + $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Additional Connections Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Button', + ] + ); + ?> +
    +
    +

    + +

    +

    + +

    + + + + +
    + + display_education_screenshots(); + $this->display_education_features_list(); + ?> + + + + +
    + assets_url . '/images/additional-connections/'; + $screenshots = [ + [ + 'url' => $assets_url . 'screenshot-01.png', + 'url_thumbnail' => $assets_url . 'thumbnail-01.png', + 'title' => __( 'Backup Connection', 'wp-mail-smtp' ), + ], + [ + 'url' => $assets_url . 'screenshot-02.png', + 'url_thumbnail' => $assets_url . 'thumbnail-02.png', + 'title' => __( 'Smart Routing', 'wp-mail-smtp' ), + ], + ]; + ?> +
    +
    + +
    + + <?php esc_attr( $screenshot['title'] ); ?> + + +
    + +
    +
    + +
    +
    +

    +
    +
      +
    • +
    +
      +
    • +
    +
      +
    • +
    +
    +
    +
    + get_label(); + } + + /** + * Register tab hooks. Runs only when this tab is the current one. + * + * @since 4.9.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue the tab's install/activate script and localized strings. + * + * @since 4.9.0 + */ + public function enqueue_assets() { + + wp_enqueue_script( + 'wp-mail-smtp-ai-mcp', + wp_mail_smtp()->assets_url . '/js/smtp-ai-mcp' . WP::asset_min() . '.js', + [ 'jquery', 'wp-mail-smtp-admin' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-ai-mcp', + 'wp_mail_smtp_ai_mcp', + [ + 'error_text' => esc_html__( 'Something went wrong. Please try again.', 'wp-mail-smtp' ), + ] + ); + } + + /** + * Resolve the WPVibe install state: not installed, installed but inactive, or active. + * + * @since 4.9.0 + * + * @return string One of 'not_installed', 'installed_inactive', 'active'. + */ + private function get_wpvibe_state() { + + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $plugins = get_plugins(); + + if ( ! array_key_exists( self::WPVIBE_BASENAME, $plugins ) ) { + return 'not_installed'; + } + + if ( ! is_plugin_active( self::WPVIBE_BASENAME ) ) { + return 'installed_inactive'; + } + + return 'active'; + } + + /** + * Output the state-dependent WPVibe CTA button. + * + * @since 4.9.0 + * + * @param string $state WPVibe state. + * @param bool $can_install Whether the user can install plugins. + * @param bool $can_activate Whether the user can activate plugins. + * @param string $setup_url WPVibe admin page URL for the active state. + */ + private function render_cta_button( $state, $can_install, $can_activate, $setup_url ) { + + if ( $state === 'active' ) { + ?> + + + + + + + + get_wpvibe_state(); + $is_pro = wp_mail_smtp()->is_pro(); + $can_install = current_user_can( 'install_plugins' ); + $can_activate = current_user_can( 'activate_plugins' ); + $wpvibe_setup = admin_url( 'admin.php?page=' . self::WPVIBE_PAGE_SLUG ); + $pro_badge_url = wp_mail_smtp()->assets_url . '/images/pro-badge-small.svg'; + + $docs_url = wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/using-wpmailsmtp-with-ai-assistants/', + [ + 'medium' => 'ai-mcp', + 'content' => 'View Abilities API Documentation', + ] + ); + + // Icon classes are literal so the Tailwind scanner can generate the Iconify utilities. + $cards = [ + [ + 'icon' => 'wpms:icon-[fa6-solid--rectangle-list]', + 'title' => esc_html__( 'Email Logs', 'wp-mail-smtp' ), + 'bullets' => [ + esc_html__( 'Browse and filter logged emails by status, mailer, date, and recipient', 'wp-mail-smtp' ), + esc_html__( "Get a single email's full details and content", 'wp-mail-smtp' ), + ], + 'pro' => true, + ], + [ + 'icon' => 'wpms:icon-[fa6-solid--chart-column]', + 'title' => esc_html__( 'Email Stats', 'wp-mail-smtp' ), + 'bullets' => [ + esc_html__( 'See aggregate sending stats for any period or date range', 'wp-mail-smtp' ), + esc_html__( 'Scope stats to a single mailer and check the success rate', 'wp-mail-smtp' ), + ], + 'pro' => true, + ], + [ + 'icon' => 'wpms:icon-[fa6-solid--bug]', + 'title' => esc_html__( 'Debug Events', 'wp-mail-smtp' ), + 'bullets' => [ + esc_html__( 'List recorded send errors and debug entries', 'wp-mail-smtp' ), + ], + 'pro' => false, + ], + ]; + + ?> +
    + +
    + +
    + +

    +

    +

    + WPVibe.ai', + esc_url( 'https://wpvibe.ai/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=ai-mcp-page' ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + 'strong' => [], + ] + ); + ?> +

    + +
    + render_cta_button( $state, $can_install, $can_activate, $wpvibe_setup ); ?> +
    + + +

    + +

    + + +
    + + + +
    + +
    + +
    +

    + + + + +
    + +
    + +
    + +
    + +

    +
    + +
      + +
    • + + +
    • + +
    + + + <?php esc_attr_e( 'Pro feature', 'wp-mail-smtp' ); ?> + + +
    + +
    + +
    + +
    + get_label(); + } + + /** + * Output HTML of the alerts settings preview. + * + * @since 3.5.0 + */ + public function display() { + + $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Alerts Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', + ] + ); + $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Alerts Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Button', + ] + ); + ?> +
    +
    +

    + +

    +

    + +

    + + + + +
    + +
    +
    +
    + +
    +
    + esc_html__( 'The initial email sending request fails', 'wp-mail-smtp' ), + 'checked' => true, + 'disabled' => true, + ] + ); + ?> +

    + +

    +
    + + esc_html__( 'The deliverability verification process detects a hard bounce', 'wp-mail-smtp' ), + 'disabled' => true, + ] + ); + ?> +

    + +

    +
    +
    +
    + +
    +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    +

    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + + + +
    + get_capability_manage_global_options() ) ) { + return; + } + + $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + + /** + * Filters auth connection object. + * + * @since 3.7.0 + * + * @param ConnectionInterface $connection The Connection object. + */ + $connection = apply_filters( 'wp_mail_smtp_admin_pages_auth_tab_process_auth_connection', $connection ); + + $auth = wp_mail_smtp()->get_providers()->get_auth( $connection->get_mailer_slug(), $connection ); + + if ( + $auth && + $auth instanceof AuthAbstract && + method_exists( $auth, 'process' ) + ) { + $auth->process(); + } + } + + /** + * Return nothing, as we don't need this functionality. + * + * @since 1.0.0 + */ + public function get_label() { + return ''; + } + + /** + * Return nothing, as we don't need this functionality. + * + * @since 1.0.0 + */ + public function get_title() { + return ''; + } + + /** + * Do nothing, as we don't need this functionality. + * + * @since 1.0.0 + */ + public function display() { + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/CodeSnippetsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/CodeSnippetsTab.php new file mode 100644 index 00000000..e9b8268b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/CodeSnippetsTab.php @@ -0,0 +1,479 @@ +get_label(); + } + + /** + * Register tab hooks. Runs only when this tab is the current one. + * + * @since 4.9.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Register tab AJAX handlers. Runs on every admin AJAX request. + * + * @since 4.9.0 + */ + public function ajax() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks -- ajax() is the framework's AJAX-registration method; hooks() never runs during an AJAX request. + + add_action( 'wp_ajax_wp_mail_smtp_get_wpcode_snippet_code', [ $this, 'ajax_snippet_code' ] ); + add_action( 'wp_ajax_wp_mail_smtp_get_wpcode_snippet_install_url', [ $this, 'ajax_snippet_install_url' ] ); + } + + /** + * Enqueue the tab's scripts and localized strings. + * + * @since 4.9.0 + */ + public function enqueue_assets() { + + wp_enqueue_script( + 'wp-mail-smtp-listjs', + wp_mail_smtp()->assets_url . '/js/vendor/list.min.js', + [ 'jquery' ], + '1.5.0', + true + ); + + // WP core CodeMirror for the read-only syntax-highlighted snippet preview. + $code_editor_settings = wp_enqueue_code_editor( [ 'type' => 'text/x-php' ] ); + + wp_enqueue_script( + 'wp-mail-smtp-code-snippets', + wp_mail_smtp()->assets_url . '/js/smtp-code-snippets' . WP::asset_min() . '.js', + [ 'jquery', 'wp-mail-smtp-listjs', 'wp-mail-smtp-admin', 'wp-mail-smtp-admin-jconfirm', 'code-editor' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-code-snippets', + 'wp_mail_smtp_code_snippets', + [ + 'wpcode_active' => function_exists( 'wpcode_get_library_snippets_by_username' ), + 'wpcode_path' => $this->get_wpcode_plugin_path(), + 'activate_url' => $this->get_wpcode_activate_url(), + 'code_editor' => $code_editor_settings, + 'install_text' => esc_html__( 'Install Snippet', 'wp-mail-smtp' ), + 'installing_text' => esc_html__( 'Installing…', 'wp-mail-smtp' ), + 'loading_text' => esc_html__( 'Loading snippet…', 'wp-mail-smtp' ), + 'error_text' => esc_html__( 'Could not load this snippet. Please try again.', 'wp-mail-smtp' ), + 'install_popup_title' => esc_html__( 'Please Install WPCode to Use the Snippet Library', 'wp-mail-smtp' ), + 'install_popup_desc' => esc_html__( 'Using WPCode, you can install WP Mail SMTP code snippets with 1 click right from this page.', 'wp-mail-smtp' ), + 'install_popup_btn' => esc_html__( 'Install & Activate WPCode', 'wp-mail-smtp' ), + 'learn_more_text' => esc_html__( 'Learn more about WPCode', 'wp-mail-smtp' ), + 'learn_more_url' => 'https://wordpress.org/plugins/insert-headers-and-footers/', + 'install_popup_icon' => esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/wpcode/logo.svg' ), + 'activate_popup_title' => esc_html__( 'Please Activate WPCode to Use the Snippet Library', 'wp-mail-smtp' ), + 'activate_popup_desc' => esc_html__( 'WPCode is installed but not active. Activate it to install WP Mail SMTP code snippets with 1 click right from this page.', 'wp-mail-smtp' ), + 'activate_popup_btn' => esc_html__( 'Activate WPCode', 'wp-mail-smtp' ), + 'install_error_text' => esc_html__( 'WPCode could not be installed. Please try again.', 'wp-mail-smtp' ), + 'activate_error_text' => esc_html__( 'WPCode was installed but could not be activated.', 'wp-mail-smtp' ), + 'activate_manual_link' => esc_html__( 'Activate it manually.', 'wp-mail-smtp' ), + 'lite_activate_url' => $this->get_lite_activate_url(), + ] + ); + } + + /** + * Whether WPCode is active (its library helper is available). + * + * @since 4.9.0 + * + * @return bool + */ + private function is_wpcode_active() { + + return function_exists( 'wpcode_get_library_snippets_by_username' ); + } + + /** + * Path of the installed WPCode plugin (pro preferred), or empty if none is installed. + * + * @since 4.9.0 + * + * @return string + */ + private function get_wpcode_plugin_path() { + + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $installed = get_plugins(); + + foreach ( [ 'wpcode-premium/wpcode.php', 'insert-headers-and-footers/ihaf.php' ] as $path ) { + if ( isset( $installed[ $path ] ) ) { + return $path; + } + } + + return ''; + } + + /** + * WordPress activation URL for the installed WPCode plugin, or empty if none is installed. + * + * @since 4.9.0 + * + * @return string + */ + private function get_wpcode_activate_url() { + + $path = $this->get_wpcode_plugin_path(); + + return $path === '' ? '' : $this->build_activate_url( $path ); + } + + /** + * WordPress activation URL for WPCode Lite by its known basename. + * + * Built regardless of install state (nonces don't require the plugin to + * exist), so the install popup can offer a manual-activation link when an + * install succeeds but silent activation fails. + * + * @since 4.9.0 + * + * @return string + */ + private function get_lite_activate_url() { + + return $this->build_activate_url( 'insert-headers-and-footers/ihaf.php' ); + } + + /** + * Build a WordPress plugin-activation URL for a plugin basename. + * + * Uses esc_url_raw (not wp_nonce_url, which HTML-encodes &) so the URL stays + * valid when assigned to window.location.href on the client. + * + * @since 4.9.0 + * + * @param string $path Plugin basename. + * + * @return string + */ + private function build_activate_url( $path ) { + + // In the multisite network admin, plugins are activated from the network + // plugins screen; the nonce action is the same on both. + $plugins_url = is_network_admin() + ? network_admin_url( 'plugins.php' ) + : admin_url( 'plugins.php' ); + + $url = add_query_arg( + [ + 'action' => 'activate', + 'plugin' => $path, + '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $path ), + ], + $plugins_url + ); + + return esc_url_raw( $url ); + } + + /** + * Resolve the snippets to display. + * + * @since 4.9.0 + * + * @return array + */ + private function get_snippets() { + + $provider = new SnippetsProvider( ( new RegisterLibrary() )->get_username() ); + $snippets = $provider->get_snippets(); + $this->display_fallback = $provider->is_fallback(); + + return $snippets; + } + + /** + * Output HTML of the tab. + * + * @since 4.9.0 + */ + public function display() { + + $snippets = $this->get_snippets(); + $wpcode_active = $this->is_wpcode_active(); + $display_fallback = $this->display_fallback; + $wpcode_url = 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip'; + $container_class = $display_fallback ? 'wp-mail-smtp-wpcode-blur' : ''; + $wpcode_path = $this->get_wpcode_plugin_path(); + $activate_required = ! $wpcode_active && $wpcode_path !== ''; + ?> +
    +
    +
    +
    +

    +

    + WPCode library.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( admin_url( 'admin.php?page=wpcode-library' ) ) + ); + ?> +

    +
    + +
    + +
    +
    + + render_snippet_card( $snippet, $wpcode_active, $wpcode_url, $activate_required, $wpcode_path ); ?> + +
    +
    + +
    +
    +
    + + +
    + +
    + +
    +
    + +
    + + +
    + +
    + get_capability_manage_options() ) + ) { + wp_send_json_error(); + } + } + + /** + * AJAX: return a single snippet's code for the preview modal. + * + * Resolves by library_id from the active-WPCode helper when available, + * otherwise from the inactive-state fetcher. Code is returned raw (JSON + * transport); the client renders it in a text context (CodeMirror), never + * as HTML. + * + * @since 4.9.0 + */ + public function ajax_snippet_code() { + + $this->verify_ajax(); + + // Nonce verified in verify_ajax(). + $library_id = isset( $_POST['library_id'] ) ? absint( wp_unslash( $_POST['library_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + if ( empty( $library_id ) ) { + wp_send_json_error(); + } + + $snippet = ( new SnippetsProvider( ( new RegisterLibrary() )->get_username() ) )->get_snippet( $library_id ); + + if ( empty( $snippet ) || empty( $snippet['code'] ) ) { + wp_send_json_error(); + } + + // The provider already normalized + sanitized these fields. + wp_send_json_success( + [ + 'title' => $snippet['title'], + 'note' => $snippet['note'], + 'code' => $snippet['code'], + 'code_type' => $snippet['code_type'], + ] + ); + } + + /** + * AJAX: return a snippet's WPCode install URL. + * + * Used after WPCode is activated for an installed-but-inactive plugin: with + * WPCode now loaded, the active-state resolver returns the snippet's install + * URL, which the client navigates to. + * + * @since 4.9.0 + */ + public function ajax_snippet_install_url() { + + $this->verify_ajax(); + + // Nonce verified in verify_ajax(). + $library_id = isset( $_POST['library_id'] ) ? absint( wp_unslash( $_POST['library_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + if ( empty( $library_id ) ) { + wp_send_json_error(); + } + + $snippet = ( new SnippetsProvider( ( new RegisterLibrary() )->get_username() ) )->get_snippet( $library_id ); + + if ( empty( $snippet ) || empty( $snippet['install'] ) ) { + wp_send_json_error(); + } + + wp_send_json_success( [ 'install_url' => $snippet['install'] ] ); + } + + /** + * Output the markup for a single snippet card. + * + * @since 4.9.0 + * + * @param array $snippet Snippet view-model. + * @param bool $wpcode_active Whether WPCode is active. + * @param string $wpcode_url WPCode plugin download URL. + * @param bool $activate_required Whether WPCode is installed but inactive. + * @param string $wpcode_path Installed WPCode plugin path (empty if not installed). + */ + private function render_snippet_card( array $snippet, $wpcode_active, $wpcode_url, $activate_required = false, $wpcode_path = '' ) { + + $installed = ! empty( $snippet['installed'] ); + ?> +
    +
    +

    +
    +
    + +
    + get_label(); + } + + /** + * Get the list of all available emails that we can manage. + * + * @see https://github.com/johnbillion/wp_mail Apr 12th 2019. + * + * @since 3.1.0 + * + * @return array + */ + public static function get_controls() { + + return [ + 'comments' => [ + 'title' => esc_html__( 'Comments', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_comments_awaiting_moderation' => [ + 'label' => esc_html__( 'Awaiting Moderation', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Comment is awaiting moderation. Sent to the site admin and post author if they can edit comments.', 'wp-mail-smtp' ), + ], + 'dis_comments_published' => [ + 'label' => esc_html__( 'Published', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Comment has been published. Sent to the post author.', 'wp-mail-smtp' ), + ], + ], + ], + 'admin_email' => [ + 'title' => esc_html__( 'Change of Admin Email', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_admin_email_attempt' => [ + 'label' => esc_html__( 'Site Admin Email Change Attempt', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Change of site admin email address was attempted. Sent to the proposed new email address.', 'wp-mail-smtp' ), + ], + 'dis_admin_email_changed' => [ + 'label' => esc_html__( 'Site Admin Email Changed', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Site admin email address was changed. Sent to the old site admin email address.', 'wp-mail-smtp' ), + ], + 'dis_admin_email_network_attempt' => [ + 'label' => esc_html__( 'Network Admin Email Change Attempt', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Change of network admin email address was attempted. Sent to the proposed new email address.', 'wp-mail-smtp' ), + ], + 'dis_admin_email_network_changed' => [ + 'label' => esc_html__( 'Network Admin Email Changed', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Network admin email address was changed. Sent to the old network admin email address.', 'wp-mail-smtp' ), + ], + ], + ], + 'user_details' => [ + 'title' => esc_html__( 'Change of User Email or Password', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_user_details_password_reset_request' => [ + 'label' => esc_html__( 'Reset Password Request', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User requested a password reset via "Lost your password?". Sent to the user.', 'wp-mail-smtp' ), + ], + 'dis_user_details_password_reset' => [ + 'label' => esc_html__( 'Password Reset Successfully', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User reset their password from the password reset link. Sent to the site admin.', 'wp-mail-smtp' ), + ], + 'dis_user_details_password_changed' => [ + 'label' => esc_html__( 'Password Changed', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User changed their password. Sent to the user.', 'wp-mail-smtp' ), + ], + 'dis_user_details_email_change_attempt' => [ + 'label' => esc_html__( 'Email Change Attempt', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User attempted to change their email address. Sent to the proposed new email address.', 'wp-mail-smtp' ), + ], + 'dis_user_details_email_changed' => [ + 'label' => esc_html__( 'Email Changed', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User changed their email address. Sent to the user.', 'wp-mail-smtp' ), + ], + ], + ], + 'personal_data' => [ + 'title' => esc_html__( 'Personal Data Requests', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_personal_data_user_confirmed' => [ + 'label' => esc_html__( 'User Confirmed Export / Erasure Request', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User clicked a confirmation link in personal data export or erasure request email. Sent to the site or network admin.', 'wp-mail-smtp' ), + ], + 'dis_personal_data_erased_data' => [ + 'label' => esc_html__( 'Admin Erased Data', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Site admin clicked "Erase Personal Data" button next to a confirmed data erasure request. Sent to the requester email address.', 'wp-mail-smtp' ), + ], + 'dis_personal_data_sent_export_link' => [ + 'label' => esc_html__( 'Admin Sent Link to Export Data', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Site admin clicked "Email Data" button next to a confirmed data export request. Sent to the requester email address.', 'wp-mail-smtp' ) . '
    ' . + '' . esc_html__( 'Disabling this option will block users from being able to export their personal data, as they will not receive an email with a link.', 'wp-mail-smtp' ) . '', + ], + ], + ], + 'auto_updates' => [ + 'title' => esc_html__( 'Automatic Updates', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_auto_updates_plugin_status' => [ + 'label' => esc_html__( 'Plugin Status', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Completion or failure of a background automatic plugin update. Sent to the site or network admin.', 'wp-mail-smtp' ), + ], + 'dis_auto_updates_theme_status' => [ + 'label' => esc_html__( 'Theme Status', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Completion or failure of a background automatic theme update. Sent to the site or network admin.', 'wp-mail-smtp' ), + ], + 'dis_auto_updates_status' => [ + 'label' => esc_html__( 'WP Core Status', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Completion or failure of a background automatic core update. Sent to the site or network admin.', 'wp-mail-smtp' ), + ], + 'dis_auto_updates_full_log' => [ + 'label' => esc_html__( 'Full Log', 'wp-mail-smtp' ), + 'desc' => esc_html__( '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.', 'wp-mail-smtp' ), + ], + ], + ], + 'new_user' => [ + 'title' => esc_html__( 'New User', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_new_user_created_to_admin' => [ + 'label' => esc_html__( 'Created (Admin)', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'A new user was created. Sent to the site admin.', 'wp-mail-smtp' ), + ], + 'dis_new_user_created_to_user' => [ + 'label' => esc_html__( 'Created (User)', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'A new user was created. Sent to the new user.', 'wp-mail-smtp' ), + ], + 'dis_new_user_invited_to_site_network' => [ + 'label' => esc_html__( 'Invited To Site', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'A new user was invited to a site from Users -> Add New -> Add New User. Sent to the invited user.', 'wp-mail-smtp' ), + ], + 'dis_new_user_created_network' => [ + 'label' => esc_html__( 'Created On Site', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'A new user account was created. Sent to Network Admin.', 'wp-mail-smtp' ), + ], + 'dis_new_user_added_activated_network' => [ + 'label' => esc_html__( 'Added / Activated on Site', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'A user has been added, or their account activation has been successful. Sent to the user, that has been added/activated.', 'wp-mail-smtp' ), + ], + ], + ], + 'network_new_site' => [ + 'title' => esc_html__( 'New Site', 'wp-mail-smtp' ), + 'emails' => [ + 'dis_new_site_user_registered_site_network' => [ + 'label' => esc_html__( 'User Created Site', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User registered for a new site. Sent to the site admin.', 'wp-mail-smtp' ), + ], + 'dis_new_site_user_added_activated_site_in_network_to_admin' => [ + 'label' => esc_html__( 'Network Admin: User Activated / Added Site', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User activated their new site, or site was added from Network Admin -> Sites -> Add New. Sent to Network Admin.', 'wp-mail-smtp' ), + ], + 'dis_new_site_user_added_activated_site_in_network_to_site' => [ + 'label' => esc_html__( 'Site Admin: Activated / Added Site', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'User activated their new site, or site was added from Network Admin -> Sites -> Add New. Sent to Site Admin.', 'wp-mail-smtp' ), + ], + ], + ], + ]; + } + + /** + * Output HTML of the email controls settings preview. + * + * @since 1.6.0 + * @since 2.1.0 Replaced images with SVGs. + * @since 3.1.0 Updated layout to inactive settings preview. + */ + public function display() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $top_upgrade_button_url = add_query_arg( + [ 'discount' => 'LITEUPGRADE' ], + wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Email Controls', + 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', + ] + ) + ); + $bottom_upgrade_button_url = add_query_arg( + [ 'discount' => 'LITEUPGRADE' ], + wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Email Controls', + 'content' => 'Upgrade to WP Mail SMTP Pro Button', + ] + ) + ); + ?> + +
    +
    +

    + +

    +

    + +

    + + + + +
    + +
    + $section ) : + if ( empty( $section['emails'] ) ) { + continue; + } + + if ( $this->is_it_for_multisite( sanitize_key( $section_id ) ) && ! WP::use_global_plugin_settings() ) { + continue; + } + ?> +
    +
    +
    +
    +
    + + $email ) : + $email_id = sanitize_key( $email_id ); + + if ( empty( $email_id ) || empty( $email['label'] ) ) { + continue; + } + + if ( $this->is_it_for_multisite( sanitize_key( $email_id ) ) && ! WP::use_global_plugin_settings() ) { + continue; + } + ?> +
    +
    + +
    +
    + true ] ); + ?> + +

    + +

    + +
    +
    + + +
    + + + + +
    + options = Options::init(); + + parent::__construct( $parent_page ); + + // Remove unnecessary $_GET parameters and prevent url duplications in _wp_http_referer input. + $this->remove_get_parameters(); + } + + /** + * Link label of a tab. + * + * @since 3.0.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Debug Events', 'wp-mail-smtp' ); + } + + /** + * Title of a tab. + * + * @since 3.0.0 + * + * @return string + */ + public function get_title() { + + return $this->get_label(); + } + + /** + * Register hooks. + * + * @since 3.0.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.0.0 + */ + public function enqueue_assets() { + + $min = WP::asset_min(); + + wp_enqueue_style( + 'wp-mail-smtp-flatpickr', + wp_mail_smtp()->assets_url . '/css/vendor/flatpickr.min.css', + [], + '4.6.9' + ); + wp_enqueue_script( + 'wp-mail-smtp-flatpickr', + wp_mail_smtp()->assets_url . '/js/vendor/flatpickr.min.js', + [ 'jquery' ], + '4.6.9', + true + ); + + wp_enqueue_script( + 'wp-mail-smtp-tools-debug-events', + wp_mail_smtp()->assets_url . "/js/smtp-tools-debug-events{$min}.js", + [ 'jquery', 'wp-mail-smtp-flatpickr' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-tools-debug-events', + 'wp_mail_smtp_tools_debug_events', + [ + 'lang_code' => sanitize_key( WP::get_language_code() ), + 'plugin_url' => wp_mail_smtp()->plugin_url, + 'loader' => wp_mail_smtp()->prepare_loader( 'blue' ), + 'texts' => [ + 'delete_all_notice' => esc_html__( 'Are you sure you want to permanently delete all debug events?', 'wp-mail-smtp' ), + 'cancel' => esc_html__( 'Cancel', 'wp-mail-smtp' ), + 'close' => esc_html__( 'Close', 'wp-mail-smtp' ), + 'yes' => esc_html__( 'Yes', 'wp-mail-smtp' ), + 'ok' => esc_html__( 'OK', 'wp-mail-smtp' ), + 'notice_title' => esc_html__( 'Heads up!', 'wp-mail-smtp' ), + 'error_occurred' => esc_html__( 'An error occurred!', 'wp-mail-smtp' ), + ], + ] + ); + } + + /** + * Get email logs list table. + * + * @since 3.0.0 + * + * @return Table + */ + public function get_table() { + + if ( $this->table === null ) { + $this->table = new Table(); + } + + return $this->table; + } + + /** + * Display scheduled actions table. + * + * @since 3.0.0 + */ + public function display() { + + ?> + + +
    +
    +

    +
    +

    + +

    +
    + +
    + wp_nonce_field(); ?> + + +
    +
    +

    +
    +

    + +

    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[debug_events][email_errors]', + 'id' => 'wp-mail-smtp-setting-debug_events_email_errors', + 'value' => 'true', + 'checked' => true, + 'disabled' => true, + ] + ); + ?> +

    + +

    +

    + +

    +
    + 'wp-mail-smtp[debug_events][email_debug]', + 'id' => 'wp-mail-smtp-setting-debug_events_email_debug', + 'value' => 'true', + 'checked' => (bool) $this->options->get( 'debug_events', 'email_debug' ), + ] + ); + ?> +

    + +

    +

    + +

    +
    +
    + +
    +
    + +
    +
    + +

    + options->is_const_defined( 'debug_events', 'retention_period' ) ) { + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
    ' . $this->options->get_const_set_message( 'WPMS_DEBUG_EVENTS_RETENTION_PERIOD' ); + } + ?> +

    +
    +
    + + display_save_btn(); ?> +
    + + display_debug_events_not_installed(); + } else { + $table = $this->get_table(); + $table->prepare_items(); + + ?> +
    + + + get_filtered_types() !== false ) { + printf( '', esc_attr( $table->get_filtered_types() ) ); + } + + if ( $this->get_filters_html() ) { + ?> +
    + get_filtered_types(); + + echo wp_kses( + sprintf( /* translators: %1$s - number of debug events found; %2$s - filtered type. */ + _n( + 'Found %1$s %2$s event', + 'Found %1$s %2$s events', + absint( $table->get_pagination_arg( 'total_items' ) ), + 'wp-mail-smtp' + ), + absint( $table->get_pagination_arg( 'total_items' ) ), + $type !== false && isset( $table->get_types()[ $type ] ) ? $table->get_types()[ $type ] : '' + ), + [ + 'strong' => [], + ] + ); + ?> + + get_filters_html() as $id => $html ) : ?> + [] ] + ); + ?> + + +
    + search_box( + esc_html__( 'Search Events', 'wp-mail-smtp' ), + Area::SLUG . '-debug-events-search-input' + ); + + $table->views(); + $table->display(); + ?> +
    + options->set( $data, false, false ); + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } + + /** + * Return an array with information (HTML and id) for each filter for this current view. + * + * @since 3.0.0 + * + * @return array + */ + private function get_filters_html() { + + $filters = [ + '.search-box' => $this->get_filter_search_html(), + '.wp-mail-smtp-filter-date' => $this->get_filter_date_html(), + ]; + + return array_filter( $filters ); + } + + /** + * Return HTML with information about the search filter. + * + * @since 3.0.0 + * + * @return string + */ + private function get_filter_search_html() { + + $table = $this->get_table(); + $term = $table->get_filtered_search(); + + if ( $term === false ) { + return ''; + } + + return sprintf( /* translators: %s The searched term. */ + __( 'where event contains "%s"', 'wp-mail-smtp' ), + '' . esc_html( $term ) . '' + ); + } + + /** + * Return HTML with information about the date filter. + * + * @since 3.0.0 + * + * @return string + */ + private function get_filter_date_html() { + + $table = $this->get_table(); + $dates = $table->get_filtered_dates(); + + if ( $dates === false ) { + return ''; + } + + $dates = array_map( + function ( $date ) { + return date_i18n( 'M j, Y', strtotime( $date ) ); + }, + $dates + ); + + $html = ''; + + switch ( count( $dates ) ) { + case 1: + $html = sprintf( /* translators: %s - Date. */ + esc_html__( 'on %s', 'wp-mail-smtp' ), + '' . $dates[0] . '' + ); + break; + case 2: + $html = sprintf( /* translators: %1$s - Date. %2$s - Date. */ + esc_html__( 'between %1$s and %2$s', 'wp-mail-smtp' ), + '' . $dates[0] . '', + '' . $dates[1] . '' + ); + break; + } + + return $html; + } + + /** + * Display a message when debug events DB table is missing. + * + * @since 3.0.0 + */ + private function display_debug_events_not_installed() { + + $error_message = get_option( Migration::ERROR_OPTION_NAME ); + + $create_missing_tables_url = wp_nonce_url( + add_query_arg( + [ + 'create-missing-db-tables' => 1, + ], + $this->get_link() + ), + Area::SLUG . '-create-missing-db-tables' + ); + + $contact_support_url = wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/account/support/', + [ + 'medium' => 'debug-events', + 'content' => 'Debug Events not installed correctly', + ] + ); + ?> + +
    +

    + +

    + create the missing DB tables by clicking on this link. If this issue persists, please contact our support and provide the error message below:', 'wp-mail-smtp' ), + esc_url( $create_missing_tables_url ), + esc_url( $contact_support_url ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + echo '

    '; + echo '' . esc_html( $error_message ) . ''; + } else { + echo wp_kses( + sprintf( /* translators: %1$s - create missing tables link; %2$s - contact support link. */ + __( '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.', 'wp-mail-smtp' ), + esc_url( $create_missing_tables_url ), + esc_url( $contact_support_url ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + } + ?> +

    +
    + + esc_html__( '1 Week', 'wp-mail-smtp' ), + 2628000 => esc_html__( '1 Month', 'wp-mail-smtp' ), + 7885000 => esc_html__( '3 Months', 'wp-mail-smtp' ), + 15770000 => esc_html__( '6 Months', 'wp-mail-smtp' ), + 31540000 => esc_html__( '1 Year', 'wp-mail-smtp' ), + ]; + + $debug_event_retention_period = $this->options->get( 'debug_events', 'retention_period' ); + + // Check if defined value already in list and add it if not. + if ( + ! empty( $debug_event_retention_period ) && + ! isset( $options[ $debug_event_retention_period ] ) + ) { + $debug_event_retention_period_days = floor( $debug_event_retention_period / DAY_IN_SECONDS ); + + $options[ $debug_event_retention_period ] = sprintf( + /* translators: %d - days count. */ + _n( '%d Day', '%d Days', $debug_event_retention_period_days, 'wp-mail-smtp' ), + $debug_event_retention_period_days + ); + + ksort( $options ); + } + + /** + * Filter debug events retention period options. + * + * @since 3.6.0 + * + * @param array $options Debug Events retention period options. + * Option key in seconds. + */ + return apply_filters( + 'wp_mail_smtp_admin_pages_debug_events_tab_get_debug_events_retention_period_options', + $options + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReports.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReports.php new file mode 100644 index 00000000..0e983b19 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReports.php @@ -0,0 +1,55 @@ +get_label(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReportsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReportsTab.php new file mode 100644 index 00000000..5ac0cb25 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/EmailReportsTab.php @@ -0,0 +1,180 @@ +get_label(); + } + + /** + * Register hooks. + * + * @since 3.0.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.0.0 + */ + public function enqueue_assets() { + + wp_enqueue_style( + 'wp-mail-smtp-admin-lity', + wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', + [], + '2.4.1' + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-lity', + wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', + [], + '2.4.1', + false + ); + } + + /** + * Output HTML of the email reports education. + * + * @since 3.0.0 + */ + public function display() { + + $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'email-reports', + 'content' => 'upgrade-to-wp-mail-smtp-pro-button-link-top', + ] + ); + $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'email-reports', + 'content' => 'upgrade-to-wp-mail-smtp-pro-button-link', + ] + ); + + $assets_url = wp_mail_smtp()->assets_url . '/images/email-reports/'; + $screenshots = [ + [ + 'url' => $assets_url . 'screenshot-01.png', + 'url_thumbnail' => $assets_url . 'thumbnail-01.png', + 'title' => __( 'Stats at a Glance', 'wp-mail-smtp' ), + ], + [ + 'url' => $assets_url . 'screenshot-02.png', + 'url_thumbnail' => $assets_url . 'thumbnail-02.png', + 'title' => __( 'Detailed Stats by Subject Line', 'wp-mail-smtp' ), + ], + [ + 'url' => $assets_url . 'screenshot-03.png', + 'url_thumbnail' => $assets_url . 'thumbnail-03.png', + 'title' => __( 'Weekly Email Report', 'wp-mail-smtp' ), + ], + ]; + ?> +
    +
    +

    + +

    + + + + +
    + +
    +
    + +
    + + <?php esc_attr( $screenshot['title'] ); ?> + + +
    + +
    +
    + +
    +
    +

    +
    +
      +
    • +
    • +
    +
      +
    • +
    • +
    +
      +
    • +
    • +
    +
    +
    +
    + + + + +
    + get_label(); + } + + /** + * Output HTML of the email logs export form preview. + * + * @since 2.9.0 + */ + public function display() { + + $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'tools-export', + 'content' => 'upgrade-to-wp-mail-smtp-pro-button-top', + ] + ); + $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'tools-export', + 'content' => 'upgrade-to-wp-mail-smtp-pro-button', + ] + ); + ?> +
    +
    +

    + +

    +

    + +

    + + + + +
    + +
    +
    +
    + + + +
    + +
    +
    + + + + + + + + +
    + +
    +
    + + + + + + + + +
    + +
    +
    + +
    + +
    +
    + + +
    +
    + + + + +
    + get_label(); + } + + /** + * Tab content. + * + * @since 4.9.0 + */ + public function display() { + + ?> +
    + + display_hero(); ?> + display_features(); ?> + display_comparison(); ?> + +
    + +
    + +
    +

    + +

    + +

    + +

    + +
    +
    + display_upgrade_button( 'Hero' ); ?> + display_discount_badge(); ?> +
    + + + + +
    +
    + +
    + +
    + +
    + +
    +
    + +

    + +

    + +
    + get_feature_cards() as $card ) : ?> +
    + +
    +

    + +

    +

    + +

    +
    +
    + +
    + +
    + display_upgrade_button( 'Features Grid' ); ?> + display_discount_badge(); ?> +
    + +
    +
    + +
    +
    + +
    +

    + +

    +

    + +

    +
    + +
    +
    + display_comparison_header(); ?> + get_comparison_rows() as $row ) : ?> + display_comparison_row( $row ); ?> + +
    +
    + +
    + display_upgrade_button( 'Bottom' ); ?> + display_discount_badge(); ?> +
    + +
    +
    + +
    +
    +

    +
    +
    +

    +
    +
    +

    +
    +
    + +
    +
    +

    + +

    +
    +
    + display_comparison_cell( $row['lite'] ); ?> +
    +
    + display_comparison_cell( $row['pro'] ); ?> +
    +
    + +
    + display_status_icon( $cell['status'] ); ?> +

    +
    +

    +
    + [ 'file' => 'status-full', 'label' => esc_attr__( 'Available', 'wp-mail-smtp' ) ], + 'partial' => [ 'file' => 'status-partial', 'label' => esc_attr__( 'Partially available', 'wp-mail-smtp' ) ], + 'none' => [ 'file' => 'status-none', 'label' => esc_attr__( 'Not available', 'wp-mail-smtp' ) ], + ]; + + if ( ! isset( $icons[ $status ] ) ) { + return; + } + + $icon = $icons[ $status ]; + ?> + <?php echo esc_attr( $icon['label'] ); ?> + get_upgrade_link( [ 'medium' => 'get-pro-tab', 'content' => $content ] ) + ); + ?> + + + + +
    + + + + + +
    + 'card-email-logs', + 'title' => esc_html__( 'Email Logs', 'wp-mail-smtp' ), + 'description' => esc_html__( 'Save details about every email sent from your WordPress site.', 'wp-mail-smtp' ), + ], + [ + 'image' => 'card-email-failure-alerts', + 'title' => esc_html__( 'Email Failure Alerts', 'wp-mail-smtp' ), + 'description' => esc_html__( 'Receive immediate alerts for email failures to quickly address issues.', 'wp-mail-smtp' ), + ], + [ + 'image' => 'card-backup-connections', + 'title' => esc_html__( 'Backup Connections', 'wp-mail-smtp' ), + 'description' => esc_html__( 'Set up a secondary email provider in case your primary provider fails.', 'wp-mail-smtp' ), + ], + [ + 'image' => 'card-one-click-mailer-setups', + 'title' => esc_html__( 'One-Click Mailer Setups', 'wp-mail-smtp' ), + 'description' => esc_html__( 'An easy and secure way to configure your Gmail and Outlook mailers.', 'wp-mail-smtp' ), + ], + [ + 'image' => 'card-smart-routing', + 'title' => esc_html__( 'Smart Routing', 'wp-mail-smtp' ), + 'description' => esc_html__( 'Use conditional logic to send emails through different providers.', 'wp-mail-smtp' ), + ], + [ + 'image' => 'card-advanced-mailers', + 'title' => esc_html__( 'Advanced mailers', 'wp-mail-smtp' ), + 'description' => esc_html__( 'Connect with top providers like Amazon SES, Microsoft 365 / Outlook, and Zoho Mail.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Twelve-row Lite vs Pro feature comparison. + * + * @since 4.9.0 + * + * @return array + */ + private function get_comparison_rows() { + + $not_available = [ + 'status' => 'none', + 'lead' => esc_html__( 'Not available', 'wp-mail-smtp' ), + 'rest' => '', + ]; + + return [ + [ + 'feature' => esc_html__( 'Improved Email Deliverability', 'wp-mail-smtp' ), + 'lite' => [ + 'status' => 'partial', + 'lead' => esc_html__( 'Connect your WordPress site to any of the following SMTP providers:', 'wp-mail-smtp' ), + 'rest' => esc_html__( 'SendLayer, SMTP.com, Brevo, Google Workspace / Gmail, Mailgun, Postmark, SendGrid, SMTP2GO, Sparkpost, and any other SMTP provider using your SMTP credentials.', 'wp-mail-smtp' ), + ], + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Connect your WordPress site to any of our Lite SMTP providers, as well as:', 'wp-mail-smtp' ), + 'rest' => esc_html__( 'Amazon SES, Microsoft 365 / Outlook, and Zoho Mail.', 'wp-mail-smtp' ), + ], + ], + [ + 'feature' => esc_html__( 'Weekly Email Summaries', 'wp-mail-smtp' ), + 'lite' => [ + 'status' => 'partial', + 'lead' => esc_html__( 'Receive a basic report of your total emails sent including a summary of the previous week\'s sent emails.', 'wp-mail-smtp' ), + 'rest' => '', + ], + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( '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.', 'wp-mail-smtp' ), + 'rest' => '', + ], + ], + [ + 'feature' => esc_html__( 'Email Error Tracking', 'wp-mail-smtp' ), + 'lite' => [ + 'status' => 'full', + 'lead' => esc_html__( 'View email sending errors in your WordPress dashboard.', 'wp-mail-smtp' ), + 'rest' => '', + ], + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'View email sending errors in your WordPress dashboard.', 'wp-mail-smtp' ), + 'rest' => '', + ], + ], + [ + 'feature' => esc_html__( 'Google Workspace', 'wp-mail-smtp' ), + 'lite' => [ + 'status' => 'partial', + 'lead' => esc_html__( 'Manually connect to your account with Google Workspace/ Gmail mailer', 'wp-mail-smtp' ), + 'rest' => '', + ], + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Access your Google Workspace immediately with Gmail One Click Setup', 'wp-mail-smtp' ), + 'rest' => '', + ], + ], + [ + 'feature' => esc_html__( 'Outlook', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Access Outlook immediately with Outlook One Click Setup', 'wp-mail-smtp' ), + 'rest' => '', + ], + ], + [ + 'feature' => esc_html__( 'Email Logging', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'View detailed email logs in your WordPress dashboard.', 'wp-mail-smtp' ), + 'rest' => esc_html__( 'Includes delivery status, email content, attachments, source, and technical details, plus the option to resend the email.', 'wp-mail-smtp' ), + ], + ], + [ + 'feature' => esc_html__( 'Instant Email Alerts', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Receive an alert via your preferred channel whenever an email fails to send.', 'wp-mail-smtp' ), + 'rest' => esc_html__( 'Email, Slack, SMS via Twilio, Microsoft Teams, Discord, or custom webhooks.', 'wp-mail-smtp' ), + ], + ], + [ + 'feature' => esc_html__( 'Backup Connections', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Connect to multiple SMTP providers.', 'wp-mail-smtp' ), + 'rest' => esc_html__( '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.', 'wp-mail-smtp' ), + ], + ], + [ + 'feature' => esc_html__( 'Manage Email Notifications', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Choose which default WordPress email notifications your site sends.', 'wp-mail-smtp' ), + 'rest' => '', + ], + ], + [ + 'feature' => esc_html__( 'Open & Click Tracking', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Monitor email open and click-through rates.', 'wp-mail-smtp' ), + 'rest' => '', + ], + ], + [ + 'feature' => esc_html__( 'Email Reports', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'View advanced email reports in your WordPress dashboard.', 'wp-mail-smtp' ), + 'rest' => esc_html__( 'Includes total number of emails sent, emails delivered, failed emails, opened emails, links clicked.', 'wp-mail-smtp' ), + ], + ], + [ + 'feature' => esc_html__( 'Smart Routing', 'wp-mail-smtp' ), + 'lite' => $not_available, + 'pro' => [ + 'status' => 'full', + 'lead' => esc_html__( 'Send emails through more than one SMTP provider.', 'wp-mail-smtp' ), + 'rest' => esc_html__( 'Connect WP Mail SMTP to multiple providers, then route emails through your preferred provider based on custom conditional logic rules.', 'wp-mail-smtp' ), + ], + ], + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Logs.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Logs.php new file mode 100644 index 00000000..ed3690bc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Logs.php @@ -0,0 +1,68 @@ +slug, + WP::admin_url( 'admin.php?page=' . Area::SLUG ) + ); + } + + /** + * Link label of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_label() { + return esc_html__( 'Email Log', 'wp-mail-smtp' ); + } + + /** + * Title of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_title() { + return $this->get_label(); + } + + /** + * Tab content. + * + * @since 2.1.0 Moved the display content to the email log settings tab. + */ + public function display() {} +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/LogsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/LogsTab.php new file mode 100644 index 00000000..b996a900 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/LogsTab.php @@ -0,0 +1,204 @@ +get_admin()->is_admin_page() && $current_tab === 'logs' ) { + $this->hooks(); + } + } + + /** + * Link label of a tab. + * + * @since 1.6.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Email Log', 'wp-mail-smtp' ); + } + + /** + * Title of a tab. + * + * @since 1.6.0 + * + * @return string + */ + public function get_title() { + + return $this->get_label(); + } + + /** + * Register hooks. + * + * @since 3.0.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.0.0 + */ + public function enqueue_assets() { + + wp_enqueue_style( + 'wp-mail-smtp-admin-lity', + wp_mail_smtp()->assets_url . '/css/vendor/lity.min.css', + [], + '2.4.1' + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-lity', + wp_mail_smtp()->assets_url . '/js/vendor/lity.min.js', + [], + '2.4.1', + false + ); + } + + /** + * Display the upsell content for the Email Log feature. + * + * @since 1.6.0 + * @since 2.1.0 Moved the display content from the email log page (WP admin menu "Email Log" page). + */ + public function display() { + + $top_upgrade_button_url = add_query_arg( + [ 'discount' => 'LITEUPGRADE' ], + wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'logs', + 'content' => 'Upgrade to Pro Button Top', + ] + ) + ); + $bottom_upgrade_button_url = add_query_arg( + [ 'discount' => 'LITEUPGRADE' ], + wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'logs', + 'content' => 'Upgrade to Pro Button', + ] + ) + ); + + $assets_url = wp_mail_smtp()->assets_url . '/images/logs/'; + $screenshots = [ + [ + 'url' => $assets_url . 'archive.png', + 'url_thumbnail' => $assets_url . 'archive-thumbnail.png', + 'title' => __( 'Email Log Index', 'wp-mail-smtp' ), + ], + [ + 'url' => $assets_url . 'single.png', + 'url_thumbnail' => $assets_url . 'single-thumbnail.png', + 'title' => __( 'Individual Email Log', 'wp-mail-smtp' ), + ], + ]; + ?> + +
    +
    +

    + +

    +

    + +

    + + + + +
    + +
    +
    + +
    + + <?php esc_attr( $screenshot['title'] ); ?> + + +
    + +
    +
    + +
    +
    +

    +
    +
      +
    • +
    • +
    +
      +
    • +
    • +
    +
      +
    • +
    • +
    +
    +
    +
    + + + + +
    + + +
    + wp_nonce_field(); ?> + + +
    +
    +

    get_title(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

    +
    +
    + + +
    +
    + + + info + + wp_mail() function to send emails.', 'wp-mail-smtp' ), + [ + 'code' => [], + ] + ) + ); + ?> +
    +
    + +
    + +
    +
    +
    +
    + 'wp-mail-smtp[general][do_not_send]', + 'id' => 'wp-mail-smtp-setting-do_not_send', + 'value' => 'true', + 'checked' => (bool) $options->get( 'general', 'do_not_send' ), + 'disabled' => $options->is_const_defined( 'general', 'do_not_send' ), + ] + ); + ?> +

    + +

    +

    + is_const_defined( 'general', 'do_not_send' ) ) { + echo $options->get_const_set_message( 'WPMS_DO_NOT_SEND' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + printf( + wp_kses( /* translators: %s - The URL to the constants support article. */ + __( 'Please read this support article if you want to enable this option using constants.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/how-to-secure-smtp-settings-by-using-constants/', + [ + 'medium' => 'misc-settings', + 'content' => 'Do not send setting description - support article', + ] + ) + ) + ); + } + ?> +

    + is_pro() ) { + $is_log_blocked_emails_const_defined = $options->is_const_defined( 'logs', 'log_blocked_emails' ); + ?> +
    + 'wp-mail-smtp[logs][log_blocked_emails]', + 'id' => 'wp-mail-smtp-setting-log_blocked_emails', + 'value' => 'true', + 'label' => esc_html__( 'Log Blocked Emails', 'wp-mail-smtp' ), + 'checked' => (bool) $options->get( 'logs', 'log_blocked_emails' ), + 'disabled' => $is_log_blocked_emails_const_defined, + ] + ); + ?> +

    + +

    + +

    + get_const_set_message( 'WPMS_LOG_BLOCKED_EMAILS' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> +

    + +
    + +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[general][am_notifications_hidden]', + 'id' => 'wp-mail-smtp-setting-am_notifications_hidden', + 'value' => 'true', + 'checked' => (bool) $options->get( 'general', 'am_notifications_hidden' ), + ] + ); + ?> +

    + +

    +
    +
    + + +
    +
    + +
    +
    + get_admin()->is_error_delivery_notice_enabled(); + ?> + 'wp-mail-smtp[general][email_delivery_errors_hidden]', + 'id' => 'wp-mail-smtp-setting-email_delivery_errors_hidden', + 'value' => 'true', + 'checked' => $is_hard_disabled || (bool) $options->get( 'general', 'email_delivery_errors_hidden' ), + 'disabled' => $is_hard_disabled, + ] + ); + ?> +

    + +

    + +

    + wp_mail_smtp_admin_is_error_delivery_notice_enabled' + ); + ?> +

    + +

    + This is not recommended and should only be done for staging or development sites.', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ); + ?> +

    + +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[general][dashboard_widget_hidden]', + 'id' => 'wp-mail-smtp-setting-dashboard_widget_hidden', + 'value' => 'true', + 'checked' => (bool) $options->get( 'general', 'dashboard_widget_hidden' ), + ] + ); + ?> +

    + +

    +
    +
    + + + +
    +
    + +
    +
    + 'wp-mail-smtp[general][' . UsageTracking::SETTINGS_SLUG . ']', + 'id' => 'wp-mail-smtp-setting-usage-tracking', + 'value' => 'true', + 'checked' => (bool) $options->get( 'general', UsageTracking::SETTINGS_SLUG ), + ] + ); + ?> +

    + +

    +
    +
    + + + +
    +
    + +
    +
    + 'wp-mail-smtp[general][' . SummaryReportEmail::SETTINGS_SLUG . ']', + 'id' => 'wp-mail-smtp-setting-summary-report-email', + 'value' => 'true', + 'checked' => (bool) SummaryReportEmail::is_disabled(), + 'disabled' => ( + $options->is_const_defined( 'general', SummaryReportEmail::SETTINGS_SLUG ) || + ( wp_mail_smtp()->is_pro() && empty( Options::init()->get( 'logs', 'enabled' ) ) ) + ), + ] + ); + ?> +

    + + is_pro() && empty( Options::init()->get( 'logs', 'enabled' ) ) ) { + echo wp_kses( + sprintf( /* translators: %s - Email Log settings url. */ + __( 'Please enable Email Logging first, before this setting can be configured.', 'wp-mail-smtp' ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '&tab=logs' ) ) + ), + [ + 'a' => [ + 'href' => [], + ], + ] + ); + } else { + printf( + '%2$s', + esc_url( SummaryReportEmail::get_preview_link() ), + esc_html__( 'View Email Summary Example', 'wp-mail-smtp' ) + ); + } + + if ( $options->is_const_defined( 'general', SummaryReportEmail::SETTINGS_SLUG ) ) { + echo '
    ' . $options->get_const_set_message( 'WPMS_SUMMARY_REPORT_EMAIL_DISABLED' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> +

    +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[general][' . OptimizedEmailSending::SETTINGS_SLUG . ']', + 'id' => 'wp-mail-smtp-setting-optimize-email-sending', + 'value' => 'true', + 'checked' => (bool) OptimizedEmailSending::is_enabled(), + ] + ); + ?> +

    + Learn More', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/a-complete-guide-to-miscellaneous-settings/#optimize-email-sending', + [ + 'medium' => 'misc-settings', + 'content' => 'Optimize Email Sending - support article', + ] + ) + ) + ); + ?> +

    +
    +
    + + + display_rate_limit_settings(); ?> + + +
    +
    + +
    +
    + 'wp-mail-smtp[general][uninstall]', + 'id' => 'wp-mail-smtp-setting-uninstall', + 'value' => 'true', + 'checked' => (bool) $options->get( 'general', 'uninstall' ), + ] + ); + ?> +

    + +

    +

    + +

    +
    +
    + + display_save_btn(); ?> +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-rate_limit-lite', + ] + ); + ?> +

    + Learn More', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/a-complete-guide-to-miscellaneous-settings/#email-rate-limiting', + [ + 'medium' => 'misc-settings', + 'content' => 'Email Rate Limiting - support article', + ] + ) + ) + ); + ?> +

    +
    +
    + is_option_changed( + $options->parse_boolean( $data['general'][ SummaryReportEmail::SETTINGS_SLUG ] ), + 'general', + SummaryReportEmail::SETTINGS_SLUG + ); + + // If this option was changed, cancel summary report email task. + if ( $is_summary_report_email_opt_changed ) { + ( new SummaryReportEmailTask() )->cancel(); + } + + // All the sanitization is done there. + $options->set( $data, false, false ); + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SettingsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SettingsTab.php new file mode 100644 index 00000000..375e0c8d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SettingsTab.php @@ -0,0 +1,439 @@ +get_label(); + } + + /** + * @inheritdoc + */ + public function display() { + + $options = Options::init(); + ?> + +
    + wp_nonce_field(); ?> + + + + +
    +
    +

    + +

    + +

    +
    +
    + + is_pro() ) : ?> +
    +

    WP Mail SMTP Lite - no license needed. Enjoy!', 'wp-mail-smtp' ), [ 'strong' => [] ] ); ?> 🙂

    + +

    + %s discount, automatically applied at checkout to unlock even more features!', 'wp-mail-smtp' ), + [ + 'strong' => [], + 'br' => [], + ] + ), + '$50' + ); + ?> +

    + + +
    + + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +

    +
    +
    + + + +
    +
    + +
    +
    + + + + +

    + +

    +
    +
    + + + get_connections_manager()->get_primary_connection(); + $connection_settings = new ConnectionSettings( $connection ); + + // Display connection settings. + $connection_settings->display(); + ?> + + display_backup_connection_education(); ?> + + + + display_save_btn(); ?> + +
    + + display_wpforms(); + $this->display_pro_banner(); + } + + /** + * License key text for a Lite version of the plugin. + * + * @since 1.5.0 + * + * @param Options $options + */ + public static function display_license_key_field_content( $options ) { + ?> +

    + +

    + +

    + + +

    + + is_pro() ) { + return; + } + + $is_dismissed = get_user_meta( get_current_user_id(), 'wp_mail_smtp_pro_banner_dismissed', true ); + + // Do not display if user dismissed. + if ( (bool) $is_dismissed === true ) { + return; + } + + $assets_url = wp_mail_smtp()->assets_url; + $screenshots = [ + [ + 'url' => $assets_url . '/images/logs/archive.png', + 'url_thumbnail' => $assets_url . '/images/logs/archive-thumbnail.png', + 'title' => __( 'Email Logs', 'wp-mail-smtp' ), + ], + [ + 'url' => $assets_url . '/images/logs/single.png', + 'url_thumbnail' => $assets_url . '/images/logs/single-thumbnail.png', + 'title' => __( 'Individual Email Log', 'wp-mail-smtp' ), + ], + [ + 'url' => $assets_url . '/images/email-reports/screenshot-01.png', + 'url_thumbnail' => $assets_url . '/images/email-reports/thumbnail-01.png', + 'title' => __( 'Email Reports', 'wp-mail-smtp' ), + ], + ]; + ?> + +
    + + + + +
    +

    + +

    +

    + 4,000,000 websites!', 'wp-mail-smtp' ), [ 'strong' => [] ] ); ?> +

    +
    + +
    +

    + +

    + +
      +
    • +
      +

      +
    • +
    • +
      +

      +
    • +
    • +
      +

      +
    • +
    • +
      +

      +
    • +
    +
    + +
    + + + + +
    + <?php esc_attr_e( 'Discount', 'wp-mail-smtp' ); ?> +

    + %s OFF for WP Mail SMTP users, applied at checkout.', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ), + '$50' + ); + ?> +

    +
    +
    + +
    +
    + +
    + + <?php esc_attr( $screenshot['title'] ); ?> + + +
    + +
    +
    +
    + is_pro() ) { + return; + } + + $upgrade_link_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Backup Connection Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Link', + ] + ); + ?> +
    +
    +

    + +

    +

    + Upgrade to WP Mail SMTP Pro!', 'wp-mail-smtp' ), + esc_url( $upgrade_link_url ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + ?> +

    +
    +
    +
    +
    + +
    +
    +
    + +
    +

    + additional connection, you can select it here.', 'wp-mail-smtp' ), + add_query_arg( + [ + 'tab' => 'connections', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + ?> +

    +
    +
    + get_connections_manager()->get_primary_connection(); + $connection_settings = new ConnectionSettings( $connection ); + + $old_data = $connection->get_options()->get_all(); + + $data = $connection_settings->process( $data, $old_data ); + + /** + * Filters mail settings before save. + * + * @since 2.2.1 + * + * @param array $data Settings data. + */ + $data = apply_filters( 'wp_mail_smtp_settings_tab_process_post', $data ); + + // All the sanitization is done in Options class. + Options::init()->set( $data, false, false ); + + $connection_settings->post_process( $data, $old_data ); + + if ( $connection_settings->get_scroll_to() !== false ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + wp_safe_redirect( sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) ) . $connection_settings->get_scroll_to() ); + exit; + } + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SmartRoutingTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SmartRoutingTab.php new file mode 100644 index 00000000..fc1db00b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/SmartRoutingTab.php @@ -0,0 +1,313 @@ +get_admin()->get_current_tab() === $this->slug && ! wp_mail_smtp()->is_pro() ) { + $this->hooks(); + } + } + + /** + * Link label of a tab. + * + * @since 3.7.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Smart Routing', 'wp-mail-smtp' ); + } + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.7.0 + */ + public function enqueue_assets() { + + wp_enqueue_style( + 'wp-mail-smtp-smart-routing', + wp_mail_smtp()->plugin_url . '/assets/css/smtp-smart-routing.min.css', + [], + WPMS_PLUGIN_VER + ); + } + + /** + * Output HTML of smart routing education. + * + * @since 3.7.0 + */ + public function display() { + + $top_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Smart Routing Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Button Top', + ] + ); + $bottom_upgrade_button_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'Smart Routing Settings', + 'content' => 'Upgrade to WP Mail SMTP Pro Button', + ] + ); + ?> +
    +
    +

    + +

    +

    + +

    + + + + +
    +
    +
    + esc_html__( 'Enable Smart Routing', 'wp-mail-smtp' ), + 'class' => 'wp-mail-smtp-smart-routing-toggle', + 'checked' => true, + ] + ); + ?> +
    +
    +
    +
    +
    + + + +
    +
    + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    + + + + + + + + +
    + + + + + + + + +
    +
    +
    +
    + + + + + + + + + +
    + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + + + +
    +
    + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + +
    + + + + + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + <?php esc_attr_e( 'Light bulb icon', 'wp-mail-smtp' ); ?> + +
    +
    +
    + + + + +
    + get_label(); + } + + /** + * Display the content of the tab. + * + * @since 1.0.0 + */ + public function display() { + + if ( $this->result === self::FAILED_DOMAIN_CHECK ) { + $this->display_domain_check_details(); + + return; + } + + if ( $this->result === self::SUCCESS ) { + $this->display_success_banner(); + } + + $this->display_form(); + } + + /** + * Process the email-test form submission. + * + * @since 1.0.0 + * + * @param array $data Submitted POST data. + * + * @inheritdoc + */ + public function process_post( $data ) { + + $connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + + /** + * Filters test email connection object. + * + * @since 3.7.0 + * + * @param ConnectionInterface $connection The Connection object. + * @param array $data Post data. + */ + $this->connection = apply_filters( 'wp_mail_smtp_admin_pages_test_tab_process_post_connection', $connection, $data ); + + if ( ! empty( $data['test']['email'] ) ) { + $data['test']['email'] = wp_unslash( $data['test']['email'] ); + $data['test']['email'] = filter_var( $data['test']['email'], FILTER_VALIDATE_EMAIL ); + } + + if ( empty( $data['test']['email'] ) ) { + WP::add_admin_notice( + esc_html__( 'Test failed. Please use a valid email address and try to resend the test email.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_WARNING + ); + + return; + } + + $is_html = ! empty( $data['test']['html'] ); + + $test_email = ( new TestEmail() ) + ->with_connection( $this->connection ) + ->with_context( TestEmail::CONTEXT_ADMIN_TEST ) + ->as_html( $is_html ) + ->with_domain_check( true ); + + $test_email->send( $data['test']['email'] ); + + $this->result = $test_email->get_result(); + $this->domain_checker = $test_email->get_domain_checker(); + + // Persist the form values so the next visit prefills with what the + // user last entered (matches ESMTP's TestTab behavior). + $test_email_options = get_option( self::TEST_EMAIL_OPTION_KEY, [] ); + $test_email_options['to'] = $data['test']['email']; + $test_email_options['html'] = $is_html; + + update_option( self::TEST_EMAIL_OPTION_KEY, $test_email_options, false ); + } + + /** + * Display test email title section. + * + * @since 3.0.0 + */ + private function display_title_section() { + + ?> + +
    +
    +

    +
    +
    + '', + 'html' => true, + ], + get_option( self::TEST_EMAIL_OPTION_KEY, [] ) + ); + + if ( empty( $test_email_options['to'] ) ) { + $test_email_options['to'] = wp_get_current_user()->user_email; + } + + ?> +
    + wp_nonce_field(); ?> + + display_title_section(); ?> + + +
    +
    + +
    +
    + +

    + +

    +
    +
    + + + + +
    +
    + +
    +
    + 'wp-mail-smtp[test][html]', + 'id' => 'wp-mail-smtp-setting-test_email_html', + 'checked' => (bool) $test_email_options['html'], + ] + ); + ?> +

    + +

    +
    +
    + +

    + get_providers()->get_mailer( + Options::init()->get( 'mail', 'mailer' ), + wp_mail_smtp()->get_processor()->get_phpmailer() + ); + + if ( ! $mailer || ! $mailer->is_mailer_complete() ) { + $btn = 'wp-mail-smtp-btn-red'; + $disabled = 'disabled'; + + $help_text = '' . esc_html__( 'You cannot send an email. Mailer is not properly configured. Please check your settings.', 'wp-mail-smtp' ) . ''; + } + ?> + + +

    + post_form_hidden_field(); ?> +
    + + is_mailer_complete() && isset( $_GET['auto-start'] ) ) : // phpcs:ignore ?> + + domain_checker ) || $this->domain_checker->no_issues() ) { + return; + } + + $this->display_title_section(); + ?> + + domain_checker->is_supported_mailer() ) : ?> +
    +

    +
    + + + domain_checker->get_results_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + +
    + + + +
    + assets_url; + $illustration = $assets_url . '/images/test-success/illustration-lite.svg'; + + $upgrade_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'lite-test-email-success', + 'content' => 'Upgrade Button', + ] + ); + + $bullets = [ + esc_html__( 'Log, track, and resend any email your site sends', 'wp-mail-smtp' ), + esc_html__( 'Get instant failure alerts via Email, Slack, SMS, Discord, etc', 'wp-mail-smtp' ), + esc_html__( 'Never miss an email with an automatic backup mailer', 'wp-mail-smtp' ), + ]; + ?> +
    + display_success_banner_dismiss(); ?> + +
    +
    +
    +
    + +

    + +

    +
    +

    + ' . esc_html__( 'Pro', 'wp-mail-smtp' ) . '' + ), + [ 'strong' => [ 'class' => [] ] ] + ); + ?> +

    +
    + +
      + +
    • + + +
    • + +
    • + + + + + + + +
    • +
    + +
    + + + + +

    + + + + + +

    +
    +
    + +
    + <?php esc_attr_e( 'A person celebrating after sending an email.', 'wp-mail-smtp' ); ?> +
    +
    + + display_success_pro_tip_strip(); ?> +
    + + + get_cross_sell_recommendations( 1 ); + + if ( empty( $recommendations ) ) { + return; + } + + $product = $recommendations[0]; + $install_url = $this->get_install_plugin_url( $product['install'] ); + ?> +
    + + + + , e.g. WPConsent. */ + __( '%1$s with our sister plugin %2$s', 'wp-mail-smtp' ), + esc_html( $product['pro_tip'] ), + '' . esc_html( $product['name'] ) . '' + ), + [ 'strong' => [ 'class' => [] ] ] + ); + ?> + - + + + + + +
    + > + */ + protected function get_cross_sell_recommendations( $limit = 3 ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded -- Inner foreach over per-product competitor list is intentional; flattening would obscure the catalog structure. + + if ( ! current_user_can( 'install_plugins' ) ) { + return []; + } + + $assets_url = wp_mail_smtp()->assets_url; + + $catalog = [ + [ + 'name' => 'ActiveLayer', + 'title' => esc_html__( 'Smarter Spam Protection for WordPress', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Catch spam in milliseconds with AI, invisible to your real visitors.', 'wp-mail-smtp' ), + 'pro_tip' => esc_html__( 'Stop spam at the door', 'wp-mail-smtp' ), + 'icon' => $assets_url . '/images/about/icon-activelayer.svg', + 'plugin' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', + 'install' => 'activelayer-anti-spam-spam-protection-for-forms-comments', + 'install_url' => 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip', + 'settings_page_url' => admin_url( 'admin.php?page=activelayer-settings' ), + 'framed_icon' => true, + 'competitors' => [ + 'akismet/akismet.php', + 'antispam-bee/antispam_bee.php', + 'honeypot/wp-armour.php', + 'wp-armour-extended/wp-armour-extended.php', + 'cleantalk-spam-protect/cleantalk.php', + 'wp-cerber/wp-cerber.php', + 'anti-spam/anti-spam.php', + ], + ], + [ + 'name' => 'WPConsent', + 'title' => esc_html__( 'Stay GDPR & Privacy Compliant', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Add a cookie consent banner to your site and meet privacy laws in minutes.', 'wp-mail-smtp' ), + 'pro_tip' => esc_html__( 'Stay GDPR & Privacy compliant', 'wp-mail-smtp' ), + 'icon' => $assets_url . '/images/about/icon-wpconsent.svg', + 'plugin' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', + 'install' => 'wpconsent-cookies-banner-privacy-suite', + 'install_url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', + 'settings_page_url' => admin_url( 'admin.php?page=wpconsent-cookies' ), + 'framed_icon' => true, + 'competitors' => [ + 'cookie-law-info/cookie-law-info.php', + 'complianz-gdpr/complianz-gpdr.php', + 'complianz-gdpr-premium/complianz-gpdr-premium.php', + 'cookie-notice/cookie-notice.php', + 'gdpr-cookie-compliance/moove-gdpr.php', + 'iubenda-cookie-law-solution/iubenda_cookie_solution.php', + 'real-cookie-banner/index.php', + 'cookiebot/cookiebot.php', + 'uk-cookie-consent/uk-cookie-consent.php', + 'borlabs-cookie/borlabs-cookie.php', + ], + ], + [ + 'name' => 'Duplicator', + 'title' => esc_html__( 'Add Secure WordPress Backups', 'wp-mail-smtp' ), + 'desc' => esc_html__( 'Automated, encrypted backups with 1-click restore to keep your site safe.', 'wp-mail-smtp' ), + 'pro_tip' => esc_html__( 'Protect your site with automated backups', 'wp-mail-smtp' ), + 'icon' => $assets_url . '/images/about/icon-duplicator.svg', + 'plugin' => 'duplicator/duplicator.php', + 'plugin_pro' => 'duplicator-pro/duplicator-pro.php', + 'install' => 'duplicator', + 'install_url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', + 'settings_page_url' => admin_url( 'admin.php?page=duplicator-settings' ), + 'framed_icon' => false, + 'competitors' => [ + 'all-in-one-wp-migration/all-in-one-wp-migration.php', + 'all-in-one-wp-migration-unlimited-extension/all-in-one-wp-migration-unlimited-extension.php', + 'updraftplus/updraftplus.php', + 'wpvivid-backuprestore/wpvivid-backuprestore.php', + 'wpvivid-backup-pro/wpvivid-backup-pro.php', + 'backwpup/backwpup.php', + 'backwpup-pro/backwpup.php', + 'migrate-guru/migrateguru.php', + 'wp-migrate-db/wp-migrate-db.php', + 'wp-migrate-db-pro/wp-migrate-db-pro.php', + 'wp-staging/wp-staging.php', + 'wp-staging-pro/wp-staging-pro.php', + 'backupbuddy/backupbuddy.php', + ], + ], + ]; + + if ( ! function_exists( 'get_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $installed = get_plugins(); + + $candidates = array_filter( + $catalog, + function ( $product ) use ( $installed ) { + if ( array_key_exists( $product['plugin'], $installed ) ) { + return false; + } + + // Pro variant present too — treat as "already provided" so we + // don't push the Lite when the Pro version is installed. + if ( ! empty( $product['plugin_pro'] ) && array_key_exists( $product['plugin_pro'], $installed ) ) { + return false; + } + + // Drop the entry when a competitor that covers the same need + // is installed (e.g., recommending Duplicator to an + // UpdraftPlus user is wasted screen space). + if ( ! empty( $product['competitors'] ) ) { + foreach ( $product['competitors'] as $competitor ) { + if ( array_key_exists( $competitor, $installed ) ) { + return false; + } + } + } + + return true; + } + ); + + return array_slice( array_values( $candidates ), 0, $limit ); + } + + /** + * Build a nonce-protected install-plugin URL for the WP-Admin updater. + * Users without the install_plugins capability get the public wp.org + * page so the link still makes sense. + * + * @since 4.9.0 + * + * @param string $slug WP.org plugin slug. + * + * @return string + */ + private function get_install_plugin_url( $slug ) { + + if ( ! current_user_can( 'install_plugins' ) ) { + return 'https://wordpress.org/plugins/' . $slug . '/'; + } + + return wp_nonce_url( + self_admin_url( 'update.php?action=install-plugin&plugin=' . $slug ), + 'install-plugin_' . $slug + ); + } + + /** + * Get the plain text prepared message for test email. + * + * Use {@see TestEmail::get_email_message_text()} instead. + * + * @since 1.4.0 + * @since 1.5.0 Display an upsell to WP Mail SMTP Pro if free version installed. + * @since 2.6.0 Change visibility, so it can be used elsewhere. + * @deprecated {VERSION} + * + * @return string + */ + public static function get_email_message_text() { + + return TestEmail::get_email_message_text(); + } + + /** + * Returns debug information for detection, processing, and display. + * + * @since 1.3.0 + * @deprecated {VERSION} + * + * @return array + */ + protected function get_debug_details() { + + _deprecated_function( __METHOD__, '4.9.0', '\WPMailSMTP\Admin\EmailSendingErrors\EmailSendingErrors::get_local_failure_info' ); + + return []; + } + + /** + * Displays all the various error and debug details. + * + * @since 1.3.0 + * @deprecated {VERSION} + */ + protected function display_debug_details() { + + _deprecated_function( __METHOD__, '4.9.0', '\WPMailSMTP\Admin\EmailSendingErrors\EmailSendingErrors::print_banner_body' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Tools.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Tools.php new file mode 100644 index 00000000..8b9f0522 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Pages/Tools.php @@ -0,0 +1,55 @@ +get_label(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/ParentPageAbstract.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/ParentPageAbstract.php new file mode 100644 index 00000000..5ee8a827 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/ParentPageAbstract.php @@ -0,0 +1,392 @@ +slug . '_tabs', $tabs ); + + if ( wp_mail_smtp()->get_admin()->is_admin_page( $this->slug ) ) { + $this->init_tabs( $tabs ); + $this->hooks(); + } + + if ( WP::is_doing_self_ajax() ) { + $this->init_ajax( $tabs ); + } + } + + /** + * Hooks. + * + * @since 2.8.0 + */ + protected function hooks() { + + add_action( 'admin_init', [ $this, 'process_actions' ] ); + + // Register tab related hooks. + if ( isset( $this->tabs[ $this->get_current_tab() ] ) ) { + $this->tabs[ $this->get_current_tab() ]->hooks(); + } + } + + /** + * Initialize ajax actions. + * + * @since 3.0.0 + * + * @param array $tabs Page tabs. + */ + private function init_ajax( $tabs ) { + + foreach ( $tabs as $tab ) { + if ( $this->is_valid_tab( $tab ) ) { + ( new $tab( $this ) )->ajax(); + } + } + } + + /** + * Get the page slug. + * + * @since 2.8.0 + * + * @return string + */ + public function get_slug() { + + return $this->slug; + } + + /** + * Get the page tabs. + * + * @since 2.8.0 + * + * @return PageAbstract[] + */ + public function get_tabs() { + + return $this->tabs; + } + + /** + * Get the page tabs slugs. + * + * @since 2.8.0 + * + * @return string[] + */ + public function get_tabs_slugs() { + + return array_map( + function ( $tab ) { + return $tab->get_slug(); + }, + $this->tabs + ); + } + + /** + * Get the page/tab link. + * + * @since 2.8.0 + * + * @param string $tab Tab to generate a link to. + * + * @return string + */ + public function get_link( $tab = '' ) { + + return add_query_arg( + 'tab', + $this->get_defined_tab( $tab ), + WP::admin_url( 'admin.php?page=' . Area::SLUG . '-' . $this->slug ) + ); + } + + /** + * Get the current tab. + * + * @since 2.8.0 + * + * @return string + */ + public function get_current_tab() { + + $tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + return $this->get_defined_tab( $tab ); + } + + /** + * Get the tab label. + * + * @since 2.9.0 + * + * @param string $tab Tab key. + * + * @return string + */ + public function get_tab_label( $tab ) { + + $tabs = $this->get_tabs(); + + return isset( $tabs[ $tab ] ) ? $tabs[ $tab ]->get_label() : ''; + } + + /** + * Get the tab title. + * + * @since 2.9.0 + * + * @param string $tab Tab key. + * + * @return string + */ + public function get_tab_title( $tab ) { + + $tabs = $this->get_tabs(); + + return isset( $tabs[ $tab ] ) ? $tabs[ $tab ]->get_title() : ''; + } + + /** + * Get the defined or default tab. + * + * @since 2.8.0 + * + * @param string $tab Tab to check. + * + * @return string Defined tab. Fallback to default one if it doesn't exist. + */ + protected function get_defined_tab( $tab ) { + + $tab = sanitize_key( $tab ); + + return in_array( $tab, $this->get_tabs_slugs(), true ) ? $tab : $this->default_tab; + } + + /** + * Initialize tabs. + * + * @since 2.8.0 + * + * @param array $tabs Page tabs. + */ + public function init_tabs( $tabs ) { + + foreach ( $tabs as $key => $tab ) { + if ( ! $this->is_valid_tab( $tab ) ) { + continue; + } + + $this->tabs[ $key ] = new $tab( $this ); + } + + // Sort tabs by priority. + $this->sort_tabs(); + } + + /** + * All possible plugin forms manipulation and hooks registration will be done here. + * + * @since 2.8.0 + */ + public function process_actions() { + + $tabs = $this->get_tabs_slugs(); + + // Allow to process only own tabs. + if ( ! array_key_exists( $this->get_current_tab(), $tabs ) ) { + return; + } + + // Process POST only if it exists. + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + if ( ! empty( $_POST ) && isset( $_POST['wp-mail-smtp-post'] ) ) { + $current_tab = $this->tabs[ $this->get_current_tab() ]; + + // Verify nonce. + $current_tab->check_admin_referer(); + + // Verify capability. + // Per-site cap is intentional: the only tab routed here that writes to the global option store (Debug Events) gates itself with manage_network_options. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) ); + } + + if ( ! empty( $_POST['wp-mail-smtp'] ) ) { + $post = $_POST['wp-mail-smtp']; + } else { + $post = []; + } + + $current_tab->process_post( $post ); + } + // phpcs:enable + + // This won't do anything for most pages. + // Works for plugin page only, when GET params are allowed. + $this->tabs[ $this->get_current_tab() ]->process_auth(); + } + + /** + * Display page content based on the current tab. + * + * @since 2.8.0 + */ + public function display() { + + $current_tab = $this->get_current_tab(); + $page_slug = $this->slug; + ?> +
    + tabs ) > 1 ) : ?> + tabs as $tab ) : ?> + + get_label() ); ?> + + + + tabs )[0]->get_title() ); ?> + + + +
    + +
    + tabs as $tab ) { + if ( $tab->get_slug() === $current_tab ) { + + printf( '

    %s

    ', esc_html( $tab->get_title() ) ); + + /** + * Fires before tab content. + * + * @since 2.8.0 + * + * @param PageAbstract $tab Current tab. + */ + do_action( 'wp_mail_smtp_admin_pages_before_content', $tab ); + + /** + * Fires before tab content. + * + * @since 2.9.0 + * + * @param PageAbstract $tab Current tab. + */ + do_action( "wp_mail_smtp_admin_page_{$page_slug}_{$current_tab}_display_before", $tab ); + + $tab->display(); + + /** + * Fires after tab content. + * + * @since 2.8.0 + * + * @param PageAbstract $tab Current tab. + */ + do_action( "wp_mail_smtp_admin_page_{$page_slug}_{$current_tab}_display_after", $tab ); + + break; + } + } + ?> +
    + tabs, + function ( $a, $b ) { + + return ( $a->get_priority() < $b->get_priority() ) ? - 1 : 1; + } + ); + } + + /** + * Whether tab is valid. + * + * @since 3.0.0 + * + * @param array $tab Page tab. + * + * @return bool + */ + private function is_valid_tab( $tab ) { + + return is_subclass_of( $tab, '\WPMailSMTP\Admin\PageAbstract' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/PluginsInstallSkin.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/PluginsInstallSkin.php new file mode 100644 index 00000000..e8e1c0a2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/PluginsInstallSkin.php @@ -0,0 +1,58 @@ +check_wpconsent_activation( $seeded ); + $this->check_duplicator_activation( $seeded ); + $this->check_wpcode_activation( $seeded ); + $this->seed_active_plugins_as_adopted( $seeded ); + + // Only write the option when there is something to seed. + // Use add_option (not update_option) so we never clobber a value the + // live activated_plugin hook may already have written. + if ( ! empty( $seeded ) ) { + add_option( RecommendedPlugins::ACTIVATED_OPTION, $seeded ); + } + + $this->update_db_ver( 1 ); + } + + /** + * Seed WPConsent activation timestamp from the partner's own option. + * + * @since 4.9.0 + * + * @param array $seeded Array of slug => timestamp being built. Passed by reference. + */ + private function check_wpconsent_activation( &$seeded ) { + + $value = get_option( 'wpconsent_activated', [] ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return; + } + + // Lite stores its timestamp under 'wpconsent'; Pro under 'wpconsent_pro'. + $timestamp = isset( $value['wpconsent'] ) ? $value['wpconsent'] : ''; + + if ( empty( $timestamp ) ) { + $timestamp = isset( $value['wpconsent_pro'] ) ? $value['wpconsent_pro'] : ''; + } + + if ( ! empty( $timestamp ) ) { + $seeded['wpconsent'] = (int) $timestamp; + } + } + + /** + * Seed Duplicator activation timestamp from the partner's own option. + * + * @since 4.9.0 + * + * @param array $seeded Array of slug => timestamp being built. Passed by reference. + */ + private function check_duplicator_activation( &$seeded ) { + + $install_info = get_option( 'duplicator_install_info' ); + $timestamp = ( is_array( $install_info ) && ! empty( $install_info['time'] ) ) ? $install_info['time'] : null; + + // Fall back to Duplicator Pro install info when the Lite timestamp is absent. + if ( empty( $timestamp ) ) { + $pro_install_info = get_option( 'duplicator_pro_install_info' ); + $timestamp = ( is_array( $pro_install_info ) && ! empty( $pro_install_info['time'] ) ) ? $pro_install_info['time'] : null; + } + + if ( ! empty( $timestamp ) ) { + $seeded['duplicator'] = (int) $timestamp; + } + } + + /** + * Seed WPCode activation timestamp from the partner's own option. + * + * @since 4.9.0 + * + * @param array $seeded Array of slug => timestamp being built. Passed by reference. + */ + private function check_wpcode_activation( &$seeded ) { + + $value = get_option( 'ihaf_activated', [] ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return; + } + + $timestamp = isset( $value['wpcode'] ) ? $value['wpcode'] : ''; + + if ( ! empty( $timestamp ) ) { + $seeded['wpcode'] = (int) $timestamp; + } + } + + /** + * Mark any currently-active catalog plugin not already recorded as adopted. + * + * Pre-existing installs should not be promoted after upgrade. Products that + * expose no activation-time option can't be backdated from their own data, + * so detect an active install directly and stamp it past the rotation + * window, making it count as already adopted. + * + * @since 4.9.0 + * + * @param array $seeded Array of slug => timestamp being built. Passed by reference. + */ + private function seed_active_plugins_as_adopted( &$seeded ) { + + if ( ! function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $adopted_time = time() - RecommendedPlugins::ADOPTED_AFTER - DAY_IN_SECONDS; + + foreach ( ( new RecommendedPlugins() )->get_products() as $product ) { + if ( isset( $seeded[ $product['slug'] ] ) ) { + continue; + } + + $files = array_filter( + [ + isset( $product['plugin'] ) ? $product['plugin'] : '', + isset( $product['plugin_pro'] ) ? $product['plugin_pro'] : '', + ] + ); + + foreach ( $files as $file ) { + if ( is_plugin_active( $file ) ) { + $seeded[ $product['slug'] ] = $adopted_time; + + break; + } + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/PageAbstract.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/PageAbstract.php new file mode 100644 index 00000000..3eb2cc7a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/PageAbstract.php @@ -0,0 +1,746 @@ + Setup -> Result landing page for a single + * recommended product, handles the per-product status-check AJAX, and records + * install attribution. + * + * @since 4.9.0 + */ +abstract class PageAbstract { + + /** + * Admin menu page slug. + * + * @since 4.9.0 + * + * @var string + */ + public const SLUG = ''; + + /** + * Configuration. + * + * @since 4.9.0 + * + * @var array + */ + protected $config = []; + + /** + * Runtime data used for generating page HTML. + * + * @since 4.9.0 + * + * @var array + */ + protected $output_data = []; + + /** + * Constructor. + * + * @since 4.9.0 + */ + public function __construct() { + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + return; + } + + $this->hooks(); + } + + /** + * Hooks. + * + * @since 4.9.0 + */ + public function hooks(): void { + + $plugin = static::get_plugin_name(); + + if ( wp_doing_ajax() ) { + add_action( "wp_ajax_wp_mail_smtp_page_check_{$plugin}_status", [ $this, 'ajax_check_plugin_status' ] ); + add_action( 'activated_plugin', [ $this, 'plugin_activated' ] ); + } + + // Check what page we are on. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : ''; + + // Only load if we are actually on the correct page. + if ( $page !== static::SLUG ) { + return; + } + + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue JS and CSS files. + * + * @since 4.9.0 + */ + public function enqueue_assets(): void { + + $min = WP::asset_min(); + + // Lity (registered by Admin\Area). + wp_enqueue_style( 'wp-mail-smtp-admin-lity' ); + wp_enqueue_script( 'wp-mail-smtp-admin-lity' ); + + // Custom styles for Lity image size limitation. + wp_add_inline_style( + 'wp-mail-smtp-admin-lity', + ' + .lity-image .lity-container { + max-width: 1040px !important; + } + .lity-image img { + max-width: 1040px !important; + width: 100%; + height: auto; + } + ' + ); + + wp_enqueue_script( + 'wp-mail-smtp-admin-recommendations-' . static::get_plugin_name(), + wp_mail_smtp()->assets_url . "/js/smtp-recommendations{$min}.js", + [ 'jquery', 'wp-mail-smtp-admin' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-admin-recommendations-' . static::get_plugin_name(), + 'wp_mail_smtp_recommendations', + [ 'plugin_page' => $this->get_js_strings() ] + ); + } + + /** + * Generate and output page HTML. + * + * @since 4.9.0 + */ + public function output(): void { + + echo '
    '; + + $this->output_section_heading(); + $this->output_section_screenshot(); + $this->output_section_footer(); + $this->output_section_step_install(); + $this->output_section_step_setup(); + $this->output_section_step_result(); + + echo '
    '; + } + + /** + * Generate and output step 'Install' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_install(): void { + + $step = $this->get_data_step_install(); + + if ( empty( $step ) ) { + return; + } + + $button_format = ''; + $button_allowed_html = [ + 'button' => [ + 'class' => true, + 'data-plugin' => true, + 'data-action' => true, + 'data-provider' => true, + ], + ]; + + if ( + ! $this->output_data['plugin_installed'] && + ! $this->output_data['pro_plugin_installed'] && + ! current_user_can( 'install_plugins' ) + ) { + $button_format = '%2$s '; + $button_allowed_html = [ + 'a' => [ + 'class' => true, + 'href' => true, + 'target' => true, + 'rel' => true, + ], + 'span' => [ + 'class' => true, + 'aria-hidden' => true, + ], + ]; + } + + $is_url = (bool) preg_match( '#^https?://#i', (string) $step['plugin'] ); + $plugin_attr = $is_url ? esc_url( $step['plugin'] ) : esc_attr( $step['plugin'] ); + $button = sprintf( $button_format, $plugin_attr, esc_html( $step['button_text'] ), esc_attr( $step['button_class'] ), esc_attr( $step['button_action'] ), esc_attr( static::get_plugin_name() ) ); + + printf( + '
    + +
    +

    %3$s

    +

    %4$s

    + %5$s +
    +
    ', + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 1', 'wp-mail-smtp' ), + esc_html( $step['heading'] ), + esc_html( $step['description'] ), + wp_kses( $button, $button_allowed_html ) + ); + } + + /** + * Generate and output step 'Setup' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_setup(): void { + + $step = $this->get_data_step_setup(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 2', 'wp-mail-smtp' ), + esc_html( $step['heading'] ), + esc_html( $step['description'] ), + esc_attr( $step['button_class'] ), + esc_url( admin_url( $this->config[ static::get_plugin_name() . '_onboarding' ] ) ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Generate and output footer section HTML. + * + * @since 4.9.0 + */ + protected function output_section_footer(): void { + // Default implementation - can be overridden by child classes. + } + + /** + * Step 'Install' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_install(): array { + + $step = []; + $step['heading'] = $this->get_install_heading(); + $step['description'] = $this->get_install_description(); + + $this->output_data['all_plugins'] = get_plugins(); + $this->output_data['plugin_installed'] = array_key_exists( $this->config['lite_plugin'], $this->output_data['all_plugins'] ); + $this->output_data['plugin_activated'] = false; + $this->output_data['pro_plugin_installed'] = array_key_exists( $this->config['pro_plugin'], $this->output_data['all_plugins'] ); + $this->output_data['pro_plugin_activated'] = false; + + if ( ! $this->output_data['plugin_installed'] && ! $this->output_data['pro_plugin_installed'] ) { + $step['icon'] = 'plugin-page/step-1.svg'; + $step['button_text'] = $this->get_install_button_text(); + $step['button_class'] = 'button-primary'; + $step['button_action'] = 'install'; + $step['plugin'] = $this->config['lite_download_url']; + + if ( ! current_user_can( 'install_plugins' ) ) { + $step['heading'] = $this->get_plugin_title(); + $step['description'] = ''; + $step['button_text'] = $this->get_plugin_title() . ' on WordPress.org'; + $step['plugin'] = $this->config['lite_wporg_url']; + } + } else { + $this->output_data['plugin_activated'] = + is_plugin_active( $this->config['lite_plugin'] ) || is_plugin_active( $this->config['pro_plugin'] ); + $step['icon'] = $this->output_data['plugin_activated'] ? 'plugin-page/complete.svg' : 'plugin-page/step-1.svg'; + $step['button_text'] = + $this->output_data['plugin_activated'] + ? $this->get_installed_activated_text() + : $this->get_activate_text(); + $step['button_class'] = $this->output_data['plugin_activated'] + ? 'grey disabled' + : 'button-primary'; + $step['button_action'] = $this->output_data['plugin_activated'] ? '' : 'activate'; + $step['plugin'] = + $this->output_data['pro_plugin_installed'] ? $this->config['pro_plugin'] : $this->config['lite_plugin']; + $step['is_pro'] = $this->output_data['pro_plugin_installed']; + } + + return $step; + } + + /** + * Step 'Setup' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_setup(): array { + + $step = []; + + $this->output_data['plugin_setup'] = false; + + if ( $this->output_data['plugin_activated'] ) { + $this->output_data['plugin_setup'] = $this->is_plugin_configured(); + } + + $step['icon'] = 'plugin-page/step-2.svg'; + $step['section_class'] = $this->output_data['plugin_activated'] ? '' : 'grey'; + $step['heading'] = $this->get_setup_heading(); + $step['description'] = $this->get_setup_description(); + $step['button_text'] = $this->get_setup_button_text(); + $step['button_class'] = 'grey disabled'; + + if ( $this->output_data['plugin_setup'] ) { + $step['icon'] = 'plugin-page/complete.svg'; + $step['section_class'] = ''; + $step['button_text'] = $this->get_setup_completed_text(); + } else { + $step['button_class'] = $this->output_data['plugin_activated'] ? 'button-primary' : 'grey disabled'; + } + + return $step; + } + + /** + * Ajax endpoint. Check plugin setup status. + * Used to properly init the step 2 section after completing step 1. + * + * @since 4.9.0 + */ + public function ajax_check_plugin_status(): void { + + // Security checks. + if ( + ! check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) || + ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) + ) { + wp_send_json_error( + [ 'error' => esc_html__( 'You do not have permission.', 'wp-mail-smtp' ) ] + ); + } + + $result = []; + + if ( ! $this->is_plugin_available() ) { + wp_send_json_error( + [ 'error' => esc_html__( 'Plugin unavailable.', 'wp-mail-smtp' ) ] + ); + } + + $result['setup_status'] = (int) $this->is_plugin_configured(); + + $result['license_level'] = 'lite'; + $result['step3_button_url'] = $this->config[ static::get_plugin_name() . '_addon_page' ]; + + if ( $this->is_pro_active() ) { + $result['license_level'] = 'pro'; + } + + $result['result_status'] = $this->is_plugin_finished_setup(); + + $result['addon_installed'] = (int) array_key_exists( $this->config[ static::get_plugin_name() . '_addon' ], get_plugins() ); + + wp_send_json_success( $result ); + } + + /** + * Set the source of the plugin installation. + * + * @since 4.9.0 + * + * @param string $plugin_basename The basename of the plugin. + */ + public function plugin_activated( string $plugin_basename ): void { + + if ( $plugin_basename !== $this->config['lite_plugin'] ) { + return; + } + + $source = wp_mail_smtp()->is_pro() ? 'WP Mail SMTP Pro' : 'WP Mail SMTP'; + + update_option( static::get_plugin_name() . '_source', $source, false ); + update_option( static::get_plugin_name() . '_date', time(), false ); + } + + /** + * JS strings. + * + * @since 4.9.0 + * + * @return array Array of strings. + * @noinspection HtmlUnknownTarget + */ + protected function get_js_strings(): array { + + $error_could_not_install = sprintf( + wp_kses( /* translators: %1$s - Lite plugin download URL. */ + __( 'Could not install the plugin automatically. Please download it and install it manually.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => true, + ], + ] + ), + esc_url( $this->config['lite_download_url'] ?? '' ) + ); + + $error_could_not_activate = sprintf( + wp_kses( /* translators: %1$s - Plugins page URL. */ + __( 'Could not activate the plugin. Please activate it on the Plugins page.', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => true, + ], + ] + ), + esc_url( admin_url( 'plugins.php' ) ) + ); + + return [ + 'installing' => esc_html__( 'Installing...', 'wp-mail-smtp' ), + 'activating' => esc_html__( 'Activating...', 'wp-mail-smtp' ), + 'activated' => $this->get_installed_activated_text(), + 'activated_pro' => $this->get_pro_installed_activated_text(), + 'install_now' => esc_html__( 'Install Now', 'wp-mail-smtp' ), + 'activate_now' => esc_html__( 'Activate Now', 'wp-mail-smtp' ), + 'download_now' => esc_html__( 'Download Now', 'wp-mail-smtp' ), + 'plugins_page' => esc_html__( 'Go to Plugins page', 'wp-mail-smtp' ), + 'error_could_not_install' => $error_could_not_install, + 'error_could_not_activate' => $error_could_not_activate, + static::get_plugin_name() . '_manual_install_url' => $this->config['lite_download_url'], + static::get_plugin_name() . '_manual_activate_url' => admin_url( 'plugins.php' ), + ]; + } + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + abstract protected static function get_plugin_name(): string; + + /** + * Generate and output heading section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + public function output_section_heading(): void { + + $strings = $this->get_heading_strings(); + + // Heading section. + printf( + '
    + %2$s +

    %3$s

    +

    %4$s

    +
    ', + esc_url( $this->get_heading_image_url() ), + esc_attr( $this->get_heading_alt_text() ), + esc_html( $this->get_heading_title() ), + esc_html( implode( ' ', $strings ) ) + ); + } + + /** + * Get heading image URL. + * + * @since 4.9.0 + * + * @return string Heading image URL. + */ + protected function get_heading_image_url(): string { + + return wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/logo.svg'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + abstract protected function get_heading_title(): string; + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + abstract protected function get_heading_alt_text(): string; + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + abstract protected function get_heading_strings(): array; + + /** + * Generate and output screenshot section HTML. + * + * @since 4.9.0 + */ + protected function output_section_screenshot(): void { + + $features = $this->get_screenshot_features(); + + $list = ''; + + foreach ( $features as $feature ) { + $list .= sprintf( + '
  • %s
  • ', + esc_html( $feature ) + ); + } + + // Screenshot section. + printf( + '
    +
    + %2$s + +
    +
      %5$s
    +
    ', + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/screenshot-tnail.png' ), + esc_attr( $this->get_screenshot_alt_text() ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/screenshot-full@2x.png' ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/plugins/' . static::get_plugin_name() . '/screenshot-tnail@2x.png' ), + wp_kses( + $list, + [ + 'li' => [], + 'span' => [ + 'class' => [], + 'aria-hidden' => [], + ], + ] + ) + ); + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + abstract protected function get_screenshot_features(): array; + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + abstract protected function get_screenshot_alt_text(): string; + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + */ + abstract protected function output_section_step_result(): void; + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if a plugin is configured properly. + */ + abstract protected function is_plugin_configured(): bool; + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is active. + */ + abstract protected function is_plugin_activated(): bool; + + /** + * Whether a plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + abstract protected function is_plugin_finished_setup(): bool; + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if a plugin is available. + */ + abstract protected function is_plugin_available(): bool; + + /** + * Whether a pro-version is active. + * + * @since 4.9.0 + * + * @return bool True if a pro-version is active. + */ + abstract protected function is_pro_active(): bool; + + /** + * Get the heading for the installation step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + abstract protected function get_install_heading(): string; + + /** + * Get the description for the installation step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + abstract protected function get_install_description(): string; + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + abstract protected function get_plugin_title(): string; + + /** + * Get the installation button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + abstract protected function get_install_button_text(): string; + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + abstract protected function get_installed_activated_text(): string; + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + abstract protected function get_activate_text(): string; + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + abstract protected function get_setup_heading(): string; + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + abstract protected function get_setup_description(): string; + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + abstract protected function get_setup_button_text(): string; + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + abstract protected function get_setup_completed_text(): string; + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + abstract protected function get_pro_installed_activated_text(): string; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/ActiveLayer.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/ActiveLayer.php new file mode 100644 index 00000000..5f6243a1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/ActiveLayer.php @@ -0,0 +1,375 @@ + 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/activelayer-anti-spam-spam-protection-for-forms-comments/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/activelayer-anti-spam-spam-protection-for-forms-comments.zip', + 'pro_plugin' => '', + 'activelayer_addon' => '', + 'activelayer_addon_page' => 'https://activelayer.com/pricing/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=spam-protection-page', + 'activelayer_onboarding' => 'admin.php?page=activelayer-settings', + ]; + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + protected static function get_plugin_name(): string { + + return 'activelayer'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + protected function get_heading_title(): string { + + return esc_html__( 'Block Spam Without Blocking Real Visitors', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + protected function get_heading_alt_text(): string { + + return esc_attr__( 'WP Mail SMTP ♥ ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + protected function get_heading_strings(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'Smarter protection for your forms, comments, and reviews.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + protected function get_screenshot_features(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'AI detection using content, sender reputation, and behavior signals.', 'wp-mail-smtp' ), + esc_html__( 'No CAPTCHAs, no friction for real visitors.', 'wp-mail-smtp' ), + esc_html__( 'Millisecond verdicts, invisible to legitimate users.', 'wp-mail-smtp' ), + esc_html__( 'Works with 9+ popular form plugins out of the box.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + protected function get_screenshot_alt_text(): string { + + return esc_attr__( 'ActiveLayer screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_result(): void { + + $step = $this->get_data_step_result(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 3', 'wp-mail-smtp' ), + esc_html__( 'Protect Even More of Your Site', 'wp-mail-smtp' ), + esc_html__( 'Upgrade your plan for higher limits and advanced controls across all your forms, comments, and reviews.', 'wp-mail-smtp' ), + esc_attr( $step['button_class'] ), + esc_url( $step['button_url'] ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Step 'Result' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_result(): array { + + $step = []; + + $step['icon'] = 'plugin-page/step-3.svg'; + $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; + $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); + $step['button_url'] = $this->config['activelayer_addon_page']; + $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; + + return $step; + } + + /** + * Whether the plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + protected function is_plugin_finished_setup(): bool { + + return $this->is_plugin_configured(); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is configured properly. + */ + protected function is_plugin_configured(): bool { + + if ( ! $this->is_plugin_activated() ) { + return false; + } + + $settings = get_option( 'activelayer_global_settings' ); + + return is_array( $settings ) && ! empty( $settings['api_key'] ); + } + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is active. + */ + protected function is_plugin_activated(): bool { + + return is_plugin_active( $this->config['lite_plugin'] ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if plugin is available. + */ + protected function is_plugin_available(): bool { + + return class_exists( 'ActiveLayer\Plugin' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether pro version is active. + * + * @since 4.9.0 + * + * @return bool True if pro version is active. + */ + protected function is_pro_active(): bool { + + return false; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the install step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + protected function get_install_heading(): string { + + return esc_html__( 'Install & Activate ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the install step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + protected function get_install_description(): string { + + return esc_html__( 'Install ActiveLayer from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + protected function get_plugin_title(): string { + + return esc_html__( 'ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the install button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + protected function get_install_button_text(): string { + + return esc_html__( 'Install ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + protected function get_installed_activated_text(): string { + + return esc_html__( 'ActiveLayer Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + protected function get_activate_text(): string { + + return esc_html__( 'Activate ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + protected function get_setup_heading(): string { + + return esc_html__( 'Connect ActiveLayer', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + protected function get_setup_description(): string { + + return esc_html__( 'Add your free API key on the settings page to switch on protection.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + protected function get_setup_button_text(): string { + + return esc_html__( 'Open Settings', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + protected function get_setup_completed_text(): string { + + return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + protected function get_pro_installed_activated_text(): string { + + return esc_html__( 'ActiveLayer Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Duplicator.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Duplicator.php new file mode 100644 index 00000000..50a3efb4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Duplicator.php @@ -0,0 +1,481 @@ + 'duplicator/duplicator.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/duplicator/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/duplicator.zip', + 'pro_plugin' => 'duplicator-pro/duplicator-pro.php', + 'duplicator_addon' => 'duplicator-pro/duplicator-pro.php', + 'duplicator_addon_page' => 'https://duplicator.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=duplicator-page', + 'duplicator_onboarding' => 'admin.php?page=duplicator', + ]; + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + protected static function get_plugin_name(): string { + + return 'duplicator'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + protected function get_heading_title(): string { + + return esc_html__( 'WP Mail SMTP Delivers It. Duplicator Protects It.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + protected function get_heading_alt_text(): string { + + return esc_attr__( 'WP Mail SMTP ♥ Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + protected function get_heading_strings(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'Trusted by over 1.5 million websites.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + protected function get_screenshot_features(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'Back up your entire site automatically.', 'wp-mail-smtp' ), + esc_html__( 'Restore your site with one click if anything goes wrong.', 'wp-mail-smtp' ), + esc_html__( 'Store backups safely in Google Drive, Dropbox, or Amazon S3.', 'wp-mail-smtp' ), + esc_html__( 'Schedule daily backups so you never have to think about it.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + protected function get_screenshot_alt_text(): string { + + return esc_attr__( 'Duplicator screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_result(): void { + + $step = $this->get_data_step_result(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 3', 'wp-mail-smtp' ), + esc_html__( 'Set Up Scheduled Cloud Backups', 'wp-mail-smtp' ), + esc_html__( 'Keep your data safe forever with automatic daily backups to Google Drive, Dropbox, or Amazon S3.', 'wp-mail-smtp' ), + esc_attr( $step['button_class'] ), + esc_url( admin_url( 'admin.php?page=duplicator-schedules' ) ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Step 'Result' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_result(): array { + + $count = $this->get_package_count(); + + $data = [ + 'section_class' => $count ? '' : 'grey', + 'button_class' => ! $count ? 'grey disabled' : 'button-primary', + 'icon' => 'plugin-page/step-3.svg', + 'button_text' => esc_html__( 'Set Up Cloud Backups', 'wp-mail-smtp' ), + ]; + + if ( $count && $this->is_pro_active() ) { + $data['section_class'] = ''; + $data['button_class'] = 'button-primary'; + + if ( $this->get_schedule_count() ) { + $data['icon'] = 'plugin-page/complete.svg'; + $data['button_class'] = 'grey disabled'; + $data['button_text'] = esc_html__( 'Cloud Backups Set Up', 'wp-mail-smtp' ); + } + } + + return $data; + } + + /** + * Get the number of Duplicator packages (backups) in the database. + * + * Duplicator stores backups in a custom DB table. There is no core API to + * inspect custom plugin tables, so a direct query is required and the result + * is cached with the object cache to limit DB hits per request. + * + * @since 4.9.0 + * + * @return int Number of packages. + */ + protected function get_package_count(): int { + + if ( ! $this->is_plugin_available() ) { + return 0; + } + + global $wpdb; + + $packages_table = $this->is_pro_active() ? $wpdb->prefix . 'duplicator_backups' : $wpdb->prefix . 'duplicator_packages'; + + $blog_id = function_exists( 'get_current_blog_id' ) ? get_current_blog_id() : 0; + $table_exists_cache_key = "wpms_dup_table_exists_{$blog_id}"; + $package_count_cache_key = "wpms_dup_package_count_{$blog_id}"; + + $table_exists = wp_cache_get( $table_exists_cache_key, 'wp-mail-smtp' ); + + if ( $table_exists === false ) { + // PHPCS: Direct query required — no WP API exists for custom plugin tables. + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $packages_table ) ) ); + + wp_cache_set( $table_exists_cache_key, $table_exists, 'wp-mail-smtp', 60 ); + } + + $package_count = 0; + + if ( $table_exists === $packages_table ) { + $package_count = wp_cache_get( $package_count_cache_key, 'wp-mail-smtp' ); + + if ( $package_count === false ) { + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $package_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$packages_table}" ); + + wp_cache_set( $package_count_cache_key, $package_count, 'wp-mail-smtp', 60 ); + } + } + + return (int) $package_count; + } + + /** + * Count saved Duplicator backup schedules. + * + * The schedule model's namespace changed between Duplicator releases, so + * both the current and legacy class locations are checked. + * + * @since 4.9.0 + * + * @return int Number of saved schedules. + */ + private function get_schedule_count(): int { + + $classes = [ + '\Duplicator\Addons\ScheduleAddon\Models\ScheduleEntity', + '\Duplicator\Models\ScheduleEntity', + ]; + + foreach ( $classes as $class ) { + if ( class_exists( $class ) && method_exists( $class, 'count' ) ) { + return (int) $class::count(); + } + } + + return 0; + } + + /** + * Whether the plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + protected function is_plugin_finished_setup(): bool { + + if ( ! $this->is_plugin_configured() ) { + return false; + } + + $count = $this->get_package_count(); + $schedule_count = ( $count && $this->is_pro_active() ) ? $this->get_schedule_count() : 0; + + return $count && $schedule_count; + } + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is configured properly. + */ + protected function is_plugin_configured(): bool { + + if ( ! $this->is_plugin_activated() ) { + return false; + } + + return $this->get_package_count() > 0; + } + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is active. + */ + protected function is_plugin_activated(): bool { + + return ( // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + ( defined( 'DUPLICATOR_VERSION' ) || class_exists( 'Duplicator\Plugin' ) || class_exists( 'Duplicator\Pro\Requirements' ) ) && + ( + is_plugin_active( $this->config['lite_plugin'] ) || + is_plugin_active( $this->config['pro_plugin'] ) + ) + ); + } + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if plugin is available. + */ + protected function is_plugin_available(): bool { + + return class_exists( 'Duplicator\Plugin' ) || defined( 'DUPLICATOR_VERSION' ) || class_exists( 'DUP_PRO_Plugin' ) || defined( 'DUPLICATOR_PRO_VERSION' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether pro version is active. + * + * @since 4.9.0 + * + * @return bool True if pro version is active. + */ + protected function is_pro_active(): bool { + + // Duplicator Pro 4.x exposes no Pro-only class/constant (it shares + // DUPLICATOR_VERSION and the Duplicator\ namespace with Lite), so detect + // the active Pro plugin file directly; the class/constant checks keep + // older Pro releases covered. + return is_plugin_active( $this->config['pro_plugin'] ) // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + || class_exists( 'DUP_PRO_Plugin' ) + || defined( 'DUPLICATOR_PRO_VERSION' ); + } + + /** + * Get the heading for the install step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + protected function get_install_heading(): string { + + return esc_html__( 'Install & Activate Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the install step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + protected function get_install_description(): string { + + return esc_html__( 'Your first step toward bulletproof backups.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + protected function get_plugin_title(): string { + + return esc_html__( 'Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the install button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + protected function get_install_button_text(): string { + + return esc_html__( 'Install Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + protected function get_installed_activated_text(): string { + + return esc_html__( 'Duplicator Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + protected function get_activate_text(): string { + + return esc_html__( 'Activate Duplicator', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + protected function get_setup_heading(): string { + + return esc_html__( 'Create Your First Backup', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + protected function get_setup_description(): string { + + return esc_html__( 'Back up your site, in just one click.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + protected function get_setup_button_text(): string { + + return esc_html__( 'Create First Backup', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + protected function get_setup_completed_text(): string { + + return esc_html__( 'Backup Created', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + protected function get_pro_installed_activated_text(): string { + + return esc_html__( 'Duplicator Pro Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Universally.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Universally.php new file mode 100644 index 00000000..13a80609 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/Universally.php @@ -0,0 +1,373 @@ + 'universally-language-translation-multilingual-tool/universally.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/universally-language-translation-multilingual-tool/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/universally-language-translation-multilingual-tool.zip', + 'pro_plugin' => '', + 'universally_addon' => '', + 'universally_addon_page' => 'https://universally.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=translation-page', + 'universally_onboarding' => 'admin.php?page=universally_settings', + ]; + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + protected static function get_plugin_name(): string { + + return 'universally'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + protected function get_heading_title(): string { + + return esc_html__( 'Translate Your Website and Go Global in Minutes', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + protected function get_heading_alt_text(): string { + + return esc_attr__( 'WP Mail SMTP ♥ Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + protected function get_heading_strings(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'Built for global reach. Designed for everyone.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + protected function get_screenshot_features(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'AI translation into 110+ languages.', 'wp-mail-smtp' ), + esc_html__( 'Multilingual SEO with translated URLs and meta.', 'wp-mail-smtp' ), + esc_html__( 'Auto-translates new content when you publish.', 'wp-mail-smtp' ), + esc_html__( 'Exclude specific pages, classes, or IDs from translation.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + protected function get_screenshot_alt_text(): string { + + return esc_attr__( 'Universally screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_result(): void { + + $step = $this->get_data_step_result(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 3', 'wp-mail-smtp' ), + esc_html__( 'Reach Even More of the World', 'wp-mail-smtp' ), + esc_html__( 'Upgrade your plan for more languages and higher translation limits.', 'wp-mail-smtp' ), + esc_attr( $step['button_class'] ), + esc_url( $step['button_url'] ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Step 'Result' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_result(): array { + + $step = []; + + $step['icon'] = 'plugin-page/step-3.svg'; + $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; + $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); + $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; + $step['button_url'] = $this->config['universally_addon_page']; + + return $step; + } + + /** + * Whether the plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + protected function is_plugin_finished_setup(): bool { + + return $this->is_plugin_configured(); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is configured properly. + */ + protected function is_plugin_configured(): bool { + + if ( ! $this->is_plugin_activated() ) { + return false; + } + + return ! empty( get_option( 'universally_api_key' ) ); + } + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is active. + */ + protected function is_plugin_activated(): bool { + + return is_plugin_active( $this->config['lite_plugin'] ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if plugin is available. + */ + protected function is_plugin_available(): bool { + + return class_exists( 'Universally\AdminBar', false ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether pro version is active. + * + * @since 4.9.0 + * + * @return bool True if pro version is active. + */ + protected function is_pro_active(): bool { + + return false; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the install step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + protected function get_install_heading(): string { + + return esc_html__( 'Install & Activate Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the install step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + protected function get_install_description(): string { + + return esc_html__( 'Install Universally from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + protected function get_plugin_title(): string { + + return esc_html__( 'Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the install button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + protected function get_install_button_text(): string { + + return esc_html__( 'Install Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + protected function get_installed_activated_text(): string { + + return esc_html__( 'Universally Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + protected function get_activate_text(): string { + + return esc_html__( 'Activate Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + protected function get_setup_heading(): string { + + return esc_html__( 'Connect Universally', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + protected function get_setup_description(): string { + + return esc_html__( 'Add your API key on the settings page and choose the languages for your site.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + protected function get_setup_button_text(): string { + + return esc_html__( 'Open Settings', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + protected function get_setup_completed_text(): string { + + return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + protected function get_pro_installed_activated_text(): string { + + return esc_html__( 'Universally Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPCode.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPCode.php new file mode 100644 index 00000000..f7abab45 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPCode.php @@ -0,0 +1,397 @@ + 'insert-headers-and-footers/ihaf.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/insert-headers-and-footers/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/insert-headers-and-footers.zip', + 'pro_plugin' => 'wpcode-premium/wpcode.php', + 'wpcode_addon' => 'wpcode-premium/wpcode.php', + 'wpcode_addon_page' => 'https://wpcode.com/lite/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=code-snippets-page', + 'wpcode_onboarding' => 'admin.php?page=wpcode', + ]; + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + protected static function get_plugin_name(): string { + + return 'wpcode'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + protected function get_heading_title(): string { + + return esc_html__( 'Add Custom Code to WordPress Without Breaking Your Site', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + protected function get_heading_alt_text(): string { + + return esc_attr__( 'WP Mail SMTP ♥ WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + protected function get_heading_strings(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'Trusted by over 3 million websites.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + protected function get_screenshot_features(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'Add snippets without editing theme files.', 'wp-mail-smtp' ), + esc_html__( 'PHP, JavaScript, CSS, and HTML all supported.', 'wp-mail-smtp' ), + esc_html__( '100+ ready-made snippets for common tasks.', 'wp-mail-smtp' ), + esc_html__( 'Auto-insert and one-click toggle for every snippet.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + protected function get_screenshot_alt_text(): string { + + return esc_attr__( 'WPCode screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_result(): void { + + $step = $this->get_data_step_result(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 3', 'wp-mail-smtp' ), + esc_html__( 'Get Advanced Code Snippet Features', 'wp-mail-smtp' ), + esc_html__( 'WPCode Pro adds conditional logic, a cloud snippet library, revisions, and conversion pixels to your workflow.', 'wp-mail-smtp' ), + esc_attr( $step['button_class'] ), + esc_url( $step['button_url'] ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Step 'Result' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_result(): array { + + $step = []; + + $step['icon'] = 'plugin-page/step-3.svg'; + $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; + $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); + $step['button_class'] = 'grey disabled'; + $step['button_url'] = ''; + + if ( ! $this->is_pro_active() ) { + $step['button_url'] = $this->config['wpcode_addon_page']; + $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; + } else { + $addon_installed = array_key_exists( $this->config['wpcode_addon'], $this->output_data['all_plugins'] ); + $step['button_text'] = $addon_installed + ? esc_html__( 'WPCode Pro Installed & Activated', 'wp-mail-smtp' ) + : esc_html__( 'Install Now', 'wp-mail-smtp' ); + $step['button_class'] = $this->output_data['plugin_setup'] ? 'grey disabled' : 'button-primary'; + $step['icon'] = $addon_installed ? 'plugin-page/complete.svg' : 'plugin-page/step-3.svg'; + } + + return $step; + } + + /** + * Whether the plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + protected function is_plugin_finished_setup(): bool { + + if ( ! $this->is_plugin_configured() ) { + return false; + } + + return $this->is_pro_active(); + } + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is configured properly. + */ + protected function is_plugin_configured(): bool { + + if ( ! $this->is_plugin_activated() ) { + return false; + } + + $activated = get_option( 'ihaf_activated' ); + + return is_array( $activated ) && ! empty( $activated['wpcode'] ); + } + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is active. + */ + protected function is_plugin_activated(): bool { + + return ( // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + function_exists( 'WPCode' ) && + ( + is_plugin_active( $this->config['lite_plugin'] ) || + is_plugin_active( $this->config['pro_plugin'] ) + ) + ); + } + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if plugin is available. + */ + protected function is_plugin_available(): bool { + + return function_exists( 'WPCode' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether pro version is active. + * + * @since 4.9.0 + * + * @return bool True if pro version is active. + */ + protected function is_pro_active(): bool { + + return class_exists( 'WPCode_License' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the install step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + protected function get_install_heading(): string { + + return esc_html__( 'Install & Activate WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the install step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + protected function get_install_description(): string { + + return esc_html__( 'Install WPCode from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + protected function get_plugin_title(): string { + + return esc_html__( 'WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the install button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + protected function get_install_button_text(): string { + + return esc_html__( 'Install WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + protected function get_installed_activated_text(): string { + + return esc_html__( 'WPCode Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + protected function get_activate_text(): string { + + return esc_html__( 'Activate WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + protected function get_setup_heading(): string { + + return esc_html__( 'Set Up WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + protected function get_setup_description(): string { + + return esc_html__( 'Open WPCode to create your first snippet and start customizing your site safely.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + protected function get_setup_button_text(): string { + + return esc_html__( 'Open WPCode', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + protected function get_setup_completed_text(): string { + + return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + protected function get_pro_installed_activated_text(): string { + + return esc_html__( 'WPCode Pro Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPConsent.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPConsent.php new file mode 100644 index 00000000..11328b5d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPConsent.php @@ -0,0 +1,462 @@ + 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/wpconsent-cookies-banner-privacy-suite/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/wpconsent-cookies-banner-privacy-suite.zip', + 'pro_plugin' => 'wpconsent-premium/wpconsent-premium.php', + 'wpconsent_addon' => 'wpconsent-premium/wpconsent-premium.php', + 'wpconsent_addon_page' => 'https://wpconsent.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=privacy-compliance-page', + 'wpconsent_onboarding' => 'admin.php?page=wpconsent-onboarding', + ]; + + /** + * Hooks. + * + * Removes WPConsent's onboarding redirect during AJAX to prevent it from + * hijacking the status-check JSON response. + * + * @since 4.9.0 + */ + public function hooks(): void { + + if ( wp_doing_ajax() ) { + remove_action( 'admin_init', 'wpconsent_maybe_redirect_onboarding', 9999 ); + } + + parent::hooks(); + } + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + protected static function get_plugin_name(): string { + + return 'wpconsent'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + protected function get_heading_title(): string { + + return esc_html__( 'Make Your Website Privacy-Compliant in Minutes', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + protected function get_heading_alt_text(): string { + + return esc_attr__( 'WP Mail SMTP ♥ WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + protected function get_heading_strings(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'Build trust with clear, compliant privacy practices. WPConsent adds clean, professional banners and handles the technical side for you.', 'wp-mail-smtp' ), + esc_html__( 'Built for transparency. Designed for ease.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + protected function get_screenshot_features(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'A professional banner that fits your site.', 'wp-mail-smtp' ), + esc_html__( 'Tools like Google Analytics and Facebook Pixel paused until consent.', 'wp-mail-smtp' ), + esc_html__( 'Peace of mind knowing you’re aligned with global laws.', 'wp-mail-smtp' ), + esc_html__( 'Self-hosted. Your data remains on your site.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + protected function get_screenshot_alt_text(): string { + + return esc_attr__( 'WPConsent screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_result(): void { + + $step = $this->get_data_step_result(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 3', 'wp-mail-smtp' ), + esc_html__( 'Get Advanced Cookie Consent Features', 'wp-mail-smtp' ), + esc_html__( 'With WPConsent Pro you can access advanced features like geolocation, popup layout, records of consent, multilanguage support, and more.', 'wp-mail-smtp' ), + esc_attr( $step['button_class'] ), + esc_url( $step['button_url'] ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Step 'Result' data. + * + * @since 4.9.0 + * + * @return array Step data. + * @noinspection PhpUndefinedFunctionInspection + */ + protected function get_data_step_result(): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $step = []; + + $step['icon'] = 'plugin-page/step-3.svg'; + $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; + $step['button_text'] = esc_html__( 'Learn More', 'wp-mail-smtp' ); + $step['button_class'] = 'grey disabled'; + $step['button_url'] = ''; + + $plugin_license_level = $this->get_license_level(); + + switch ( $plugin_license_level ) { + case 'lite': + $step['button_url'] = $this->config['wpconsent_addon_page']; + $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; + break; + + case 'pro': + $addon_installed = array_key_exists( $this->config['wpconsent_addon'], $this->output_data['all_plugins'] ); + $step['button_text'] = + $addon_installed + ? esc_html__( 'WPConsent Pro Installed & Activated', 'wp-mail-smtp' ) + : esc_html__( 'Install Now', 'wp-mail-smtp' ); + $step['button_class'] = $this->output_data['plugin_setup'] ? 'grey disabled' : 'button-primary'; + $step['icon'] = $addon_installed ? 'plugin-page/complete.svg' : 'plugin-page/step-3.svg'; + break; + } + + return $step; + } + + /** + * Retrieve the license level of the plugin. + * + * @since 4.9.0 + * + * @return string The plugin license level ('lite' or 'pro'). + */ + protected function get_license_level(): string { + + $plugin_license_level = 'lite'; + + // Check if premium features are available. + if ( function_exists( 'wpconsent' ) ) { + $wpconsent = wpconsent(); + + if ( isset( $wpconsent->license ) && method_exists( $wpconsent->license, 'is_active' ) ) { + $plugin_license_level = $wpconsent->license->is_active() ? 'pro' : 'lite'; + } + } + + return $plugin_license_level; + } + + /** + * Whether the plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + protected function is_plugin_finished_setup(): bool { + + if ( ! $this->is_plugin_configured() ) { + return false; + } + + return $this->get_license_level() === 'pro'; + } + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is configured properly. + * @noinspection PhpUndefinedFunctionInspection + */ + protected function is_plugin_configured(): bool { + + if ( ! $this->is_plugin_activated() ) { + return false; + } + + // The plugin is considered configured if the consent banner is enabled. + if ( function_exists( 'wpconsent' ) ) { + $wpconsent = wpconsent(); + + if ( isset( $wpconsent->settings ) ) { + $enable_consent_banner = $wpconsent->settings->get_option( 'enable_consent_banner', 0 ); + + return ! empty( $enable_consent_banner ); + } + } + + return false; + } + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is active. + */ + protected function is_plugin_activated(): bool { + + return ( // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + function_exists( 'wpconsent' ) && + ( + is_plugin_active( $this->config['lite_plugin'] ) || + is_plugin_active( $this->config['pro_plugin'] ) + ) + ); + } + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if plugin is available. + */ + protected function is_plugin_available(): bool { + + return function_exists( 'wpconsent' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether pro version is active. + * + * @since 4.9.0 + * + * @return bool True if pro version is active. + * @noinspection PhpUndefinedFunctionInspection + */ + protected function is_pro_active(): bool { + + if ( ! function_exists( 'wpconsent' ) ) { + return false; + } + + $wpconsent = wpconsent(); + + return isset( $wpconsent->license ) && method_exists( $wpconsent->license, 'is_active' ) && $wpconsent->license->is_active(); + } + + /** + * Get the heading for the install step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + protected function get_install_heading(): string { + + return esc_html__( 'Install & Activate WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the install step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + protected function get_install_description(): string { + + return esc_html__( 'Install WPConsent from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + protected function get_plugin_title(): string { + + return esc_html__( 'WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the install button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + protected function get_install_button_text(): string { + + return esc_html__( 'Install WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + protected function get_installed_activated_text(): string { + + return esc_html__( 'WPConsent Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + protected function get_activate_text(): string { + + return esc_html__( 'Activate WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + protected function get_setup_heading(): string { + + return esc_html__( 'Set Up WPConsent', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + protected function get_setup_description(): string { + + return esc_html__( 'WPConsent has an intuitive setup wizard to guide you through the cookie consent configuration process.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + protected function get_setup_button_text(): string { + + return esc_html__( 'Run Setup Wizard', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + protected function get_setup_completed_text(): string { + + return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + protected function get_pro_installed_activated_text(): string { + + return esc_html__( 'WPConsent Pro Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPVibe.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPVibe.php new file mode 100644 index 00000000..c257e86f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/Pages/WPVibe.php @@ -0,0 +1,373 @@ + 'vibe-ai/vibe-ai.php', + 'lite_wporg_url' => 'https://wordpress.org/plugins/vibe-ai/', + 'lite_download_url' => 'https://downloads.wordpress.org/plugin/vibe-ai.zip', + 'pro_plugin' => '', + 'wpvibe_addon' => '', + 'wpvibe_addon_page' => 'https://wpvibe.ai/docs', + 'wpvibe_onboarding' => 'admin.php?page=vibe-ai', + ]; + + /** + * Get the plugin name for use in IDs, CSS classes, and config keys. + * + * @since 4.9.0 + * + * @return string Plugin name. + */ + protected static function get_plugin_name(): string { + + return 'wpvibe'; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading title text. + * + * @since 4.9.0 + * + * @return string Heading title. + */ + protected function get_heading_title(): string { + + return esc_html__( 'Your AI Just Learned WordPress', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading alt text for logo. + * + * @since 4.9.0 + * + * @return string Heading alt text. + */ + protected function get_heading_alt_text(): string { + + return esc_attr__( 'WP Mail SMTP ♥ WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get heading description strings. + * + * @since 4.9.0 + * + * @return array Array of description strings. + */ + protected function get_heading_strings(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( '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.', 'wp-mail-smtp' ), + esc_html__( 'Manage WordPress through conversation, not clicking.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot features list. + * + * @since 4.9.0 + * + * @return array Array of feature strings. + */ + protected function get_screenshot_features(): array { + + return [ // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + esc_html__( 'Works with Claude, ChatGPT, Cursor, and any MCP client.', 'wp-mail-smtp' ), + esc_html__( 'Create and edit posts, pages, media, and users through conversation.', 'wp-mail-smtp' ), + esc_html__( 'Run 40+ WP-CLI commands without leaving your AI chat.', 'wp-mail-smtp' ), + esc_html__( 'One-click setup in about a minute.', 'wp-mail-smtp' ), + ]; + } + + /** + * Get screenshot alt text. + * + * @since 4.9.0 + * + * @return string Alt text for screenshot image. + */ + protected function get_screenshot_alt_text(): string { + + return esc_attr__( 'WPVibe screenshot', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Generate and output step 'Result' section HTML. + * + * @since 4.9.0 + * + * @noinspection HtmlUnknownTarget + */ + protected function output_section_step_result(): void { + + $step = $this->get_data_step_result(); + + if ( empty( $step ) ) { + return; + } + + printf( + '
    + +
    +

    %4$s

    +

    %5$s

    + +
    +
    ', + esc_attr( $step['section_class'] ), + esc_url( wp_mail_smtp()->assets_url . '/images/recommendations/' . $step['icon'] ), + esc_attr__( 'Step 3', 'wp-mail-smtp' ), + esc_html__( 'See What Your AI Can Do', 'wp-mail-smtp' ), + esc_html__( 'WPVibe is free to use. Explore the docs to see every ability your AI assistant now has for your WordPress site.', 'wp-mail-smtp' ), + esc_attr( $step['button_class'] ), + esc_url( $step['button_url'] ), + esc_html( $step['button_text'] ) + ); + } + + /** + * Step 'Result' data. + * + * @since 4.9.0 + * + * @return array Step data. + */ + protected function get_data_step_result(): array { + + $step = []; + + $step['icon'] = 'plugin-page/step-3.svg'; + $step['section_class'] = $this->output_data['plugin_setup'] ? '' : 'grey'; + $step['button_text'] = esc_html__( 'View Docs', 'wp-mail-smtp' ); + $step['button_class'] = $this->output_data['plugin_setup'] ? 'button-primary' : 'grey disabled'; + $step['button_url'] = $this->config['wpvibe_addon_page']; + + return $step; + } + + /** + * Whether the plugin is finished setup or not. + * + * @since 4.9.0 + * + * @return bool True if the plugin is finished setup. + */ + protected function is_plugin_finished_setup(): bool { + + return $this->is_plugin_configured(); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether a plugin is configured or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is configured properly. + */ + protected function is_plugin_configured(): bool { + + if ( ! $this->is_plugin_activated() ) { + return false; + } + + return (int) get_option( 'wpvibe_last_active', 0 ) > 0; + } + + /** + * Whether a plugin is active or not. + * + * @since 4.9.0 + * + * @return bool True if plugin is active. + */ + protected function is_plugin_activated(): bool { + + return is_plugin_active( $this->config['lite_plugin'] ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether a plugin is available (class/function exists). + * + * @since 4.9.0 + * + * @return bool True if plugin is available. + */ + protected function is_plugin_available(): bool { + + return defined( 'WPVIBE_VERSION' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Whether pro version is active. + * + * @since 4.9.0 + * + * @return bool True if pro version is active. + */ + protected function is_pro_active(): bool { + + return false; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the install step. + * + * @since 4.9.0 + * + * @return string Install step heading. + */ + protected function get_install_heading(): string { + + return esc_html__( 'Install & Activate WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the install step. + * + * @since 4.9.0 + * + * @return string Install step description. + */ + protected function get_install_description(): string { + + return esc_html__( 'Install WPVibe from the WordPress.org plugin repository.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the plugin title. + * + * @since 4.9.0 + * + * @return string Plugin title. + */ + protected function get_plugin_title(): string { + + return esc_html__( 'WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the install button text. + * + * @since 4.9.0 + * + * @return string Install button text. + */ + protected function get_install_button_text(): string { + + return esc_html__( 'Install WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a plugin is installed and activated. + * + * @since 4.9.0 + * + * @return string Installed & activated text. + */ + protected function get_installed_activated_text(): string { + + return esc_html__( 'WPVibe Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the activate button text. + * + * @since 4.9.0 + * + * @return string Activate button text. + */ + protected function get_activate_text(): string { + + return esc_html__( 'Activate WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the heading for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step heading. + */ + protected function get_setup_heading(): string { + + return esc_html__( 'Connect Your AI Assistant', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the description for the setup step. + * + * @since 4.9.0 + * + * @return string Setup step description. + */ + protected function get_setup_description(): string { + + return esc_html__( 'Open WPVibe and authorize your AI client in one click.', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the setup button text. + * + * @since 4.9.0 + * + * @return string Setup button text. + */ + protected function get_setup_button_text(): string { + + return esc_html__( 'Open WPVibe', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when setup is completed. + * + * @since 4.9.0 + * + * @return string Setup completed text. + */ + protected function get_setup_completed_text(): string { + + return esc_html__( 'Setup Complete', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } + + /** + * Get the text when a pro-version is installed and activated. + * + * @since 4.9.0 + * + * @return string Pro installed and activated text. + */ + protected function get_pro_installed_activated_text(): string { + + return esc_html__( 'WPVibe Installed & Activated', 'wp-mail-smtp' ); // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/RecommendedPlugins.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/RecommendedPlugins.php new file mode 100644 index 00000000..4879d597 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Recommendations/RecommendedPlugins.php @@ -0,0 +1,257 @@ +get_products() as $product ) { + if ( class_exists( $product['page_class'] ) ) { + new $product['page_class'](); + } + } + } + + /** + * Register the rotating submenu item for the currently surfaced product. + * + * Called by Area during its menu build so the item lands in the right + * position; does nothing once every product is adopted. + * + * @since 4.9.0 + * + * @param string $access_capability Capability required to view the page. + */ + public function add_submenu_item( $access_capability ) { + + $product = $this->get_current_product(); + + if ( $product === null ) { + return; + } + + add_submenu_page( + Area::SLUG, + $product['label'], + $product['label'], + $access_capability, + $product['page_class']::SLUG, + [ $this, 'render_current_page' ] + ); + + $short_slug = substr( $product['page_class']::SLUG, strlen( Area::SLUG . '-' ) ); + + if ( ! in_array( $short_slug, Area::$pages_registered, true ) ) { + Area::$pages_registered[] = $short_slug; + } + } + + /** + * Render the landing page for the currently surfaced product. + * + * @since 4.9.0 + */ + public function render_current_page() { + + $product = $this->get_current_product(); + + if ( $product === null ) { + return; + } + + $page = new $product['page_class'](); + + // The id is required: all `wpms:` Tailwind utilities are build-scoped + // under `#wp-mail-smtp`, so the landing markup must live beneath it. + echo '
    '; + $page->output(); + echo '
    '; + } + + /** + * Ordered product catalog. + * + * @since 4.9.0 + * + * @return array> + */ + public function get_products() { + + return [ + [ + 'slug' => 'wpconsent', + 'name' => 'WPConsent', + 'label' => esc_html__( 'Privacy Compliance', 'wp-mail-smtp' ), + 'page_class' => Pages\WPConsent::class, + 'plugin' => 'wpconsent-cookies-banner-privacy-suite/wpconsent.php', + 'plugin_pro' => 'wpconsent-premium/wpconsent-premium.php', + 'activation_option' => 'wpconsent_activated', + ], + [ + 'slug' => 'activelayer', + 'name' => 'ActiveLayer', + 'label' => esc_html__( 'Spam Protection', 'wp-mail-smtp' ), + 'page_class' => Pages\ActiveLayer::class, + 'plugin' => 'activelayer-anti-spam-spam-protection-for-forms-comments/activelayer-anti-spam-spam-protection-for-forms-comments.php', + 'activation_option' => '', // No activation-time option exposed by the plugin. + ], + [ + 'slug' => 'duplicator', + 'name' => 'Duplicator', + 'label' => esc_html__( 'Backups', 'wp-mail-smtp' ), + 'page_class' => Pages\Duplicator::class, + 'plugin' => 'duplicator/duplicator.php', + 'plugin_pro' => 'duplicator-pro/duplicator-pro.php', + 'activation_option' => 'duplicator_install_info', // migration reads ['time']. + ], + [ + 'slug' => 'wpvibe', + 'name' => 'WPVibe', + 'label' => esc_html__( 'AI MCP', 'wp-mail-smtp' ), + 'page_class' => Pages\WPVibe::class, + 'plugin' => 'vibe-ai/vibe-ai.php', + 'activation_option' => '', // No activation-time option exposed by the plugin. + ], + [ + 'slug' => 'universally', + 'name' => 'Universally', + 'label' => esc_html__( 'Translations', 'wp-mail-smtp' ), + 'page_class' => Pages\Universally::class, + 'plugin' => 'universally-language-translation-multilingual-tool/universally.php', + 'activation_option' => '', // No activation-time option exposed by the plugin. + ], + [ + 'slug' => 'wpcode', + 'name' => 'WPCode', + 'label' => esc_html__( 'Code Snippets', 'wp-mail-smtp' ), + 'page_class' => Pages\WPCode::class, + 'plugin' => 'insert-headers-and-footers/ihaf.php', + 'plugin_pro' => 'wpcode-premium/wpcode.php', + 'activation_option' => 'ihaf_activated', // migration reads ['wpcode']. + ], + ]; + } + + /** + * Map of catalog plugin files (lite + pro) to product slug. + * + * @since 4.9.0 + * + * @return array + */ + private function get_plugin_slug_map() { + + $map = []; + + foreach ( $this->get_products() as $product ) { + if ( ! empty( $product['plugin'] ) ) { + $map[ $product['plugin'] ] = $product['slug']; + } + if ( ! empty( $product['plugin_pro'] ) ) { + $map[ $product['plugin_pro'] ] = $product['slug']; + } + } + + return $map; + } + + /** + * Record first activation time of a catalog plugin (write-once). + * + * @since 4.9.0 + * + * @param string $plugin Plugin file relative to the plugins dir. + * @param bool $network_wide Whether activation is network-wide. + */ + public function record_activation( $plugin, $network_wide ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed + + $slug = $this->get_plugin_slug_map()[ $plugin ] ?? ''; + + if ( $slug === '' ) { + return; + } + + $activated = (array) get_option( self::ACTIVATED_OPTION, [] ); + + if ( isset( $activated[ $slug ] ) ) { + return; + } + + $activated[ $slug ] = time(); + + update_option( self::ACTIVATED_OPTION, $activated ); + } + + /** + * The product to surface right now, or null once every product is adopted. + * + * @since 4.9.0 + * + * @return array|null + */ + public function get_current_product() { + + $products = $this->get_products(); + $activated = (array) get_option( self::ACTIVATED_OPTION, [] ); + $now = time(); + + foreach ( $products as $product ) { + $timestamp = isset( $activated[ $product['slug'] ] ) ? (int) $activated[ $product['slug'] ] : 0; + + if ( $timestamp === 0 || ( $now - $timestamp ) < self::ADOPTED_AFTER ) { + return $product; + } + } + + // Everything adopted: surface nothing. The standard "About Us" submenu + // remains as the trailing item. + return null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/Review.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Review.php new file mode 100644 index 00000000..8ffb7643 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/Review.php @@ -0,0 +1,226 @@ + $time, + 'dismissed' => false, + ]; + update_option( self::NOTICE_OPTION, $review ); + } else { + // Check if it has been dismissed or not. + if ( isset( $review['dismissed'] ) && ! $review['dismissed'] ) { + $load = true; + } + } + + // If we cannot load, return early. + if ( ! $load ) { + return; + } + + $this->review(); + } + + /** + * Maybe show review request. + * + * @since 2.1.0 + */ + private function review() { + + // Get the currently selected mailer. + $mailer = Options::init()->get( 'mail', 'mailer' ); + + // Skip if no or the default mailer is selected. + if ( empty( $mailer ) || $mailer === 'mail' ) { + return; + } + + // Fetch when plugin was initially activated. + $activated = get_option( 'wp_mail_smtp_activated_time' ); + + // Skip if the plugin activated time is not set. + if ( empty( $activated ) ) { + return; + } + + $mailer_object = wp_mail_smtp() + ->get_providers() + ->get_mailer( $mailer, wp_mail_smtp()->get_processor()->get_phpmailer() ); + + // Check if mailer setup is complete. + $mailer_setup_complete = ! empty( $mailer_object ) ? $mailer_object->is_mailer_complete() : false; + + // Skip if the mailer is not set or the plugin is active for less then a defined number of days. + if ( ! $mailer_setup_complete || ( $activated + ( DAY_IN_SECONDS * self::WAIT_PERIOD ) ) > time() ) { + return; + } + + // We have a candidate! Output a review message. + ?> +
    +
    +

    +

    +  •  + +

    +
    + + +
    + + blog_id ); + + update_option( self::NOTICE_OPTION, $review ); + + restore_current_blog(); + } + } + + wp_send_json_success(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/SetupWizard.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/SetupWizard.php new file mode 100644 index 00000000..e4cfd12c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/SetupWizard.php @@ -0,0 +1,1520 @@ +get_admin()->get_admin_page_url() . '-setup-wizard'; + } + + /** + * Checks if the Wizard should be loaded in current context. + * + * @since 2.6.0 + */ + public function maybe_load_wizard() { + + // Check for wizard-specific parameter + // Allow plugins to disable the setup wizard + // Check if current user is allowed to save settings. + if ( + ! ( + isset( $_GET['page'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + Area::SLUG . '-setup-wizard' === $_GET['page'] && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $this->should_setup_wizard_load() && + current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) + ) + ) { + return; + } + + // Don't load the interface if doing an ajax call. + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return; + } + + set_current_screen(); + + // Remove an action in the Gutenberg plugin ( not core Gutenberg ) which throws an error. + remove_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' ); + + // Remove hooks for deprecated functions in WordPress 6.4.0. + remove_action( 'admin_print_styles', 'print_emoji_styles' ); + remove_action( 'admin_head', 'wp_admin_bar_header' ); + + $this->load_setup_wizard(); + } + + /** + * Maybe redirect to the setup wizard after plugin activation on a new install. + * + * @since 2.6.0 + */ + public function maybe_redirect_after_activation() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( wp_doing_ajax() || wp_doing_cron() ) { + return; + } + + // Only users who can activate plugins should be redirected. + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + // Check if we should consider redirection. + if ( ! get_transient( 'wp_mail_smtp_activation_redirect' ) ) { + return; + } + + delete_transient( 'wp_mail_smtp_activation_redirect' ); + + // Check option to disable setup wizard redirect. + if ( get_option( 'wp_mail_smtp_activation_prevent_redirect' ) ) { + return; + } + + // Only do this for single site installs if Network Wide setting is not enabled. + if ( isset( $_GET['activate-multi'] ) || is_network_admin() || WP::use_global_plugin_settings() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return; + } + + // Don't redirect if the Setup Wizard is disabled. + if ( ! $this->should_setup_wizard_load() ) { + return; + } + + // Initial install. + if ( get_option( 'wp_mail_smtp_initial_version' ) === WPMS_PLUGIN_VER ) { + update_option( 'wp_mail_smtp_activation_prevent_redirect', true ); + wp_safe_redirect( self::get_site_url() ); + exit; + } + } + + /** + * Register page through WordPress's hooks. + * + * Create a dummy admin page, where the Setup Wizard app can be displayed, + * but it's not visible in the admin dashboard menu. + * + * @since 2.6.0 + */ + public function add_dashboard_page() { + + if ( ! $this->should_setup_wizard_load() ) { + return; + } + + add_submenu_page( '', '', '', wp_mail_smtp()->get_capability_manage_global_options(), Area::SLUG . '-setup-wizard', '' ); + } + + /** + * Load the Setup Wizard template. + * + * @since 2.6.0 + */ + private function load_setup_wizard() { + + /** + * Before setup wizard load. + * + * @since 2.8.0 + * + * @param \WPMailSMTP\Admin\SetupWizard $setup_wizard SetupWizard instance. + */ + do_action( 'wp_mail_smtp_admin_setup_wizard_load_setup_wizard_before', $this ); + + $this->enqueue_scripts(); + + $this->setup_wizard_header(); + $this->setup_wizard_content(); + $this->setup_wizard_footer(); + + /** + * After setup wizard load. + * + * @since 2.8.0 + * + * @param \WPMailSMTP\Admin\SetupWizard $setup_wizard SetupWizard instance. + */ + do_action( 'wp_mail_smtp_admin_setup_wizard_load_setup_wizard_after', $this ); + + exit; + } + + /** + * Load the scripts needed for the Setup Wizard. + * + * @since 2.6.0 + */ + public function enqueue_scripts() { + + if ( ! defined( 'WPMS_VUE_LOCAL_DEV' ) || ! WPMS_VUE_LOCAL_DEV ) { + $rtl = is_rtl() ? '.rtl' : ''; + wp_enqueue_style( 'wp-mail-smtp-vue-style', wp_mail_smtp()->assets_url . '/vue/css/wizard' . $rtl . '.min.css', [], WPMS_PLUGIN_VER ); + } + + wp_enqueue_script( 'wp-mail-smtp-vue-vendors', wp_mail_smtp()->assets_url . '/vue/js/chunk-vendors.min.js', [], WPMS_PLUGIN_VER, true ); + wp_enqueue_script( 'wp-mail-smtp-vue-script', wp_mail_smtp()->assets_url . '/vue/js/wizard.min.js', [ 'wp-mail-smtp-vue-vendors' ], WPMS_PLUGIN_VER, true ); + + wp_localize_script( + 'wp-mail-smtp-vue-script', + 'wp_mail_smtp_vue', + [ + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wpms-admin-nonce' ), + 'is_multisite' => is_multisite(), + 'translations' => WP::get_jed_locale_data( 'wp-mail-smtp' ), + 'exit_url' => wp_mail_smtp()->get_admin()->get_admin_page_url(), + 'email_test_tab_url' => add_query_arg( 'tab', 'test', wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) ), + 'is_pro' => wp_mail_smtp()->is_pro(), + 'is_ssl' => is_ssl(), + 'license_exists' => apply_filters( 'wp_mail_smtp_admin_setup_wizard_license_exists', false ), + 'plugin_version' => WPMS_PLUGIN_VER, + 'other_smtp_plugins' => $this->detect_other_smtp_plugins(), + 'mailer_options' => $this->prepare_mailer_options(), + 'defined_constants' => $this->prepare_defined_constants(), + 'upgrade_link' => wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'setup-wizard' ] ), + 'versions' => $this->prepare_versions_data(), + 'public_url' => wp_mail_smtp()->assets_url . '/vue/', + 'current_user_email' => wp_get_current_user()->user_email, + 'completed_time' => self::get_stats()['completed_time'], + 'sendlayer' => [ + 'connect_nonce' => wp_create_nonce( 'wp-mail-smtp-sendlayer-connect' ), + 'return_url' => self::get_site_url() . '#/step/configure_mailer/sendlayer', + ], + 'education' => [ + 'upgrade_text' => esc_html__( 'We\'re sorry, the %mailer% mailer is not available on your plan. Please upgrade to the PRO plan to unlock all these awesome features.', 'wp-mail-smtp' ), + 'upgrade_button' => esc_html__( 'Upgrade to Pro', 'wp-mail-smtp' ), + 'upgrade_url' => add_query_arg( 'discount', 'SMTPLITEUPGRADE', wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'setup-wizard' ] ) ), + 'upgrade_bonus' => sprintf( + wp_kses( /* Translators: %s - discount value $50 */ + __( 'Bonus: WP Mail SMTP users get %s off regular price,
    applied at checkout.', 'wp-mail-smtp' ), + [ + 'strong' => [], + 'span' => [ + 'class' => [], + ], + 'br' => [], + ] + ), + '$50' + ), + 'upgrade_doc' => sprintf( + '%2$s', + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-upgrade-wp-mail-smtp-to-pro-version/', [ 'medium' => 'setup-wizard', 'content' => 'Wizard Pro Mailer Popup - Already purchased' ] ) ), + esc_html__( 'Already purchased?', 'wp-mail-smtp' ) + ) + ], + ] + ); + } + + /** + * Outputs the simplified header used for the Setup Wizard. + * + * @since 2.6.0 + */ + public function setup_wizard_header() { + ?> + + > + + + + <?php esc_html_e( 'WP Mail SMTP › Setup Wizard', 'wp-mail-smtp' ); ?> + + + + + + settings_error_page( 'wp-mail-smtp-vue-setup-wizard', '' . esc_html__( 'Go back to the Dashboard', 'wp-mail-smtp' ) . '' ); + $this->settings_inline_js(); + } + + /** + * Outputs the simplified footer used for the Setup Wizard. + * + * @since 2.6.0 + */ + public function setup_wizard_footer() { + ?> + + + + is_pro() ) { + $contact_url = 'https://wordpress.org/support/plugin/wp-mail-smtp/'; + } else { + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + $contact_url = esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/contact/', [ 'medium' => 'setup-wizard', 'content' => 'Contact Us' ] ) ); + } + + ?> + + +
    +
    +
    +

    + +

    +
    +
    +
    +
    +
    +

    +

    +

    + +

    +
      +
    • +
    • +
    • +
    +

    + +

    + + + + +
    +
    +
    + +
    +
    +
    + + + get_capability_manage_global_options() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have permission to change options for this WP site!', 'wp-mail-smtp' ) ); + } + + $options = Options::init(); + + wp_send_json_success( $options->get_all() ); + } + + /** + * Ajax handler for starting the Setup Wizard steps. + * + * @since 3.1.0 + */ + public function wizard_steps_started() { + + 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 permission to change options for this WP site!', 'wp-mail-smtp' ) ); + } + + self::update_stats( + [ + 'launched_time' => time(), + ] + ); + + wp_send_json_success(); + } + + /** + * Ajax handler for updating the settings. + * + * @since 2.6.0 + */ + public function update_settings() { + + 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(); + $overwrite = ! empty( $_POST['overwrite'] ); + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $value = isset( $_POST['value'] ) ? wp_slash( json_decode( wp_unslash( $_POST['value'] ), true ) ) : []; + + // Cancel summary report email task if summary report email was disabled. + if ( + ! SummaryReportEmail::is_disabled() && + isset( $value['general'][ SummaryReportEmail::SETTINGS_SLUG ] ) && + $value['general'][ SummaryReportEmail::SETTINGS_SLUG ] === true + ) { + ( new SummaryReportEmailTask() )->cancel(); + } + + /** + * Before updating settings in Setup Wizard. + * + * @since 3.3.0 + * + * @param array $post POST data. + */ + do_action( 'wp_mail_smtp_admin_setup_wizard_update_settings', $value ); + + $options->set( $value, false, $overwrite ); + + wp_send_json_success(); + } + + /** + * Ajax handler for importing settings from other SMTP plugins. + * + * @since 2.6.0 + */ + public function import_settings() { + + 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 permission to change options for this WP site!', 'wp-mail-smtp' ) ); + } + + $other_plugin = ! empty( $_POST['value'] ) ? sanitize_text_field( wp_unslash( $_POST['value'] ) ) : ''; + + if ( empty( $other_plugin ) ) { + wp_send_json_error(); + } + + $other_plugin_settings = ( new PluginImportDataRetriever( $other_plugin ) )->get(); + + if ( empty( $other_plugin_settings ) ) { + wp_send_json_error(); + } + + $options = Options::init(); + + $options->set( $other_plugin_settings, false, false ); + + wp_send_json_success(); + } + + /** + * Detect if any other SMTP plugin options are defined. + * Other SMTP plugins: + * - Easy WP SMTP + * - Post SMTP Mailer + * - SMTP Mailer + * - WP SMTP + * - FluentSMTP + * + * @since 2.6.0 + * @since 3.2.0 Added FluentSMTP. + * + * @return array + */ + private function detect_other_smtp_plugins() { + + $data = []; + + $plugins = [ + 'easy-smtp' => 'swpsmtp_options', + 'post-smtp-mailer' => 'postman_options', + 'smtp-mailer' => 'smtp_mailer_options', + 'wp-smtp' => 'wp_smtp_options', + 'fluent-smtp' => 'fluentmail-settings', + ]; + + foreach ( $plugins as $plugin_slug => $plugin_options ) { + $options = get_option( $plugin_options ); + + if ( ! empty( $options ) ) { + $data[] = $plugin_slug; + } + } + + return $data; + } + + /** + * Prepare mailer options for all mailers. + * + * @since 2.6.0 + * @since 3.10.0 Supply WPMS_AMAZONSES_DISPLAY_IDENTITIES constant value to control display of Amazon SES identity list. + * @since 3.11.0 Removed WPMS_AMAZONSES_DISPLAY_IDENTITIES constant handling. + * + * @return array + */ + private function prepare_mailer_options() { + + $data = []; + + foreach ( wp_mail_smtp()->get_providers()->get_options_all() as $provider ) { + $data[ $provider->get_slug() ] = [ + 'slug' => $provider->get_slug(), + 'title' => $provider->get_title(), + 'description' => $provider->get_description(), + 'edu_notice' => $provider->get_notice( 'educational' ), + 'min_php' => $provider->get_php_version(), + 'disabled' => $provider->is_disabled(), + ]; + + if ( $provider->get_slug() === 'gmail' ) { + $data['gmail']['redirect_uri'] = \WPMailSMTP\Providers\Gmail\Auth::get_oauth_redirect_url(); + } + } + + return apply_filters( 'wp_mail_smtp_admin_setup_wizard_prepare_mailer_options', $data ); + } + + /** + * AJAX callback for getting the oAuth authorization URL. + * + * @since 2.6.0 + */ + public function get_oauth_url() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error(); + } + + $data = []; + $mailer = ! empty( $_POST['mailer'] ) ? sanitize_text_field( wp_unslash( $_POST['mailer'] ) ) : ''; + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $settings = isset( $_POST['settings'] ) ? wp_slash( json_decode( wp_unslash( $_POST['settings'] ), true ) ) : []; + + if ( empty( $mailer ) ) { + wp_send_json_error(); + } + + $settings = array_merge( $settings, [ 'is_setup_wizard_auth' => true ] ); + + $options = Options::init(); + $options->set( [ $mailer => $settings ], false, false ); + + switch ( $mailer ) { + case 'gmail': + $auth = wp_mail_smtp()->get_providers()->get_auth( 'gmail' ); + + if ( $auth->is_clients_saved() && $auth->is_auth_required() ) { + $data['oauth_url'] = $auth->get_auth_url(); + } + break; + } + + $data = apply_filters( 'wp_mail_smtp_admin_setup_wizard_get_oauth_url', $data, $mailer ); + + wp_send_json_success( array_merge( [ 'mailer' => $mailer ], $data ) ); + } + + /** + * AJAX callback for getting the oAuth connected data. + * + * @since 2.6.0 + */ + public function get_connected_data() { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error(); + } + + $data = []; + $mailer = ! empty( $_POST['mailer'] ) ? sanitize_text_field( wp_unslash( $_POST['mailer'] ) ) : ''; + + if ( empty( $mailer ) ) { + wp_send_json_error(); + } + + switch ( $mailer ) { + case 'gmail': + $auth = wp_mail_smtp()->get_providers()->get_auth( 'gmail' ); + + if ( $auth->is_clients_saved() && ! $auth->is_auth_required() ) { + $user_info = $auth->get_user_info(); + $data['connected_email'] = $user_info['email']; + } + break; + } + + wp_send_json_success( array_merge( [ 'mailer' => $mailer ], $data ) ); + } + + /** + * AJAX callback for removing the oAuth authorization connection. + * + * @since 2.6.0 + */ + public function remove_oauth_connection() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error(); + } + + $mailer = ! empty( $_POST['mailer'] ) ? sanitize_text_field( wp_unslash( $_POST['mailer'] ) ) : ''; + + if ( empty( $mailer ) ) { + wp_send_json_error(); + } + + $options = Options::init(); + $old_opt = $options->get_all_raw(); + + /* + * Since Gmail mailer uses the same settings array for both the custom app and One-Click Setup, + * we need to make sure we don't remove the wrong settings. + */ + if ( $mailer === 'gmail' ) { + unset( $old_opt[ $mailer ]['access_token'] ); + unset( $old_opt[ $mailer ]['refresh_token'] ); + unset( $old_opt[ $mailer ]['user_details'] ); + unset( $old_opt[ $mailer ]['auth_code'] ); + } else { + foreach ( $old_opt[ $mailer ] as $key => $value ) { + // Unset everything except Client ID, Client Secret and Domain (for Zoho). + if ( ! in_array( $key, [ 'domain', 'client_id', 'client_secret' ], true ) ) { + unset( $old_opt[ $mailer ][ $key ] ); + } + } + } + + $options->set( $old_opt ); + + wp_send_json_success(); + } + + /** + * AJAX callback for installing a plugin. + * Has to contain the `slug` POST parameter. + * + * @since 2.6.0 + */ + public function install_plugin() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + // Check for permissions. + if ( ! current_user_can( 'install_plugins' ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. You don\'t have permission to install plugins.', 'wp-mail-smtp' ) ); + } + + if ( ! current_user_can( 'activate_plugins' ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. You don\'t have permission to activate plugins.', 'wp-mail-smtp' ) ); + } + + $slug = ! empty( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; + + if ( empty( $slug ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. Plugin slug is missing.', 'wp-mail-smtp' ) ); + } + + if ( ! in_array( $slug, wp_list_pluck( $this->get_partner_plugins(), 'slug' ), true ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. Plugin is not whitelisted.', 'wp-mail-smtp' ) ); + } + + $url = esc_url_raw( WP::admin_url( 'admin.php?page=' . Area::SLUG . '-setup-wizard' ) ); + + /* + * The `request_filesystem_credentials` function will output a credentials form in case of failure. + * We don't want that, since it will break AJAX response. So just hide output with a buffer. + */ + ob_start(); + // phpcs:ignore WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine + $creds = request_filesystem_credentials( $url, '', false, false, null ); + ob_end_clean(); + + // Check for file system permissions. + if ( false === $creds ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. Don\'t have file permission.', 'wp-mail-smtp' ) ); + } + + if ( ! WP_Filesystem( $creds ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. Don\'t have file permission.', 'wp-mail-smtp' ) ); + } + + // Do not allow WordPress to search/download translations, as this will break JS output. + remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); + + // Import the plugin upgrader. + Helpers::include_plugin_upgrader(); + + // Create the plugin upgrader with our custom skin. + $installer = new Plugin_Upgrader( new PluginsInstallSkin() ); + + // Error check. + if ( ! method_exists( $installer, 'install' ) || empty( $slug ) ) { + wp_send_json_error( esc_html__( 'Could not install the plugin. WP Plugin installer initialization failed.', 'wp-mail-smtp' ) ); + } + + include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; + + $api = plugins_api( + 'plugin_information', + [ + 'slug' => $slug, + 'fields' => [ + 'short_description' => false, + 'sections' => false, + 'requires' => false, + 'rating' => false, + 'ratings' => false, + 'downloaded' => false, + 'last_updated' => false, + 'added' => false, + 'tags' => false, + 'compatibility' => false, + 'homepage' => false, + 'donate_link' => false, + ], + ] + ); + + if ( is_wp_error( $api ) ) { + wp_send_json_error( $api->get_error_message() ); + } + + $installer->install( $api->download_link ); + + // Flush the cache and return the newly installed plugin basename. + wp_cache_flush(); + + if ( $installer->plugin_info() ) { + $plugin_basename = $installer->plugin_info(); + + // Disable the WPForms redirect after plugin activation. + if ( $slug === 'wpforms-lite' ) { + update_option( 'wpforms_activation_redirect', true ); + add_option( 'wpforms_installation_source', 'wp-mail-smtp-setup-wizard' ); + } + + // Disable the AIOSEO redirect after plugin activation. + if ( $slug === 'all-in-one-seo-pack' ) { + update_option( 'aioseo_activation_redirect', true ); + } + + // Activate the plugin silently. + $activated = activate_plugin( $plugin_basename ); + + // Disable the RafflePress redirect after plugin activation. + if ( $slug === 'rafflepress' ) { + delete_transient( '_rafflepress_welcome_screen_activation_redirect' ); + } + + // Disable the MonsterInsights redirect after plugin activation. + if ( $slug === 'google-analytics-for-wordpress' ) { + delete_transient( '_monsterinsights_activation_redirect' ); + } + + // Disable the SeedProd redirect after the plugin activation. + if ( $slug === 'coming-soon' ) { + delete_transient( '_seedprod_welcome_screen_activation_redirect' ); + } + + if ( ! is_wp_error( $activated ) ) { + wp_send_json_success( + [ + 'slug' => $slug, + 'is_installed' => true, + 'is_activated' => true, + ] + ); + } else { + wp_send_json_success( + [ + 'slug' => $slug, + 'is_installed' => true, + 'is_activated' => false, + ] + ); + } + } + + wp_send_json_error( esc_html__( 'Could not install the plugin. WP Plugin installer could not retrieve plugin information.', 'wp-mail-smtp' ) ); + } + + /** + * AJAX callback for getting all partner's plugin information. + * + * @since 2.6.0 + */ + public function get_partner_plugins_info() { + + 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' ) ); + } + + $plugins = $this->get_partner_plugins(); + + $contact_form_plugin_already_installed = false; + + $contact_form_basenames = [ + 'wpforms-lite/wpforms.php', + 'wpforms/wpforms.php', + 'formidable/formidable.php', + 'formidable/formidable-pro.php', + 'gravityforms/gravityforms.php', + 'ninja-forms/ninja-forms.php', + ]; + + $installed_plugins = get_plugins(); + + foreach ( $installed_plugins as $basename => $plugin_info ) { + if ( in_array( $basename, $contact_form_basenames, true ) ) { + $contact_form_plugin_already_installed = true; + break; + } + } + + // Final check if maybe WPForms is already install and active as a MU plugin. + if ( class_exists( '\WPForms\WPForms' ) ) { + $contact_form_plugin_already_installed = true; + } + + $data = [ + 'plugins' => $plugins, + 'contact_form_plugin_already_installed' => $contact_form_plugin_already_installed, + ]; + + wp_send_json_success( $data ); + } + + /** + * Get the partner plugins data. + * + * @since 3.3.0 + * + * @return array[] + */ + private function get_partner_plugins() { + + $installed_plugins = get_plugins(); + + return [ + [ + 'slug' => 'wpforms-lite', + 'name' => esc_html__( 'Contact Forms by WPForms', 'wp-mail-smtp' ), + 'is_activated' => function_exists( 'wpforms' ), + 'is_installed' => array_key_exists( 'wpforms-lite/wpforms.php', $installed_plugins ), + ], + [ + 'slug' => 'all-in-one-seo-pack', + 'name' => esc_html__( 'All in One SEO', 'wp-mail-smtp' ), + 'is_activated' => class_exists( 'AIOSEOP_Core' ), + 'is_installed' => array_key_exists( 'all-in-one-seo-pack/all_in_one_seo_pack.php', $installed_plugins ), + ], + [ + 'slug' => 'google-analytics-for-wordpress', + 'name' => esc_html__( 'Google Analytics by MonsterInsights', 'wp-mail-smtp' ), + 'is_activated' => function_exists( 'MonsterInsights' ), + 'is_installed' => array_key_exists( 'google-analytics-for-wordpress/googleanalytics.php', $installed_plugins ), + ], + [ + 'slug' => 'insert-headers-and-footers', + 'name' => esc_html__( 'Code Snippets by WPCode', 'wp-mail-smtp' ), + 'is_activated' => class_exists( 'InsertHeadersAndFooters' ), + 'is_installed' => array_key_exists( 'insert-headers-and-footers/ihaf.php', $installed_plugins ), + ], + [ + 'slug' => 'rafflepress', + 'name' => esc_html__( 'Giveaways by RafflePress', 'wp-mail-smtp' ), + 'is_activated' => defined( 'RAFFLEPRESS_BUILD' ), + 'is_installed' => array_key_exists( 'rafflepress/rafflepress.php', $installed_plugins ), + ], + [ + 'slug' => 'instagram-feed', + 'name' => esc_html__( 'Smash Balloon Social Photo Feed', 'wp-mail-smtp' ), + 'is_activated' => function_exists( 'sb_instagram_feed_init' ), + 'is_installed' => array_key_exists( 'instagram-feed/instagram-feed.php', $installed_plugins ), + ], + [ + 'slug' => 'coming-soon', + 'name' => esc_html__( 'SeedProd Landing Page Builder', 'wp-mail-smtp' ), + 'is_activated' => defined( 'SEEDPROD_BUILD' ), + 'is_installed' => array_key_exists( 'coming-soon/coming-soon.php', $installed_plugins ), + ], + [ + 'slug' => 'wp-call-button', + 'name' => esc_html__( 'WP Call Button', 'wp-mail-smtp' ), + 'is_activated' => defined( 'WP_CALL_BUTTON_VERSION' ), + 'is_installed' => array_key_exists( 'wp-call-button/wp-call-button.php', $installed_plugins ), + ], + ]; + } + + /** + * AJAX callback for subscribing an email address to the WP Mail SMTP Drip newsletter. + * + * @since 2.6.0 + */ + public function subscribe_to_newsletter() { + + 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' ) ); + } + + $email = ! empty( $_POST['email'] ) ? filter_var( wp_unslash( $_POST['email'] ), FILTER_VALIDATE_EMAIL ) : ''; + + if ( empty( $email ) ) { + wp_send_json_error(); + } + + $body = [ + 'email' => base64_encode( $email ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + ]; + + $wpforms_version_type = $this->get_wpforms_version_type(); + + if ( ! empty( $wpforms_version_type ) ) { + $body['wpforms_version_type'] = $wpforms_version_type; + } + + wp_remote_post( + 'https://connect.wpmailsmtp.com/subscribe/drip/', + [ + 'user-agent' => Helpers::get_default_user_agent(), + 'body' => $body, + ] + ); + + wp_send_json_success(); + } + + /** + * Get the WPForms version type if it's installed. + * + * @since 3.9.0 + * + * @return false|string Return `false` if WPForms is not installed, otherwise return either `lite` or `pro`. + */ + private function get_wpforms_version_type() { + + if ( ! function_exists( 'wpforms' ) ) { + return false; + } + + if ( method_exists( wpforms(), 'is_pro' ) ) { + $is_wpforms_pro = wpforms()->is_pro(); + } else { + $is_wpforms_pro = wpforms()->pro; + } + + return $is_wpforms_pro ? 'pro' : 'lite'; + } + + /** + * AJAX callback for plugin upgrade, from lite to pro. + * + * @since 2.6.0 + */ + public function upgrade_plugin() { + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + if ( wp_mail_smtp()->is_pro() ) { + wp_send_json_success( esc_html__( 'You are already using the WP Mail SMTP PRO version. Please refresh this page and verify your license key.', 'wp-mail-smtp' ) ); + } + + if ( ! current_user_can( 'install_plugins' ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the permission to perform this action.', 'wp-mail-smtp' ) ); + } + + $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' ) ); + } + + $url = Connect::generate_url( + $license_key, + '', + add_query_arg( 'upgrade-redirect', '1', self::get_site_url() ) . '#/step/license' + ); + + if ( empty( $url ) ) { + wp_send_json_error( esc_html__( 'Upgrade functionality not available!', 'wp-mail-smtp' ) ); + } + + wp_send_json_success( [ 'redirect_url' => $url ] ); + } + + /** + * AJAX callback for checking the mailer configuration. + * - Send a test email + * - Check the domain setup with the Domain Checker API. + * + * @since 2.6.0 + */ + public function check_mailer_configuration() { + + 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' ) ); + } + + $options = Options::init(); + $mailer = $options->get( 'mail', 'mailer' ); + $from_email = $options->get( 'mail', 'from_email' ); + $domain = ''; + + // Send the test mail. Domain check runs below with its own (warnings-tolerated) + // threshold, so we opt out of TestEmail's stricter no_issues() check. + $test_email = ( new TestEmail() ) + ->with_context( TestEmail::CONTEXT_SETUP_WIZARD ) + ->as_html( false ) + ->with_domain_check( false ); + + $test_email->send( $this->get_test_email_recipient() ); + + if ( ! $test_email->is_successful() ) { + $this->update_completed_stat( false, $mailer ); + + ( new UsageTracking() )->send_failed_setup_wizard_usage_tracking_data(); + + wp_send_json_error(); + } + + // Add the optional sending domain parameter. + if ( in_array( $mailer, [ 'mailgun', 'sendinblue', 'sendgrid' ], true ) ) { + $domain = $options->get( $mailer, 'domain' ); + } + + // Perform the domain checker API test. + $domain_checker = new DomainChecker( $mailer, $from_email, $domain ); + + if ( $domain_checker->has_errors() ) { + $this->update_completed_stat( false, $mailer ); + + ( new UsageTracking() )->send_failed_setup_wizard_usage_tracking_data( $domain_checker ); + + wp_send_json_error(); + } + + $this->update_completed_stat( true, $mailer ); + + wp_send_json_success(); + } + + /** + * Get the test email recipient. + * + * @since 4.7.0 + * + * @return string + */ + private function get_test_email_recipient() { + + $options = Options::init(); + $mailer = $options->get( 'mail', 'mailer' ); + $from_email = $options->get( 'mail', 'from_email' ); + + /* + * Some mailers in a test mode allows to send emails only to the registered + * From email address, so we need to cover this case. + */ + $to_email = $from_email; + + $mailer_specific_constant_name = 'WPMS_SETUP_WIZARD_TEST_' . strtoupper( $mailer ) . '_EMAIL_RECIPIENT'; + + if ( + defined( $mailer_specific_constant_name ) && + is_email( constant( $mailer_specific_constant_name ) ) + ) { + $to_email = constant( $mailer_specific_constant_name ); + } elseif ( + defined( 'WPMS_SETUP_WIZARD_TEST_EMAIL_RECIPIENT' ) && + is_email( WPMS_SETUP_WIZARD_TEST_EMAIL_RECIPIENT ) + ) { + $to_email = WPMS_SETUP_WIZARD_TEST_EMAIL_RECIPIENT; + } + + return $to_email; + } + + /** + * AJAX callback for sending feedback. + * + * @since 2.6.0 + */ + public function send_feedback() { + + check_ajax_referer( 'wpms-admin-nonce', 'nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error(); + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $data = ! empty( $_POST['data'] ) ? json_decode( wp_unslash( $_POST['data'] ), true ) : []; + + $feedback = ! empty( $data['feedback'] ) ? sanitize_textarea_field( $data['feedback'] ) : ''; + $permission = ! empty( $data['permission'] ); + + wp_remote_post( + 'https://wpmailsmtp.com/wizard-feedback/', + [ + 'user-agent' => Helpers::get_default_user_agent(), + 'body' => [ + 'wpforms' => [ + 'id' => 87892, + 'fields' => [ + '1' => $feedback, + '2' => $permission ? wp_get_current_user()->user_email : '', + '3' => wp_mail_smtp()->get_license_type(), + '4' => WPMS_PLUGIN_VER, + ], + ], + ], + ] + ); + + wp_send_json_success(); + } + + /** + * Data used for the Vue scripts to display old PHP and WP versions warnings. + * + * @since 2.6.0 + */ + private function prepare_versions_data() { + + global $wp_version; + + return array( + 'php_version' => phpversion(), + 'php_version_below_55' => apply_filters( 'wp_mail_smtp_temporarily_hide_php_under_55_upgrade_warnings', version_compare( phpversion(), '5.5', '<' ) ), + 'php_version_below_56' => apply_filters( 'wp_mail_smtp_temporarily_hide_php_56_upgrade_warnings', version_compare( phpversion(), '5.6', '<' ) ), + 'wp_version' => $wp_version, + 'wp_version_below_49' => version_compare( $wp_version, '4.9', '<' ), + ); + } + + /** + * Remove 'error' from the automatic clearing list of query arguments after page loads. + * This will fix the issue with missing oAuth 'error' argument for the Setup Wizard. + * + * @since 2.6.0 + * + * @param array $defaults Array of query arguments to be cleared after page load. + * + * @return array + */ + public function maybe_disable_automatic_query_args_removal( $defaults ) { + + if ( + ( isset( $_GET['page'] ) && $_GET['page'] === 'wp-mail-smtp-setup-wizard' ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ( ! empty( $_GET['error'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) { + $defaults = array_values( array_diff( $defaults, [ 'error' ] ) ); + } + + return $defaults; + } + + /** + * Check if the Setup Wizard should load. + * + * @since 2.6.0 + * + * @return bool + */ + public function should_setup_wizard_load() { + + return (bool) apply_filters( 'wp_mail_smtp_admin_setup_wizard_load_wizard', true ); + } + + /** + * Get the Setup Wizard stats. + * - launched_time -> when the Setup Wizard was last launched. + * - completed_time -> when the Setup Wizard was last completed. + * - was_successful -> if the Setup Wizard was completed successfully. + * - mailer -> mailer slug configured in the Setup Wizard on its last completion attempt. + * + * @since 3.1.0 + * + * @return array + */ + public static function get_stats() { + + $defaults = [ + 'launched_time' => 0, + 'completed_time' => 0, + 'was_successful' => false, + 'mailer' => '', + ]; + + return get_option( self::STATS_OPTION_KEY, $defaults ); + } + + /** + * Update the Setup Wizard stats. + * + * @since 3.1.0 + * + * @param array $options Take a look at SetupWizard::get_stats method for the possible array keys. + */ + public static function update_stats( $options ) { + + update_option( self::STATS_OPTION_KEY, array_merge( self::get_stats(), $options ) , false ); + } + + /** + * Update the completed Setup Wizard stats. + * + * @since 3.1.0 + * + * @param bool $was_successful If the Setup Wizard was completed successfully. + * @param string $mailer Mailer slug configured in the Setup Wizard. + */ + private function update_completed_stat( $was_successful, $mailer = '' ) { + + self::update_stats( + [ + 'completed_time' => time(), + 'was_successful' => $was_successful, + 'mailer' => $mailer, + ] + ); + } + + /** + * Prepare an array of WP Mail SMTP PHP constants in use. + * Those that are used in the setup wizard. + * + * @since 3.2.0 + * + * @return array + */ + private function prepare_defined_constants() { + + $options = Options::init(); + + if ( ! $options->is_const_enabled() ) { + return []; + } + + $constants = [ + 'WPMS_MAIL_FROM' => [ 'mail', 'from_email' ], + 'WPMS_MAIL_FROM_FORCE' => [ 'mail', 'from_email_force' ], + 'WPMS_MAIL_FROM_NAME' => [ 'mail', 'from_name' ], + 'WPMS_MAIL_FROM_NAME_FORCE' => [ 'mail', 'from_name_force' ], + 'WPMS_MAILER' => [ 'mail', 'mailer' ], + 'WPMS_SMTPCOM_API_KEY' => [ 'smtpcom', 'api_key' ], + 'WPMS_SMTPCOM_CHANNEL' => [ 'smtpcom', 'channel' ], + 'WPMS_SENDINBLUE_API_KEY' => [ 'sendinblue', 'api_key' ], + 'WPMS_SENDINBLUE_DOMAIN' => [ 'sendinblue', 'domain' ], + 'WPMS_AMAZONSES_CLIENT_ID' => [ 'amazonses', 'client_id' ], + 'WPMS_AMAZONSES_CLIENT_SECRET' => [ 'amazonses', 'client_secret' ], + 'WPMS_AMAZONSES_REGION' => [ 'amazonses', 'region' ], + 'WPMS_GMAIL_CLIENT_ID' => [ 'gmail', 'client_id' ], + 'WPMS_GMAIL_CLIENT_SECRET' => [ 'gmail', 'client_secret' ], + 'WPMS_MAILGUN_API_KEY' => [ 'mailgun', 'api_key' ], + 'WPMS_MAILGUN_DOMAIN' => [ 'mailgun', 'domain' ], + 'WPMS_MAILGUN_REGION' => [ 'mailgun', 'region' ], + 'WPMS_OUTLOOK_CLIENT_ID' => [ 'outlook', 'client_id' ], + 'WPMS_OUTLOOK_CLIENT_SECRET' => [ 'outlook', 'client_secret' ], + 'WPMS_POSTMARK_SERVER_API_TOKEN' => [ 'postmark', 'server_api_token' ], + 'WPMS_POSTMARK_MESSAGE_STREAM' => [ 'postmark', 'message_stream' ], + 'WPMS_SENDGRID_API_KEY' => [ 'sendgrid', 'api_key' ], + 'WPMS_SENDGRID_DOMAIN' => [ 'sendgrid', 'domain' ], + 'WPMS_SPARKPOST_API_KEY' => [ 'sparkpost', 'api_key' ], + 'WPMS_SPARKPOST_REGION' => [ 'sparkpost', 'region' ], + 'WPMS_ZOHO_DOMAIN' => [ 'zoho', 'domain' ], + 'WPMS_ZOHO_CLIENT_ID' => [ 'zoho', 'client_id' ], + 'WPMS_ZOHO_CLIENT_SECRET' => [ 'zoho', 'client_secret' ], + 'WPMS_RESEND_API_KEY' => [ 'resend', 'api_key' ], + 'WPMS_SMTP_HOST' => [ 'smtp', 'host' ], + 'WPMS_SMTP_PORT' => [ 'smtp', 'port' ], + 'WPMS_SSL' => [ 'smtp', 'encryption' ], + 'WPMS_SMTP_AUTH' => [ 'smtp', 'auth' ], + 'WPMS_SMTP_AUTOTLS' => [ 'smtp', 'autotls' ], + 'WPMS_SMTP_USER' => [ 'smtp', 'user' ], + 'WPMS_SMTP_PASS' => [ 'smtp', 'pass' ], + 'WPMS_LOGS_ENABLED' => [ 'logs', 'enabled' ], + 'WPMS_SUMMARY_REPORT_EMAIL_DISABLED' => [ 'general', SummaryReportEmail::SETTINGS_SLUG ], + ]; + + $defined = []; + + foreach ( $constants as $constant => $group_and_key ) { + if ( $options->is_const_defined( $group_and_key[0], $group_and_key[1] ) ) { + $defined[] = $constant; + } + } + + return $defined; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Admin/WooCommerceActiveLayerEducation.php b/web/app/plugins/wp-mail-smtp-pro/src/Admin/WooCommerceActiveLayerEducation.php new file mode 100644 index 00000000..f38ddfb0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Admin/WooCommerceActiveLayerEducation.php @@ -0,0 +1,402 @@ +is_activelayer_active() ) { + return $this->is_api_key_connected() ? 'connected' : 'connect'; + } + + $is_installed = $this->is_activelayer_installed(); + + if ( ! $is_installed && $this->can_install() ) { + return 'install'; + } + + if ( $is_installed && current_user_can( 'activate_plugins' ) ) { + return 'activate'; + } + + // Admins who cannot run the installer (file mods locked) can still follow a link. + if ( current_user_can( 'install_plugins' ) ) { + return 'goto-url'; + } + + // Users who cannot act at all (e.g. shop managers) get no dead-end card. + return ''; + } + + /** + * Whether the section should be displayed for the current user. + * + * @since 4.9.0 + * + * @return bool + */ + private function should_display() { + + /** + * Filters whether the ActiveLayer education section is rendered on the + * WooCommerce Accounts & Privacy settings tab. + * + * @since 4.9.0 + * + * @param bool $should_display Whether the section should be displayed. + */ + if ( ! apply_filters( 'wp_mail_smtp_admin_woo_commerce_active_layer_education_should_display', true ) ) { + return false; + } + + if ( get_user_meta( get_current_user_id(), self::DISMISS_META, true ) ) { + return false; + } + + return $this->get_state() !== ''; + } + + /** + * Output the section after the WooCommerce account settings block. + * + * Fires on woocommerce_settings_account_registration_options_after, which + * WooCommerce runs after the section's closing table tag, so free-form + * markup is safe here. + * + * @since 4.9.0 + */ + public function output_section() { + + if ( ! $this->should_display() ) { + return; + } + + $state = $this->get_state(); + ?> +
    + +
    + +
    +
    +

    + +

    + output_state( $state ); ?> +
    +
    + +

    + +

    + + + +

    + + + + + + +

    + +

    + +

    + + should_display() ) { + return; + } + + wp_enqueue_style( + 'wp-mail-smtp-activelayer-wc', + wp_mail_smtp()->assets_url . '/css/smtp-activelayer-wc' . WP::asset_min() . '.css', + [], + WPMS_PLUGIN_VER + ); + + wp_enqueue_script( + 'wp-mail-smtp-activelayer-wc', + wp_mail_smtp()->assets_url . '/js/smtp-activelayer-wc' . WP::asset_min() . '.js', + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-activelayer-wc', + 'wp_mail_smtp_activelayer_wc', + [ + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wp-mail-smtp-admin' ), + 'plugin' => self::PLUGIN_BASENAME, + 'download_url' => self::DOWNLOAD_URL, + 'settings_url' => $this->get_settings_url(), + 'wporg_url' => self::WPORG_URL, + 'installing' => esc_html__( 'Installing...', 'wp-mail-smtp' ), + 'activating' => esc_html__( 'Activating...', 'wp-mail-smtp' ), + 'goto_settings' => esc_html__( 'Connect Your Free Account', 'wp-mail-smtp' ), + 'get_activelayer' => esc_html__( 'Get ActiveLayer', 'wp-mail-smtp' ), + 'error_install' => esc_html__( 'Could not install ActiveLayer. Please download it from WordPress.org and install manually.', 'wp-mail-smtp' ), + 'error_activate' => esc_html__( 'Could not activate ActiveLayer. Please activate it from the Plugins page.', 'wp-mail-smtp' ), + ] + ); + } + + /** + * AJAX: dismiss the section for the current user. + * + * @since 4.9.0 + */ + public function ajax_dismiss() { + + if ( check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) === false ) { + wp_send_json_error( esc_html__( 'Could not dismiss the section. Please reload the page and try again.', 'wp-mail-smtp' ) ); + } + + update_user_meta( get_current_user_id(), self::DISMISS_META, time() ); + + wp_send_json_success(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Compatibility.php b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Compatibility.php new file mode 100644 index 00000000..56064dde --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Compatibility.php @@ -0,0 +1,80 @@ +setup_compatibility(); + } + + /** + * Setup compatibility plugins. + * + * @since 2.8.0 + */ + public function setup_compatibility() { + + $plugins = [ + 'admin-2020' => Admin2020::class, + 'wpforms-lite' => WPFormsLite::class, + 'wpforms' => WPForms::class, + 'woocommerce' => WooCommerce::class, + 'wpml' => WPML::class, + 'polylang' => Polylang::class, + 'polylang-pro' => PolylangPro::class, + ]; + + foreach ( $plugins as $key => $classname ) { + if ( class_exists( $classname ) && is_callable( [ $classname, 'is_applicable' ] ) ) { + if ( $classname::is_applicable() ) { + $this->plugins[ $key ] = new $classname(); + } + } + } + } + + /** + * Get compatibility plugin. + * + * @since 2.8.0 + * + * @param string $key Plugin key. + * + * @return PluginAbstract|false + */ + public function get_plugin( $key ) { + + return isset( $this->plugins[ $key ] ) ? $this->plugins[ $key ] : false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/Admin2020.php b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/Admin2020.php new file mode 100644 index 00000000..e15f8413 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/Admin2020.php @@ -0,0 +1,70 @@ +after_plugins_loaded(); + } + + /** + * Is plugin can be loaded. + * + * @since 2.8.0 + * + * @return bool + */ + public static function is_applicable() { + + return static::is_activated(); + } + + /** + * Is plugin activated. + * + * @since 2.8.0 + * + * @return bool + */ + public static function is_activated() { + + return WP::is_plugin_activated( static::get_path() ); + } + + /** + * Execute after plugins loaded. + * + * @since 2.8.0 + */ + public function after_plugins_loaded() { + } + + /** + * Execute on init action in admin area. + * + * @since 2.8.0 + */ + public function load_admin() { + } + + /** + * Execute on init action. + * + * @since 2.8.0 + */ + public function load() { + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PluginInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PluginInterface.php new file mode 100644 index 00000000..448572ed --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PluginInterface.php @@ -0,0 +1,68 @@ +options['force_lang'] ) || + PLL()->options['force_lang'] !== 3 + ) { + return; + } + + // Use unfiltered site URL for multidomain setup. + add_filter( 'wp_mail_smtp_wp_get_site_url_unfiltered', '__return_true' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PolylangPro.php b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PolylangPro.php new file mode 100644 index 00000000..2259eac8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/PolylangPro.php @@ -0,0 +1,36 @@ +get_queue()->is_enabled() ) { + add_filter( 'wpforms_tasks_entry_emails_trigger_send_same_process', '__return_true', PHP_INT_MAX ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WPML.php b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WPML.php new file mode 100644 index 00000000..1848411e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WPML.php @@ -0,0 +1,69 @@ +get_setting( 'language_negotiation_type' ) + ) { + return; + } + + // Use unfiltered site URL for multidomain setup. + add_filter( 'wp_mail_smtp_wp_get_site_url_unfiltered', '__return_true' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WooCommerce.php b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WooCommerce.php new file mode 100644 index 00000000..415b0d90 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Compatibility/Plugin/WooCommerce.php @@ -0,0 +1,47 @@ +get_queue()->is_enabled() ) { + add_filter( 'woocommerce_defer_transactional_emails', '__return_false', PHP_INT_MAX ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Conflicts.php b/web/app/plugins/wp-mail-smtp-pro/src/Conflicts.php new file mode 100644 index 00000000..87470c44 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Conflicts.php @@ -0,0 +1,685 @@ + 'Easy WP SMTP', + 'slug' => 'easy-wp-smtp/easy-wp-smtp.php', + 'class' => 'EasyWPSMTP', + ], + + /** + * Closed. + * + * Url: https://wordpress.org/plugins/postman-smtp/ + */ + [ + 'name' => 'Postman SMTP', + 'slug' => 'postman-smtp/postman-smtp.php', + 'function' => 'postman_start', + ], + + /** + * Url: https://wordpress.org/plugins/post-smtp/ + */ + [ + 'name' => 'Post SMTP', + 'slug' => 'post-smtp/postman-smtp.php', + 'function' => 'post_smtp_start', + ], + + /** + * Url: https://wordpress.org/plugins/wp-mail-bank/ + */ + [ + 'name' => 'Mail Bank', + 'slug' => 'wp-mail-bank/wp-mail-bank.php', + 'function' => 'mail_bank', + ], + + /** + * Url: https://wordpress.org/plugins/smtp-mailer/ + */ + [ + 'name' => 'SMTP Mailer', + 'slug' => 'smtp-mailer/main.php', + 'class' => 'SMTP_MAILER', + ], + + /** + * Url: https://wordpress.org/plugins/gmail-smtp/ + */ + [ + 'name' => 'Gmail SMTP', + 'slug' => 'gmail-smtp/main.php', + 'class' => 'GMAIL_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/wp-email-smtp/ + */ + [ + 'name' => 'WP Email SMTP', + 'class' => 'WP_Email_Smtp', + ], + + /** + * Url: https://wordpress.org/plugins/smtp-mail/ + */ + [ + 'name' => 'SMTP Mail', + 'slug' => 'smtp-mail/index.php', + 'function' => 'smtpmail_include', + ], + + /** + * Url: https://wordpress.org/plugins/bws-smtp/ + */ + [ + 'name' => 'SMTP by BestWebSoft', + 'slug' => 'bws-smtp/bws-smtp.php', + 'function' => 'bwssmtp_init', + ], + + /** + * Url: https://wordpress.org/plugins/wp-sendgrid-smtp/ + */ + [ + 'name' => 'WP SendGrid SMTP', + 'slug' => 'wp-sendgrid-smtp/wp-sendgrid-smtp.php', + 'class' => 'WPSendGrid_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/sar-friendly-smtp/ + */ + [ + 'name' => 'SAR Friendly SMTP', + 'slug' => 'sar-friendly-smtp/sar-friendly-smtp.php', + 'function' => 'sar_friendly_smtp', + ], + + /** + * Url: https://wordpress.org/plugins/wp-gmail-smtp/ + */ + [ + 'name' => 'WP Gmail SMTP', + 'slug' => 'wp-gmail-smtp/wp-gmail-smtp.php', + 'class' => 'WPGmail_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/cimy-swift-smtp/ + */ + [ + 'name' => 'Cimy Swift SMTP', + 'slug' => 'cimy-swift-smtp/cimy_swift_smtp.php', + 'function' => 'st_smtp_check_config', + ], + + /** + * Closed. + * + * Url: https://wordpress.org/plugins/wp-easy-smtp/ + */ + [ + 'name' => 'WP Easy SMTP', + 'slug' => 'wp-easy-smtp/wp-easy-smtp.php', + 'class' => 'WP_Easy_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/wp-mailgun-smtp/ + */ + [ + 'name' => 'WP Mailgun SMTP', + 'slug' => 'wp-mailgun-smtp/wp-mailgun-smtp.php', + 'class' => 'WPMailgun_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/my-smtp-wp/ + */ + [ + 'name' => 'MY SMTP WP', + 'slug' => 'my-smtp-wp/my-smtp-wp.php', + 'function' => 'my_smtp_wp', + ], + + /** + * Closed. + * + * Url: https://wordpress.org/plugins/wp-mail-booster/ + */ + [ + 'name' => 'WP Mail Booster', + 'slug' => 'wp-mail-booster/wp-mail-booster.php', + 'function' => 'mail_booster', + ], + + /** + * Url: https://wordpress.org/plugins/sendgrid-email-delivery-simplified/ + */ + [ + 'name' => 'SendGrid', + 'slug' => 'sendgrid-email-delivery-simplified/wpsendgrid.php', + 'class' => 'Sendgrid_Settings', + ], + + /** + * Url: https://wordpress.org/plugins/wp-mail-smtp-mailer/ + */ + [ + 'name' => 'WP Mail Smtp Mailer', + 'slug' => 'wp-mail-smtp-mailer/wp-mail-smtp-mailer.php', + 'function' => 'WPMS_php_mailer', + ], + + /** + * Closed. + * + * Url: https://wordpress.org/plugins/wp-amazon-ses-smtp/ + */ + [ + 'name' => 'WP Amazon SES SMTP', + 'slug' => 'wp-amazon-ses-smtp/wp-amazon-ses.php', + 'class' => 'WPAmazonSES_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/postmark-approved-wordpress-plugin/ + */ + [ + 'name' => 'Postmark (Official)', + 'slug' => 'postmark-approved-wordpress-plugin/postmark.php', + 'class' => 'Postmark_Mail', + ], + + /** + * Url: https://wordpress.org/plugins/mailgun/ + */ + [ + 'name' => 'Mailgun', + 'slug' => 'mailgun/mailgun.php', + 'class' => 'Mailgun', + ], + + /** + * Url: https://wordpress.org/plugins/sparkpost/ + */ + [ + 'name' => 'SparkPost', + 'slug' => 'sparkpost/wordpress-sparkpost.php', + 'class' => 'WPSparkPost\SparkPost', + ], + + /** + * Url: https://wordpress.org/plugins/wp-yahoo-smtp/ + */ + [ + 'name' => 'WP Yahoo SMTP', + 'slug' => 'wp-yahoo-smtp/wp-yahoo-smtp.php', + 'class' => 'WPYahoo_SMTP', + ], + + /** + * Url: https://wordpress.org/plugins/wp-ses/ + */ + [ + 'name' => 'WP Offload SES Lite', + 'slug' => 'wp-ses/wp-ses.php', + 'function' => 'wp_offload_ses_lite_init', + ], + + /** + * Url: https://deliciousbrains.com/wp-offload-ses/ + */ + [ + 'name' => 'WP Offload SES', + 'slug' => 'wp-offload-ses/wp-offload-ses.php', + ], + + /** + * Url: https://wordpress.org/plugins/turbosmtp/ + */ + [ + 'name' => 'turboSMTP', + 'slug' => 'turbosmtp/turbo-smtp-plugin.php', + 'function' => 'TSPHPMailer', + ], + + /** + * Url: https://wordpress.org/plugins/wp-smtp/ + */ + [ + 'name' => 'Solid Mail', + 'slug' => 'wp-smtp/wp-smtp.php', + 'class' => 'WP_SMTP', + ], + + /** + * This plugin can be used along with our plugin if disable next option + * WooCommerce -> Settings -> Sendinblue -> Email Options -> Enable Sendinblue to send WooCommerce emails. + * + * Url: https://wordpress.org/plugins/woocommerce-sendinblue-newsletter-subscription + */ + [ + 'name' => 'Sendinblue - WooCommerce Email Marketing', + 'slug' => 'woocommerce-sendinblue-newsletter-subscription/woocommerce-sendinblue.php', + 'class' => 'WC_Sendinblue_Integration', + 'test' => 'test_wc_sendinblue_integration', + 'message' => esc_html__( 'Or disable the Sendinblue email sending setting in WooCommerce > Settings > Sendinblue (tab) > Email Options (tab) > Enable Sendinblue to send WooCommerce emails.', 'wp-mail-smtp' ), + ], + + /** + * Url: https://wordpress.org/plugins/disable-emails/ + */ + [ + 'name' => 'Disable Emails', + 'slug' => 'disable-emails/disable-emails.php', + 'class' => '\webaware\disable_emails\Plugin', + ], + + /** + * Url: https://wordpress.org/plugins/fluent-smtp/ + */ + [ + 'name' => 'FluentSMTP', + 'slug' => 'fluent-smtp/fluent-smtp.php', + 'function' => 'fluentSmtpInit', + ], + + /** + * This plugin can be used along with our plugin if enable next option + * Settings > Email template > Sender (tab) -> Do not change email sender by default. + * + * Url: https://wordpress.org/plugins/wp-html-mail/ + */ + [ + 'name' => 'WP HTML Mail - Email Template Designer', + 'slug' => 'wp-html-mail/wp-html-mail.php', + 'function' => 'Haet_Mail', + 'test' => 'test_wp_html_mail_integration', + 'message' => esc_html__( 'Or enable "Do not change email sender by default" setting in Settings > Email template > Sender (tab).', 'wp-mail-smtp' ), + ], + + /** + * This plugin can be used along with our plugin if "SMTP" module is deactivated. + * + * Url: https://wordpress.org/plugins/branda-white-labeling/ + */ + [ + 'name' => 'Branda', + 'slug' => 'branda-white-labeling/ultimate-branding.php', + 'function' => 'set_ultimate_branding', + 'test' => 'test_branda_integration', + 'message' => esc_html__( 'Or deactivate "SMTP" module in Branda > Emails > SMTP.', 'wp-mail-smtp' ), + ], + + /** + * Url: https://wordpress.org/plugins/zoho-mail/ + */ + [ + 'name' => 'Zoho Mail for WordPress', + 'slug' => 'zoho-mail/zohoMail.php', + 'function' => 'zmail_send_mail_callback', + ], + + /** + * Url: https://elementor.com/products/site-mailer/ + */ + [ + 'name' => 'Site Mailer - SMTP Replacement, Email API Deliverability & Email Log', + 'slug' => 'site-mailer/site-mailer.php', + 'class' => 'SiteMailer', + ], + + /** + * Url: https://wordpress.org/plugins/suremails/ + */ + [ + 'name' => 'SureMail', + 'slug' => 'suremails/suremails.php', + 'class' => 'MailHandler', + ], + + /** + * Url: https://www.gravityforms.com/gravity-smtp/ + */ + [ + 'name' => 'Gravity SMTP', + 'slug' => 'gravitysmtp/gravitysmtp.php', + 'class' => 'Gravity_SMTP', + ], + + ]; + } + + /** + * Whether we have a conflict with predefined list of plugins. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_detected() { + + foreach ( self::$plugins as $plugin ) { + if ( $this->is_conflicting_plugin( $plugin ) ) { + $this->conflict[] = $plugin; + } + } + + return ! empty( $this->conflict ); + } + + /** + * Whether we have a conflict with plugin. + * + * @since 2.9.0 + * + * @param array $plugin Plugin data. + * + * @return bool + */ + protected function is_conflicting_plugin( $plugin ) { + + $conflict = false; + + if ( isset( $plugin['slug'] ) && WP::is_plugin_activated( $plugin['slug'] ) ) { + $conflict = true; + } elseif ( isset( $plugin['class'] ) && class_exists( $plugin['class'], false ) ) { + $conflict = true; + } elseif ( isset( $plugin['function'] ) && function_exists( $plugin['function'] ) ) { + $conflict = true; + } + + if ( + $conflict && + isset( $plugin['test'] ) && + is_callable( [ $this, $plugin['test'] ] ) + ) { + $conflict = call_user_func( [ $this, $plugin['test'] ] ); + } + + /** + * Filters whether the plugin is conflicting. + * + * @since 2.9.0 + * + * @param bool $conflict Whether the plugin is conflicting. + * @param array $plugin { + * Plugin data. + * + * @type string $name Plugin name. + * @type string $slug Plugin slug. + * } + */ + return apply_filters( 'wp_mail_smtp_conflicts_is_conflicting_plugin', $conflict, $plugin ); + } + + /** + * Add a warning admin message to a user about the conflicting plugin. + * + * @since 1.5.0 + */ + public function notify() { + + if ( empty( $this->conflict ) ) { + return; + } + + foreach ( $this->conflict as $conflict_plugin ) { + WP::add_admin_notice( $this->get_conflict_message( $conflict_plugin ), WP::ADMIN_NOTICE_WARNING ); + } + } + + /** + * Get the conflicting plugin name is any. + * + * @since 1.5.0 + * @since 3.6.0 Added optional conflict_plugin parameter. + * + * @param array $conflict_plugin The conflicting plugin array. If provided then extract the name from the array. + * Else get the name from first conflicting plugin. + * + * @return null|string + */ + public function get_conflict_name( $conflict_plugin = [] ) { + + $name = null; + + if ( empty( $conflict_plugin ) && isset( $this->conflict[0] ) ) { + $conflict_plugin = $this->conflict[0]; + } + + if ( ! empty( $conflict_plugin['name'] ) ) { + $name = $conflict_plugin['name']; + } + + return $name; + } + + /** + * Get the conflicting plugin message. + * + * @since 2.9.0 + * @since 3.6.0 Added optional conflict_plugin parameter. + * @since 4.9.0 Append a one-click deactivation link when the conflicting plugin has a known slug. + * + * @param array $conflict_plugin The conflicting plugin array. If provided then extract the message from the array. + * Else get the message from first conflicting plugin. + * + * @return string + */ + public function get_conflict_message( $conflict_plugin = [] ) { + + if ( empty( $conflict_plugin ) && isset( $this->conflict[0] ) ) { + $conflict_plugin = $this->conflict[0]; + } + + $message = sprintf( /* translators: %1$s - Plugin name causing conflict. */ + esc_html__( 'Heads up! WP Mail SMTP has detected %1$s is activated. Please deactivate %1$s to prevent conflicts.', 'wp-mail-smtp' ), + $this->get_conflict_name( $conflict_plugin ) + ); + + if ( ! empty( $conflict_plugin['message'] ) ) { + $message .= ' ' . $conflict_plugin['message']; + } + + $deactivate_link = $this->get_conflict_deactivate_link( $conflict_plugin ); + + if ( $deactivate_link !== '' ) { + $message .= ' ' . $deactivate_link; + } + + return $message; + } + + /** + * Build a one-click deactivation link for the conflicting plugin. + * + * @since 4.9.0 + * + * @param array $conflict_plugin The conflicting plugin array. + * + * @return string Escaped anchor markup, or an empty string when no link applies. + */ + private function get_conflict_deactivate_link( $conflict_plugin ) { + + if ( empty( $conflict_plugin['slug'] ) || ! WP::is_plugin_activated( $conflict_plugin['slug'] ) ) { + return ''; + } + + $slug = $conflict_plugin['slug']; + + if ( ! function_exists( 'is_plugin_active_for_network' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + // Network-active plugins can only be deactivated from the network admin plugins screen. + if ( is_multisite() && is_plugin_active_for_network( $slug ) ) { + if ( ! current_user_can( 'manage_network_plugins' ) ) { + return ''; + } + + $base_url = network_admin_url( 'plugins.php?action=deactivate&plugin=' . $slug ); + } else { + if ( ! current_user_can( 'activate_plugins' ) ) { + return ''; + } + + $base_url = admin_url( 'plugins.php?action=deactivate&plugin=' . $slug ); + } + + $url = wp_nonce_url( $base_url, 'deactivate-plugin_' . $slug ); + + return sprintf( + '%2$s', + esc_url( $url ), + sprintf( /* translators: %1$s - Plugin name causing conflict. */ + esc_html__( 'Deactivate %1$s', 'wp-mail-smtp' ), + $this->get_conflict_name( $conflict_plugin ) + ) + ); + } + + /** + * Returns array containing (names) of all the conflicting plugins. + * + * @since 3.6.0 + * + * @return array + */ + public function get_all_conflict_names() { + + if ( empty( $this->conflict ) ) { + return []; + } + + $names_arr = []; + + foreach ( $this->conflict as $conflict_plugin ) { + $names_arr[] = $this->get_conflict_name( $conflict_plugin ); + } + + return $names_arr; + } + + /** + * Check whether we have conflict with "WooCommerce Sendinblue Newsletter Subscription" plugin. + * + * @since 2.9.0 + * @since 3.7.0 Added a version compatibility check. + * + * @return bool Returns true if we have conflict otherwise false. + */ + protected function test_wc_sendinblue_integration() { + + // Since version `3.0.0` "Sendinblue - WooCommerce Email Marketing" plugin no longer conflicts with WP Mail SMTP. + if ( defined( 'SENDINBLUE_WC_PLUGIN_VERSION' ) && version_compare( SENDINBLUE_WC_PLUGIN_VERSION, '3.0.0', '>=' ) ) { + return false; + } + + // Check requirements for test. + if ( + ! class_exists( 'WC_Sendinblue_Integration', false ) || + ! property_exists( 'WC_Sendinblue_Integration', 'ws_smtp_enabled' ) + ) { + return true; + } + + // Display or hide conflict message after toggle "Enable Sendinblue to send WooCommerce emails" option. + // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing + if ( + ! empty( $_POST ) && + ( isset( $_GET['page'] ) && $_GET['page'] === 'wc-settings' ) && + ( isset( $_GET['tab'] ) && $_GET['tab'] === 'sendinblue' ) && + ( isset( $_GET['section'] ) && $_GET['section'] === 'email_options' ) + ) { + return isset( $_POST['ws_smtp_enable'] ); + } + // phpcs:enable + + return \WC_Sendinblue_Integration::$ws_smtp_enabled === 'yes'; + } + + /** + * Check whether we have conflict with "WP HTML Mail - Email Template Designer" plugin. + * + * @since 3.3.0 + * + * @return bool Returns true if we have conflict otherwise false. + */ + protected function test_wp_html_mail_integration() { + + // Check requirements for test. + if ( + ! function_exists( 'Haet_Mail' ) || + ! is_object( Haet_Mail() ) || + ! method_exists( Haet_Mail(), 'get_options' ) + ) { + return true; + } + + $options = Haet_Mail()->get_options(); + + return ! isset( $options['disable_sender'] ) || ! $options['disable_sender']; + } + + /** + * Check whether we have conflict with "Branda" plugin. + * + * @since 3.5.0 + * + * @return bool Returns true if we have conflict otherwise false. + */ + protected function test_branda_integration() { + + // Check requirements for test. + if ( ! function_exists( 'branda_is_active_module' ) ) { + return true; + } + + return branda_is_active_module( 'emails/smtp.php' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Connect.php b/web/app/plugins/wp-mail-smtp-pro/src/Connect.php new file mode 100644 index 00000000..32410235 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Connect.php @@ -0,0 +1,308 @@ +assets_url . '/js/connect' . WP::asset_min() . '.js', + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-connect', + 'wp_mail_smtp_connect', + [ + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'plugin_url' => wp_mail_smtp()->plugin_url, + 'nonce' => wp_create_nonce( 'wp-mail-smtp-connect' ), + 'text' => [ + 'plugin_activate_btn' => esc_html__( 'Activate', 'wp-mail-smtp' ), + 'almost_done' => esc_html__( 'Almost Done', 'wp-mail-smtp' ), + 'oops' => esc_html__( 'Oops!', 'wp-mail-smtp' ), + 'ok' => esc_html__( 'OK', 'wp-mail-smtp' ), + 'server_error' => esc_html__( 'Unfortunately there was a server connection error.', 'wp-mail-smtp' ), + ], + ] + ); + } + + /** + * Generate and return WP Mail SMTP Connect URL. + * + * @since 2.6.0 + * + * @param string $key The license key. + * @param string $oth The One-time hash. + * @param string $redirect The redirect URL. + * + * @return bool|string + */ + public static function generate_url( $key, $oth = '', $redirect = '' ) { + + if ( empty( $key ) || wp_mail_smtp()->is_pro() ) { + return false; + } + + $oth = ! empty( $oth ) ? $oth : hash( 'sha512', wp_rand() ); + $hashed_oth = hash_hmac( 'sha512', $oth, wp_salt() ); + + $redirect = ! empty( $redirect ) ? $redirect : wp_mail_smtp()->get_admin()->get_admin_page_url(); + + update_option( 'wp_mail_smtp_connect_token', $oth ); + update_option( 'wp_mail_smtp_connect', $key ); + + return add_query_arg( + [ + 'key' => $key, + 'oth' => $hashed_oth, + 'endpoint' => admin_url( 'admin-ajax.php' ), + 'version' => WPMS_PLUGIN_VER, + 'siteurl' => admin_url(), + 'homeurl' => site_url(), + 'redirect' => rawurldecode( base64_encode( $redirect ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'v' => 2, + ], + 'https://upgrade.wpmailsmtp.com' + ); + } + + /** + * AJAX callback to generate and return the WP Mail SMTP Connect URL. + * + * @since 2.6.0 + */ + public function ajax_generate_url() { //phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Run a security check. + check_ajax_referer( 'wp-mail-smtp-connect', 'nonce' ); + + // Check for permissions. + if ( ! current_user_can( 'install_plugins' ) ) { + wp_send_json_error( + [ + 'message' => esc_html__( 'You are not allowed to install plugins.', 'wp-mail-smtp' ), + ] + ); + } + + $key = ! empty( $_POST['key'] ) ? sanitize_text_field( wp_unslash( $_POST['key'] ) ) : ''; + + if ( empty( $key ) ) { + wp_send_json_error( + [ + 'message' => esc_html__( 'Please enter your license key to connect.', 'wp-mail-smtp' ), + ] + ); + } + + if ( wp_mail_smtp()->is_pro() ) { + wp_send_json_error( + [ + 'message' => esc_html__( 'Only the Lite version can be upgraded.', 'wp-mail-smtp' ), + ] + ); + } + + // Verify pro version is not installed. + $active = activate_plugin( 'wp-mail-smtp-pro/wp_mail_smtp.php', false, false, true ); + + if ( ! is_wp_error( $active ) ) { + + // Deactivate Lite. + deactivate_plugins( plugin_basename( WPMS_PLUGIN_FILE ) ); + + wp_send_json_success( + [ + 'message' => esc_html__( 'WP Mail SMTP Pro was already installed, but was not active. We activated it for you.', 'wp-mail-smtp' ), + 'reload' => true, + ] + ); + } + + $url = self::generate_url( $key ); + + if ( empty( $url ) ) { + wp_send_json_error( + [ + 'message' => esc_html__( 'There was an error while generating an upgrade URL. Please try again.', 'wp-mail-smtp' ), + ] + ); + } + + wp_send_json_success( [ 'url' => $url ] ); + } + + /** + * AJAX callback to process WP Mail SMTP Connect. + * + * @since 2.6.0 + */ + public function process() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $error = esc_html__( 'There was an error while installing an upgrade. Please download the plugin from wpmailsmtp.com and install it manually.', 'wp-mail-smtp' ); + + // Verify params present (oth & download link). + $post_oth = ! empty( $_REQUEST['oth'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['oth'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + $post_url = ! empty( $_REQUEST['file'] ) ? esc_url_raw( wp_unslash( $_REQUEST['file'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification + + if ( empty( $post_oth ) || empty( $post_url ) ) { + wp_send_json_error( $error ); + } + + // Verify oth. + $oth = get_option( 'wp_mail_smtp_connect_token' ); + + if ( empty( $oth ) ) { + wp_send_json_error( $error ); + } + + if ( hash_hmac( 'sha512', $oth, wp_salt() ) !== $post_oth ) { + wp_send_json_error( $error ); + } + + // Delete so cannot replay. + delete_option( 'wp_mail_smtp_connect_token' ); + + // Set the current screen to avoid undefined notices. + set_current_screen( 'toplevel_page_wp-mail-smtp' ); + + // Prepare variables. + $url = esc_url_raw( wp_mail_smtp()->get_admin()->get_admin_page_url() ); + + // Verify pro not activated. + if ( wp_mail_smtp()->is_pro() ) { + wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'wp-mail-smtp' ) ); + } + + // Verify pro not installed. + $active = activate_plugin( 'wp-mail-smtp-pro/wp_mail_smtp.php', $url, false, true ); + + if ( ! is_wp_error( $active ) ) { + deactivate_plugins( plugin_basename( WPMS_PLUGIN_FILE ) ); + wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'wp-mail-smtp' ) ); + } + + /* + * The `request_filesystem_credentials` function will output a credentials form in case of failure. + * We don't want that, since it will break AJAX response. So just hide output with a buffer. + */ + ob_start(); + // phpcs:ignore WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine + $creds = request_filesystem_credentials( $url, '', false, false, null ); + ob_end_clean(); + + // Check for file system permissions. + $perm_error = esc_html__( 'There was an error while installing an upgrade. Please check file system permissions and try again. Also, you can download the plugin from wpmailsmtp.com and install it manually.', 'wp-mail-smtp' ); + + if ( false === $creds || ! WP_Filesystem( $creds ) ) { + wp_send_json_error( $perm_error ); + } + + /* + * We do not need any extra credentials if we have gotten this far, so let's install the plugin. + */ + + // Do not allow WordPress to search/download translations, as this will break JS output. + remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); + + // Import the plugin upgrader. + Helpers::include_plugin_upgrader(); + + // Create the plugin upgrader with our custom skin. + $installer = new Plugin_Upgrader( new PluginsInstallSkin() ); + + // Error check. + if ( ! method_exists( $installer, 'install' ) ) { + wp_send_json_error( $error ); + } + + // Check license key. + $key = get_option( 'wp_mail_smtp_connect', false ); + delete_option( 'wp_mail_smtp_connect' ); + + if ( empty( $key ) ) { + wp_send_json_error( + new WP_Error( + '403', + esc_html__( 'There was an error while installing an upgrade. Please try again.', 'wp-mail-smtp' ) + ) + ); + } + + $installer->install( $post_url ); + + // Flush the cache and return the newly installed plugin basename. + wp_cache_flush(); + + $plugin_basename = $installer->plugin_info(); + + if ( $plugin_basename ) { + + // Deactivate the lite version first. + deactivate_plugins( plugin_basename( WPMS_PLUGIN_FILE ) ); + + // Activate the plugin silently. + $activated = activate_plugin( $plugin_basename, '', false, true ); + + if ( ! is_wp_error( $activated ) ) { + + // Save the license data, since it was verified on the connect page. + $options = Options::init(); + $all_opt = $options->get_all_raw(); + + $all_opt['license']['key'] = $key; + $all_opt['license']['type'] = 'pro'; + $all_opt['license']['is_expired'] = false; + $all_opt['license']['is_disabled'] = false; + $all_opt['license']['is_invalid'] = false; + $all_opt['license']['is_limit_reached'] = false; + + $options->set( $all_opt, false, true ); + + wp_send_json_success( esc_html__( 'Plugin installed & activated.', 'wp-mail-smtp' ) ); + } else { + // Reactivate the lite plugin if pro activation failed. + activate_plugin( plugin_basename( WPMS_PLUGIN_FILE ), '', false, true ); + wp_send_json_error( esc_html__( 'Pro version installed but needs to be activated on the Plugins page.', 'wp-mail-smtp' ) ); + } + } + + wp_send_json_error( $error ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Connection.php b/web/app/plugins/wp-mail-smtp-pro/src/Connection.php new file mode 100644 index 00000000..23cf423f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Connection.php @@ -0,0 +1,78 @@ +options = Options::init(); + } + + /** + * Get the connection identifier. + * + * @since 3.7.0 + * + * @return string + */ + public function get_id() { + + return 'primary'; + } + + /** + * Get the connection name. + * + * @since 3.7.0 + * + * @return string + */ + public function get_name() { + + return esc_html__( 'Primary', 'wp-mail-smtp' ); + } + + /** + * Get connection options object. + * + * @since 3.7.0 + * + * @return Options + */ + public function get_options() { + + return $this->options; + } + + /** + * Whether the connection is primary or not. + * + * @since 3.7.0 + * + * @return bool + */ + public function is_primary() { + + return true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/ConnectionInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/ConnectionInterface.php new file mode 100644 index 00000000..321d6c8f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/ConnectionInterface.php @@ -0,0 +1,76 @@ +get_primary_connection(); + } + + /** + * Get the primary connection object. + * + * @since 3.7.0 + * + * @return ConnectionInterface + */ + public function get_primary_connection() { + + if ( is_null( $this->primary_connection ) ) { + $this->primary_connection = new Connection(); + } + + return $this->primary_connection; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Core.php b/web/app/plugins/wp-mail-smtp-pro/src/Core.php new file mode 100644 index 00000000..ba77ba05 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Core.php @@ -0,0 +1,1418 @@ +pro->example(). + * + * @since 1.5.0 + * + * @var \WPMailSMTP\Pro\Pro + */ + public $pro; + + /** + * Abilities API registrar. + * + * @since 4.9.0 + * + * @var AbilityRegistrar + */ + private $abilities_registrar; + + /** + * Core constructor. + * + * @since 1.0.0 + */ + public function __construct() { + + $this->plugin_url = rtrim( plugin_dir_url( __DIR__ ), '/\\' ); + $this->assets_url = $this->plugin_url . '/assets'; + $this->plugin_path = rtrim( plugin_dir_path( __DIR__ ), '/\\' ); + + if ( $this->is_not_loadable() ) { + add_action( 'admin_notices', 'wp_mail_smtp_insecure_php_version_notice' ); + + if ( WP::use_global_plugin_settings() ) { + add_action( 'network_admin_notices', 'wp_mail_smtp_insecure_php_version_notice' ); + } + + return; + } + + // Finally, load all the plugin. + $this->hooks(); + $this->init_early(); + } + + /** + * Currently used for Pro version only. + * + * @since 1.5.0 + * + * @return bool + */ + protected function is_not_loadable() { + + // Check the Pro. + if ( + is_readable( $this->plugin_path . '/src/Pro/Pro.php' ) && + ! $this->is_pro_allowed() + ) { + // So there is a Pro version, but its PHP version check failed. + return true; + } + + return false; + } + + /** + * Assign all hooks to proper places. + * + * @since 1.0.0 + */ + public function hooks() { + + // Activation hook. + register_activation_hook( WPMS_PLUGIN_FILE, [ $this, 'activate' ] ); + + // Initialize DB migrations. + add_action( 'plugins_loaded', [ $this, 'get_migrations' ] ); + + // Load Pro if available. + add_action( 'plugins_loaded', [ $this, 'get_pro' ] ); + + // Redefine PHPMailer. + add_action( 'plugins_loaded', [ $this, 'get_processor' ] ); + add_action( 'plugins_loaded', [ $this, 'replace_phpmailer' ] ); + + // Various notifications. + add_action( 'admin_init', [ $this, 'init_notifications' ] ); + + add_action( 'init', [ $this, 'init' ] ); + + // Initialize Action Scheduler tasks. + add_action( 'init', [ $this, 'get_tasks' ], 5 ); + + add_action( 'plugins_loaded', [ $this, 'get_usage_tracking' ] ); + add_action( 'plugins_loaded', [ $this, 'get_admin_bar_menu' ] ); + add_action( 'plugins_loaded', [ $this, 'get_notifications' ] ); + add_action( 'plugins_loaded', [ $this, 'get_connect' ], 15 ); + add_action( 'plugins_loaded', [ $this, 'get_compatibility' ], 0 ); + add_action( 'plugins_loaded', [ $this, 'get_dashboard_widget' ], 20 ); + add_action( 'plugins_loaded', [ $this, 'get_reports' ] ); + add_action( 'plugins_loaded', [ $this, 'get_db_repair' ] ); + add_action( 'plugins_loaded', [ $this, 'get_connections_manager' ], 20 ); + add_action( 'plugins_loaded', [ $this, 'get_wp_mail_initiator' ] ); + add_action( 'plugins_loaded', [ $this, 'get_queue' ] ); + add_action( + 'plugins_loaded', + function() { + ( new OptimizedEmailSending() )->hooks(); + ( new OutlookProvider() )->hooks(); + ( new SendlayerQuickConnect() )->hooks(); + ( new EmailSendingErrors() )->hooks(); + ( new RegisterWPCodeLibrary() )->hooks(); + $this->get_abilities_registrar()->hooks(); + } + ); + + if ( defined( 'WP_CLI' ) && WP_CLI ) { + add_action( 'init', [ new WPCLIBootstrap(), 'register' ], 20 ); + } + } + + /** + * Initial plugin actions. + * + * @since 1.0.0 + */ + public function init() { + + // Load translations just in case. + load_plugin_textdomain( 'wp-mail-smtp', false, plugin_basename( wp_mail_smtp()->plugin_path ) . '/assets/languages' ); + + /* + * Constantly check in admin area, that we don't need to upgrade DB. + * Do not wait for the `admin_init` hook, because some actions are already done + * on `plugins_loaded`, so migration has to be done before. + * We should not fire this in AJAX requests. + */ + if ( WP::in_wp_admin() ) { + $this->get_upgrade(); + $this->detect_conflicts(); + } + + // In admin area, regardless of AJAX or not AJAX request. + if ( is_admin() ) { + $this->get_admin(); + $this->get_site_health()->init(); + + // Register Debug Event hooks. + ( new DebugEvents() )->hooks(); + } + + // Plugin admin area notices. Display to "admins" only. + if ( current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + add_action( 'admin_notices', array( '\WPMailSMTP\WP', 'display_admin_notices' ) ); + add_action( 'admin_notices', array( $this, 'display_general_notices' ) ); + + if ( WP::use_global_plugin_settings() ) { + add_action( 'network_admin_notices', array( '\WPMailSMTP\WP', 'display_admin_notices' ) ); + add_action( 'network_admin_notices', array( $this, 'display_general_notices' ) ); + } + } + } + + /** + * Whether the Pro part of the plugin is allowed to be loaded. + * + * @since 1.5.0 + * @since 1.6.0 Added a filter. + * + * @return bool + */ + protected function is_pro_allowed() { + + $is_allowed = true; + + if ( ! is_readable( $this->plugin_path . '/src/Pro/Pro.php' ) ) { + $is_allowed = false; + } + + return apply_filters( 'wp_mail_smtp_core_is_pro_allowed', $is_allowed ); + } + + /** + * Get/Load the Pro code of the plugin if it exists. + * + * @since 1.6.2 + * + * @return \WPMailSMTP\Pro\Pro + */ + public function get_pro() { + + if ( ! $this->is_pro_allowed() ) { + return $this->pro; + } + + if ( ! $this->is_pro() ) { + $this->pro = new \WPMailSMTP\Pro\Pro(); + } + + return $this->pro; + } + + /** + * Get the Abilities API registrar. + * + * @since 4.9.0 + * + * @return AbilityRegistrar + */ + public function get_abilities_registrar() { + + if ( ! isset( $this->abilities_registrar ) ) { + $this->abilities_registrar = new AbilityRegistrar( + [ + GetDebugEventsAbility::class, + ] + ); + } + + return $this->abilities_registrar; + } + + /** + * Get/Load the Tasks code of the plugin. + * + * @since 2.1.0 + * + * @return \WPMailSMTP\Tasks\Tasks + */ + public function get_tasks() { + + static $tasks; + + if ( ! isset( $tasks ) ) { + $tasks = apply_filters( 'wp_mail_smtp_core_get_tasks', new Tasks\Tasks() ); + $tasks->init(); + } + + return $tasks; + } + + /** + * This method allows to overwrite certain core WP functions, because it's fired: + * - after `muplugins_loaded` hook, + * - before WordPress own `wp-includes/pluggable.php` file include, + * - before `plugin_loaded` and `plugins_loaded` hooks. + * + * @since 1.5.0 + */ + protected function init_early() { + + // Action Scheduler requires a special early loading procedure. + $this->load_action_scheduler(); + + // Load Pro specific files early. + $pro_files = $this->is_pro_allowed() ? \WPMailSMTP\Pro\Pro::PLUGGABLE_FILES : array(); + + $files = (array) apply_filters( 'wp_mail_smtp_core_init_early_include_files', $pro_files ); + + foreach ( $files as $file ) { + $path = $this->plugin_path . '/' . $file; + + if ( is_readable( $path ) ) { + /** @noinspection PhpIncludeInspection */ + include_once $path; + } + } + } + + /** + * Load the plugin core processor. + * + * @since 1.0.0 + * + * @return Processor + */ + public function get_processor() { + + static $processor; + + if ( ! isset( $processor ) ) { + + /** + * Filters Processor instance. + * + * @since 4.0.0 + * + * @param Processor $processor Processor instance. + */ + $processor = apply_filters( + 'wp_mail_smtp_core_get_processor', + new Processor() + ); + + if ( method_exists( $processor, 'hooks' ) ) { + $processor->hooks(); + } + } + + return $processor; + } + + /** + * Load the plugin admin area. + * + * @since 1.0.0 + * + * @return Admin\Area + */ + public function get_admin() { + + static $admin; + + if ( ! isset( $admin ) ) { + $admin = apply_filters( 'wp_mail_smtp_core_get_admin', new Admin\Area() ); + + if ( method_exists( $admin, 'hooks' ) ) { + $admin->hooks(); + } + } + + return $admin; + } + + /** + * Load the plugin providers loader. + * + * @since 1.0.0 + * + * @return Providers\Loader + */ + public function get_providers() { + + static $providers; + + if ( ! isset( $providers ) ) { + $providers = apply_filters( 'wp_mail_smtp_core_get_providers', new Providers\Loader() ); + } + + return $providers; + } + + /** + * Load the plugin option migrator. + * + * @deprecated 3.0.0 + * + * @since 1.0.0 + * + * @return Migration + */ + public function get_migration() { + + _deprecated_function( __METHOD__, '3.0.0' ); + + static $migration; + + if ( ! isset( $migration ) ) { + $migration = apply_filters( 'wp_mail_smtp_core_get_migration', new Migration() ); + } + + return $migration; + } + + /** + * Initialize DB migrations. + * + * @deprecated 4.0.0 + * + * @since 3.0.0 + */ + public function init_migrations() { + + _deprecated_function( __METHOD__, '3.10.0', '\WPMailSMTP\Migrations::init_migrations_on_request' ); + + $this->get_migrations()->init_migrations_on_request(); + } + + /** + * Get the Migrations object. + * + * @since 4.0.0 + * + * @return Migrations + */ + public function get_migrations() { + + static $migrations; + + if ( ! isset( $migrations ) ) { + $migrations = new Migrations(); + + $migrations->hooks(); + } + + return $migrations; + } + + /** + * Load the plugin upgrader. + * + * @since 1.1.0 + * + * @return Upgrade + */ + public function get_upgrade() { + + static $upgrade; + + if ( ! isset( $upgrade ) ) { + $upgrade = apply_filters( 'wp_mail_smtp_core_get_upgrade', new Upgrade() ); + + if ( method_exists( $upgrade, 'run' ) ) { + $upgrade->run(); + } + } + + return $upgrade; + } + + /** + * Get the plugin's WP Site Health object. + * + * @since 1.9.0 + * + * @return SiteHealth + */ + public function get_site_health() { + + static $site_health; + + if ( ! isset( $site_health ) ) { + $site_health = apply_filters( 'wp_mail_smtp_core_get_site_health', new SiteHealth() ); + } + + return $site_health; + } + + /** + * Display various notifications to a user. + * + * @since 1.0.0 + */ + public function init_notifications() { } + + /** + * Display all debug mail-delivery related notices. + * + * @since 1.3.0 + * @since 1.6.0 Added a filter that allows to hide debug errors. + */ + public static function display_general_notices() { + + if ( wp_mail_smtp()->is_blocked() ) { + ?> + +
    +

    + EMAILING DISABLED: The %s is currently blocking all emails from being sent.', 'wp-mail-smtp' ), + esc_html( 'WP Mail SMTP v' . WPMS_PLUGIN_VER ) + ); + + if ( Options::init()->is_const_defined( 'general', 'do_not_send' ) ) { + $notices[] = sprintf( + /* translators: %1$s - constant name; %2$s - constant value. */ + __( 'To send emails, change the value of the %1$s constant to %2$s.', 'wp-mail-smtp' ), + 'WPMS_DO_NOT_SEND', + 'false' + ); + } else { + $notices[] = sprintf( + /* translators: %s - plugin Misc settings page URL. */ + __( 'To send emails, go to plugin Misc settings and disable the "Do Not Send" option.', 'wp-mail-smtp' ), + esc_url( add_query_arg( 'tab', 'misc', wp_mail_smtp()->get_admin()->get_admin_page_url() ) ) + ); + } + + if ( + wp_mail_smtp()->get_admin()->is_admin_page( 'tools' ) && + ( + ! isset( $_GET['tab'] ) || + ( isset( $_GET['tab'] ) && $_GET['tab'] === 'test' ) + ) + ) { + $notices[] = esc_html__( 'If you create a test email on this page, it will still be sent.', 'wp-mail-smtp' ); + } + + echo wp_kses_post( implode( ' ', $notices ) ); + ?> +

    +
    + + get_admin()->is_admin_page() ) { + wp_mail_smtp()->wp_mail_function_incorrect_location_notice(); + } + } + + /** + * Check whether we are working with a new plugin install. + * + * @since 1.3.0 + * + * @return bool + */ + protected function is_new_install() { + + /* + * No previously installed 0.*. + * 'wp_mail_smtp_initial_version' option appeared in 1.3.0. So we make sure it exists. + * No previous plugin upgrades. + */ + if ( + ! get_option( 'mailer', false ) && + get_option( 'wp_mail_smtp_initial_version', false ) && + version_compare( WPMS_PLUGIN_VER, get_option( 'wp_mail_smtp_initial_version' ), '=' ) + ) { + return true; + } + + return false; + } + + /** + * Detect if there are plugins activated that will cause a conflict. + * + * @since 1.3.0 + * @since 1.5.0 Moved the logic to Conflicts class. + */ + public function detect_conflicts() { + + // Display only for those who can actually deactivate plugins. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + return; + } + + $conflicts = new Conflicts(); + + if ( $conflicts->is_detected() ) { + $conflicts->notify(); + } + } + + /** + * Init the \PHPMailer replacement. + * + * @since 1.0.0 + * + * @return MailCatcherInterface + */ + public function replace_phpmailer() { + + global $phpmailer; + + return $this->replace_w_fake_phpmailer( $phpmailer ); + } + + /** + * Overwrite default PhpMailer with our MailCatcher. + * + * @since 1.0.0 + * @since 1.5.0 Throw external PhpMailer exceptions, inherits default WP behavior. + * + * @param null $obj PhpMailer object to override with own implementation. + * + * @return MailCatcherInterface + */ + protected function replace_w_fake_phpmailer( &$obj = null ) { + + $obj = $this->generate_mail_catcher( true ); + + return $obj; + } + + /** + * What to do on plugin activation. + * + * @since 1.0.0 + * @since 2.0.0 Changed from general `plugin_activate` hook to this plugin specific activation hook. + */ + public function activate() { + + // Store the plugin version when initial install occurred. + add_option( 'wp_mail_smtp_initial_version', WPMS_PLUGIN_VER, '', false ); + + // Store the plugin version activated to reference with upgrades. + update_option( 'wp_mail_smtp_version', WPMS_PLUGIN_VER, false ); + + // Save default options, only once. + Options::init()->set( Options::get_defaults(), true ); + + /** + * Store the timestamp of first plugin activation. + * + * @since 2.1.0 + */ + add_option( 'wp_mail_smtp_activated_time', time(), '', false ); + + /** + * Store the timestamp of the first plugin activation by license type. + * + * @since 2.3.0 + */ + $license_type = is_readable( $this->plugin_path . '/src/Pro/Pro.php' ) ? 'pro' : 'lite'; + $activated = get_option( 'wp_mail_smtp_activated', [] ); + + if ( empty( $activated[ $license_type ] ) ) { + $activated[ $license_type ] = time(); + update_option( 'wp_mail_smtp_activated', $activated ); + } + + set_transient( 'wp_mail_smtp_just_activated', true, 60 ); + + // Add transient to trigger redirect to the Setup Wizard. + set_transient( 'wp_mail_smtp_activation_redirect', true, 30 ); + } + + /** + * Whether this is a Pro version of a plugin. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_pro() { + + return apply_filters( 'wp_mail_smtp_core_is_pro', ! empty( $this->pro ) ); + } + + /** + * Get the current license type. + * + * @since 1.5.0 + * + * @return string Default value: lite. + */ + public function get_license_type() { + + $type = Options::init()->get( 'license', 'type' ); + + if ( empty( $type ) ) { + $type = 'lite'; + } + + return strtolower( $type ); + } + + /** + * Get the current license key. + * + * @since 1.5.0 + * + * @return string + */ + public function get_license_key() { + + $key = Options::init()->get( 'license', 'key' ); + + if ( empty( $key ) ) { + $key = ''; + } + + return $key; + } + + /** + * Upgrade link used within the various admin pages. + * + * @since 1.5.0 + * @since 1.5.1 Support all UTM params. + * + * @param array|string $utm Array of UTM params, or if string provided - utm_content URL parameter. + * + * @return string + */ + public function get_upgrade_link( $utm ) { + + $url = $this->get_utm_url( 'https://wpmailsmtp.com/lite-upgrade/', $utm ); + + /** + * Filters upgrade link. + * + * @since 1.5.0 + * + * @param string $url Upgrade link. + */ + return apply_filters( 'wp_mail_smtp_core_get_upgrade_link', $url ); + } + + /** + * Get UTM URL. + * + * @since 3.4.0 + * + * @param string $url Base url. + * @param array|string $utm Array of UTM params, or if string provided - utm_content URL parameter. + * + * @return string + */ + public function get_utm_url( $url, $utm ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Defaults. + $source = 'WordPress'; + $medium = 'plugin-settings'; + $campaign = $this->is_pro() ? 'plugin' : 'liteplugin'; + $content = 'general'; + $locale = get_user_locale(); + + if ( is_array( $utm ) ) { + if ( isset( $utm['source'] ) ) { + $source = $utm['source']; + } + if ( isset( $utm['medium'] ) ) { + $medium = $utm['medium']; + } + if ( isset( $utm['campaign'] ) ) { + $campaign = $utm['campaign']; + } + if ( isset( $utm['content'] ) ) { + $content = $utm['content']; + } + if ( isset( $utm['locale'] ) ) { + $locale = $utm['locale']; + } + } elseif ( is_string( $utm ) ) { + $content = $utm; + } + + $query_args = [ + 'utm_source' => esc_attr( rawurlencode( $source ) ), + 'utm_medium' => esc_attr( rawurlencode( $medium ) ), + 'utm_campaign' => esc_attr( rawurlencode( $campaign ) ), + 'utm_locale' => esc_attr( sanitize_key( $locale ) ), + ]; + + if ( ! empty( $content ) ) { + $query_args['utm_content'] = esc_attr( rawurlencode( $content ) ); + } + + return add_query_arg( $query_args, $url ); + } + + /** + * Whether the emailing functionality is blocked, with either an option or a constatnt. + * + * @since 1.7.0 + * + * @return bool + */ + public function is_blocked() { + + return (bool) Options::init()->get( 'general', 'do_not_send' ); + } + + /** + * Whether the white-labeling is enabled. + * White-labeling disables the plugin "About us" page, it replaces any plugin marketing texts or images with + * white label ones. + * + * @since 2.0.0 + * + * @return bool + */ + public function is_white_labeled() { + + return (bool) apply_filters( 'wp_mail_smtp_is_white_labeled', false ); + } + + /** + * Require the action scheduler in an early plugins_loaded hook (-10). + * + * @see https://actionscheduler.org/usage/#load-order + * + * @since 2.1.0 + */ + public function load_action_scheduler() { + + require_once $this->plugin_path . '/vendor/woocommerce/action-scheduler/action-scheduler.php'; + } + + /** + * Get the list of all custom DB tables that should be present in the DB. + * + * @since 2.1.2 + * + * @return array List of table names. + */ + public function get_custom_db_tables() { + + $tables = [ + Meta::get_table_name(), + DebugEvents::get_table_name(), + ]; + + if ( $this->get_queue()->is_enabled() ) { + $tables[] = Queue::get_table_name(); + } + + return apply_filters( 'wp_mail_smtp_core_get_custom_db_tables', $tables ); + } + + /** + * Generate the correct MailCatcher object based on the PHPMailer version used in WP. + * + * Also conditionally require the needed class files. + * + * @see https://make.wordpress.org/core/2020/07/01/external-library-updates-in-wordpress-5-5-call-for-testing/ + * + * @since 2.2.0 + * + * @param bool $exceptions True if external exceptions should be thrown. + * + * @return MailCatcherInterface + */ + public function generate_mail_catcher( $exceptions = null ) { + + $is_old_version = version_compare( get_bloginfo( 'version' ), '5.5-alpha', '<' ); + + if ( $is_old_version ) { + if ( ! class_exists( '\PHPMailer', false ) ) { + require_once ABSPATH . WPINC . '/class-phpmailer.php'; + } + + $class_name = MailCatcher::class; + } else { + if ( ! class_exists( '\PHPMailer\PHPMailer\PHPMailer', false ) ) { + require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php'; + } + + if ( ! class_exists( '\PHPMailer\PHPMailer\Exception', false ) ) { + require_once ABSPATH . WPINC . '/PHPMailer/Exception.php'; + } + + if ( ! class_exists( '\PHPMailer\PHPMailer\SMTP', false ) ) { + require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php'; + } + + $class_name = MailCatcherV6::class; + } + + /** + * Filters MailCatcher class name. + * + * @since 3.7.0 + * + * @param string $mail_catcher The MailCatcher class name. + */ + $class_name = apply_filters( 'wp_mail_smtp_core_generate_mail_catcher', $class_name ); + + $mail_catcher = new $class_name( $exceptions ); + + if ( $is_old_version ) { + $mail_catcher::$validator = static function ( $email ) { + return (bool) is_email( $email ); + }; + } + + return $mail_catcher; + } + + /** + * Check if the passed object is a valid PHPMailer object. + * + * @since 2.2.0 + * + * @param object $phpmailer A potential PHPMailer object to be tested. + * + * @return bool + */ + public function is_valid_phpmailer( $phpmailer ) { + + return $phpmailer instanceof MailCatcherInterface || + $phpmailer instanceof \PHPMailer || + $phpmailer instanceof \PHPMailer\PHPMailer\PHPMailer; + } + + /** + * Force the `mail.from_email_force` plugin option to always return true if the current saved mailer is Gmail. + * Alters the plugin options retrieving via the Options::get method. + * + * The gmail mailer check is performed when this filter is added. + * + * @deprecated 2.7.0 + * + * @since 2.2.0 + * + * @param mixed $value The value of the plugin option that is being retrieved via Options::get method. + * @param string $group The group of the plugin option that is being retrieved via Options::get method. + * @param string $key The key of the plugin option that is being retrieved via Options::get method. + * + * @return mixed + */ + public function gmail_mailer_get_from_email_force( $value, $group, $key ) { + + _deprecated_function( __METHOD__, '2.7.0' ); + + if ( $group === 'mail' && $key === 'from_email_force' ) { + $value = true; + } + + return $value; + } + + /** + * Load the plugin admin bar menu and initialize it. + * + * @since 2.3.0 + * + * @return AdminBarMenu + */ + public function get_admin_bar_menu() { + + static $admin_bar_menu; + + if ( ! isset( $admin_bar_menu ) ) { + $admin_bar_menu = apply_filters( + 'wp_mail_smtp_core_get_admin_bar_menu', + new AdminBarMenu() + ); + + if ( method_exists( $admin_bar_menu, 'init' ) ) { + $admin_bar_menu->init(); + } + } + + return $admin_bar_menu; + } + + /** + * Load the plugin usage tracking. + * + * @since 2.3.0 + * + * @return UsageTracking + */ + public function get_usage_tracking() { + + static $usage_tracking; + + if ( ! isset( $usage_tracking ) ) { + $usage_tracking = apply_filters( 'wp_mail_smtp_core_get_usage_tracking', new UsageTracking() ); + + if ( method_exists( $usage_tracking, 'load' ) ) { + add_action( 'after_setup_theme', [ $usage_tracking, 'load' ] ); + } + } + + return $usage_tracking; + } + + /** + * Load the plugin admin notifications functionality and initializes it. + * + * @since 2.3.0 + * + * @return Notifications + */ + public function get_notifications() { + + static $notifications; + + if ( ! isset( $notifications ) ) { + $notifications = apply_filters( + 'wp_mail_smtp_core_get_notifications', + new Notifications() + ); + + if ( method_exists( $notifications, 'init' ) ) { + $notifications->init(); + } + } + + return $notifications; + } + + /** + * Prepare the HTML output for a plugin loader/spinner. + * + * @since 2.4.0 + * + * @param string $color The color of the loader ('', 'blue' or 'white'), where '' is default orange. + * @param string $size The size of the loader ('lg', 'md', 'sm'). + * + * @return string + */ + public function prepare_loader( $color = '', $size = 'md' ) { + + $svg_name = 'loading'; + + if ( in_array( $color, [ 'blue', 'white' ], true ) ) { + $svg_name .= '-' . $color; + } + + if ( ! in_array( $size, [ 'lg', 'md', 'sm' ], true ) ) { + $size = 'md'; + } + + return '' . esc_attr__( 'Loading', 'wp-mail-smtp' ) . ''; + } + + /** + * Initialize the Connect functionality. + * This has to execute after pro was loaded, since we need check for plugin license type (if pro or not). + * That's why it's hooked to the same WP hook (`plugins_loaded`) as `get_pro` with lower priority. + * + * @since 2.6.0 + */ + public function get_connect() { + + static $connect; + + if ( ! isset( $connect ) && ! $this->is_pro() ) { + $connect = apply_filters( 'wp_mail_smtp_core_get_connect', new Connect() ); + + if ( method_exists( $connect, 'hooks' ) ) { + $connect->hooks(); + } + } + + return $connect; + } + + /** + * Load the plugin compatibility functionality and initializes it. + * + * @since 2.8.0 + * + * @return Compatibility + */ + public function get_compatibility() { + + static $compatibility; + + if ( ! isset( $compatibility ) ) { + + /** + * Filters compatibility instance. + * + * @since 2.8.0 + * + * @param \WPMailSMTP\Compatibility\Compatibility $compatibility Compatibility instance. + */ + $compatibility = apply_filters( 'wp_mail_smtp_core_get_compatibility', new Compatibility() ); + + if ( method_exists( $compatibility, 'init' ) ) { + $compatibility->init(); + } + } + + return $compatibility; + } + + /** + * Get the Dashboard Widget object (lite or pro version). + * + * @since 2.9.0 + * + * @return DashboardWidget + */ + public function get_dashboard_widget() { + + static $dashboard_widget; + + if ( ! isset( $dashboard_widget ) ) { + + /** + * Filter the dashboard widget class name. + * + * @since 2.9.0 + * + * @param DashboardWidget $class_name The dashboard widget class name to be instantiated. + */ + $class_name = apply_filters( 'wp_mail_smtp_core_get_dashboard_widget', DashboardWidget::class ); + $dashboard_widget = new $class_name(); + + if ( method_exists( $dashboard_widget, 'init' ) ) { + $dashboard_widget->init(); + } + } + + return $dashboard_widget; + } + + /** + * Get the reports object (lite or pro version). + * + * @since 3.0.0 + * + * @return Reports + */ + public function get_reports() { + + static $reports; + + if ( ! isset( $reports ) ) { + + /** + * Filter the reports class name. + * + * @since 3.0.0 + * + * @param Reports $class_name The reports class name to be instantiated. + */ + $class_name = apply_filters( 'wp_mail_smtp_core_get_reports', Reports::class ); + $reports = new $class_name(); + + if ( method_exists( $reports, 'init' ) ) { + $reports->init(); + } + } + + return $reports; + } + + /** + * Get the DBRepair object (lite or pro version). + * + * @since 3.6.0 + * + * @return DBRepair + */ + public function get_db_repair() { + + static $db_repair; + + if ( ! isset( $db_repair ) ) { + + /** + * Filter the DBRepair class name. + * + * @since 3.6.0 + * + * @param DBRepair $class_name The reports class name to be instantiated. + */ + $class_name = apply_filters( 'wp_mail_smtp_core_get_db_repair', DBRepair::class ); + $db_repair = new $class_name(); + + if ( method_exists( $db_repair, 'hooks' ) ) { + $db_repair->hooks(); + } + } + + return $db_repair; + } + + /** + * Get connections manager. + * + * @since 3.7.0 + * + * @return ConnectionsManager + */ + public function get_connections_manager() { + + static $connections_manager = null; + + if ( is_null( $connections_manager ) ) { + + /** + * Filter the connections manager class name. + * + * @since 3.7.0 + * + * @param ConnectionsManager $connections_manager The connections manager class name to be instantiated. + */ + $class_name = apply_filters( 'wp_mail_smtp_core_get_connections_manager', ConnectionsManager::class ); + $connections_manager = new $class_name(); + + if ( method_exists( $connections_manager, 'hooks' ) ) { + $connections_manager->hooks(); + } + } + + return $connections_manager; + } + + /** + * Get the `wp_mail` function initiator. + * + * @since 3.7.0 + * + * @return WPMailInitiator + */ + public function get_wp_mail_initiator() { + + static $wp_mail_initiator = null; + + if ( is_null( $wp_mail_initiator ) ) { + + /** + * Filter the `wp_mail` function initiator class name. + * + * @since 3.7.0 + * + * @param WPMailInitiator $wp_mail_initiator The `wp_mail` function initiator class name to be instantiated. + */ + $class_name = apply_filters( 'wp_mail_smtp_core_get_wp_mail_initiator', WPMailInitiator::class ); + $wp_mail_initiator = new $class_name(); + + if ( method_exists( $wp_mail_initiator, 'hooks' ) ) { + $wp_mail_initiator->hooks(); + } + } + + return $wp_mail_initiator; + } + + /** + * Detect incorrect `wp_mail` function location and display warning. + * + * @since 3.5.0 + */ + private function wp_mail_function_incorrect_location_notice() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + /** + * Filters whether to display incorrect `wp_mail` function location warning. + * + * @since 3.5.0 + * + * @param bool $display Whether to display incorrect `wp_mail` function location warning. + */ + $display_notice = apply_filters( 'wp_mail_smtp_core_wp_mail_function_incorrect_location_notice', true ); + + if ( ! $display_notice || ! defined( 'ABSPATH' ) || ! defined( 'WPINC' ) ) { + return; + } + + try { + $wp_mail_reflection = new ReflectionFunction( 'wp_mail' ); + $wp_mail_filepath = $wp_mail_reflection->getFileName(); + $separator = defined( 'DIRECTORY_SEPARATOR' ) ? DIRECTORY_SEPARATOR : '/'; + + $wp_mail_original_filepath = ABSPATH . WPINC . $separator . 'pluggable.php'; + + if ( str_replace( '\\', '/', $wp_mail_filepath ) === str_replace( '\\', '/', $wp_mail_original_filepath ) ) { + return; + } + + if ( strpos( $wp_mail_filepath, WPINC . $separator . 'pluggable.php' ) !== false ) { + return; + } + + $conflict = WP::get_initiator( $wp_mail_filepath ); + + $message = esc_html__( 'WP Mail SMTP has detected incorrect "wp_mail" function location. Usually, this means that emails will not be sent successfully!', 'wp-mail-smtp' ); + + if ( $conflict['type'] === 'plugin' ) { + $message .= '

    ' . sprintf( + /* translators: %s - plugin name. */ + esc_html__( 'It looks like the "%s" plugin is overwriting the "wp_mail" function. Please reach out to the plugin developer on how to disable or remove the "wp_mail" function overwrite to prevent conflicts with WP Mail SMTP.', 'wp-mail-smtp' ), + esc_html( $conflict['name'] ) + ); + } elseif ( $conflict['type'] === 'mu-plugin' ) { + $message .= '

    ' . sprintf( + /* translators: %s - must-use plugin name. */ + esc_html__( 'It looks like the "%s" must-use plugin is overwriting the "wp_mail" function. Please reach out to your hosting provider on how to disable or remove the "wp_mail" function overwrite to prevent conflicts with WP Mail SMTP.', 'wp-mail-smtp' ), + esc_html( $conflict['name'] ) + ); + } elseif ( $wp_mail_filepath === ABSPATH . 'wp-config.php' ) { + $message .= '

    ' . esc_html__( 'It looks like it\'s overwritten in the "wp-config.php" file. Please reach out to your hosting provider on how to disable or remove the "wp_mail" function overwrite to prevent conflicts with WP Mail SMTP.', 'wp-mail-smtp' ); + } + + $message .= '

    ' . sprintf( + /* translators: %s - path. */ + esc_html__( 'Current function path: %s', 'wp-mail-smtp' ), + $wp_mail_filepath . ':' . $wp_mail_reflection->getStartLine() + ); + + printf( + '

    %2$s

    ', + esc_attr( WP::ADMIN_NOTICE_ERROR ), + wp_kses( $message, [ 'br' => [] ] ) + ); + } catch ( Exception $e ) { + return; + } + } + + /** + * Get the default capability to manage everything for WP Mail SMTP. + * + * @since 3.11.0 + * + * @return string + */ + public function get_capability_manage_options() { + + /** + * Filters the default capability to manage everything for WP Mail SMTP. + * + * @since 3.11.0 + * + * @param string $capability The default capability to manage everything for WP Mail SMTP. + */ + return apply_filters( 'wp_mail_smtp_core_get_capability_manage_options', 'manage_options' ); + } + + /** + * Capability required to manage WP Mail SMTP actions that operate on global plugin state. + * + * Returns `manage_network_options` on multisite when WP Mail SMTP's network-wide settings + * mode is on, and `manage_options` otherwise. Use this getter (rather than + * {@see Core::get_capability_manage_options()}) when the gated action reads or writes + * state that lives on the main site / network scope in network-wide mode (the + * `wp_mail_smtp` and `wp_mail_smtp_connections` option rows, license fields, OAuth + * credentials, alerts subscriptions). Naming mirrors {@see WP::use_global_plugin_settings()}. + * + * @since 4.9.0 + * + * @return string + */ + public function get_capability_manage_global_options() { + + $default = ( is_multisite() && WP::use_global_plugin_settings() ) + ? 'manage_network_options' + : 'manage_options'; + + /** + * Filters the capability required to manage global WP Mail SMTP actions. + * + * @since 4.9.0 + * + * @param string $capability The default capability for global actions. Defaults to + * `manage_network_options` on multisite when network-wide + * settings are on, and `manage_options` otherwise. + */ + return apply_filters( 'wp_mail_smtp_core_get_capability_manage_global_options', $default ); + } + + /** + * Load the queue functionality. + * + * @since 4.0.0 + * + * @return Queue + */ + public function get_queue() { + + static $queue; + + if ( ! isset( $queue ) ) { + /** + * Filter the Queue object. + * + * @since 4.0.0 + * + * @param Queue $queue The Queue object. + */ + $queue = apply_filters( 'wp_mail_smtp_core_get_queue', new Queue() ); + } + + return $queue; + } + +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/DBRepair.php b/web/app/plugins/wp-mail-smtp-pro/src/DBRepair.php new file mode 100644 index 00000000..34da4af6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/DBRepair.php @@ -0,0 +1,240 @@ +get_admin()->is_admin_page() && + current_user_can( wp_mail_smtp()->get_capability_manage_options() ) + ) { + check_admin_referer( Area::SLUG . '-create-missing-db-tables' ); + + $missing_tables = $this->get_missing_tables(); + + if ( ! empty( $missing_tables ) ) { + foreach ( $missing_tables as $missing_table ) { + $this->fix_missing_db_table( $missing_table ); + } + + $redirect_page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : Area::SLUG; + $redirect_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : ''; + $query_args = [ + 'check-db-tables' => 1, + 'nonce' => wp_create_nonce( 'wp-mail-smtp-check-db-tables' ), + ]; + + if ( ! empty( $redirect_tab ) ) { + $query_args['tab'] = $redirect_tab; + } + + $redirect_url = add_query_arg( + $query_args, + wp_mail_smtp()->get_admin()->get_admin_page_url( $redirect_page ) + ); + + wp_safe_redirect( $redirect_url ); + exit; + } + } + } + + /** + * Update the Migration option to fix the missing table. + * + * @since 3.6.0 + * + * @param string $missing_table The name of the table. + */ + protected function fix_missing_db_table( $missing_table ) { + + if ( $missing_table === DebugEvents::get_table_name() ) { + update_option( DebugMigration::OPTION_NAME, 0 ); + } elseif ( $missing_table === Meta::get_table_name() ) { + update_option( Migration::OPTION_NAME, 1 ); + } elseif ( $missing_table === Queue::get_table_name() ) { + update_option( QueueMigration::OPTION_NAME, 0 ); + } + } + + /** + * Default Unknown error message - If the table is not created. + * + * @since 3.6.0 + * + * @return string + */ + protected function get_missing_table_default_error_message() { + + $unknown_reason_msg = esc_html__( 'Unknown.', 'wp-mail-smtp' ); + + /** + * Filter the default error message for unknown reason. + * + * @since 3.6.0 + * + * @param string $unknown_reason_msg The default unknown reason message. + */ + return apply_filters( 'wp_mail_smtp_db_repair_get_missing_table_default_error_message', $unknown_reason_msg ); + } + + /** + * Get the error message (Reason) if the table is missing. + * + * @since 3.6.0 + * + * @param string $missing_table The table name that we are checking. + * @param array $reasons The array that holds all the error messages or reason. + */ + protected function get_error_message_for_missing_table( $missing_table, &$reasons ) { + + $reason = ''; + + if ( $missing_table === DebugEvents::get_table_name() ) { + $reason .= $this->get_reason_output_message( + $missing_table, + get_option( DebugMigration::ERROR_OPTION_NAME, $this->get_missing_table_default_error_message() ) + ); + } elseif ( $missing_table === Meta::get_table_name() ) { + $reason .= $this->get_reason_output_message( + $missing_table, + get_option( Migration::ERROR_OPTION_NAME, $this->get_missing_table_default_error_message() ) + ); + } elseif ( $missing_table === Queue::get_table_name() ) { + $reason .= $this->get_reason_output_message( + $missing_table, + get_option( QueueMigration::ERROR_OPTION_NAME, $this->get_missing_table_default_error_message() ) + ); + } + + $reasons[] = $reason; + } + + /** + * Get the reason output message, why the DB table creation failed. + * + * @since 3.6.0 + * + * @param string $table The DB table name. + * @param string $error_message The error message. + * + * @return string + */ + protected function get_reason_output_message( $table, $error_message ) { + + return sprintf( + wp_kses( /* translators: %1$s - missing table name; %2$s - error message. */ + __( 'Table: %1$s. Reason: %2$s', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ), + esc_html( $table ), + esc_html( $error_message ) + ); + } + + /** + * Verify the tables. + * If there is any missing table then display the Admin Notice of error type. + * Else display the success message (Success Admin Notice). + * + * @since 3.6.0 + */ + public function verify_db_tables_after_fixing() { + + // Display success or error message based on if there is any missing table available or not. + if ( + isset( $_GET['check-db-tables'] ) && $_GET['check-db-tables'] === '1' && + wp_mail_smtp()->get_admin()->is_admin_page() && + current_user_can( wp_mail_smtp()->get_capability_manage_options() ) + ) { + check_admin_referer( 'wp-mail-smtp-check-db-tables', 'nonce' ); + + $missing_tables = $this->get_missing_tables(); + + if ( empty( $missing_tables ) ) { + WP::add_admin_notice( + esc_html__( 'Missing DB tables were created successfully.', 'wp-mail-smtp' ), + WP::ADMIN_NOTICE_SUCCESS + ); + + return; + } + + $reasons = []; + + foreach ( $missing_tables as $missing_table ) { + $this->get_error_message_for_missing_table( $missing_table, $reasons ); + } + + $reasons = array_filter( $reasons ); // Filtering out the empty values. + + if ( ! empty( $reasons ) ) { + $msg = sprintf( + wp_kses( + _n( 'The following DB table is still missing.', 'The following DB tables are still missing.', count( $missing_tables ), 'wp-mail-smtp' ) . '
    %s', + [ + 'br' => [], + ] + ), + implode( '
    ', $reasons ) + ); + } else { + $msg = esc_html__( 'Some DB Tables are still missing.', 'wp-mail-smtp' ); + } + + WP::add_admin_notice( + $msg, + WP::ADMIN_NOTICE_ERROR + ); + } + } + + /** + * Get the missing tables. + * + * @since 3.6.0 + * + * @return array The array of the missing tables. + */ + protected function get_missing_tables() { + + $site_health = new SiteHealth(); + + return $site_health->get_missing_db_tables(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Debug.php b/web/app/plugins/wp-mail-smtp-pro/src/Debug.php new file mode 100644 index 00000000..8fe6f2b2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Debug.php @@ -0,0 +1,207 @@ +` + EOL + ``, or an empty string when + * the record does not exist or has no error_message. + * + * @since 4.9.0 + * + * @param string $connection_id Connection id. + * + * @return string + */ + public static function get_message( $connection_id ) { + + return self::format_record( self::get( $connection_id ) ); + } + + /** + * Return formatted failure messages for every connection with a non-empty + * `error_message`. Each entry is formatted as `Mailer: ` + EOL + + * `<error_message>`. + * + * @since 4.9.0 + * + * @return string[] + */ + public static function get_messages() { + + $messages = []; + + foreach ( self::get() as $record ) { + $formatted = self::format_record( $record ); + + if ( $formatted !== '' ) { + $messages[] = $formatted; + } + } + + return $messages; + } + + /** + * Build the `Mailer: <title>` + EOL + `<error_message>` string for a single + * failure record. Returns an empty string when the record is empty or has + * no `error_message`. + * + * @since 4.9.0 + * + * @param array $record Failure-record payload. + * + * @return string + */ + private static function format_record( $record ) { + + if ( empty( $record['error_message'] ) ) { + return ''; + } + + $options = ! empty( $record['mailer'] ) + ? wp_mail_smtp()->get_providers()->get_options( $record['mailer'] ) + : null; + + $mailer_title = ! empty( $options ) + ? $options->get_title() + : esc_html__( 'Unknown', 'wp-mail-smtp' ); + + return 'Mailer: ' . $mailer_title . WP::EOL . (string) $record['error_message']; + } + + /** + * Read failure records. Returns the full map when `$connection_id` is null, + * otherwise that connection's record (or an empty array when none stored). + * + * @since 4.9.0 + * + * @param string|null $connection_id Optional connection id. + * + * @return array + */ + public static function get( $connection_id = null ) { + + $all = self::get_raw(); + + if ( $connection_id === null ) { + return $all; + } + + return isset( $all[ $connection_id ] ) ? $all[ $connection_id ] : []; + } + + /** + * Raw read with in-memory caching. + * + * @since 4.9.0 + * + * @return array + */ + private static function get_raw() { + + if ( self::$cached !== null ) { + return self::$cached; + } + + $all = get_option( self::OPTION_KEY, [] ); + + if ( ! is_array( $all ) ) { + $all = []; + } + + self::$cached = $all; + + return $all; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Geo.php b/web/app/plugins/wp-mail-smtp-pro/src/Geo.php new file mode 100644 index 00000000..86e51edf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Geo.php @@ -0,0 +1,225 @@ +<?php + +namespace WPMailSMTP; + +/** + * Class Geo to work with location, domain, IPs etc. + * + * @since 1.5.0 + */ +class Geo { + + /** + * Get the current site hostname. + * In case of CLI we don't have SERVER_NAME, so use host name instead, may be not a domain name. + * Examples: example.com, localhost. + * + * @since 1.5.0 + * + * @return string + */ + public static function get_site_domain() { + + return ! empty( $_SERVER['SERVER_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) : wp_parse_url( get_home_url( get_current_blog_id() ), PHP_URL_HOST ); + } + + /** + * Get the domain IP address. + * Uses gethostbyname() which is quite slow, but this is done only one time. + * + * @since 1.5.0 + * + * @param string $domain + * + * @return string + */ + public static function get_ip_by_domain( $domain ) { + + if ( $domain === 'localhost' ) { + return '127.0.0.1'; + } + + return gethostbyname( $domain ); + } + + /** + * Get the location coordinates by IP address. + * We make a request to 3rd party services. + * + * @since 1.5.0 + * @since 1.6.0 Added new geo API endpoint, provided by WPForms. + * @since 2.0.0 Updated the WPForms geo API endpoint to v3. + * + * @param string $ip The IP address. + * + * @return array Empty array for localhost. + */ + public static function get_location_by_ip( $ip ) { + + // Check for a non-local IP. + if ( empty( $ip ) || in_array( $ip, [ '127.0.0.1', '::1' ], true ) ) { + return []; + } + + $request = wp_remote_get( 'https://geo.wpforms.com/v3/geolocate/json/' . $ip ); + + if ( ! is_wp_error( $request ) ) { + $request = json_decode( wp_remote_retrieve_body( $request ), true ); + if ( ! empty( $request['latitude'] ) && ! empty( $request['longitude'] ) ) { + $data = [ + 'latitude' => sanitize_text_field( $request['latitude'] ), + 'longitude' => sanitize_text_field( $request['longitude'] ), + 'city' => isset( $request['city'] ) ? sanitize_text_field( $request['city'] ) : '', + 'region' => isset( $request['region_name'] ) ? sanitize_text_field( $request['region_name'] ) : '', + 'country' => isset( $request['country_iso'] ) ? sanitize_text_field( $request['country_iso'] ) : '', + 'postal' => isset( $request['zip_code'] ) ? sanitize_text_field( $request['zip_code'] ) : '', + ]; + + return $data; + } + } + + $request = wp_remote_get( 'https://ipapi.co/' . $ip . '/json' ); + + if ( ! is_wp_error( $request ) ) { + + $request = json_decode( wp_remote_retrieve_body( $request ), true ); + + if ( ! empty( $request['latitude'] ) && ! empty( $request['longitude'] ) ) { + + $data = [ + 'latitude' => sanitize_text_field( $request['latitude'] ), + 'longitude' => sanitize_text_field( $request['longitude'] ), + 'city' => isset( $request['city'] ) ? sanitize_text_field( $request['city'] ) : '', + 'region' => isset( $request['region'] ) ? sanitize_text_field( $request['region'] ) : '', + 'country' => isset( $request['country'] ) ? sanitize_text_field( $request['country'] ) : '', + 'postal' => isset( $request['postal'] ) ? sanitize_text_field( $request['postal'] ) : '', + ]; + + return $data; + } + } + + $request = wp_remote_get( + 'https://tools.keycdn.com/geo.json?host=' . $ip, + [ + 'user-agent' => 'keycdn-tools:' . get_home_url(), + ] + ); + + if ( ! is_wp_error( $request ) ) { + + $request = json_decode( wp_remote_retrieve_body( $request ), true ); + + if ( ! empty( $request['data']['geo']['latitude'] ) && ! empty( $request['data']['geo']['longitude'] ) ) { + + $data = [ + 'latitude' => sanitize_text_field( $request['data']['geo']['latitude'] ), + 'longitude' => sanitize_text_field( $request['data']['geo']['longitude'] ), + 'city' => isset( $request['data']['geo']['city'] ) ? sanitize_text_field( $request['data']['geo']['city'] ) : '', + 'region' => isset( $request['data']['geo']['region_name'] ) ? sanitize_text_field( $request['data']['geo']['region_name'] ) : '', + 'country' => isset( $request['data']['geo']['country_code'] ) ? sanitize_text_field( $request['data']['geo']['country_code'] ) : '', + 'postal' => isset( $request['data']['geo']['postal_code'] ) ? sanitize_text_field( $request['data']['geo']['postal_code'] ) : '', + ]; + + return $data; + } + } + + return []; + } + + /** + * This routine calculates the distance between two points (given the latitude/longitude of those points). + * Definitions: South latitudes are negative, east longitudes are positive. + * + * @see https://www.geodatasource.com/developers/php + * + * @since 1.5.0 + * + * @param float $lat1 Latitude of point 1 (in decimal degrees). + * @param float $lon1 Longitude of point 1 (in decimal degrees). + * @param float $lat2 Latitude of point 2 (in decimal degrees). + * @param float $lon2 Longitude of point 2 (in decimal degrees). + * @param string $unit Supported values: M, K, N. Miles by default. + * + * @return float|int + */ + public static function get_distance_between( $lat1, $lon1, $lat2, $lon2, $unit = 'M' ) { + + if ( ( $lat1 === $lat2 ) && ( $lon1 === $lon2 ) ) { + return 0; + } + + $theta = $lon1 - $lon2; + $dist = sin( deg2rad( $lat1 ) ) * sin( deg2rad( $lat2 ) ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * cos( deg2rad( $theta ) ); + $dist = acos( $dist ); + $dist = rad2deg( $dist ); + $miles = $dist * 60 * 1.1515; + $unit = strtoupper( $unit ); + + if ( $unit === 'K' ) { + return ( $miles * 1.609344 ); + } elseif ( $unit === 'N' ) { + return ( $miles * 0.8684 ); + } + + return $miles; + } + + /** + * Get the user IP address. + * + * @since 3.11.0 + * + * Code based on the: + * - WordPress method \WP_Community_Events::get_unsafe_client_ip + * - Cloudflare documentation https://support.cloudflare.com/hc/en-us/articles/206776727 + * + * @return string + */ + public static function get_ip() { + + $ip = '127.0.0.1'; + + $address_headers = [ + 'HTTP_TRUE_CLIENT_IP', + 'HTTP_CF_CONNECTING_IP', + 'HTTP_X_REAL_IP', + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED', + 'HTTP_X_CLUSTER_CLIENT_IP', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR', + ]; + + foreach ( $address_headers as $header ) { + if ( empty( $_SERVER[ $header ] ) ) { + continue; + } + + /* + * HTTP_X_FORWARDED_FOR can contain a chain of comma-separated addresses, with or without spaces. + * The first address is the original client. It can't be trusted for authenticity, + * but we don't need to for this purpose. + */ + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $address_chain = explode( ',', wp_unslash( $_SERVER[ $header ] ) ); + $ip = filter_var( trim( $address_chain[0] ), FILTER_VALIDATE_IP ); + + break; + } + + /** + * Filter detected IP address. + * + * @since 3.11.0 + * + * @param string $ip IP address. + */ + return filter_var( apply_filters( 'wp_mail_smtp_geo_get_ip', $ip ), FILTER_VALIDATE_IP ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Crypto.php b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Crypto.php new file mode 100644 index 00000000..4f617d80 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Crypto.php @@ -0,0 +1,163 @@ +<?php + +namespace WPMailSMTP\Helpers; + +/** + * Class for encryption functionality. + * + * @since 2.5.0 + * + * @link https://www.php.net/manual/en/intro.sodium.php + */ +class Crypto { + + /** + * Get a secret key for encrypt/decrypt. + * + * @since 2.5.0 + * + * @param bool $create Should the key be created, if it does not exist yet. + * + * @return string|bool + */ + public static function get_secret_key( $create = false ) { + + if ( defined( 'WPMS_CRYPTO_KEY' ) ) { + return WPMS_CRYPTO_KEY; + } + + $secret_key = apply_filters( 'wp_mail_smtp_helpers_crypto_get_secret_key', get_option( 'wp_mail_smtp_mail_key' ) ); + + // If we already have the secret, send it back. + if ( false !== $secret_key ) { + return base64_decode( $secret_key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + } + + if ( $create ) { + // We don't have a secret, so let's generate one. + try { + $secret_key = sodium_crypto_secretbox_keygen(); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_crypto_secretbox_keygenFound + } catch ( \Exception $e ) { + $secret_key = wp_generate_password( SODIUM_CRYPTO_SECRETBOX_KEYBYTES ); // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_crypto_secretbox_keybytesFound + } + + add_option( 'wp_mail_smtp_mail_key', base64_encode( $secret_key ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + + return $secret_key; + } + + return false; + } + + /** + * Encrypt a message. + * + * @since 2.5.0 + * + * @param string $message Message to encrypt. + * @param string $key Encryption key. + * + * @return string + * @throws \Exception The exception object. + */ + public static function encrypt( $message, $key = '' ) { + + if ( apply_filters( 'wp_mail_smtp_helpers_crypto_stop', false ) ) { + return $message; + } + + // Create a nonce for this operation. It will be stored and recovered in the message itself. + // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_bytesFound, PHPCompatibility.Constants.NewConstants.sodium_crypto_secretbox_noncebytesFound + $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); + + if ( empty( $key ) ) { + $key = self::get_secret_key( true ); + } + + // Encrypt message and combine with nonce. + $cipher = base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + $nonce . + sodium_crypto_secretbox( // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_crypto_secretboxFound + $message, + $nonce, + $key + ) + ); + + try { + sodium_memzero( $message ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_memzeroFound + sodium_memzero( $key ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_memzeroFound + } catch ( \Exception $e ) { + return $cipher; + } + + return $cipher; + } + + /** + * Decrypt a message. + * Returns encrypted message on any failure and the decrypted message on success. + * + * @since 2.5.0 + * + * @param string $encrypted Encrypted message. + * @param string $key Encryption key. + * + * @return string + * @throws \Exception The exception object. + */ + public static function decrypt( $encrypted, $key = '' ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( apply_filters( 'wp_mail_smtp_helpers_crypto_stop', false ) ) { + return $encrypted; + } + + // Unpack base64 message. + $decoded = base64_decode( $encrypted ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + + if ( false === $decoded ) { + return $encrypted; + } + + // Include polyfill if mbstring PHP extension is not enabled. + if ( ! function_exists( 'mb_strlen' ) || ! function_exists( 'mb_substr' ) ) { + Helpers::include_mbstring_polyfill(); + } + + // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_crypto_secretbox_noncebytesFound, PHPCompatibility.Constants.NewConstants.sodium_crypto_secretbox_macbytesFound + if ( mb_strlen( $decoded, '8bit' ) < ( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES ) ) { + return $encrypted; + } + + // Pull nonce and ciphertext out of unpacked message. + $nonce = mb_substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit' ); // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_crypto_secretbox_noncebytesFound + $ciphertext = mb_substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit' ); // phpcs:ignore PHPCompatibility.Constants.NewConstants.sodium_crypto_secretbox_noncebytesFound + + $key = empty( $key ) ? self::get_secret_key() : $key; + + if ( empty( $key ) ) { + return $encrypted; + } + + // Decrypt it. + $message = sodium_crypto_secretbox_open( // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_crypto_secretbox_openFound + $ciphertext, + $nonce, + $key + ); + + // Check for decryption failures. + if ( false === $message ) { + return $encrypted; + } + + try { + sodium_memzero( $ciphertext ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_memzeroFound + sodium_memzero( $key ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.sodium_memzeroFound + } catch ( \Exception $e ) { + return $message; + } + + return $message; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Helpers/DB.php b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/DB.php new file mode 100644 index 00000000..cfab7f00 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/DB.php @@ -0,0 +1,41 @@ +<?php + +namespace WPMailSMTP\Helpers; + +/** + * Class for Database functionality. + * + * @since 3.6.0 + */ +class DB { + + /** + * The function is used to check if the given index exists in the given table. + * + * @since 3.6.0 + * + * @param string $table The table name. + * @param string $index The index name. + * + * @return bool If index exists then return true else returns false. + */ + public static function index_exists( $table, $index ) { + + global $wpdb; + + $query = $wpdb->prepare( + 'SELECT COUNT(1) IndexIsThere + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_schema = DATABASE() + AND table_name = %s + AND index_name = %s', + $table, + $index + ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $result = $wpdb->get_var( $query ); + + return $result === '1'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Data.php b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Data.php new file mode 100644 index 00000000..30fec51c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Data.php @@ -0,0 +1,172 @@ +<?php + +namespace WPMailSMTP\Helpers; + +use ArrayAccess; + +/** + * Class Data. + * + * Helper class that allows to get/set value in the nested array by string key. + * + * @since 4.9.0 + */ +class Data { + + /** + * Keys string separator. + * + * @since 4.9.0 + * + * @var string + */ + const KEY_SEPARATOR = '.'; + + /** + * Get nested array value by string key. + * + * @since 4.9.0 + * + * @param array $array Input array. + * @param string $str_key String key. E.g. "level1.level2.level3". + * @param mixed $default The default value that should be returned if value by key not found. + * + * @return mixed + */ + public static function get( $array, $str_key, $default = null ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound, Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound + + if ( is_array( $array ) && array_key_exists( $str_key, $array ) ) { + return $array[ $str_key ]; + } + + $keys = self::get_keys_array( $str_key ); + + foreach ( $keys as $key ) { + if ( ! is_array( $array ) && ! $array instanceof ArrayAccess ) { + return $default; + } + + if ( + ( $array instanceof ArrayAccess && $array->offsetExists( $key ) ) || + array_key_exists( $key, $array ) + ) { + $array = $array[ $key ]; + } else { + return $default; + } + } + + return $array; + } + + /** + * Get keys array from keys string. + * + * @since 4.9.0 + * + * @param string $keys String key. E.g. "level1.level2.level3". + * + * @return array + */ + private static function get_keys_array( $keys ) { + + return explode( self::KEY_SEPARATOR, $keys ); + } + + /** + * Set value in the nested array by string key (by reference). + * + * @since 4.9.0 + * + * @param array &$array Input array (passed by reference). + * @param string $str_key String key. E.g. "level1.level2.level3". + * @param mixed $value Value that should be added to array. + * + * @return void + */ + public static function set( array &$array, $str_key, $value ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound + + // Fast path for non-nested key. + if ( strpos( $str_key, self::KEY_SEPARATOR ) === false ) { + $array[ $str_key ] = $value; + + return; + } + + $keys = self::get_keys_array( $str_key ); + $tmp = &$array; + + $keys_count = count( $keys ); + + while ( $keys_count > 0 ) { + $key = array_shift( $keys ); + + --$keys_count; + + if ( ! is_array( $tmp ) ) { + $tmp = []; + } + + $tmp = &$tmp[ $key ]; + } + + $tmp = $value; + } + + /** + * Find a value in an array. + * + * @since 4.9.0 + * + * @param array $array The array to search in. + * @param string $key The key to search for. + * @param mixed $value The value to search for. + * @param string|false $desired_key The key to return from the found item, or false to return the whole item. + * @param mixed $default The default value to return if not found. + * + * @return mixed + */ + public static function find( $array, $key, $value, $desired_key = false, $default = '' ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded, Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound, Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound + + foreach ( $array as $item ) { + if ( isset( $item[ $key ] ) && $item[ $key ] === $value ) { + if ( $desired_key !== false ) { + if ( isset( $item[ $desired_key ] ) ) { + return $item[ $desired_key ]; + } + break; + } + + return $item; + } + } + + return $default; + } + + /** + * Walk through array recursively. + * + * @since 4.9.0 + * + * @param array $array Array to walk through. + * @param callable $callback Callback function that will be executed for each scalar array item. + * @param string $parent_key Parent key. Used for recursive calls. + */ + public static function walk_recursive( &$array, $callback, $parent_key = '' ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.arrayFound + + foreach ( $array as $key => &$value ) { + if ( ! is_numeric( $key ) ) { + $current_key = $parent_key ? $parent_key . '.' . $key : $key; + } else { + $current_key = $parent_key; + } + + if ( is_array( $value ) ) { + self::walk_recursive( $value, $callback, $current_key ); + } else { + call_user_func_array( $callback, [ &$value, $current_key ] ); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Helpers.php b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Helpers.php new file mode 100644 index 00000000..69b5fd06 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/Helpers.php @@ -0,0 +1,218 @@ +<?php + +namespace WPMailSMTP\Helpers; + +use WPMailSMTP\Options; +use WPMailSMTP\WP; +use WP_Error; + +/** + * Class with all the misc helper functions that don't belong elsewhere. + * + * @since 3.0.0 + */ +class Helpers { + + /** + * Check if the current active mailer has email send confirmation functionality. + * + * @since 3.0.0 + * + * @return bool + */ + public static function mailer_without_send_confirmation() { + + return ! in_array( + Options::init()->get( 'mail', 'mailer' ), + [ + 'sendlayer', + 'smtpcom', + 'sendinblue', + 'mailgun', + 'postmark', + 'sparkpost', + 'elasticemail', + 'smtp2go', + 'mailjet', + 'mailersend', + 'mandrill', + 'resend', + ], + true + ); + } + + /** + * Include mbstring polyfill. + * + * @since 3.1.0 + */ + public static function include_mbstring_polyfill() { + + static $included = false; + + if ( $included === true ) { + return; + } + + require_once wp_mail_smtp()->plugin_path . '/vendor_prefixed/symfony/polyfill-mbstring/Mbstring.php'; + require_once wp_mail_smtp()->plugin_path . '/vendor_prefixed/symfony/polyfill-mbstring/bootstrap.php'; + + $included = true; + } + + /** + * Test if the REST API is accessible. + * + * @since 3.3.0 + * + * @return true|\WP_Error + */ + public static function test_rest_availability() { + + $headers = [ + 'Cache-Control' => 'no-cache', + ]; + + /** This filter is documented in wp-includes/class-wp-http-streams.php */ + $sslverify = apply_filters( 'https_local_ssl_verify', false ); + + $url = rest_url( 'wp-mail-smtp/v1' ); + + $response = wp_remote_get( + $url, + [ + 'headers' => $headers, + 'sslverify' => $sslverify, + ] + ); + + if ( is_wp_error( $response ) ) { + return $response; + } elseif ( wp_remote_retrieve_response_code( $response ) !== 200 ) { + return new WP_Error( wp_remote_retrieve_response_code( $response ), wp_remote_retrieve_body( $response ) ); + } + + return true; + } + + /** + * Get string size in bytes. + * + * @since 3.4.0 + * + * @param string $str String. + * + * @return int + */ + public static function strsize( $str ) { + + if ( ! function_exists( 'mb_strlen' ) ) { + self::include_mbstring_polyfill(); + } + + return mb_strlen( $str, '8bit' ); + } + + /** + * Format a variable for debug-style output as an inline <code> block. + * + * Bools and empty values are rendered via var_dump (which shows the type); + * everything else via print_r. Line breaks are stripped so the output fits + * on a single rendered line. + * + * @since 4.9.0 + * + * @param mixed $var Variable to format. + * + * @return string + */ + public static function pvar( $var = '' ) { + + ob_start(); + + echo '<code>'; + + if ( is_bool( $var ) || empty( $var ) ) { + var_dump( $var ); + } else { + print_r( $var ); + } + + echo '</code>'; + + $output = ob_get_clean(); + + return str_replace( [ "\r\n", "\r", "\n" ], '', $output ); + } + + /** + * Format error message. + * + * @since 3.4.0 + * + * @param string $message Error message. + * @param string $code Error code. + * @param string $description Error description. + * + * @return string + */ + public static function format_error_message( $message, $code = '', $description = '' ) { + + $error_text = ''; + + if ( ! empty( $code ) ) { + $error_text .= $code . ': '; + } + + if ( ! is_string( $message ) ) { + $error_text .= wp_json_encode( $message ); + } else { + $error_text .= $message; + } + + if ( ! empty( $description ) ) { + $error_text .= WP::EOL . $description; + } + + return $error_text; + } + + /** + * Get the default user agent. + * + * @since 3.9.0 + * + * @return string + */ + public static function get_default_user_agent() { + + $license_type = wp_mail_smtp()->get_license_type(); + + return 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) . '; WPMailSMTP/' . $license_type . '-' . WPMS_PLUGIN_VER; + } + + /** + * Import Plugin_Upgrader class from core. + * + * @since 3.11.0 + */ + public static function include_plugin_upgrader() { + + /** \WP_Upgrader class */ + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + + /** \Plugin_Upgrader class */ + require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php'; + } + + /** + * Whether the current request is a WP CLI request. + * + * @since 4.0.0 + */ + public static function is_wp_cli() { + + return defined( 'WP_CLI' ) && WP_CLI; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Helpers/PluginImportDataRetriever.php b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/PluginImportDataRetriever.php new file mode 100644 index 00000000..1561c935 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/PluginImportDataRetriever.php @@ -0,0 +1,323 @@ +<?php + +namespace WPMailSMTP\Helpers; + +/** + * Class for preparing import data from other SMTP plugins. + * + * @since 2.6.0 + */ +class PluginImportDataRetriever { + + /** + * The slug of the SMTP plugin to prepare the data for. + * + * @since 2.6.0 + * + * @var string + */ + private $slug; + + /** + * PluginImportDataRetriever constructor. + * + * @since 2.6.0 + * + * @param string $slug The SMTP plugin slug. + */ + public function __construct( $slug ) { + + $this->slug = $slug; + } + + /** + * Get the data for the current plugin slug. + * + * @since 2.6.0 + * + * @return false|array + */ + public function get() { + + $method_name = preg_replace( '/[\-]/', '_', sanitize_key( "get_$this->slug" ) ); + + if ( method_exists( $this, $method_name ) ) { + return $this->$method_name(); + } + + return false; + } + + /** + * Check if Easy WP SMTP plugin settings are present and extract them. + * + * @since 2.6.0 + * + * @return array + */ + private function get_easy_smtp() { + + $options = get_option( 'swpsmtp_options' ); + + if ( empty( $options ) ) { + return []; + } + + return [ + 'mail' => [ + 'mailer' => 'smtp', + 'from_email' => isset( $options['from_email_field'] ) ? $options['from_email_field'] : '', + 'from_name' => isset( $options['from_name_field'] ) ? $options['from_name_field'] : '', + 'from_name_force' => isset( $options['force_from_name_replace'] ) ? $options['force_from_name_replace'] : false, + ], + 'smtp' => [ + 'host' => isset( $options['smtp_settings']['host'] ) ? $options['smtp_settings']['host'] : '', + 'encryption' => isset( $options['smtp_settings']['type_encryption'] ) ? $options['smtp_settings']['type_encryption'] : 'none', + 'port' => isset( $options['smtp_settings']['port'] ) ? $options['smtp_settings']['port'] : 25, + 'auth' => isset( $options['smtp_settings']['autentication'] ) ? $options['smtp_settings']['autentication'] : true, + 'user' => isset( $options['smtp_settings']['username'] ) ? $options['smtp_settings']['username'] : '', + 'pass' => '', + 'autotls' => true, + ], + ]; + } + + /** + * Check if FluentSMTP plugin settings are present and extract them. + * + * @since 3.2.0 + * + * @return array + */ + private function get_fluent_smtp() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $options = get_option( 'fluentmail-settings' ); + + if ( empty( $options ) ) { + return []; + } + + if ( empty( $options['misc']['default_connection'] ) || empty( $options['connections'][ $options['misc']['default_connection'] ]['provider_settings'] ) ) { + return []; + } + + $fluent_data = $options['connections'][ $options['misc']['default_connection'] ]['provider_settings']; + + $allowed_mailers = [ + 'smtp' => 'smtp', + 'ses' => 'amazonses', + 'mailgun' => 'mailgun', + 'sendgrid' => 'sendgrid', + 'sendinblue' => 'sendinblue', + 'sparkpost' => 'sparkpost', + 'postmark' => 'postmark', + 'outlook' => 'outlook', + ]; + + if ( empty( $fluent_data['provider'] ) || ! in_array( $fluent_data['provider'], array_keys( $allowed_mailers ), true ) ) { + return []; + } + + $data = [ + 'mail' => [ + 'mailer' => $allowed_mailers[ $fluent_data['provider'] ], + 'from_email' => isset( $fluent_data['sender_email'] ) ? $fluent_data['sender_email'] : '', + 'from_name' => isset( $fluent_data['sender_name'] ) ? $fluent_data['sender_name'] : '', + 'from_email_force' => isset( $fluent_data['force_from_email'] ) && $fluent_data['force_from_email'] === 'yes', + 'from_name_force' => isset( $fluent_data['force_from_name'] ) && $fluent_data['force_from_name'] === 'yes', + ], + ]; + + switch ( $data['mail']['mailer'] ) { + case 'smtp': + $data['smtp'] = [ + 'host' => isset( $fluent_data['host'] ) ? $fluent_data['host'] : '', + 'encryption' => isset( $fluent_data['encryption'] ) && in_array( $fluent_data['encryption'], [ 'none', 'ssl', 'tls' ], true ) ? $fluent_data['encryption'] : 'none', + 'port' => isset( $fluent_data['port'] ) ? $fluent_data['port'] : 25, + 'auth' => isset( $fluent_data['auth'] ) && $fluent_data['auth'] === 'yes', + 'user' => isset( $fluent_data['username'] ) ? $fluent_data['username'] : '', + 'pass' => isset( $fluent_data['password'] ) ? $fluent_data['password'] : '', + 'autotls' => isset( $fluent_data['auto_tls'] ) && $fluent_data['auto_tls'] === 'yes', + ]; + break; + + case 'amazonses': + $data['amazonses'] = [ + 'client_id' => isset( $fluent_data['access_key'] ) ? $fluent_data['access_key'] : '', + 'client_secret' => isset( $fluent_data['secret_key'] ) ? $fluent_data['secret_key'] : '', + 'region' => isset( $fluent_data['region'] ) ? $fluent_data['region'] : '', + ]; + break; + + case 'mailgun': + $data['mailgun'] = [ + 'api_key' => isset( $fluent_data['api_key'] ) ? $fluent_data['api_key'] : '', + 'domain' => isset( $fluent_data['domain_name'] ) ? $fluent_data['domain_name'] : '', + 'region' => isset( $fluent_data['region'] ) && in_array( $fluent_data['region'], [ 'us', 'eu' ], true ) ? strtoupper( $fluent_data['region'] ) : '', + ]; + break; + + case 'sendgrid': + $data['sendgrid'] = [ + 'api_key' => isset( $fluent_data['api_key'] ) ? $fluent_data['api_key'] : '', + ]; + break; + + case 'sendinblue': + $data['sendinblue'] = [ + 'api_key' => isset( $fluent_data['api_key'] ) ? $fluent_data['api_key'] : '', + ]; + break; + + case 'sparkpost': + $data['sparkpost'] = [ + 'api_key' => isset( $fluent_data['api_key'] ) ? $fluent_data['api_key'] : '', + ]; + break; + + case 'postmark': + $data['postmark'] = [ + 'api_key' => isset( $fluent_data['api_key'] ) ? $fluent_data['api_key'] : '', + 'message_stream' => isset( $fluent_data['message_stream'] ) ? $fluent_data['message_stream'] : '', + ]; + break; + + case 'outlook': + $data['outlook'] = [ + 'client_id' => isset( $fluent_data['client_id'] ) ? $fluent_data['client_id'] : '', + 'client_secret' => isset( $fluent_data['client_secret'] ) ? $fluent_data['client_secret'] : '', + ]; + break; + } + + return $data; + } + + /** + * Check if Post SMTP Mailer plugin settings are present and extract them. + * + * @since 2.6.0 + * + * @return array + */ + private function get_post_smtp_mailer() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $options = get_option( 'postman_options' ); + + if ( empty( $options ) ) { + return []; + } + + $allowed_mailers = [ + 'smtp' => 'smtp', + 'gmail_api' => 'gmail', + 'sendgrid_api' => 'sendgrid', + 'mailgun_api' => 'mailgun', + ]; + + $data = [ + 'mail' => [ + 'mailer' => ( isset( $options['transport_type'] ) && in_array( $options['transport_type'], array_keys( $allowed_mailers ), true ) ) ? $allowed_mailers[ $options['transport_type'] ] : 'mail', + 'from_email' => isset( $options['sender_email'] ) ? $options['sender_email'] : '', + 'from_name' => isset( $options['sender_name'] ) ? $options['sender_name'] : '', + ], + 'smtp' => [ + 'host' => isset( $options['hostname'] ) ? $options['hostname'] : '', + 'encryption' => isset( $options['enc_type'] ) ? $options['enc_type'] : 'none', + 'port' => isset( $options['port'] ) ? $options['port'] : 25, + 'auth' => isset( $options['auth_type'] ) && $options['auth_type'] !== 'none', + 'user' => isset( $options['basic_auth_username'] ) ? $options['basic_auth_username'] : '', + 'pass' => ! empty( $options['basic_auth_password'] ) ? base64_decode( $options['basic_auth_password'] ) : '', // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + 'autotls' => true, + ], + 'gmail' => [ + 'client_id' => isset( $options['oauth_client_id'] ) ? $options['oauth_client_id'] : '', + 'client_secret' => isset( $options['oauth_client_secret'] ) ? $options['oauth_client_secret'] : '', + ], + 'sendgrid' => [ + 'api_key' => ! empty( $options['sendgrid_api_key'] ) ? base64_decode( $options['sendgrid_api_key'] ) : '', // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + ], + 'mailgun' => [ + 'api_key' => ! empty( $options['mailgun_api_key'] ) ? base64_decode( $options['mailgun_api_key'] ) : '', // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + 'domain' => isset( $options['mailgun_domain_name'] ) ? $options['mailgun_domain_name'] : '', + 'region' => ( isset( $options['mailgun_region'] ) && ! empty( $options['mailgun_region'] ) ) ? 'EU' : 'US', + ], + ]; + + if ( class_exists( '\PostmanOptions' ) ) { + $pm_options = \PostmanOptions::getInstance(); + + $data['sendgrid']['api_key'] = $pm_options->getSendGridApiKey(); + $data['mailgun']['api_key'] = $pm_options->getMailgunApiKey(); + $data['smtp']['pass'] = $pm_options->getPassword(); + } + + return $data; + } + + /** + * Check if SMTP Mailer plugin settings are present and extract them. + * + * @since 2.6.0 + * + * @return array + */ + private function get_smtp_mailer() { + + $options = get_option( 'smtp_mailer_options' ); + + if ( empty( $options ) ) { + return []; + } + + return [ + 'mail' => [ + 'mailer' => 'smtp', + 'from_email' => isset( $options['from_email'] ) ? $options['from_email'] : '', + 'from_name' => isset( $options['from_name'] ) ? $options['from_name'] : '', + ], + 'smtp' => [ + 'host' => isset( $options['smtp_host'] ) ? $options['smtp_host'] : '', + 'encryption' => isset( $options['type_of_encryption'] ) ? $options['type_of_encryption'] : 'none', + 'port' => isset( $options['smtp_port'] ) ? $options['smtp_port'] : 25, + 'auth' => isset( $options['smtp_auth'] ) && $options['smtp_auth'] === 'true', + 'user' => isset( $options['smtp_username'] ) ? $options['smtp_username'] : '', + 'pass' => ! empty( $options['smtp_password'] ) ? base64_decode( $options['smtp_password'] ) : '', // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + 'autotls' => true, + ], + ]; + } + + /** + * Check if WP SMTP plugin settings are present and extract them. + * + * @since 2.6.0 + * + * @return array + */ + private function get_wp_smtp() { + + $options = get_option( 'wp_smtp_options' ); + + if ( empty( $options ) ) { + return []; + } + + return [ + 'mail' => [ + 'mailer' => 'smtp', + 'from_email' => isset( $options['from'] ) ? $options['from'] : '', + 'from_name' => isset( $options['fromname'] ) ? $options['fromname'] : '', + ], + 'smtp' => [ + 'host' => isset( $options['host'] ) ? $options['host'] : '', + 'encryption' => ! empty( $options['smtpsecure'] ) ? $options['smtpsecure'] : 'none', + 'port' => isset( $options['port'] ) ? $options['port'] : 25, + 'auth' => isset( $options['smtpauth'] ) && $options['smtpauth'] === 'yes', + 'user' => isset( $options['username'] ) ? $options['username'] : '', + 'pass' => isset( $options['password'] ) ? $options['password'] : '', + 'autotls' => true, + ], + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Helpers/UI.php b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/UI.php new file mode 100644 index 00000000..6768d523 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Helpers/UI.php @@ -0,0 +1,120 @@ +<?php + +namespace WPMailSMTP\Helpers; + +/** + * Reusable interface components. + * + * @since 3.10.0 + */ +class UI { + + /** + * Toggle component. + * + * @since 3.10.0 + * + * @param array $args { + * Toggle parameters. + * + * @type string $name Name attribute of the toggle's input element. Default ''. + * @type string $value Value attribute of the toggle's input element. Default 'yes'. + * @type string|string[] $label Single label, or a 2-elements array of on/off labels. Default ''. + * @type string $id ID attribute of the toggle's container element. Default ''. + * @type string $class Class attribute of the toggle's container element. Default ''. + * @type bool $checked Checked attribute of the toggle's input element. Default false. + * @type bool $disabled Disabled attribute of the toggle's input element. Default false. + * } + */ + public static function toggle( $args = [] ) { + + $args = wp_parse_args( + $args, + [ + 'name' => '', + 'value' => 'yes', + 'label' => [ + esc_html__( 'On', 'wp-mail-smtp' ), + esc_html__( 'Off', 'wp-mail-smtp' ), + ], + 'id' => '', + 'class' => '', + 'checked' => false, + 'disabled' => false, + ] + ); + ?> + <label class="wp-mail-smtp-toggle"> + <input type="checkbox" + name="<?php echo esc_attr( $args['name'] ); ?>" + <?php echo empty( $args['class'] ) ? '' : ' class="' . esc_attr( $args['class'] ) . '"'; ?> + <?php echo empty( $args['id'] ) ? '' : ' id="' . esc_attr( $args['id'] ) . '"'; ?> + value="<?php echo esc_attr( $args['value'] ); ?>" + <?php checked( (bool) $args['checked'] ); ?> + <?php disabled( (bool) $args['disabled'] ); ?> /> + <span class="wp-mail-smtp-toggle__switch"></span> + <?php if ( is_array( $args['label'] ) ) : ?> + <?php if ( count( $args['label'] ) > 0 ) : ?> + <span class="wp-mail-smtp-toggle__label wp-mail-smtp-toggle__label--checked"><?php echo esc_html( $args['label'][0] ); ?></span> + <?php endif; ?> + <?php if ( count( $args['label'] ) > 1 ) : ?> + <span class="wp-mail-smtp-toggle__label wp-mail-smtp-toggle__label--unchecked"><?php echo esc_html( $args['label'][1] ); ?></span> + <?php endif; ?> + <?php else : ?> + <span class="wp-mail-smtp-toggle__label wp-mail-smtp-toggle__label--static"><?php echo esc_html( $args['label'] ); ?></span> + <?php endif; ?> + </label> + <?php + } + + /** + * Output an obfuscated password field. + * + * @since 4.1.0 + * + * @param array $args Field attributes. + * + * @return void + */ + public static function hidden_password_field( $args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $args = wp_parse_args( + $args, + [ + 'name' => '', + 'id' => '', + 'value' => '', + 'clear_text' => esc_html__( 'Remove', 'wp-mail-smtp' ), + 'clear_url' => '', + ] + ); + + $value = str_repeat( '*', strlen( $args['value'] ) ); + + // phpcs:disable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace + ?> + + <div class="wp-mail-smtp-input-btn-row"> + + <input type="password" + spellcheck="false" + autocomplete="new-password" + <?php if ( ! empty( $value ) ) : ?>disabled<?php endif; ?> + <?php if ( ! empty( $args['name'] && empty( $value ) ) ) : ?>name="<?php echo esc_attr( $args['name'] ); ?>"<?php endif; ?> + <?php if ( ! empty( $args['name'] ) ) : ?>data-name="<?php echo esc_attr( $args['name'] ); ?>"<?php endif; ?> + <?php if ( ! empty( $args['id'] ) ) : ?>id="<?php echo esc_attr( $args['id'] ); ?>"<?php endif; ?> + <?php if ( ! empty( $value ) ) : ?>value="<?php echo esc_attr( $value ); ?>"<?php endif; ?>/> + + <?php if ( ! empty( $value ) && ! empty( $args['clear_url'] ) ) : ?> + <a href="<?php echo esc_url( $args['clear_url'] ); ?>" + class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"><?php echo esc_html( $args['clear_text'] ); ?></a> + <?php elseif ( ! empty( $value ) ) : ?> + <button type="button" + class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey" + data-clear-field="<?php echo esc_attr( $args['id'] ); ?>"><?php echo esc_html( $args['clear_text'] ); ?></button> + <?php endif; ?> + </div> + <?php + // phpcs:enable Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/RegisterLibrary.php b/web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/RegisterLibrary.php new file mode 100644 index 00000000..53ef00e1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/RegisterLibrary.php @@ -0,0 +1,48 @@ +<?php + +namespace WPMailSMTP\Integrations\WPCode; + +/** + * Register the WP Mail SMTP username on the WPCode snippet library so the + * plugin's snippets load inside an active WPCode install. + * + * @since 4.9.0 + */ +class RegisterLibrary { + + /** + * Register hooks. + * + * @since 4.9.0 + */ + public function hooks() { + + add_action( 'plugins_loaded', [ $this, 'register_wpcode_username' ], 20 ); + } + + /** + * The WPCode library username for this plugin. + * + * @since 4.9.0 + * + * @return string + */ + public function get_username() { + + return 'wpmailsmtp'; + } + + /** + * Register the library username with WPCode, when available. + * + * @since 4.9.0 + */ + public function register_wpcode_username() { + + if ( ! function_exists( 'wpcode_register_library_username' ) ) { + return; + } + + wpcode_register_library_username( $this->get_username(), 'WP Mail SMTP' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/SnippetsProvider.php b/web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/SnippetsProvider.php new file mode 100644 index 00000000..23c434d6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Integrations/WPCode/SnippetsProvider.php @@ -0,0 +1,298 @@ +<?php + +namespace WPMailSMTP\Integrations\WPCode; + +/** + * Provides the plugin's WPCode library snippets as a single, normalized + * view-model, whether or not the WPCode plugin is active. + * + * When WPCode is active its own library helper supplies the data (with real + * installed flags and install URLs); otherwise the snippets are fetched from + * the library API over HTTP, cached for a day, and fall back to placeholders. + * Both sources are normalized through map_snippet() so consumers see one shape. + * + * @since 4.9.0 + */ +class SnippetsProvider { + + /** + * Library API base. + * + * @since 4.9.0 + */ + const API_BASE = 'https://library.wpcode.com/api/profile/'; + + /** + * Transient key for the cached snippet list. + * + * @since 4.9.0 + */ + const CACHE_KEY = 'wp_mail_smtp_wpcode_snippets'; + + /** + * Library username this provider reads. + * + * @since 4.9.0 + * + * @var string + */ + private $username; + + /** + * Whether the last get_snippets() resolved to the placeholder fallback. + * + * @since 4.9.0 + * + * @var bool + */ + private $fallback = false; + + /** + * Constructor. + * + * @since 4.9.0 + * + * @param string $username Library username. + */ + public function __construct( $username ) { + + $this->username = $username; + } + + /** + * All snippets for the current state, as normalized card view-models. + * + * @since 4.9.0 + * + * @return array + */ + public function get_snippets() { + + $this->fallback = false; + + $snippets = $this->is_wpcode_active() + ? $this->get_snippets_from_wpcode() + : $this->get_snippets_from_api(); + + // Surface installed snippets first, keeping each group's source order + // (sort functions aren't stable on PHP 7.4, the plugin's minimum). + $installed = []; + $rest = []; + + foreach ( $snippets as $snippet ) { + if ( ! empty( $snippet['installed'] ) ) { + $installed[] = $snippet; + } else { + $rest[] = $snippet; + } + } + + return array_merge( $installed, $rest ); + } + + /** + * A single normalized snippet by its library id, or null when not found. + * + * @since 4.9.0 + * + * @param int $library_id Numeric library id. + * + * @return array|null + */ + public function get_snippet( $library_id ) { + + $library_id = absint( $library_id ); + + foreach ( $this->get_snippets() as $snippet ) { + if ( $snippet['library_id'] === $library_id ) { + return $snippet; + } + } + + return null; + } + + /** + * Whether the last get_snippets() call fell back to placeholders. + * + * @since 4.9.0 + * + * @return bool + */ + public function is_fallback() { + + return $this->fallback; + } + + /** + * Whether WPCode is active (its library helper is available). + * + * @since 4.9.0 + * + * @return bool + */ + private function is_wpcode_active() { + + return function_exists( 'wpcode_get_library_snippets_by_username' ); + } + + /** + * Snippets from WPCode's own library helper, normalized. + * + * @since 4.9.0 + * + * @return array + */ + private function get_snippets_from_wpcode() { + + $snippets = wpcode_get_library_snippets_by_username( $this->username ); + + if ( ! is_array( $snippets ) ) { + return []; + } + + $mapped = []; + + foreach ( $snippets as $snippet ) { + if ( empty( $snippet['library_id'] ) ) { + continue; + } + + $mapped[] = $this->map_snippet( $snippet, absint( $snippet['library_id'] ) ); + } + + return $mapped; + } + + /** + * Snippets from the library API: HTTP, cached for a day, + * with a placeholder fallback when there's no cache and the fetch fails. + * + * @since 4.9.0 + * + * @return array + */ + private function get_snippets_from_api() { + + $cached = get_transient( self::CACHE_KEY ); + + if ( ! empty( $cached ) ) { + return $cached; + } + + $response = wp_remote_get( self::API_BASE . sanitize_key( $this->username ) ); + + if ( ! is_wp_error( $response ) && (int) wp_remote_retrieve_response_code( $response ) === 200 ) { + $mapped = $this->map_response( json_decode( wp_remote_retrieve_body( $response ), true ) ); + + if ( ! empty( $mapped ) ) { + set_transient( self::CACHE_KEY, $mapped, DAY_IN_SECONDS ); + + return $mapped; + } + } + + // No cache and no live data: fall back to placeholders, blurred behind the modal. + $this->fallback = true; + + return $this->get_placeholder_snippets(); + } + + /** + * Map a raw API response to normalized card view-models. + * + * @since 4.9.0 + * + * @param array $response Decoded API response. + * + * @return array List of snippet view-models (empty on error/empty). + */ + private function map_response( $response ) { + + if ( + empty( $response['status'] ) || + $response['status'] !== 'success' || + empty( $response['data']['snippets'] ) + ) { + return []; + } + + $snippets = []; + + foreach ( $response['data']['snippets'] as $snippet ) { + if ( empty( $snippet['library_id'] ) ) { + continue; + } + + $snippets[] = $this->map_snippet( $snippet, absint( $snippet['library_id'] ) ); + } + + return $snippets; + } + + /** + * Normalize a raw snippet (from either source) to the card view-model. + * + * @since 4.9.0 + * + * @param array $snippet Raw snippet. + * @param int $library_id Numeric library id. + * + * @return array + */ + private function map_snippet( $snippet, $library_id ) { + + return [ + 'title' => isset( $snippet['title'] ) ? sanitize_text_field( $snippet['title'] ) : '', + 'note' => isset( $snippet['note'] ) ? sanitize_text_field( $snippet['note'] ) : '', + 'installed' => ! empty( $snippet['installed'] ), + 'install' => isset( $snippet['install'] ) ? esc_url_raw( $snippet['install'] ) : '', + 'library_id' => $library_id, + 'code' => isset( $snippet['code'] ) ? (string) $snippet['code'] : '', + 'code_type' => isset( $snippet['code_type'] ) ? sanitize_key( $snippet['code_type'] ) : 'php', + ]; + } + + /** + * Hardcoded placeholder snippets, shown only blurred behind the install modal + * as the fallback (ports WPForms' get_placeholder_snippets mechanism, populated + * with this plugin's real snippet titles). + * + * @since 4.9.0 + * + * @return array + */ + private function get_placeholder_snippets() { + + $titles = [ + 'Adding a Custom Filter when using Other SMTP Setting', + 'Change the redirect URI when using Google App\'s OAuth', + 'Set a Custom Reply-To Email', + 'Using an Office 365 GCC or DoD email address', + 'Change the Email Summary Email Address', + 'Block URLs Inside Text Fields', + 'Set a Custom Email Header', + 'Defining Amazon SES Constants', + 'Use Single Tenant With the Outlook Mailer', + 'Defining Google Mailer Constants', + 'Define the Mailgun Signing Key Constant', + 'Defining Outlook Mailer Constants', + ]; + + $snippets = []; + + foreach ( $titles as $title ) { + $snippets[] = [ + 'title' => $title, + 'note' => esc_html__( 'Install WPCode to use this snippet.', 'wp-mail-smtp' ), + 'installed' => false, + 'install' => 'https://library.wpcode.com/', + 'library_id' => 0, + 'code' => '', + 'code_type' => 'php', + ]; + } + + return $snippets; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/MailCatcher.php b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcher.php new file mode 100644 index 00000000..c5e323b9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcher.php @@ -0,0 +1,61 @@ +<?php + +namespace WPMailSMTP; + +use phpmailerException; + +// Load PHPMailer class, so we can subclass it. +if ( ! class_exists( 'PHPMailer', false ) ) { + require_once ABSPATH . WPINC . '/class-phpmailer.php'; +} + +/** + * Class MailCatcher replaces the \PHPMailer and modifies the email sending logic. + * Thus, we can use other mailers API to do what we need, or stop emails completely. + * + * @since 1.0.0 + */ +class MailCatcher extends \PHPMailer implements MailCatcherInterface { + + use MailCatcherTrait; + + /** + * Returns all custom headers. + * In older versions of \PHPMailer class this method didn't exist. + * As we support WordPress 3.6+ - we need to make sure this method is always present. + * + * @since 1.5.0 + * + * @return array + */ + public function getCustomHeaders() { + + return $this->CustomHeader; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + + /** + * Get the PHPMailer line ending. + * + * @since 2.2.0 + * + * @return string + */ + public function get_line_ending() { + + return $this->LE; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + + /** + * Throw PHPMailer exception. + * + * @since 4.0.0 + * + * @param string $error Error message. + * + * @throws phpmailerException PHPMailer exception. + */ + protected function throw_exception( $error ) { + + throw new phpmailerException( $error ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherInterface.php new file mode 100644 index 00000000..bf369f34 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherInterface.php @@ -0,0 +1,69 @@ +<?php + +namespace WPMailSMTP; + +/** + * Interface MailCatcherInterface. + * + * @since 2.2.0 + */ +interface MailCatcherInterface { + + /** + * Modify the default send() behaviour. + * For those mailers, that relies on PHPMailer class - call it directly. + * For others - init the correct provider and process it. + * + * @since 2.2.0 + * + * @throws \phpmailerException|\PHPMailer\PHPMailer\Exception When sending via PhpMailer fails for some reason. + * + * @return bool + */ + public function send(); + + /** + * Get the PHPMailer line ending. + * + * @since 2.2.0 + * + * @return string + */ + public function get_line_ending(); + + /** + * Create a unique ID to use for multipart email boundaries. + * + * @since 2.4.0 + * + * @return string + */ + public function generate_id(); + + /** + * Get debug event ID. + * + * @since 3.5.0 + * + * @return bool|int + */ + public function get_debug_event_id(); + + /** + * Whether the current email is a test email. + * + * @since 3.5.0 + * + * @return bool + */ + public function is_test_email(); + + /** + * Whether the current email is a Setup Wizard test email. + * + * @since 3.5.0 + * + * @return bool + */ + public function is_setup_wizard_test_email(); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherTrait.php b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherTrait.php new file mode 100644 index 00000000..266b74f9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherTrait.php @@ -0,0 +1,902 @@ +<?php + +namespace WPMailSMTP; + +use Exception; +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Providers\MailerAbstract; + +/** + * Trait MailCatcherTrait. + * + * @since 3.7.0 + */ +trait MailCatcherTrait { + + /** + * Debug output buffer. + * + * @since 3.3.0 + * + * @var array + */ + private $debug_output_buffer = []; + + /** + * Debug event ID. + * + * @since 3.5.0 + * + * @var int + */ + private $debug_event_id = false; + + /** + * Whether the current email is a test email. + * + * @since 3.5.0 + * + * @var bool + */ + private $is_test_email = false; + + /** + * Whether the current email is a Setup Wizard test email. + * + * @since 3.5.0 + * + * @var bool + */ + private $is_setup_wizard_test_email = false; + + /** + * Whether the current email is blocked to be sent. + * + * @since 3.8.0 + * + * @var bool + */ + private $is_emailing_blocked = false; + + /** + * Holds the most recent error message. + * + * @since 3.7.0 + * + * @var string + */ + protected $latest_error = ''; + + /** + * Last error code captured before PHPMailer clears it. + * + * Populated via setError() override with first-write-wins semantics. + * + * @since 4.8.0 + * + * @var string + */ + private $last_error_code = ''; + + /** + * Email addresses PHPMailer reported as failed during postSend(). + * + * PHPMailer dispatches doCallback() per recipient in smtpSend(), and per recipient + * under SingleTo=true in mailSend()/sendmailSend(). + * + * @since 4.9.0 + * + * @var string[] + */ + private $failed_recipients = []; + + /** + * Modify the default send() behaviour. + * For those mailers, that relies on PHPMailer class - call it directly. + * For others - init the correct provider and process it. + * + * @since 1.0.0 + * @since 1.4.0 Process "Do Not Send" option, but always allow test email. + * @since 4.5.0 Add support for logging blocked emails. + * + * @throws Exception When sending via PhpMailer fails for some reason. + * + * @return bool + */ + public function send() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Reset email related variables. + $this->debug_event_id = false; + $this->is_test_email = false; + $this->is_setup_wizard_test_email = false; + $this->is_emailing_blocked = false; + $this->latest_error = ''; + $this->failed_recipients = []; + + if ( wp_mail_smtp()->is_blocked() ) { + $this->is_emailing_blocked = true; + } + + // Always allow a test email - check for the specific header. + foreach ( (array) $this->getCustomHeaders() as $header ) { + if ( + ! empty( $header[0] ) && + ! empty( $header[1] ) && + $header[0] === 'X-Mailer-Type' + ) { + if ( trim( $header[1] ) === 'WPMailSMTP/Admin/Test' ) { + $this->is_emailing_blocked = false; + $this->is_test_email = true; + } elseif ( trim( $header[1] ) === 'WPMailSMTP/Admin/SetupWizard/Test' ) { + $this->is_setup_wizard_test_email = true; + } + } + } + + // Log blocked emails if the option is enabled. + if ( $this->is_emailing_blocked ) { + /** + * Fires when an email is blocked from being sent. + * + * @since 4.5.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + do_action( 'wp_mail_smtp_mail_catcher_send_blocked', $this ); + + return false; + } + + // If it's not a test email, + // check if the email should be enqueued + // instead of being sent immediately. + if ( ! $this->is_test_email && ! $this->is_setup_wizard_test_email ) { + + /** + * Filters whether an email should be enqueued or sent immediately. + * + * @since 4.0.0 + * + * @param bool $should_enqueue Whether to enqueue an email, or send it. + * @param array $wp_mail_args Original arguments of the `wp_mail` call. + */ + $should_enqueue_email = apply_filters( + 'wp_mail_smtp_mail_catcher_send_enqueue_email', + false, + wp_mail_smtp()->get_processor()->get_filtered_wp_mail_args() + ); + + $queue = wp_mail_smtp()->get_queue(); + + // If we should enqueue the email, + // and the email has been enqueued, + // bail. + if ( $should_enqueue_email && $queue->enqueue_email() ) { + return true; + } + } + + $connection = wp_mail_smtp()->get_connections_manager()->get_mail_connection(); + $mailer_slug = $connection->get_mailer_slug(); + + // Define a custom header, that will be used to identify the plugin and the mailer. + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $this->XMailer = 'WPMailSMTP/Mailer/' . $mailer_slug . ' ' . WPMS_PLUGIN_VER; + + // Clear any prior failure entry for this connection. The smtp_send()/api_send() + // catch paths set a fresh entry on failure; on success no further action needed. + EmailSendingDebug::clear( $connection->get_id() ); + + // Use the default PHPMailer, as we inject our settings there for certain providers. + if ( + $mailer_slug === 'mail' || + $mailer_slug === 'smtp' || + $mailer_slug === 'pepipost' + ) { + return $this->smtp_send( $connection ); + } else { + return $this->api_send( $connection ); + } + } + + /** + * Send email via SMTP. + * + * @since 4.0.0 + * + * @param ConnectionInterface $connection The connection object. + * + * @throws Exception When sending via PhpMailer fails for some reason. + * + * @return bool + */ + private function smtp_send( $connection ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Reset captured SMTP error code from previous send attempts. + $this->last_error_code = ''; + + $mailer_slug = $connection->get_mailer_slug(); + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + try { + // Capture PHPMailer's SMTPDebug output whenever the admin has DebugEvents + // enabled, or whenever the current send is an admin test email - test sends + // always want diagnostics regardless of the global debug setting. + if ( DebugEvents::is_debug_enabled() || $this->is_test_email ) { + $this->Debugoutput = [ $this, 'debug_output_callback' ]; + + if ( $this->is_test_email ) { + /** + * Filters the PHPMailer SMTPDebug verbosity for admin test emails. + * + * Levels: 0 (off), 1 (client commands), 2 (commands + server responses), + * 3 (commands + data + connection status), 4 (low-level data). + * + * @since 1.4.0 + * + * @param int $level Debug level passed to PHPMailer::$SMTPDebug. Default 3. + */ + $this->SMTPDebug = apply_filters( 'wp_mail_smtp_admin_test_email_smtp_debug', 3 ); + } else { + $this->SMTPDebug = 3; + } + } + + /** + * Fires before email pre send via SMTP. + * + * Allow to hook early to catch any early failed emails. + * + * @since 2.9.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + do_action( 'wp_mail_smtp_mailcatcher_smtp_pre_send_before', $this ); + + // Prepare all the headers. + if ( ! $this->preSend() ) { + $this->throw_exception( $this->ErrorInfo ); + } + + /** + * Fires before email send via SMTP. + * + * Allow to hook after all the preparation before the actual sending. + * + * @since 2.9.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + do_action( 'wp_mail_smtp_mailcatcher_smtp_send_before', $this ); + + if ( ! $this->postSend() ) { + $this->throw_exception( $this->ErrorInfo ); + } + + // PHPMailer's mailSend()/sendmailSend() can return true under SingleTo=true even + // when individual recipients failed - the result variable is overwritten each loop. + // Route those through the existing catch by throwing here. + if ( ! empty( $this->failed_recipients ) ) { + $failed = array_unique( $this->failed_recipients ); + + $this->throw_exception( + sprintf( + esc_html( + /* translators: 1: count of failed recipients, 2: comma-separated email addresses. */ + _n( + 'The mail transport accepted the message but rejected %1$d recipient address: %2$s. Other recipients in the same email have been sent.', + 'The mail transport accepted the message but rejected %1$d recipient addresses: %2$s. Other recipients in the same email have been sent.', + count( $failed ), + 'wp-mail-smtp' + ) + ), + count( $failed ), + implode( ', ', $failed ) + ) + ); + } + + DebugEvents::add_debug( + esc_html__( 'An email request was sent.', 'wp-mail-smtp' ) + ); + + /** + * Fires after a successful SMTP/PHP-mail send. + * + * @since 1.5.0 + * @since 4.9.0 Fires exactly once per send and only on success. + * + * @param bool $is_sent Always true. Kept for backwards-compatible signature. + * @param array $to To recipients. + * @param array $cc CC recipients. + * @param array $bcc BCC recipients. + * @param string $subject Email subject. + * @param string $body MIME-encoded body. + * @param string $from From address. + */ + do_action( 'wp_mail_smtp_mailcatcher_smtp_send_after', true, $this->to, $this->cc, $this->bcc, $this->Subject, $this->MIMEBody, $this->From ); + + return true; + } catch ( Exception $e ) { + $this->mailHeader = ''; + + // We need this to append SMTP error to the `PHPMailer::ErrorInfo` property. + $this->setError( $e->getMessage() ); + + $error_message = 'Mailer: ' . esc_html( wp_mail_smtp()->get_providers()->get_options( $mailer_slug )->get_title() ) . "\r\n" . $this->ErrorInfo; + $error_code = $this->get_smtp_error_code(); + $error_key = $this->build_error_key( '', $error_code, $this->ErrorInfo ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + + $this->latest_error = $error_message; + + $debug_event_message = $error_message; + $debug_output = implode( "\r\n", $this->debug_output_buffer ); + + if ( DebugEvents::is_debug_enabled() && ! empty( $debug_output ) ) { + $debug_event_message .= "\r\n" . esc_html__( 'SMTP Debug:', 'wp-mail-smtp' ) . "\r\n"; + $debug_event_message .= $debug_output; + } + + $this->debug_event_id = DebugEvents::add( $debug_event_message ); + + $this->record_send_failure( $connection, $mailer_slug, $error_code, $this->ErrorInfo, $this->debug_event_id, $debug_output, $error_key ); + + /** + * Fires after email sent failure via SMTP. + * + * @since 3.5.0 + * + * @param string $error_message Error message. + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + * @param string $mailer_slug Current mailer name. + * @param string $error_code Error code/slug. + * @param int $response_code HTTP/SMTP response code (0 for non-API mailers). + * @param ConnectionInterface $connection The connection object. + * @param string $error_key Composite error key. + */ + do_action( 'wp_mail_smtp_mailcatcher_send_failed', $this->ErrorInfo, $this, $mailer_slug, $error_code, 0, $connection, $error_key ); + + if ( $this->exceptions ) { + throw $e; + } + + return false; + } finally { + + // Clear debug output buffer. + $this->debug_output_buffer = []; + } + // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + + /** + * Send email via API integration. + * + * @since 4.0.0 + * + * @param ConnectionInterface $connection The connection object. + * + * @throws Exception When sending fails for some reason. + * + * @return bool + */ + private function api_send( $connection ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $mailer_slug = $connection->get_mailer_slug(); + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + try { + // We need this so that the \PHPMailer class will correctly prepare all the headers. + $this->Mailer = 'mail'; + + /** + * Fires before email pre send. + * + * Allow to hook early to catch any early failed emails. + * + * @since 2.9.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + do_action( 'wp_mail_smtp_mailcatcher_pre_send_before', $this ); + + // Prepare everything (including the message) for sending. + if ( ! $this->preSend() ) { + $this->throw_exception( $this->ErrorInfo ); + } + + $mailer = wp_mail_smtp()->get_providers()->get_mailer( $mailer_slug, $this, $connection ); + + if ( ! $mailer ) { + $this->throw_exception( 'The selected mailer not found.' ); + } + + if ( ! $mailer->is_php_compatible() ) { + $this->throw_exception( 'The selected mailer is not compatible with your PHP version.' ); + } + + /** + * Fires before email send. + * + * Allows to hook after all the preparation before the actual sending. + * + * @since 3.3.0 + * + * @param MailerAbstract $mailer The Mailer object. + */ + do_action( 'wp_mail_smtp_mailcatcher_send_before', $mailer ); + + /* + * Send the actual email. + * We reuse everything, that was preprocessed for usage in \PHPMailer. + */ + $mailer->send(); + + $is_sent = $mailer->is_email_sent(); + + /** + * Fires after email send. + * + * Allow to perform any actions with the data. + * + * @since 3.5.0 + * + * @param MailerAbstract $mailer The Mailer object. + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + do_action( 'wp_mail_smtp_mailcatcher_send_after', $mailer, $this ); + + if ( $is_sent !== true ) { + $this->throw_exception( $mailer->get_response_error() ); + } + + return true; + } catch ( Exception $e ) { + // Add mailer to the beginning and save to display later. + $message = 'Mailer: ' . esc_html( wp_mail_smtp()->get_providers()->get_options( $mailer_slug )->get_title() ) . "\r\n"; + + $error_code = ! empty( $mailer ) ? $mailer->get_response_error_code() : ''; + $response_code = ! empty( $mailer ) ? $mailer->get_response_code() : 0; + $error_key = $this->build_error_key( $response_code > 0 ? (string) $response_code : '', $error_code, $e->getMessage() ); + $error_message = $message . $e->getMessage(); + $this->debug_event_id = DebugEvents::add( $error_message ); + $this->latest_error = $error_message; + + $this->record_send_failure( $connection, $mailer_slug, $error_code, $e->getMessage(), $this->debug_event_id, '', $error_key ); + + /** + * Fires after email sent failure. + * + * @since 3.5.0 + * + * @param string $error_message Error message. + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + * @param string $mailer_slug Current mailer name. + * @param string $error_code Error code/slug. + * @param int $response_code HTTP response code. + * @param ConnectionInterface $connection The connection object. + * @param string $error_key Composite error key. + */ + do_action( 'wp_mail_smtp_mailcatcher_send_failed', $e->getMessage(), $this, $mailer_slug, $error_code, $response_code, $connection, $error_key ); + + if ( $this->exceptions ) { + throw $e; + } + + return false; + } + // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + + /** + * Create a unique ID to use for multipart email boundaries. + * + * @since 2.4.0 + * + * @return string + */ + public function generate_id() { + + return $this->generateId(); + } + + /** + * Get SMTP error code combining error category and numeric SMTP code. + * + * Reverse-lookups PHPMailer language key from ErrorInfo via static::$language, + * making the category language-independent even if PHPMailer is translated (WP 6.8+). + * + * @since 4.8.0 + * + * @return string Error code like "authenticate_535", "connect_host", or empty. + */ + private function get_smtp_error_code() { + + $smtp_code = $this->last_error_code; + $category = ''; + + // Reverse-lookup PHPMailer language key from ErrorInfo. + // Use getTranslations() which works in both old (instance $language) and new (static $language) PHPMailer. + $language = method_exists( $this, 'getTranslations' ) ? $this->getTranslations() : []; + + foreach ( $language as $key => $translated ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + if ( ! empty( $translated ) && strpos( $this->ErrorInfo, $translated ) === 0 ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $category = $key; + + break; + } + } + + $parts = array_filter( [ $category, $smtp_code ] ); + + return ! empty( $parts ) ? implode( '_', $parts ) : ''; + } + + /** + * Build the composite error key: {prefix}:{error_code}:{sanitized_message}. + * + * Mirrors ErrorStats::build_error_key() for valid inputs; guards mbstring and + * null-coalesces preg results so it cannot throw on the send-failure path. + * + * @since 4.9.0 + * + * @param string $prefix HTTP response code or category (e.g. "401", "delivery"). + * @param string $error_code Provider/SMTP error code. + * @param string $error_message Error message text. + * + * @return string + */ + private function build_error_key( $prefix, $error_code, $error_message ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $max_length = 50; + $part_response = ! empty( $prefix ) ? $prefix : '-'; + $part_code = ! empty( $error_code ) && $error_code !== 'unknown' && (string) $error_code !== $part_response ? $error_code : '-'; + $part_message = ! empty( $error_message ) ? $this->sanitize_error_message( $error_message ) : '-'; + + if ( $part_response !== '-' ) { + $part_message = str_replace( sanitize_title( $part_response ), '', $part_message ); + } + + if ( $part_code !== '-' ) { + $part_message = str_replace( sanitize_title( $part_code ), '', $part_message ); + } + + $part_message = (string) preg_replace( '/-{2,}/', '-', $part_message ); + $part_message = trim( $part_message, '-' ); + + if ( empty( $part_message ) ) { + $part_message = '-'; + } + + $key = $part_response . ':' . $part_code . ':' . $part_message; + + // function_exists guard so a missing mbstring extension can never fatal on + // the send path; substr is an exact byte fallback for this ASCII key. + $key_length = function_exists( 'mb_strlen' ) ? mb_strlen( $key ) : strlen( $key ); + + if ( $key_length > $max_length ) { + $key = function_exists( 'mb_substr' ) ? mb_substr( $key, 0, $max_length ) : substr( $key, 0, $max_length ); + + $last_hyphen = strrpos( $key, '-' ); + + if ( $last_hyphen !== false && $last_hyphen > strrpos( $key, ':' ) ) { + $key = substr( $key, 0, $last_hyphen ); + } + } + + return $key; + } + + /** + * Sanitize an error message into a short aggregatable slug. + * + * Mirrors ErrorStats::sanitize_message() with null-safe casts. + * + * @since 4.9.0 + * + * @param string $message Error message. + * + * @return string + */ + private function sanitize_error_message( $message ) { + + $message = html_entity_decode( $message, ENT_QUOTES, 'UTF-8' ); + $message = (string) preg_replace( '/\S+@\S+\.\S+/', '', $message ); + $message = (string) preg_replace( '#https?://\S+#i', '', $message ); + $message = (string) preg_replace( '/\b\S+\.\S+\b/', '', $message ); + $message = (string) preg_replace( '/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i', '', $message ); + $message = (string) preg_replace( '/["\'][^"\']*["\']/', '', $message ); + $message = trim( (string) preg_replace( '/\s+/', ' ', $message ) ); + + return sanitize_title( $message ); + } + + /** + * Write a failure record into EmailSendingDebug for the given connection. + * + * Shared between the SMTP and API catch paths - per-path values (error message, + * error code, debug event id) are computed locally and passed in. + * + * Also snapshots the environment data the failure-display layer needs to + * reconstruct a full debug bundle later (WP/PHP/plugin versions, multisite flag, + * settings-as-constants flag, plugin conflicts, mailer-specific debug info, and + * the captured SMTPDebug output). Capturing these at write-time matters because + * they reflect server state at the moment of failure (conflicts, constants + * enabled) and PHPMailer's transient SMTPDebug buffer is gone after the send. + * + * @since 4.9.0 + * + * @param ConnectionInterface $connection Mail connection the failure belongs to. + * @param string $mailer_slug Current mailer slug. + * @param string $error_code Provider/SMTP-specific error code. + * @param string $error_message Human-readable error message. + * @param int|false $debug_event_id DebugEvents row id for this failure, or false when none. + * @param string $smtp_debug Captured PHPMailer SMTPDebug output, or empty string when none. + * @param string $error_key Composite error key, or empty string when not yet built. + */ + private function record_send_failure( $connection, $mailer_slug, $error_code, $error_message, $debug_event_id, $smtp_debug = '', $error_key = '' ) { + + $initiator = wp_mail_smtp()->get_wp_mail_initiator(); + $connection_options = $connection->get_options(); + $conflicts = new Conflicts(); + $mailer = wp_mail_smtp()->get_providers()->get_mailer( $mailer_slug, $this, $connection ); + + EmailSendingDebug::set( + $connection->get_id(), + [ + 'occurred_at' => time(), + 'context' => ( $this->is_test_email() || $this->is_setup_wizard_test_email() ) ? 'test' : 'regular', + 'status' => 'failed', + 'mailer' => $mailer_slug, + 'error_code' => $error_code, + 'error_key' => $error_key, + 'error_message' => $error_message, + 'initiator_name' => $initiator->get_name(), + 'initiator_file' => $initiator->get_file() . ':' . $initiator->get_line(), + 'debug_event_id' => $debug_event_id, + 'wp_version' => get_bloginfo( 'version' ), + 'is_multisite' => is_multisite(), + 'php_version' => PHP_VERSION, + 'plugin_version' => WPMS_PLUGIN_VER, + 'constants_enabled' => $connection_options->is_const_enabled(), + 'conflicts' => $conflicts->is_detected() ? $conflicts->get_all_conflict_names() : [], + 'mailer_debug_info' => $mailer ? $mailer->get_debug_info() : '', + 'smtp_debug_info' => $smtp_debug, + ] + ); + } + + /** + * Override setError to capture SMTP error codes before PHPMailer clears them. + * + * PHPMailer's SMTP quit() clears the error via sendCommand success. + * By the time we catch the exception, the SMTP error code is gone. + * Uses first-write-wins so subsequent setError calls (RSET, connect_host fallback) + * cannot overwrite the original meaningful code. + * + * @since 4.8.0 + * + * @param string $msg Error message. + */ + protected function setError( $msg ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + + // Capture SMTP error code with first-write-wins before quit() wipes it. + if ( + empty( $this->last_error_code ) && + isset( $this->smtp ) && + $this->smtp instanceof \PHPMailer\PHPMailer\SMTP + ) { + $error = $this->smtp->getError(); + + if ( ! empty( $error['smtp_code'] ) ) { + $this->last_error_code = (string) $error['smtp_code']; + } + } + + parent::setError( $msg ); + } + + /** + * Override PHPMailer's per-send callback fan-out. + * + * PHPMailer dispatches this once per recipient in smtpSend(), and once per + * recipient under SingleTo=true in mailSend()/sendmailSend(). We collect failed + * recipient addresses and let smtp_send() decide once, at the end, whether the + * overall send succeeded. + * + * @since 4.9.0 + * + * @param bool $is_sent Whether PHPMailer accepted this recipient. + * @param array|string $to Recipients for this callback (single recipient in + * the per-recipient paths; full list in default mailSend). + * @param array $cc CC addresses (unused). + * @param array $bcc BCC addresses (unused). + * @param string $subject Subject (unused). + * @param string $body Body (unused). + * @param string $from From address (unused). + * @param array $extra Extra data (unused). + */ + protected function doCallback( $is_sent, $to, $cc, $bcc, $subject, $body, $from, $extra ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid, WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + + if ( $is_sent ) { + return; + } + + // $to is an array of [email, name] tuples; $recipient[0] is the email field of + // the current tuple, not the first recipient. Loop captures every failed address. + foreach ( (array) $to as $recipient ) { + if ( is_array( $recipient ) && isset( $recipient[0] ) && is_string( $recipient[0] ) && $recipient[0] !== '' ) { + $this->failed_recipients[] = $recipient[0]; + } elseif ( is_string( $recipient ) && $recipient !== '' ) { + $this->failed_recipients[] = $recipient; + } + } + } + + /** + * Debug output callback. + * Save debugging info to buffer array. + * + * @since 3.3.0 + * + * @param string $str Message. + * @param int $level Debug level. + */ + public function debug_output_callback( $str, $level ) { + + /* + * Filter out all higher levels than 3. + * SMTPDebug level 3 is commands, data and connection status. + */ + if ( $level > 3 ) { + return; + } + + $this->debug_output_buffer[] = trim( $str, "\r\n" ); + } + + /** + * Get debug event ID. + * + * @since 3.5.0 + * + * @return bool|int + */ + public function get_debug_event_id() { + + return $this->debug_event_id; + } + + /** + * Whether the current email is a test email. + * + * @since 3.5.0 + * + * @return bool + */ + public function is_test_email() { + + return $this->is_test_email; + } + + /** + * Whether the current email is a Setup Wizard test email. + * + * @since 3.5.0 + * + * @return bool + */ + public function is_setup_wizard_test_email() { + + return $this->is_setup_wizard_test_email; + } + + /** + * Whether the current email is blocked to be sent. + * + * @since 3.8.0 + * + * @return bool + */ + public function is_emailing_blocked() { + + return $this->is_emailing_blocked; + } + + /** + * Return the list of properties representing + * this class' state. + * + * @since 4.0.0 + * + * @return array State of this class. + */ + private function get_state_properties() { + + return [ + 'CharSet', + 'ContentType', + 'Encoding', + 'CustomHeader', + 'Subject', + 'Body', + 'AltBody', + 'ReplyTo', + 'to', + 'cc', + 'bcc', + 'attachment', + ]; + } + + /** + * Return an array of relevant properties. + * + * @since 4.0.0 + * + * @return array State of this class. + */ + public function get_state() { + + $state = []; + + foreach ( $this->get_state_properties() as $property ) { + $state[ $property ] = $this->{$property}; + } + + return $state; + } + + /** + * Set properties from a provided array of data. + * + * @since 4.0.0 + * + * @param array $state Array of properties to apply. + */ + public function set_state( $state ) { // phpcs:ignore Generic.Metrics.NestingLevel.MaxExceeded + + // Filter out non-allowed properties. + $state = array_intersect_key( + $state, + array_flip( $this->get_state_properties() ) + ); + + foreach ( $state as $property => $value ) { + if ( $property !== 'attachment' ) { + $this->{$property} = $value; + } else { + // Handle potential I/O exceptions + // in PHPMailer when attaching files. + $this->clearAttachments(); + + foreach ( $state['attachment'] as $attachment ) { + [ $path, , $name ] = $attachment; + + try { + $this->addAttachment( $path, $name ); + } catch ( Exception $e ) { + continue; + } + } + } + } + } + + /** + * Set the From and FromName properties. + * + * @since 4.7.1 + * + * @param string $address Email address. + * @param string $name Name. + * @param bool $auto Whether to also set the Sender address, defaults to true. + * + * @return bool Returns true on success and false on failure. + */ + public function setFrom( $address, $name = '', $auto = true ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + + // Set `$auto` param as false, to control return-path via plugin settings. + return parent::setFrom( $address, $name, false ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherV6.php b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherV6.php new file mode 100644 index 00000000..1bd9532a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/MailCatcherV6.php @@ -0,0 +1,54 @@ +<?php + +namespace WPMailSMTP; + +use PHPMailer\PHPMailer\Exception; + +/** + * Class MailCatcher replaces the \PHPMailer\PHPMailer\PHPMailer introduced in WP 5.5 and + * modifies the email sending logic. Thus, we can use other mailers API to do what we need, or stop emails completely. + * + * @since 2.2.0 + */ +class MailCatcherV6 extends \PHPMailer\PHPMailer\PHPMailer implements MailCatcherInterface { + + use MailCatcherTrait; + + /** + * Which validator to use by default when validating email addresses. + * We are using built-in WordPress function `is_email` to validate the email address. + * + * @see PHPMailer::validateAddress() + * + * @since 3.6.0 + * + * @var string|callable + */ + public static $validator = [ Processor::class, 'is_email_callback' ]; + + /** + * Get the PHPMailer line ending. + * + * @since 2.2.0 + * + * @return string + */ + public function get_line_ending() { + + return static::$LE; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + + /** + * Throw PHPMailer exception. + * + * @since 4.0.0 + * + * @param string $error Error message. + * + * @throws Exception PHPMailer exception. + */ + protected function throw_exception( $error ) { + + throw new Exception( $error ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Migration.php b/web/app/plugins/wp-mail-smtp-pro/src/Migration.php new file mode 100644 index 00000000..0c23c50a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Migration.php @@ -0,0 +1,517 @@ +<?php + +namespace WPMailSMTP; + +use WPMailSMTP\Reports\Emails\Summary as SummaryReportEmail; +use WPMailSMTP\Tasks\Meta; +use WPMailSMTP\Tasks\Reports\SummaryEmailTask as SummaryReportEmailTask; +use WPMailSMTP\Tasks\Tasks; + +/** + * Class Migration helps migrate plugin options, DB tables and more. + * + * @since 1.0.0 Migrate all plugin options saved from separate WP options into one. + * @since 2.1.0 Major overhaul of this class to use DB migrations (or any other migrations per version). + * @since 3.0.0 Extends MigrationAbstract. + */ +class Migration extends MigrationAbstract { + + /** + * Version of the latest migration. + * + * @since 2.1.0 + */ + const VERSION = 5; + + /** + * Option key where we save the current migration version. + * + * @since 2.1.0 + */ + const OPTION_NAME = 'wp_mail_smtp_migration_version'; + + /** + * Current migration version, received from static::OPTION_NAME WP option. + * + * @since 2.1.0 + * + * @var int + */ + protected $cur_ver; + + /** + * All old values for pre 1.0 version of a plugin. + * + * @since 1.0.0 + * + * @var array + */ + protected $old_keys = array( + 'pepipost_ssl', + 'pepipost_port', + 'pepipost_pass', + 'pepipost_user', + 'smtp_pass', + 'smtp_user', + 'smtp_auth', + 'smtp_ssl', + 'smtp_port', + 'smtp_host', + 'mail_set_return_path', + 'mailer', + 'mail_from_name', + 'mail_from', + 'wp_mail_smtp_am_notifications_hidden', + ); + + /** + * Old values, taken from $old_keys options. + * + * @since 1.0.0 + * + * @var array + */ + protected $old_values = array(); + + /** + * Converted array of data from previous option values. + * + * @since 1.0.0 + * + * @var array + */ + protected $new_values = array(); + + /** + * Initialize migration. + * + * @since 3.0.0 + */ + public function init() { + + $this->maybe_migrate(); + } + + /** + * Static on purpose, to get current migration version without __construct() and validation. + * + * @since 2.1.0 + * + * @return int + */ + public static function get_cur_version() { + + return (int) get_option( static::OPTION_NAME, 0 ); + } + + /** + * Run the migration if needed. + * + * @since 2.1.0 + */ + protected function maybe_migrate() { + + if ( version_compare( $this->cur_ver, static::VERSION, '<' ) ) { + $this->run( static::VERSION ); + } + } + + /** + * Actual migration launcher. + * + * @since 2.1.0 + * + * @param int $version The version of migration to run. + */ + protected function run( $version ) { + + $function_version = (int) $version; + + if ( method_exists( $this, 'migrate_to_' . $function_version ) ) { + $this->{'migrate_to_' . $function_version}(); + } else { + if ( WP::in_wp_admin() ) { + $message = sprintf( /* translators: %1$s - WP Mail SMTP, %2$s - error message. */ + esc_html__( 'There was an error while upgrading the database. Please contact %1$s support with this information: %2$s.', 'wp-mail-smtp' ), + '<strong>WP Mail SMTP</strong>', + '<code>migration from v' . static::get_cur_version() . ' to v' . static::VERSION . ' failed. Plugin version: v' . WPMS_PLUGIN_VER . '</code>' + ); + + WP::add_admin_notice( $message, WP::ADMIN_NOTICE_ERROR ); + } + } + } + + /** + * Update migration version in options table. + * + * @since 2.1.0 + * + * @param int $version Migration version. + */ + protected function update_db_ver( $version = 0 ) { + + if ( empty( $version ) ) { + $version = static::VERSION; + } + + // Autoload it, because this value is checked all the time + // and no need to request it separately from all autoloaded options. + update_option( static::OPTION_NAME, $version, true ); + } + + /** + * Prevent running the same migration twice. + * Run migration only when required. + * + * @since 2.1.0 + * + * @param string $version The version of migration to check for potential execution. + */ + protected function maybe_required_older_migrations( $version ) { + + if ( version_compare( $this->cur_ver, $version, '<' ) ) { + $this->run( $version ); + } + } + + /** + * Migration from 0.x to 1.0.0. + * Move separate plugin WP options to one main plugin WP option setting. + * + * @since 2.1.0 + */ + private function migrate_to_1() { + + if ( $this->is_migrated() ) { + return; + } + + $this->old_values = $this->get_old_values(); + $this->new_values = $this->get_converted_options(); + + Options::init()->set( $this->new_values, true ); + + $this->update_db_ver( 1 ); + } + + /** + * Migration from 1.x to 2.1.0. + * Create Tasks\Meta table, if it does not exist. + * + * @since 2.1.0 + */ + private function migrate_to_2() { + + $this->maybe_required_older_migrations( 1 ); + + $meta = new Meta(); + + // Create the table if it doesn't exist. + if ( $meta && ! $meta->table_exists() ) { + $meta->create_table(); + } + + $this->update_db_ver( 2 ); + } + + /** + * Migration to 2.6.0. + * Cancel all recurring ActionScheduler tasks, so they will be newly created and no longer + * cause PHP fatal error on PHP 8 (because of the named parameter 'tasks_meta_id'). + * + * @since 2.6.0 + */ + private function migrate_to_3() { + + $this->maybe_required_older_migrations( 2 ); + + $tasks = []; + $ut = new UsageTracking\UsageTracking(); + + if ( $ut->is_enabled() ) { + $tasks[] = '\WPMailSMTP\UsageTracking\SendUsageTask'; + } + + $recurring_tasks = apply_filters( 'wp_mail_smtp_migration_cancel_recurring_tasks', $tasks ); + + foreach ( $recurring_tasks as $task ) { + ( new $task() )->cancel(); + } + + $this->update_db_ver( 3 ); + } + + /** + * Migration to 3.0.0. + * Disable summary report email for Lite users and Multisite installations after update. + * For new installations we have default values in Options::get_defaults. + * + * @since 3.0.0 + */ + protected function migrate_to_4() { + + $this->maybe_required_older_migrations( 3 ); + + $options = Options::init(); + + $value = $options->get( 'general', SummaryReportEmail::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() ) + ) { + $data = [ + 'general' => [ + SummaryReportEmail::SETTINGS_SLUG => true, + ], + ]; + + $options->set( $data, false, false ); + + // Just to be safe cancel summary report email task. + ( new SummaryReportEmailTask() )->cancel(); + } + + $this->update_db_ver( 4 ); + } + + /** + * Cleanup scheduled actions meta table. + * + * @since 3.5.0 + */ + protected function migrate_to_5() { + + $this->maybe_required_older_migrations( 4 ); + + global $wpdb; + + $meta = new Meta(); + + if ( + $meta->table_exists() && + $meta->table_exists( $wpdb->prefix . 'actionscheduler_actions' ) && + $meta->table_exists( $wpdb->prefix . 'actionscheduler_groups' ) + ) { + $group = Tasks::GROUP; + $sql = "SELECT DISTINCT a.args FROM {$wpdb->prefix}actionscheduler_actions a + JOIN {$wpdb->prefix}actionscheduler_groups g ON g.group_id = a.group_id + WHERE g.slug = '$group' AND a.status IN ('pending', 'in-progress')"; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( $sql, 'ARRAY_A' ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + + $results = $results ? $results : []; + $meta_ids = []; + + foreach ( $results as $result ) { + $args = isset( $result['args'] ) ? json_decode( $result['args'], true ) : null; + + if ( $args && isset( $args[0] ) && is_numeric( $args[0] ) ) { + $meta_ids[] = $args[0]; + } + } + + $table = Meta::get_table_name(); + $not_in = 0; + + if ( ! empty( $meta_ids ) ) { + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $not_in = $wpdb->prepare( implode( ',', array_fill( 0, count( $meta_ids ), '%d' ) ), $meta_ids ); + } + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query( "DELETE FROM $table WHERE id NOT IN ($not_in)" ); + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + } + + // Save the current version to DB. + $this->update_db_ver( 5 ); + } + + /** + * Whether we already migrated or not. + * + * @since 1.0.0 + * + * @return bool + */ + protected function is_migrated() { + + $is_migrated = false; + $new_values = get_option( Options::META_KEY, array() ); + + if ( ! empty( $new_values ) ) { + $is_migrated = true; + } + + return $is_migrated; + } + + /** + * Get all old values from DB. + * + * @since 1.0.0 + * + * @return array + */ + protected function get_old_values() { + + $old_values = array(); + + foreach ( $this->old_keys as $old_key ) { + $value = get_option( $old_key, '' ); + + if ( ! empty( $value ) ) { + $old_values[ $old_key ] = $value; + } + } + + return $old_values; + } + + /** + * Convert old values from key=>value to a multidimensional array of data. + * + * @since 1.0.0 + */ + protected function get_converted_options() { + + $converted = array(); + + foreach ( $this->old_keys as $old_key ) { + + $old_value = isset( $this->old_values[ $old_key ] ) ? $this->old_values[ $old_key ] : ''; + + switch ( $old_key ) { + case 'pepipost_user': + case 'pepipost_pass': + case 'pepipost_port': + case 'pepipost_ssl': + // Do not migrate pepipost options if it's not activated at the moment. + if ( isset( $this->old_values['mailer'] ) && $this->old_values['mailer'] === 'pepipost' ) { + $shortcut = explode( '_', $old_key ); + + if ( $old_key === 'pepipost_ssl' ) { + $converted[ $shortcut[0] ]['encryption'] = $old_value; + } else { + $converted[ $shortcut[0] ][ $shortcut[1] ] = $old_value; + } + } + break; + + case 'smtp_host': + case 'smtp_port': + case 'smtp_ssl': + case 'smtp_auth': + case 'smtp_user': + case 'smtp_pass': + $shortcut = explode( '_', $old_key ); + + if ( $old_key === 'smtp_ssl' ) { + $converted[ $shortcut[0] ]['encryption'] = $old_value; + } elseif ( $old_key === 'smtp_auth' ) { + $converted[ $shortcut[0] ][ $shortcut[1] ] = ( $old_value === 'true' ? 'yes' : 'no' ); + } else { + $converted[ $shortcut[0] ][ $shortcut[1] ] = $old_value; + } + + break; + + case 'mail_from': + $converted['mail']['from_email'] = $old_value; + break; + case 'mail_from_name': + $converted['mail']['from_name'] = $old_value; + break; + case 'mail_set_return_path': + $converted['mail']['return_path'] = ( $old_value === 'true' ); + break; + case 'mailer': + $converted['mail']['mailer'] = ! empty( $old_value ) ? $old_value : 'mail'; + break; + case 'wp_mail_smtp_am_notifications_hidden': + $converted['general']['am_notifications_hidden'] = ( isset( $old_value ) && $old_value === 'true' ); + break; + } + } + + $converted = $this->get_converted_constants_options( $converted ); + + return $converted; + } + + /** + * Some users use constants in wp-config.php to define values. + * We need to prioritize them and reapply data to options. + * Use only those that are actually defined. + * + * @since 1.0.0 + * + * @param array $converted + * + * @return array + */ + protected function get_converted_constants_options( $converted ) { + + // Are we configured via constants? + if ( ! defined( 'WPMS_ON' ) || ! WPMS_ON ) { + return $converted; + } + + /* + * Mail settings. + */ + if ( defined( 'WPMS_MAIL_FROM' ) ) { + $converted['mail']['from_email'] = WPMS_MAIL_FROM; + } + if ( defined( 'WPMS_MAIL_FROM_NAME' ) ) { + $converted['mail']['from_name'] = WPMS_MAIL_FROM_NAME; + } + if ( defined( 'WPMS_MAILER' ) ) { + $converted['mail']['mailer'] = WPMS_MAILER; + } + if ( defined( 'WPMS_SET_RETURN_PATH' ) ) { + $converted['mail']['return_path'] = WPMS_SET_RETURN_PATH; + } + + /* + * SMTP settings. + */ + if ( defined( 'WPMS_SMTP_HOST' ) ) { + $converted['smtp']['host'] = WPMS_SMTP_HOST; + } + if ( defined( 'WPMS_SMTP_PORT' ) ) { + $converted['smtp']['port'] = WPMS_SMTP_PORT; + } + if ( defined( 'WPMS_SSL' ) ) { + $converted['smtp']['ssl'] = WPMS_SSL; + } + if ( defined( 'WPMS_SMTP_AUTH' ) ) { + $converted['smtp']['auth'] = WPMS_SMTP_AUTH; + } + if ( defined( 'WPMS_SMTP_USER' ) ) { + $converted['smtp']['user'] = WPMS_SMTP_USER; + } + if ( defined( 'WPMS_SMTP_PASS' ) ) { + $converted['smtp']['pass'] = WPMS_SMTP_PASS; + } + + return $converted; + } + + /** + * Delete all old values that are stored separately each. + * + * @since 1.0.0 + */ + protected function clean_deprecated_data() { + + foreach ( $this->old_keys as $old_key ) { + delete_option( $old_key ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/MigrationAbstract.php b/web/app/plugins/wp-mail-smtp-pro/src/MigrationAbstract.php new file mode 100644 index 00000000..088571f1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/MigrationAbstract.php @@ -0,0 +1,161 @@ +<?php + +namespace WPMailSMTP; + +/** + * Class MigrationAbstract helps migrate plugin options, DB tables and more. + * + * @since 3.0.0 + */ +abstract class MigrationAbstract { + + /** + * Version of the latest migration. + * + * @since 3.0.0 + */ + const DB_VERSION = 1; + + /** + * Option key where we save the current migration version. + * + * @since 3.0.0 + */ + const OPTION_NAME = 'wp_mail_smtp_migration_version'; + + /** + * Option key where we save any errors while performing migration. + * + * @since 3.0.0 + */ + const ERROR_OPTION_NAME = 'wp_mail_smtp_migration_error'; + + /** + * Current migration version, received from static::OPTION_NAME WP option + * + * @since 3.0.0 + * + * @var int + */ + protected $cur_ver; + + /** + * Migration constructor. + * + * @since 3.0.0 + */ + public function __construct() { + + $this->cur_ver = static::get_current_version(); + } + + /** + * Initialize migration. + * + * @since 3.0.0 + */ + public function init() { + + $this->validate_db(); + } + + /** + * Whether migration is enabled. + * + * @since 3.0.0 + * + * @return bool + */ + public static function is_enabled() { + + return true; + } + + /** + * Static on purpose, to get current DB version without __construct() and validation. + * + * @since 3.0.0 + * + * @return int + */ + public static function get_current_version() { + + return (int) get_option( static::OPTION_NAME, 0 ); + } + + /** + * Check DB version and update to the latest one. + * + * @since 3.0.0 + */ + protected function validate_db() { + + if ( $this->cur_ver < static::DB_VERSION ) { + $this->run( static::DB_VERSION ); + } + } + + /** + * Update DB version in options table. + * + * @since 3.0.0 + * + * @param int $version Version number. + */ + protected function update_db_ver( $version = 0 ) { + + $version = (int) $version; + + if ( empty( $version ) ) { + $version = static::DB_VERSION; + } + + // Autoload it, because this value is checked all the time + // and no need to request it separately from all autoloaded options. + update_option( static::OPTION_NAME, $version, true ); + } + + /** + * Prevent running the same migration twice. + * Run migration only when required. + * + * @since 3.0.0 + * + * @param int $version The current migration version. + */ + protected function maybe_required_older_migrations( $version ) { + + $version = (int) $version; + + if ( ( $version - $this->cur_ver ) > 1 ) { + $this->run( $version - 1 ); + } + } + + /** + * Actual migration launcher. + * + * @since 3.0.0 + * + * @param int $version The specified migration version to run. + */ + protected function run( $version ) { + + $version = (int) $version; + + if ( method_exists( $this, 'migrate_to_' . $version ) ) { + $this->{'migrate_to_' . $version}(); + } else { + if ( WP::in_wp_admin() ) { + $message = sprintf( /* translators: %1$s - the DB option name, %2$s - WP Mail SMTP, %3$s - error message. */ + esc_html__( 'There was an error while upgrading the %1$s database. Please contact %2$s support with this information: %3$s.', 'wp-mail-smtp' ), + static::OPTION_NAME, + '<strong>WP Mail SMTP</strong>', + '<code>migration from v' . static::get_current_version() . ' to v' . static::DB_VERSION . ' failed. Plugin version: v' . WPMS_PLUGIN_VER . '</code>' + ); + + WP::add_admin_notice( $message, WP::ADMIN_NOTICE_ERROR ); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Migrations.php b/web/app/plugins/wp-mail-smtp-pro/src/Migrations.php new file mode 100644 index 00000000..1901f33f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Migrations.php @@ -0,0 +1,185 @@ +<?php + +namespace WPMailSMTP; + +use WP_Upgrader; +use WPMailSMTP\Admin\DebugEvents\Migration as DebugEventsMigration; +use WPMailSMTP\Admin\Recommendations\Migration as RecommendationsMigration; +use WPMailSMTP\Queue\Migration as QueueMigration; + +/** + * Class Migrations. + * + * @since 4.0.0 + */ +class Migrations { + + /** + * Register hooks. + * + * @since 4.0.0 + */ + public function hooks() { + + // Initialize migrations during request in the admin panel only. + add_action( 'admin_init', [ $this, 'init_migrations_on_request' ] ); + + // Initialize migrations after plugin update. + add_action( 'upgrader_process_complete', [ $this, 'init_migrations_after_upgrade' ], PHP_INT_MAX, 2 ); + add_action( + 'wp_ajax_nopriv_wp_mail_smtp_init_migrations', + [ $this, 'init_migrations_ajax_handler' ] + ); + } + + /** + * Initialize DB migrations during request. + * + * @since 4.0.0 + */ + public function init_migrations_on_request() { + + // Do not initialize migrations during AJAX and cron requests. + if ( WP::is_doing_ajax() || wp_doing_cron() ) { + return; + } + + $this->init_migrations(); + } + + /** + * Initialize DB migrations. + * + * @since 4.0.0 + */ + private function init_migrations() { + + $migrations = $this->get_migrations(); + + foreach ( $migrations as $migration ) { + if ( is_subclass_of( $migration, MigrationAbstract::class ) && $migration::is_enabled() ) { + ( new $migration() )->init(); + } + } + } + + /** + * Get migrations classes. + * + * @since 4.0.0 + * + * @return array Migrations classes. + */ + private function get_migrations() { + + $migrations = [ + Migration::class, + DebugEventsMigration::class, + QueueMigration::class, + RecommendationsMigration::class, + ]; + + /** + * Filters DB migrations classes. + * + * @deprecated 4.0.0 + * + * @since 3.0.0 + * + * @param array $migrations Migrations classes. + */ + $migrations = apply_filters_deprecated( + 'wp_mail_smtp_core_init_migrations', + [ $migrations ], + '3.10.0', + 'wp_mail_smtp_migrations_get_migrations' + ); + + /** + * Filters DB migrations classes. + * + * @since 4.0.0 + * + * @param array $migrations Migrations classes. + */ + return apply_filters( 'wp_mail_smtp_migrations_get_migrations', $migrations ); + } + + /** + * Initialize DB migrations after plugin update. + * Initiate ajax call to perform the migration with the new plugin version code. + * + * @since 4.0.0 + * + * @param WP_Upgrader $upgrader WP_Upgrader instance. + * @param array $options Array of update data. + */ + public function init_migrations_after_upgrade( $upgrader, $options ) { + + if ( + // Skip if in admin panel. + ( is_admin() && ! wp_doing_ajax() ) || + // Skip if it's update from plugins list page. + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ( wp_doing_ajax() && isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'update-plugin' ) + ) { + return; + } + + $plugins = []; + + if ( isset( $options['plugins'] ) && is_array( $options['plugins'] ) ) { + $plugins = $options['plugins']; + } elseif ( isset( $options['plugin'] ) && is_string( $options['plugin'] ) ) { + $plugins = [ $options['plugin'] ]; + } + + if ( + ! in_array( 'wp-mail-smtp/wp_mail_smtp.php', $plugins, true ) && + ! in_array( 'wp-mail-smtp-pro/wp_mail_smtp.php', $plugins, true ) + ) { + return; + } + + $secret = wp_hash( 'wp_mail_smtp_init_migrations' . wp_salt() ); + + $url = add_query_arg( + [ + 'action' => 'wp_mail_smtp_init_migrations', + ], + admin_url( 'admin-ajax.php' ) + ); + + $timeout = (int) ini_get( 'max_execution_time' ); + + $args = [ + 'sslverify' => false, + 'timeout' => $timeout ? $timeout : 30, + 'body' => [ + 'secret' => $secret, + ], + ]; + + wp_remote_post( $url, $args ); + } + + /** + * Initialize migrations via AJAX request. + * + * @since 4.0.0 + */ + public function init_migrations_ajax_handler() { + + // Verify the secret hash since this is a nopriv endpoint. + $secret = ! empty( $_POST['secret'] ) ? sanitize_text_field( wp_unslash( $_POST['secret'] ) ) : ''; + $expected = wp_hash( 'wp_mail_smtp_init_migrations' . wp_salt() ); + + if ( ! hash_equals( $expected, $secret ) ) { + wp_send_json_error(); + } + + $this->init_migrations(); + + wp_send_json_success(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/OptimizedEmailSending.php b/web/app/plugins/wp-mail-smtp-pro/src/OptimizedEmailSending.php new file mode 100644 index 00000000..2b68e1ba --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/OptimizedEmailSending.php @@ -0,0 +1,59 @@ +<?php + +namespace WPMailSMTP; + +/** + * OptimizedEmailSending class. + * + * @since 4.0.0 + */ +class OptimizedEmailSending { + + /** + * The slug of the option that toggles optimized email sending. + * + * @since 4.0.0 + */ + const SETTINGS_SLUG = 'optimize_email_sending_enabled'; + + /** + * Register hooks. + * + * @since 4.0.0 + */ + public function hooks() { + + if ( self::is_enabled() ) { + // Enable the queue. + add_filter( 'wp_mail_smtp_queue_is_enabled', '__return_true' ); + + // Avoid enqueueing emails if current request + // is a cron request, a CLI request, + // or an ActionScheduler task as 3rd party plugins might + // be carrying out their own sending optimizations + // through it. + if ( + ! ( defined( 'WP_CLI' ) && WP_CLI ) && + ! wp_doing_cron() && + ! doing_action( 'action_scheduler_run_queue' ) + ) { + // Start enqueueing emails. + add_filter( 'wp_mail_smtp_mail_catcher_send_enqueue_email', '__return_true' ); + } + } + } + + /** + * Whether optimized email sending is enabled. + * + * @since 4.0.0 + * + * @return bool + */ + public static function is_enabled() { + + $value = Options::init()->get( 'general', self::SETTINGS_SLUG ); + + return (bool) $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Options.php new file mode 100644 index 00000000..0c494a70 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Options.php @@ -0,0 +1,1824 @@ +<?php + +namespace WPMailSMTP; + +use WPMailSMTP\Helpers\Crypto; +use WPMailSMTP\Reports\Emails\Summary as SummaryReportEmail; +use WPMailSMTP\UsageTracking\UsageTracking; + +/** + * Class Options to handle all options management. + * WordPress does all the heavy work for caching get_option() data, + * so we don't have to do that. But we want to minimize cyclomatic complexity + * of calling a bunch of WP functions, thus we will cache them in a class as well. + * + * @since 1.0.0 + */ +class Options { + + /** + * All the options keys. + * + * @since 1.3.0 + * @since 1.4.0 Added Mailgun:region. + * @since 1.5.0 Added Outlook/AmazonSES. + * @since 1.8.0 Added Pepipost API. + * @since 2.0.0 Added SMTP.com API. + * + * @var array Map of all the default options of the plugin. + */ + private static $map = [ + 'mail' => [ + 'from_name', + 'from_email', + 'mailer', + 'return_path', + 'from_name_force', + 'from_email_force', + ], + 'smtp' => [ + 'host', + 'port', + 'encryption', + 'autotls', + 'auth', + 'user', + 'pass', + ], + 'gmail' => [ + 'one_click_setup_enabled', + 'client_id', + 'client_secret', + ], + 'outlook' => [ + 'one_click_setup_enabled', + 'client_id', + 'client_secret', + ], + 'zoho' => [ + 'domain', + 'client_id', + 'client_secret', + ], + 'amazonses' => [ + 'client_id', + 'client_secret', + 'region', + ], + 'mailgun' => [ + 'api_key', + 'domain', + 'region', + ], + 'mailjet' => [ + 'api_key', + 'secret_key', + ], + 'mailersend' => [ + 'api_key', + 'has_pro_plan', + ], + 'mandrill' => [ + 'api_key', + ], + 'sendgrid' => [ + 'api_key', + 'domain', + ], + 'sparkpost' => [ + 'api_key', + 'region', + ], + 'postmark' => [ + 'server_api_token', + 'message_stream', + ], + 'smtpcom' => [ + 'api_key', + 'channel', + ], + 'sendinblue' => [ + 'api_key', + 'domain', + ], + 'sendlayer' => [ + 'api_key', + ], + 'elasticemail' => [ + 'api_key', + ], + 'smtp2go' => [ + 'api_key', + ], + 'resend' => [ + 'api_key', + ], + 'pepipostapi' => [ + 'api_key', + ], + 'pepipost' => [ + 'host', + 'port', + 'encryption', + 'auth', + 'user', + 'pass', + ], + 'license' => [ + 'key', + ], + 'alert_email' => [ + 'enabled', + 'connections', + ], + 'alert_slack_webhook' => [ + 'enabled', + 'connections', + ], + 'alert_discord_webhook' => [ + 'enabled', + 'connections', + ], + 'alert_twilio_sms' => [ + 'enabled', + 'connections', + ], + 'alert_custom_webhook' => [ + 'enabled', + 'connections', + ], + 'alert_push_notifications' => [ + 'enabled', + 'connections', + ], + 'alert_whatsapp' => [ + 'enabled', + 'connections', + ], + 'alert_events' => [ + 'email_hard_bounced', + ], + ]; + + /** + * List of all mailers (except PHP default mailer 'mail'). + * + * @since 3.3.0 + * + * @var string[] + */ + public static $mailers = [ + 'sendlayer', + 'smtpcom', + 'sendinblue', + 'amazonses', + 'gmail', + 'mailgun', + 'mailjet', + 'mailersend', + 'mandrill', + 'outlook', + 'postmark', + 'resend', + 'sendgrid', + 'sparkpost', + 'zoho', + 'elasticemail', + 'smtp2go', + 'smtp', + 'pepipost', + 'pepipostapi', + ]; + + /** + * That's where plugin options are saved in wp_options table. + * + * @since 1.0.0 + * + * @var string + */ + const META_KEY = 'wp_mail_smtp'; + + /** + * All instances of Options class that should be notified about options update. + * + * @since 3.7.0 + * + * @var Options[] + */ + protected static $update_observers; + + /** + * Options data. + * + * @since 3.7.0 + * + * @var array + */ + protected $options = []; + + /** + * Init the Options class. + * TODO: add a flag to process without retrieving const values. + * + * @since 1.0.0 + * @since 3.3.0 Deprecated instantiation via new keyword. `Options::init()` must be used. + */ + public function __construct() { + + // Store all class instances that will be notified about options update. + static::$update_observers[] = $this; + + $this->populate_options(); + } + + /** + * Initialize all the options. + * + * @since 1.0.0 + * + * @return Options + */ + public static function init() { + + static $instance; + + if ( ! $instance ) { + $instance = new self(); + } + + return $instance; + } + + /** + * Whether current class is a main options. + * + * @since 3.7.0 + * + * @var bool + */ + protected function is_main_options() { + + return true; + } + + /** + * Default options that are saved on plugin activation. + * + * @since 1.3.0 + * @since 2.1.0 Set the Force from email to "on" by default. + * + * @return array + */ + public static function get_defaults() { + + $defaults = [ + 'mail' => [ + 'from_email' => get_option( 'admin_email' ), + 'from_name' => get_bloginfo( 'name' ), + 'mailer' => 'mail', + 'return_path' => true, + 'from_email_force' => true, + 'from_name_force' => false, + ], + 'smtp' => [ + 'autotls' => true, + 'auth' => true, + ], + 'general' => [ + SummaryReportEmail::SETTINGS_SLUG => ! is_multisite() ? false : true, + ], + ]; + + /** + * Filters the default options. + * + * @since 3.11.0 + * + * @param array $defaults Default options. + */ + return apply_filters( 'wp_mail_smtp_options_get_defaults', $defaults ); + } + + /** + * Retrieve all options of the plugin. + * + * @since 1.0.0 + * @since 2.2.0 Added the filter. + */ + protected function populate_options() { + + $this->options = apply_filters( 'wp_mail_smtp_populate_options', get_option( static::META_KEY, [] ) ); + } + + /** + * Get all the options. + * + * Options::init()->get_all(); + * + * @since 1.0.0 + * + * @return array + */ + public function get_all() { + + $options = $this->options; + + foreach ( $options as $group => $g_value ) { + foreach ( $g_value as $key => $value ) { + $options[ $group ][ $key ] = $this->get( $group, $key ); + } + } + + return $this->is_main_options() ? apply_filters( 'wp_mail_smtp_options_get_all', $options ) : $options; + } + + /** + * Get all the options for a group. + * + * Options::init()->get_group('smtp') - will return the array of options for the group, including defaults and constants. + * + * @since 1.0.0 + * @since 1.5.0 Process values through the get() method which is aware of constants. + * + * @param string $group + * + * @return array + */ + public function get_group( $group ) { + + // Just to feel safe. + $group = sanitize_key( $group ); + + /* + * Get the values saved in DB. + * If plugin is configured with constants right from the start - this will not have all the values. + */ + $options = isset( $this->options[ $group ] ) ? $this->options[ $group ] : []; + + // We need to process certain constants-aware options through actual constants. + if ( isset( self::$map[ $group ] ) ) { + foreach ( self::$map[ $group ] as $key ) { + $options[ $key ] = $this->get( $group, $key ); + } + } + + return $this->is_main_options() ? apply_filters( 'wp_mail_smtp_options_get_group', $options, $group ) : $options; + } + + /** + * Get options by a group and a key. + * + * Options::init()->get( 'smtp', 'host' ) - will return only SMTP 'host' option. + * + * @since 1.0.0 + * @since 2.5.0 Added $strip_slashes method parameter. + * + * @param string $group The option group. + * @param string $key The option key. + * @param bool $strip_slashes If the slashes should be stripped from string values. + * + * @return mixed|null Null if value doesn't exist anywhere: in constants, in DB, in a map. So it's completely custom or a typo. + */ + public function get( $group, $key, $strip_slashes = true ) { + + // Just to feel safe. + $group = sanitize_key( $group ); + $key = sanitize_key( $key ); + $value = null; + + // Get the const value if we have one. + $value = $this->get_const_value( $group, $key, $value ); + + // We don't have a const value. + if ( $value === null ) { + // Ordinary database or default values. + if ( isset( $this->options[ $group ] ) ) { + // Get the options key of a group. + if ( isset( $this->options[ $group ][ $key ] ) ) { + $value = $this->get_existing_option_value( $group, $key ); + } else { + $value = $this->postprocess_key_defaults( $group, $key ); + } + } else { + /* + * Fallback to default if it doesn't exist in a map. + * Allow to retrieve only values from a map. + */ + if ( + isset( self::$map[ $group ] ) && + in_array( $key, self::$map[ $group ], true ) + ) { + $value = $this->postprocess_key_defaults( $group, $key ); + } + } + } + + // Conditionally strip slashes only from values saved in DB. Constants should be processed as is. + if ( $strip_slashes && is_string( $value ) && ! $this->is_const_defined( $group, $key ) ) { + $value = stripslashes( $value ); + } + + return $this->is_main_options() ? apply_filters( 'wp_mail_smtp_options_get', $value, $group, $key ) : $value; + } + + /** + * Get the existing cached option value. + * + * @since 2.5.0 + * + * @param string $group The options group. + * @param string $key The options key. + * + * @return mixed + */ + private function get_existing_option_value( $group, $key ) { + + if ( $group === 'smtp' && $key === 'pass' ) { + try { + return Crypto::decrypt( $this->options[ $group ][ $key ] ); + } catch ( \Exception $e ) { + return $this->options[ $group ][ $key ]; + } + } + + return $this->options[ $group ][ $key ]; + } + + /** + * Some options may be non-empty by default, + * so we need to postprocess them to convert. + * + * @since 1.0.0 + * @since 1.4.0 Added Mailgun:region. + * @since 1.5.0 Added Outlook/AmazonSES, license key support. + * + * @param string $group + * @param string $key + * + * @return mixed + */ + protected function postprocess_key_defaults( $group, $key ) { + + $value = ''; + + switch ( $key ) { + case 'from_email_force': + case 'from_name_force': + case 'return_path': + $value = $group === 'mail' ? false : true; + break; + + case 'mailer': + $value = 'mail'; + break; + + case 'encryption': + $value = in_array( $group, [ 'smtp', 'pepipost' ], true ) ? 'none' : $value; + break; + + case 'region': + $value = $group === 'mailgun' || $group === 'sparkpost' ? 'US' : $value; + break; + + case 'auth': + case 'autotls': + $value = in_array( $group, [ 'smtp', 'pepipost' ], true ) ? false : true; + break; + + case 'pass': + $value = $this->get_const_value( $group, $key, $value ); + break; + + case 'type': + $value = $group === 'license' ? 'lite' : ''; + break; + } + + return apply_filters( 'wp_mail_smtp_options_postprocess_key_defaults', $value, $group, $key ); + } + + /** + * Process the options values through the constants check. + * If we have defined associated constant - use it instead of a DB value. + * Backward compatibility is hard. + * General section of options won't have constants, so we are omitting those checks and just return default value. + * + * @since 1.0.0 + * @since 1.4.0 Added WPMS_MAILGUN_REGION. + * @since 1.5.0 Added Outlook/AmazonSES, license key support. + * @since 1.6.0 Added Sendinblue. + * @since 1.7.0 Added Do Not Send. + * @since 1.8.0 Added Pepipost API. + * @since 3.6.0 Added Debug Events Retention Period. + * + * @param string $group + * @param string $key + * @param mixed $value + * + * @return mixed + */ + protected function get_const_value( $group, $key, $value ) { + + if ( ! $this->is_const_enabled() ) { + return $value; + } + + $return = null; + + // phpcs:disable WPForms.Formatting.Switch.AddEmptyLineBefore, WPForms.Formatting.Switch.RemoveEmptyLineBefore + switch ( $group ) { + case 'mail': + switch ( $key ) { + case 'from_name': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAIL_FROM_NAME : $value; + break; + case 'from_email': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAIL_FROM : $value; + break; + case 'mailer': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILER : $value; + break; + case 'return_path': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SET_RETURN_PATH : $value; + break; + case 'from_name_force': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAIL_FROM_NAME_FORCE : $value; + break; + case 'from_email_force': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAIL_FROM_FORCE : $value; + break; + } + + break; + + case 'smtp': + switch ( $key ) { + case 'host': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTP_HOST : $value; + break; + case 'port': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTP_PORT : $value; + break; + case 'encryption': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? ( WPMS_SSL === '' ? 'none' : WPMS_SSL ) : $value; + break; + case 'auth': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? (bool) WPMS_SMTP_AUTH : $value; + break; + case 'autotls': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? (bool) WPMS_SMTP_AUTOTLS : $value; + break; + case 'user': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTP_USER : $value; + break; + case 'pass': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTP_PASS : $value; + break; + } + + break; + + case 'sendlayer': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SENDLAYER_API_KEY : $value; + break; + } + + break; + + case 'gmail': + switch ( $key ) { + case 'client_id': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_GMAIL_CLIENT_ID : $value; + break; + case 'client_secret': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_GMAIL_CLIENT_SECRET : $value; + break; + } + + break; + + case 'outlook': + switch ( $key ) { + case 'client_id': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_OUTLOOK_CLIENT_ID : $value; + break; + case 'client_secret': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_OUTLOOK_CLIENT_SECRET : $value; + break; + } + + break; + + case 'zoho': + switch ( $key ) { + case 'domain': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_ZOHO_DOMAIN : $value; + break; + case 'client_id': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_ZOHO_CLIENT_ID : $value; + break; + case 'client_secret': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_ZOHO_CLIENT_SECRET : $value; + break; + } + + break; + + case 'amazonses': + switch ( $key ) { + case 'client_id': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_AMAZONSES_CLIENT_ID : $value; + break; + case 'client_secret': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_AMAZONSES_CLIENT_SECRET : $value; + break; + case 'region': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_AMAZONSES_REGION : $value; + break; + } + + break; + + case 'mailgun': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILGUN_API_KEY : $value; + break; + case 'domain': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILGUN_DOMAIN : $value; + break; + case 'region': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILGUN_REGION : $value; + break; + } + + break; + + case 'mailjet': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILJET_API_KEY : $value; + break; + case 'secret_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILJET_SECRET_KEY : $value; + break; + } + + break; + + case 'sendgrid': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SENDGRID_API_KEY : $value; + break; + case 'domain': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SENDGRID_DOMAIN : $value; + break; + } + + break; + + case 'sparkpost': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SPARKPOST_API_KEY : $value; + break; + case 'region': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SPARKPOST_REGION : $value; + break; + } + + break; + + case 'postmark': + switch ( $key ) { + case 'server_api_token': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_POSTMARK_SERVER_API_TOKEN : $value; + break; + case 'message_stream': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_POSTMARK_MESSAGE_STREAM : $value; + break; + } + + break; + + case 'smtpcom': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTPCOM_API_KEY : $value; + break; + case 'channel': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTPCOM_CHANNEL : $value; + break; + } + + break; + + case 'sendinblue': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SENDINBLUE_API_KEY : $value; + break; + case 'domain': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SENDINBLUE_DOMAIN : $value; + break; + } + + break; + + case 'elasticemail': + switch ( $key ) { + case 'api_key': + $return = $this->is_const_defined( $group, $key ) ? WPMS_ELASTICEMAIL_API_KEY : $value; + break; + } + + break; + + case 'smtp2go': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_SMTP2GO_API_KEY : $value; + break; + } + + break; + + case 'resend': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_RESEND_API_KEY : $value; + break; + } + + break; + + case 'pepipostapi': + switch ( $key ) { + case 'api_key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_PEPIPOST_API_KEY : $value; + break; + } + + break; + + case 'alert_email': + switch ( $key ) { + case 'connections': + $return = $this->is_const_defined( $group, $key ) ? [ [ 'send_to' => WPMS_ALERT_EMAIL_SEND_TO ] ] : $value; + break; + } + + break; + + case 'alert_slack_webhook': + switch ( $key ) { + case 'connections': + $return = $this->is_const_defined( $group, $key ) ? [ [ 'webhook_url' => WPMS_ALERT_SLACK_WEBHOOK_URL ] ] : $value; + break; + } + + break; + + case 'alert_discord_webhook': + switch ( $key ) { + case 'connections': + $return = $this->is_const_defined( $group, $key ) ? [ [ 'webhook_url' => WPMS_ALERT_DISCORD_WEBHOOK_URL ] ] : $value; + break; + } + + break; + + case 'alert_teams_webhook': + switch ( $key ) { + case 'connections': + $return = $this->is_const_defined( $group, $key ) ? [ [ 'webhook_url' => WPMS_ALERT_TEAMS_WEBHOOK_URL ] ] : $value; + break; + } + + break; + + case 'alert_twilio_sms': + switch ( $key ) { + case 'connections': + if ( $this->is_const_defined( $group, $key ) ) { + $return = [ + [ + 'account_sid' => WPMS_ALERT_TWILIO_SMS_ACCOUNT_SID, + 'auth_token' => WPMS_ALERT_TWILIO_SMS_AUTH_TOKEN, + 'from_phone_number' => WPMS_ALERT_TWILIO_SMS_FROM_PHONE_NUMBER, + 'to_phone_number' => WPMS_ALERT_TWILIO_SMS_TO_PHONE_NUMBER, + ], + ]; + } else { + $return = $value; + } + break; + } + + break; + + case 'alert_custom_webhook': + switch ( $key ) { + case 'connections': + $return = $this->is_const_defined( $group, $key ) ? [ [ 'webhook_url' => WPMS_ALERT_CUSTOM_WEBHOOK_URL ] ] : $value; + break; + } + + break; + + case 'license': + switch ( $key ) { + case 'key': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_LICENSE_KEY : $value; + break; + } + + break; + + case 'general': + switch ( $key ) { + case 'do_not_send': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_DO_NOT_SEND : $value; + break; + + case SummaryReportEmail::SETTINGS_SLUG: + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? + $this->parse_boolean( WPMS_SUMMARY_REPORT_EMAIL_DISABLED ) : + $value; + break; + + case OptimizedEmailSending::SETTINGS_SLUG: + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? + $this->parse_boolean( WPMS_OPTIMIZED_EMAIL_SENDING_ENABLED ) : + $value; + break; + } + + break; + + case 'debug_events': + switch ( $key ) { + case 'retention_period': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? intval( WPMS_DEBUG_EVENTS_RETENTION_PERIOD ) : $value; + break; + } + + break; + + case 'mailersend': + switch ( $key ) { + case 'api_key': + $return = $this->is_const_defined( $group, $key ) ? WPMS_MAILERSEND_API_KEY : $value; + break; + + case 'has_pro_plan': + $return = $this->is_const_defined( $group, $key ) ? $this->parse_boolean( WPMS_MAILERSEND_HAS_PRO_PLAN ) : $value; + break; + } + break; + + case 'mandrill': + if ( $key === 'api_key' ) { + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = $this->is_const_defined( $group, $key ) ? WPMS_MANDRILL_API_KEY : $value; + } + + break; + + case 'alert_whatsapp': + switch ( $key ) { + case 'connections': + if ( $this->is_const_defined( $group, $key ) ) { + $return = [ + [ + 'access_token' => WPMS_ALERT_WHATSAPP_ACCESS_TOKEN, + 'whatsapp_business_id' => WPMS_ALERT_WHATSAPP_BUSINESS_ID, + 'phone_number_id' => WPMS_ALERT_WHATSAPP_PHONE_NUMBER_ID, + 'to_phone_number' => WPMS_ALERT_WHATSAPP_TO_PHONE_NUMBER, + 'template_language' => WPMS_ALERT_WHATSAPP_TEMPLATE_LANGUAGE, + ], + ]; + } else { + $return = $value; + } + break; + } + + break; + + default: + // Always return the default value if nothing from above matches the request. + $return = $value; + } + + // phpcs:enable WPForms.Formatting.Switch.AddEmptyLineBefore, WPForms.Formatting.Switch.RemoveEmptyLineBefore + + return apply_filters( 'wp_mail_smtp_options_get_const_value', $return, $group, $key, $value ); + } + + /** + * Whether constants redefinition is enabled or not. + * + * @since 1.0.0 + * @since 1.5.0 Added filter to redefine the value. + * + * @return bool + */ + public function is_const_enabled() { + + $return = defined( 'WPMS_ON' ) && WPMS_ON === true; + + return apply_filters( 'wp_mail_smtp_options_is_const_enabled', $return ); + } + + /** + * We need this check to reuse later in admin area, + * to distinguish settings fields that were redefined, + * and display them differently. + * + * @since 1.0.0 + * @since 1.5.0 Added a filter, Outlook/AmazonSES, license key support. + * @since 1.6.0 Added Sendinblue. + * @since 1.7.0 Added Do Not Send. + * @since 1.8.0 Added Pepipost API. + * + * @param string $group + * @param string $key + * + * @return bool + */ + public function is_const_defined( $group, $key ) { + + if ( ! $this->is_const_enabled() ) { + return false; + } + + // Just to feel safe. + $group = sanitize_key( $group ); + $key = sanitize_key( $key ); + $return = false; + + // phpcs:disable WPForms.Formatting.Switch.AddEmptyLineBefore, WPForms.Formatting.Switch.RemoveEmptyLineBefore + switch ( $group ) { + case 'mail': + switch ( $key ) { + case 'from_name': + $return = defined( 'WPMS_MAIL_FROM_NAME' ) && WPMS_MAIL_FROM_NAME; + break; + case 'from_email': + $return = defined( 'WPMS_MAIL_FROM' ) && WPMS_MAIL_FROM; + break; + case 'mailer': + $return = defined( 'WPMS_MAILER' ) && WPMS_MAILER; + break; + case 'return_path': + $return = defined( 'WPMS_SET_RETURN_PATH' ) && ( WPMS_SET_RETURN_PATH === 'true' || WPMS_SET_RETURN_PATH === true ); + break; + case 'from_name_force': + $return = defined( 'WPMS_MAIL_FROM_NAME_FORCE' ) && ( WPMS_MAIL_FROM_NAME_FORCE === 'true' || WPMS_MAIL_FROM_NAME_FORCE === true ); + break; + case 'from_email_force': + $return = defined( 'WPMS_MAIL_FROM_FORCE' ) && ( WPMS_MAIL_FROM_FORCE === 'true' || WPMS_MAIL_FROM_FORCE === true ); + break; + } + + break; + + case 'smtp': + switch ( $key ) { + case 'host': + $return = defined( 'WPMS_SMTP_HOST' ) && WPMS_SMTP_HOST; + break; + case 'port': + $return = defined( 'WPMS_SMTP_PORT' ) && WPMS_SMTP_PORT; + break; + case 'encryption': + $return = defined( 'WPMS_SSL' ); + break; + case 'auth': + $return = defined( 'WPMS_SMTP_AUTH' ); + break; + case 'autotls': + $return = defined( 'WPMS_SMTP_AUTOTLS' ); + break; + case 'user': + $return = defined( 'WPMS_SMTP_USER' ) && WPMS_SMTP_USER; + break; + case 'pass': + $return = defined( 'WPMS_SMTP_PASS' ) && WPMS_SMTP_PASS; + break; + } + + break; + + case 'sendlayer': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_SENDLAYER_API_KEY' ) && WPMS_SENDLAYER_API_KEY; + break; + } + + break; + + case 'gmail': + switch ( $key ) { + case 'client_id': + $return = defined( 'WPMS_GMAIL_CLIENT_ID' ) && WPMS_GMAIL_CLIENT_ID; + break; + case 'client_secret': + $return = defined( 'WPMS_GMAIL_CLIENT_SECRET' ) && WPMS_GMAIL_CLIENT_SECRET; + break; + } + + break; + + case 'outlook': + switch ( $key ) { + case 'client_id': + $return = defined( 'WPMS_OUTLOOK_CLIENT_ID' ) && WPMS_OUTLOOK_CLIENT_ID; + break; + case 'client_secret': + $return = defined( 'WPMS_OUTLOOK_CLIENT_SECRET' ) && WPMS_OUTLOOK_CLIENT_SECRET; + break; + } + + break; + + case 'zoho': + switch ( $key ) { + case 'domain': + $return = defined( 'WPMS_ZOHO_DOMAIN' ) && WPMS_ZOHO_DOMAIN; + break; + case 'client_id': + $return = defined( 'WPMS_ZOHO_CLIENT_ID' ) && WPMS_ZOHO_CLIENT_ID; + break; + case 'client_secret': + $return = defined( 'WPMS_ZOHO_CLIENT_SECRET' ) && WPMS_ZOHO_CLIENT_SECRET; + break; + } + + break; + + case 'amazonses': + switch ( $key ) { + case 'client_id': + $return = defined( 'WPMS_AMAZONSES_CLIENT_ID' ) && WPMS_AMAZONSES_CLIENT_ID; + break; + case 'client_secret': + $return = defined( 'WPMS_AMAZONSES_CLIENT_SECRET' ) && WPMS_AMAZONSES_CLIENT_SECRET; + break; + case 'region': + $return = defined( 'WPMS_AMAZONSES_REGION' ) && WPMS_AMAZONSES_REGION; + break; + } + + break; + + case 'mailgun': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_MAILGUN_API_KEY' ) && WPMS_MAILGUN_API_KEY; + break; + case 'domain': + $return = defined( 'WPMS_MAILGUN_DOMAIN' ) && WPMS_MAILGUN_DOMAIN; + break; + case 'region': + $return = defined( 'WPMS_MAILGUN_REGION' ) && WPMS_MAILGUN_REGION; + break; + } + + break; + + case 'mailjet': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_MAILJET_API_KEY' ) && WPMS_MAILJET_API_KEY; + break; + case 'secret_key': + $return = defined( 'WPMS_MAILJET_SECRET_KEY' ) && WPMS_MAILJET_SECRET_KEY; + break; + } + + break; + + case 'sendgrid': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_SENDGRID_API_KEY' ) && WPMS_SENDGRID_API_KEY; + break; + case 'domain': + $return = defined( 'WPMS_SENDGRID_DOMAIN' ) && WPMS_SENDGRID_DOMAIN; + break; + } + + break; + + case 'sparkpost': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_SPARKPOST_API_KEY' ) && WPMS_SPARKPOST_API_KEY; + break; + case 'region': + $return = defined( 'WPMS_SPARKPOST_REGION' ) && WPMS_SPARKPOST_REGION; + break; + } + + break; + + case 'postmark': + switch ( $key ) { + case 'server_api_token': + $return = defined( 'WPMS_POSTMARK_SERVER_API_TOKEN' ) && WPMS_POSTMARK_SERVER_API_TOKEN; + break; + case 'message_stream': + $return = defined( 'WPMS_POSTMARK_MESSAGE_STREAM' ) && WPMS_POSTMARK_MESSAGE_STREAM; + break; + } + + break; + + case 'smtpcom': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_SMTPCOM_API_KEY' ) && WPMS_SMTPCOM_API_KEY; + break; + case 'channel': + $return = defined( 'WPMS_SMTPCOM_CHANNEL' ) && WPMS_SMTPCOM_CHANNEL; + break; + } + + break; + + case 'sendinblue': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_SENDINBLUE_API_KEY' ) && WPMS_SENDINBLUE_API_KEY; + break; + case 'domain': + $return = defined( 'WPMS_SENDINBLUE_DOMAIN' ) && WPMS_SENDINBLUE_DOMAIN; + break; + } + + break; + + case 'elasticemail': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_ELASTICEMAIL_API_KEY' ) && WPMS_ELASTICEMAIL_API_KEY; + break; + } + + break; + + case 'smtp2go': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_SMTP2GO_API_KEY' ) && WPMS_SMTP2GO_API_KEY; + break; + } + + break; + + case 'resend': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_RESEND_API_KEY' ) && WPMS_RESEND_API_KEY; + break; + } + + break; + + case 'pepipostapi': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_PEPIPOST_API_KEY' ) && WPMS_PEPIPOST_API_KEY; + break; + } + + break; + + case 'alert_email': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_EMAIL_SEND_TO' ) && WPMS_ALERT_EMAIL_SEND_TO; + break; + } + + break; + + case 'alert_slack_webhook': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_SLACK_WEBHOOK_URL' ) && WPMS_ALERT_SLACK_WEBHOOK_URL; + break; + } + + break; + + case 'alert_discord_webhook': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_DISCORD_WEBHOOK_URL' ) && WPMS_ALERT_DISCORD_WEBHOOK_URL; + break; + } + + break; + + case 'alert_teams_webhook': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_TEAMS_WEBHOOK_URL' ) && WPMS_ALERT_TEAMS_WEBHOOK_URL; + break; + } + + break; + + case 'alert_twilio_sms': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_TWILIO_SMS_ACCOUNT_SID' ) && WPMS_ALERT_TWILIO_SMS_ACCOUNT_SID && + defined( 'WPMS_ALERT_TWILIO_SMS_AUTH_TOKEN' ) && WPMS_ALERT_TWILIO_SMS_AUTH_TOKEN && + defined( 'WPMS_ALERT_TWILIO_SMS_FROM_PHONE_NUMBER' ) && WPMS_ALERT_TWILIO_SMS_FROM_PHONE_NUMBER && + defined( 'WPMS_ALERT_TWILIO_SMS_TO_PHONE_NUMBER' ) && WPMS_ALERT_TWILIO_SMS_TO_PHONE_NUMBER; + break; + } + + break; + + case 'alert_custom_webhook': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_CUSTOM_WEBHOOK_URL' ) && WPMS_ALERT_CUSTOM_WEBHOOK_URL; + break; + } + + break; + + case 'alert_whatsapp': + switch ( $key ) { + case 'connections': + $return = defined( 'WPMS_ALERT_WHATSAPP_ACCESS_TOKEN' ) && WPMS_ALERT_WHATSAPP_ACCESS_TOKEN && + defined( 'WPMS_ALERT_WHATSAPP_BUSINESS_ID' ) && WPMS_ALERT_WHATSAPP_BUSINESS_ID && + defined( 'WPMS_ALERT_WHATSAPP_PHONE_NUMBER_ID' ) && WPMS_ALERT_WHATSAPP_PHONE_NUMBER_ID && + defined( 'WPMS_ALERT_WHATSAPP_TO_PHONE_NUMBER' ) && WPMS_ALERT_WHATSAPP_TO_PHONE_NUMBER && + defined( 'WPMS_ALERT_WHATSAPP_TEMPLATE_LANGUAGE' ) && WPMS_ALERT_WHATSAPP_TEMPLATE_LANGUAGE; + break; + } + + break; + + case 'license': + switch ( $key ) { + case 'key': + $return = defined( 'WPMS_LICENSE_KEY' ) && WPMS_LICENSE_KEY; + break; + } + + break; + + case 'general': + switch ( $key ) { + case 'do_not_send': + /** No inspection comment @noinspection PhpUndefinedConstantInspection */ + $return = defined( 'WPMS_DO_NOT_SEND' ) && WPMS_DO_NOT_SEND; + break; + + case SummaryReportEmail::SETTINGS_SLUG: + $return = defined( 'WPMS_SUMMARY_REPORT_EMAIL_DISABLED' ); + break; + + case OptimizedEmailSending::SETTINGS_SLUG: + $return = defined( 'WPMS_OPTIMIZED_EMAIL_SENDING_ENABLED' ); + break; + } + + break; + + case 'debug_events': + switch ( $key ) { + case 'retention_period': + $return = defined( 'WPMS_DEBUG_EVENTS_RETENTION_PERIOD' ); + break; + } + + break; + + case 'mailersend': + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_MAILERSEND_API_KEY' ) && WPMS_MAILERSEND_API_KEY; + break; + + case 'has_pro_plan': + $return = defined( 'WPMS_MAILERSEND_HAS_PRO_PLAN' ); + break; + } + break; + + case 'mandrill': // phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.BodyOnNextLineCASE + + switch ( $key ) { + case 'api_key': + $return = defined( 'WPMS_MANDRILL_API_KEY' ) && WPMS_MANDRILL_API_KEY; + break; + } + + break; + } + + // phpcs:enable WPForms.Formatting.Switch.AddEmptyLineBefore, WPForms.Formatting.Switch.RemoveEmptyLineBefore + + return apply_filters( 'wp_mail_smtp_options_is_const_defined', $return, $group, $key ); + } + + /** + * Set plugin options, all at once. + * + * @since 1.0.0 + * @since 1.3.0 Added $once argument to save options only if they don't exist already. + * @since 1.4.0 Added Mailgun:region. + * @since 1.5.0 Added Outlook/AmazonSES, Email Log. Stop saving const values into DB. + * @since 2.5.0 Added $overwrite_existing method parameter. + * + * @param array $options Plugin options to save. + * @param bool $once Whether to update existing options or to add these options only once. + * @param bool $overwrite_existing Whether to overwrite existing settings or merge these passed options with existing ones. + */ + public function set( $options, $once = false, $overwrite_existing = true ) { + + // Merge existing settings with new values. + if ( ! $overwrite_existing ) { + $options = self::array_merge_recursive( $this->get_all_raw(), $options ); + } + + $options = $this->process_generic_options( $options ); + $options = $this->process_mailer_specific_options( $options ); + $options = apply_filters( 'wp_mail_smtp_options_set', $options ); + + $this->save_options( $options, $once ); + + do_action( 'wp_mail_smtp_options_set_after', $options ); + } + + /** + * Save options to DB. + * + * @since 3.7.0 + * + * @param array $options Options to save. + * @param bool $once Whether to update existing options or to add these options only once. + */ + protected function save_options( $options, $once ) { + + // Whether to update existing options or to add these options only once if they don't exist yet. + if ( $once ) { + add_option( static::META_KEY, $options, '', 'no' ); // Do not autoload these options. + } else { + if ( is_multisite() && WP::use_global_plugin_settings() ) { + update_blog_option( get_main_site_id(), static::META_KEY, $options ); + } else { + update_option( static::META_KEY, $options, 'no' ); + } + } + + // Now we need to re-cache values of all instances. + foreach ( static::$update_observers as $observer ) { + $observer->populate_options(); + } + } + + /** + * Process the generic plugin options. + * + * @since 2.5.0 + * + * @param array $options The options array. + * + * @return array + */ + protected function process_generic_options( $options ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded + + foreach ( (array) $options as $group => $keys ) { + foreach ( $keys as $option_name => $option_value ) { + switch ( $group ) { + case 'mail': + switch ( $option_name ) { + case 'from_name': + $options[ $group ][ $option_name ] = sanitize_text_field( $option_value ); + break; + case 'mailer': + $mailer = sanitize_text_field( $option_value ); + + $mailer = in_array( $mailer, self::$mailers, true ) ? $mailer : 'mail'; + + $options[ $group ][ $option_name ] = $mailer; + break; + case 'from_email': + if ( filter_var( $option_value, FILTER_VALIDATE_EMAIL ) ) { + $options[ $group ][ $option_name ] = sanitize_email( $option_value ); + } else { + $options[ $group ][ $option_name ] = sanitize_email( + wp_mail_smtp()->get_processor()->get_default_email() + ); + } + break; + case 'return_path': + case 'from_name_force': + case 'from_email_force': + $options[ $group ][ $option_name ] = (bool) $option_value; + break; + } + break; + + case 'general': + switch ( $option_name ) { + case 'do_not_send': + case 'am_notifications_hidden': + case 'email_delivery_errors_hidden': + case 'dashboard_widget_hidden': + case 'uninstall': + case UsageTracking::SETTINGS_SLUG: + case SummaryReportEmail::SETTINGS_SLUG: + case OptimizedEmailSending::SETTINGS_SLUG: + $options[ $group ][ $option_name ] = (bool) $option_value; + break; + } + + case 'debug_events': + switch ( $option_name ) { + case 'email_debug': + $options[ $group ][ $option_name ] = (bool) $option_value; + break; + case 'retention_period': + $options[ $group ][ $option_name ] = (int) $option_value; + break; + } + } + } + } + + return $options; + } + + /** + * Process mailers-specific plugin options. + * + * @since 2.5.0 + * + * @param array $options The options array. + * + * @return array + */ + protected function process_mailer_specific_options( $options ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded + + if ( + ! empty( $options['mail']['mailer'] ) && + isset( $options[ $options['mail']['mailer'] ] ) && + in_array( $options['mail']['mailer'], self::$mailers, true ) + ) { + + $mailer = $options['mail']['mailer']; + + foreach ( $options[ $mailer ] as $option_name => $option_value ) { + switch ( $option_name ) { + case 'host': // smtp. + case 'user': // smtp. + case 'encryption': // smtp. + case 'region': // mailgun/amazonses/sparkpost. + $options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? '' : sanitize_text_field( $option_value ); + break; // smtp. + case 'port': + $options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? 25 : (int) $option_value; + break; + case 'auth': // smtp. + case 'autotls': // smtp. + $option_value = (bool) $option_value; + + $options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? false : $option_value; + break; + + case 'pass': // smtp. + // Do not process as they may contain certain special characters, but allow to be overwritten using constants. + $option_value = trim( (string) $option_value ); + $options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? '' : $option_value; + + if ( $mailer === 'smtp' && ! $this->is_const_defined( 'smtp', 'pass' ) ) { + try { + $options[ $mailer ][ $option_name ] = Crypto::encrypt( $option_value ); + } catch ( \Exception $e ) { + } // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch, Squiz.Commenting.EmptyCatchComment.Missing, Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace + } + break; + + case 'api_key': // mailgun/sendgrid/sendinblue/pepipostapi/smtpcom/sparkpost/sendlayer/smtp2go/mailjet/elasticemail/resend. + case 'secret_key': // mailjet. + case 'domain': // mailgun/zoho/sendgrid/sendinblue. + case 'client_id': // gmail/outlook/amazonses/zoho. + case 'client_secret': // gmail/outlook/amazonses/zoho. + case 'auth_code': // gmail/outlook. + case 'channel': // smtpcom. + case 'server_api_token': // postmark. + case 'message_stream': // postmark. + $options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? '' : sanitize_text_field( $option_value ); + break; + + case 'has_pro_plan': // mailersend. + $options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? false : (bool) $option_value; + break; + + case 'quick_connect': // sendlayer. + case 'is_shared_domain': // sendlayer. + $options[ $mailer ][ $option_name ] = (bool) $option_value; + break; + + case 'sender_domain': // sendlayer. + $options[ $mailer ][ $option_name ] = sanitize_text_field( $option_value ); + break; + + case 'access_token': // gmail/outlook/zoho, is an array. + case 'user_details': // outlook/zoho, is an array. + // These options don't support constants. + $options[ $mailer ][ $option_name ] = $option_value; + break; + } + } + } + + return $options; + } + + /** + * Merge recursively, including a proper substitution of values in sub-arrays when keys are the same. + * It's more like array_merge() and array_merge_recursive() combined. + * + * @since 1.0.0 + * + * @return array + */ + public static function array_merge_recursive() { + + $arrays = func_get_args(); + + if ( count( $arrays ) < 2 ) { + return isset( $arrays[0] ) ? $arrays[0] : []; + } + + $merged = []; + + while ( $arrays ) { + $array = array_shift( $arrays ); + + if ( ! is_array( $array ) ) { + return []; + } + + if ( empty( $array ) ) { + continue; + } + + foreach ( $array as $key => $value ) { + if ( is_string( $key ) ) { + if ( + is_array( $value ) && + array_key_exists( $key, $merged ) && + is_array( $merged[ $key ] ) + ) { + $merged[ $key ] = call_user_func( __METHOD__, $merged[ $key ], $value ); + } else { + $merged[ $key ] = $value; + } + } else { + $merged[] = $value; + } + } + } + + return $merged; + } + + /** + * Check whether the site is using Pepipost SMTP or not. + * + * @since 1.0.0 + * + * @return bool + */ + public function is_pepipost_active() { + + _deprecated_function( + __METHOD__, + '2.4.0', + 'WPMailSMTP\Options::is_mailer_active()' + ); + + return apply_filters( 'wp_mail_smtp_options_is_pepipost_active', $this->is_mailer_active( 'pepipost' ) ); + } + + /** + * Check whether the site is using provided mailer or not. + * + * @since 2.3.0 + * + * @param string $mailer The mailer slug. + * + * @return bool + */ + public function is_mailer_active( $mailer ) { + + $mailer = sanitize_key( $mailer ); + + return apply_filters( + "wp_mail_smtp_options_is_mailer_active_{$mailer}", + $this->get( 'mail', 'mailer' ) === $mailer + ); + } + + /** + * Check whether the site is using Pepipost/SMTP as a mailer or not. + * + * @since 1.1.0 + * + * @return bool + */ + public function is_mailer_smtp() { + + return apply_filters( 'wp_mail_smtp_options_is_mailer_smtp', in_array( $this->get( 'mail', 'mailer' ), [ 'pepipost', 'smtp' ], true ) ); + } + + /** + * Get all the options, but without stripping the slashes. + * + * @since 2.5.0 + * + * @return array + */ + public function get_all_raw() { + + $options = $this->options; + + foreach ( $options as $group => $g_value ) { + foreach ( $g_value as $key => $value ) { + $options[ $group ][ $key ] = $this->get( $group, $key, false ); + } + } + + return $options; + } + + /** + * Parse boolean value from string. + * + * @since 2.8.0 + * + * @param string|boolean $value String or boolean value. + * + * @return boolean + */ + public function parse_boolean( $value ) { + + // Return early if it's boolean. + if ( is_bool( $value ) ) { + return $value; + } + + $value = trim( $value ); + + return $value === 'true'; + } + + /** + * Get a message of a constant that was set inside wp-config.php file. + * + * @since 2.8.0 + * + * @param string $constant Constant name. + * + * @return string + */ + public function get_const_set_message( $constant ) { + + return sprintf( /* translators: %1$s - constant that was used; %2$s - file where it was used. */ + esc_html__( 'The value of this field was set using a constant %1$s most likely inside %2$s of your WordPress installation.', 'wp-mail-smtp' ), + '<code>' . esc_html( $constant ) . '</code>', + '<code>wp-config.php</code>' + ); + } + + /** + * Whether option was changed. + * Can be used only before option save to DB. + * + * @since 3.0.0 + * + * @param string $new_value Submitted value (e.g from $_POST). + * @param string $group Group key. + * @param string $key Option key. + * + * @return bool + */ + public function is_option_changed( $new_value, $group, $key ) { + + $old_value = $this->get( $group, $key ); + + return $old_value !== $new_value; + } + + /** + * Whether constant was changed. + * Can be used only for insecure options. + * + * @since 3.0.0 + * + * @param string $group Group key. + * @param string $key Option key. + * + * @return bool + */ + public function is_const_changed( $group, $key ) { + + if ( ! $this->is_const_defined( $group, $key ) ) { + return false; + } + + // Prevent double options update on multiple function call for same option. + static $cache = []; + + $cache_key = $group . '_' . $key; + + if ( isset( $cache[ $cache_key ] ) ) { + return $cache[ $cache_key ]; + } + + $value = $this->get( $group, $key ); + + // Get old value from DB. + add_filter( 'wp_mail_smtp_options_is_const_enabled', '__return_false', PHP_INT_MAX ); + $old_value = $this->get( $group, $key ); + remove_filter( 'wp_mail_smtp_options_is_const_enabled', '__return_false', PHP_INT_MAX ); + + $changed = $value !== $old_value; + + // Save new constant value to DB. + if ( $changed ) { + $old_opt = $this->get_all_raw(); + + $old_opt[ $group ][ $key ] = $value; + $this->set( $old_opt ); + } + + $cache[ $cache_key ] = $changed; + + return $changed; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/AbstractEmailLogAbility.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/AbstractEmailLogAbility.php new file mode 100644 index 00000000..370185fd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/AbstractEmailLogAbility.php @@ -0,0 +1,377 @@ +<?php + +namespace WPMailSMTP\Pro\Abilities; + +use WP_Error; +use WPMailSMTP\Abilities\AbstractAbility; + +/** + * Base for the email-log and statistics abilities. + * + * @since 4.9.0 + */ +abstract class AbstractEmailLogAbility extends AbstractAbility { + + /** + * Maps a public status string to the internal email-log status integers + * used by the query layer. + * + * @since 4.9.0 + * + * @var array + */ + const STATUS_PUBLIC_TO_INTERNAL = [ + 'unsent' => [ 0 ], // Email::STATUS_UNSENT. + 'sent' => [ 1 ], // Email::STATUS_SENT. + 'waiting' => [ 2 ], // Email::STATUS_WAITING. + 'delivered' => [ 3 ], // Email::STATUS_DELIVERED. + 'blocked' => [ 4 ], // Email::STATUS_BLOCKED. + ]; + + /** + * Maps an internal email-log status integer to the single public status + * string returned in responses. + * + * @since 4.9.0 + * + * @var array + */ + const STATUS_INTERNAL_TO_PUBLIC = [ + 0 => 'unsent', // Email::STATUS_UNSENT. + 1 => 'sent', // Email::STATUS_SENT. + 2 => 'waiting', // Email::STATUS_WAITING. + 3 => 'delivered', // Email::STATUS_DELIVERED. + 4 => 'blocked', // Email::STATUS_BLOCKED. + ]; + + /** + * Resolve the email-log response formatter. + * + * @since 4.9.0 + * + * @return EmailLogResponseFormatter + */ + protected function email_log_formatter() { + + return new EmailLogResponseFormatter(); + } + + /** + * Permission gate: viewer must hold the email-log view capability. + * + * @since 4.9.0 + * + * @return true|WP_Error + */ + public function check_permission() { + + if ( ! current_user_can( wp_mail_smtp()->get_admin()->get_logs_access_capability() ) ) { + return $this->forbidden(); + } + + return true; + } + + /** + * Guard that the Email Log storage exists before querying it. + * + * @since 4.9.0 + * + * @return true|WP_Error + */ + protected function ensure_email_log_storage() { + + $logs = wp_mail_smtp()->get_pro()->get_logs(); + + if ( ! $logs || ! $logs->is_valid_db() ) { + return new WP_Error( + 'wp_mail_smtp_logs_unavailable', + esc_html__( 'The Email Log storage is not available.', 'wp-mail-smtp-pro' ), + [ 'status' => 500 ] + ); + } + + return true; + } + + /** + * Build EmailsCollection query params shared by the listing and stats paths. + * + * Applies the status, date-range, and recipient/search filters the query + * layer can express. Returns a WP_Error when the status value is invalid. + * + * @since 4.9.0 + * + * @param array $args Normalized input. + * @param int $limit Page size. + * @param int $offset Row offset. + * + * @return array|WP_Error + */ + protected function build_collection_params( array $args, $limit, $offset ) { + + $params = [ + 'per_page' => $limit, + 'offset' => $offset, + ]; + + $status = $this->resolve_status_param( $args ); + + if ( is_wp_error( $status ) ) { + return $status; + } + + if ( $status !== null ) { + $params['status'] = $status; + } + + $date = $this->resolve_date_range( $args ); + + if ( $date !== null ) { + $params['date'] = $date; + } + + $term = $this->resolve_search_term( $args ); + + if ( $term !== '' ) { + $params['search'] = [ + 'place' => 'people', + 'term' => $term, + ]; + } + + return $params; + } + + /** + * Resolve the recipient/free-text search term, both of which target + * recipient email addresses. + * + * @since 4.9.0 + * + * @param array $args Normalized input. + * + * @return string + */ + protected function resolve_search_term( array $args ) { + + if ( ! empty( $args['recipient'] ) ) { + return sanitize_text_field( $args['recipient'] ); + } + + if ( ! empty( $args['search'] ) ) { + return sanitize_text_field( $args['search'] ); + } + + return ''; + } + + /** + * Resolve and validate a public status filter to its internal status value. + * + * A single-int status is returned as a scalar; a multi-int status is + * returned as an array so the query layer can emit a `status IN (...)` + * filter and keep totals/pagination correct. + * + * @since 4.9.0 + * + * @param array $args Normalized input. + * + * @return int|int[]|null|WP_Error Internal status value, null for no filter, or error. + */ + protected function resolve_status_param( array $args ) { + + if ( empty( $args['status'] ) ) { + return null; + } + + $public = sanitize_text_field( $args['status'] ); + + if ( ! isset( self::STATUS_PUBLIC_TO_INTERNAL[ $public ] ) ) { + return new WP_Error( + 'wp_mail_smtp_invalid_status', + esc_html__( 'Unknown status value.', 'wp-mail-smtp-pro' ), + [ 'status' => 400 ] + ); + } + + $internals = self::STATUS_PUBLIC_TO_INTERNAL[ $public ]; + + return count( $internals ) === 1 ? $internals[0] : $internals; + } + + /** + * Build a [from, to] date range for the query layer from input. + * + * @since 4.9.0 + * + * @param array $args Normalized input. + * + * @return array|null + */ + protected function resolve_date_range( array $args ) { + + $from = ! empty( $args['date_from'] ) ? sanitize_text_field( $args['date_from'] ) : ''; + $to = ! empty( $args['date_to'] ) ? sanitize_text_field( $args['date_to'] ) : ''; + + if ( $from === '' && $to === '' ) { + return null; + } + + return [ + $from !== '' ? $from : '1970-01-01', + $to !== '' ? $to : gmdate( 'Y-m-d' ), + ]; + } + + /** + * Resolve a sort field to one the query layer supports. + * + * @since 4.9.0 + * + * @param string $orderby Requested sort field. + * + * @return string + */ + protected function resolve_orderby( $orderby ) { + + return in_array( $orderby, [ 'date_sent', 'subject', 'status' ], true ) ? $orderby : 'date_sent'; + } + + /** + * Resolve a sort direction to ASC or DESC. + * + * @since 4.9.0 + * + * @param string $order Requested direction. + * + * @return string + */ + protected function resolve_order( $order ) { + + return strtolower( (string) $order ) === 'asc' ? 'ASC' : 'DESC'; + } + + /** + * Whether a mailer slug is one of the known providers. + * + * @since 4.9.0 + * + * @param string $slug Mailer slug. + * + * @return bool + */ + protected function is_known_mailer( $slug ) { + + return array_key_exists( $slug, $this->get_known_mailers() ); + } + + /** + * Validate an optional mailer slug filter. + * + * @since 4.9.0 + * + * @param string $slug Mailer slug, or empty for no filter. + * + * @return true|WP_Error + */ + protected function validate_mailer( $slug ) { + + if ( $slug === '' || $this->is_known_mailer( $slug ) ) { + return true; + } + + return new WP_Error( + 'wp_mail_smtp_invalid_mailer', + esc_html__( 'Unknown mailer slug.', 'wp-mail-smtp-pro' ), + [ 'status' => 400 ] + ); + } + + /** + * Get the known mailer slug => class map. + * + * @since 4.9.0 + * + * @return array + */ + protected function get_known_mailers() { + + return wp_mail_smtp()->get_providers()->get_providers(); + } + + /** + * Shared `status` input-schema fragment. + * + * @since 4.9.0 + * + * @return array + */ + protected function status_schema() { + + return [ + 'description' => esc_html__( 'Filter by email status: unsent (failed to send), sent, waiting (delivery confirmation pending), delivered (confirmed), or blocked.', 'wp-mail-smtp-pro' ), + 'type' => 'string', + 'enum' => [ 'unsent', 'sent', 'waiting', 'delivered', 'blocked' ], + ]; + } + + /** + * Shared `mailer` input-schema fragment. + * + * @since 4.9.0 + * + * @return array + */ + protected function mailer_schema() { + + return [ + 'description' => esc_html__( 'Filter by mailer slug (e.g. smtp, gmail, sendlayer).', 'wp-mail-smtp-pro' ), + 'type' => 'string', + ]; + } + + /** + * Shared email-log item output-schema fragment. + * + * @since 4.9.0 + * + * @return array + */ + protected function email_log_item_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'id' => [ 'type' => 'integer' ], + 'subject' => [ 'type' => 'string' ], + 'from' => [ 'type' => 'string' ], + 'to' => [ 'type' => 'array' ], + 'status' => [ + 'type' => 'string', + 'enum' => [ 'unsent', 'sent', 'waiting', 'delivered', 'blocked' ], + ], + 'date_sent' => [ 'type' => [ 'string', 'null' ] ], + 'mailer' => [ 'type' => 'string' ], + 'has_attachments' => [ 'type' => 'boolean' ], + ], + ]; + } + + /** + * Compute a success/attempted rate as a percentage. + * + * @since 4.9.0 + * + * @param int $success Successful (delivered + sent) count. + * @param int $unsent Unsent count. + * + * @return float + */ + protected function success_rate( $success, $unsent ) { + + $attempted = $success + $unsent; + + return $attempted > 0 ? round( ( $success / $attempted ) * 100, 2 ) : 0.0; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogResponseFormatter.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogResponseFormatter.php new file mode 100644 index 00000000..16e726df --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogResponseFormatter.php @@ -0,0 +1,198 @@ +<?php + +namespace WPMailSMTP\Pro\Abilities; + +use DateTime; +use DateTimeZone; +use Exception; +use WPMailSMTP\Pro\Emails\Logs\Email; + +/** + * Formats Email Log entities into the abilities-API wire shape. + * + * Owns the email-summary and email-detail payloads so the registered output + * schemas and the runtime rows stay in agreement. Lives in Pro because the + * Email Log subsystem it reads from only exists in Pro. + * + * @since 4.9.0 + */ +class EmailLogResponseFormatter { + + /** + * Format an email log row to its summary shape (no body content). + * + * @since 4.9.0 + * + * @param Email $email Email model. + * + * @return array + */ + public function email_summary( Email $email ) { + + return [ + 'id' => $email->get_id(), + 'subject' => $email->get_subject(), + 'from' => $email->get_header( 'From' ), + 'to' => $this->people_to_array( $email->get_people( 'to' ) ), + 'status' => $this->status_label( $email->get_status() ), + 'date_sent' => $this->date_sent_or_null( $email ), + 'mailer' => $email->get_mailer(), + 'has_attachments' => $email->get_attachments() > 0, + ]; + } + + /** + * Format an email log row to its detail shape (includes body content). + * + * @since 4.9.0 + * + * @param Email $email Email model. + * + * @return array + */ + public function email_detail( Email $email ) { + + return [ + 'id' => $email->get_id(), + 'subject' => $email->get_subject(), + 'from' => $email->get_header( 'From' ), + 'to' => $this->people_to_array( $email->get_people( 'to' ) ), + 'cc' => $this->people_to_array( $email->get_people( 'cc' ) ), + 'bcc' => $this->people_to_array( $email->get_people( 'bcc' ) ), + 'reply_to' => $this->people_to_array( $email->get_people( 'reply_to' ) ), + 'status' => $this->status_label( $email->get_status() ), + 'date_sent' => $this->date_sent_or_null( $email ), + 'mailer' => $email->get_mailer(), + 'content_plain' => $email->get_content_plain(), + 'content_html' => $email->get_content_html(), + 'headers' => $this->headers_to_array( $email ), + 'attachment_count' => $email->get_attachments(), + 'error_message' => $email->get_error_text(), + 'error_code' => $email->get_error_code(), + ]; + } + + /** + * Map an internal status int to its public label. + * + * @since 4.9.0 + * + * @param int $status Internal status int. + * + * @return string + */ + public function status_label( $status ) { + + return AbstractEmailLogAbility::STATUS_INTERNAL_TO_PUBLIC[ (int) $status ] ?? 'unsent'; + } + + /** + * Resolve the ISO 8601 send date, or null when the email was never sent. + * + * The model defaults an empty send date to "now" for rows that were never + * sent (unsent or blocked), which would misreport those as just-sent; + * surface null instead. + * + * @since 4.9.0 + * + * @param Email $email Email model. + * + * @return string|null + */ + public function date_sent_or_null( Email $email ) { + + if ( in_array( $email->get_status(), [ Email::STATUS_UNSENT, Email::STATUS_BLOCKED ], true ) ) { + return null; + } + + return $this->to_iso8601( $email->get_date_sent() ); + } + + /** + * Decode the stored headers JSON into an array. + * + * @since 4.9.0 + * + * @param Email $email Email model. + * + * @return array + */ + public function headers_to_array( Email $email ) { + + $decoded = json_decode( $email->get_headers(), true ); + + return is_array( $decoded ) ? $decoded : []; + } + + /** + * Normalize a `get_people()` result to a flat array of address strings. + * + * @since 4.9.0 + * + * @param mixed $people People value from the Email model. + * + * @return array + */ + public function people_to_array( $people ) { + + if ( empty( $people ) ) { + return []; + } + + $out = []; + + foreach ( (array) $people as $person ) { + // Recipients are stored as flat email strings, but tolerate the + // PHPMailer [ email, name ] pair shape by taking the address. + if ( is_array( $person ) ) { + $person = reset( $person ); + } + + if ( is_scalar( $person ) && $person !== '' ) { + $out[] = (string) $person; + } + } + + return $out; + } + + /** + * Convert a DateTime to an ISO 8601 UTC string. + * + * @since 4.9.0 + * + * @param DateTime $datetime Date to format. + * + * @return string + */ + public function to_iso8601( DateTime $datetime ) { + + // Clone before retiming so the caller's DateTime keeps its own timezone; + // formatting on a UTC copy guarantees the `+00:00` offset the docblock promises. + $datetime = clone $datetime; + + $datetime->setTimezone( new DateTimeZone( 'UTC' ) ); + + return $datetime->format( 'c' ); + } + + /** + * Convert a MySQL datetime string (UTC) to an ISO 8601 string. + * + * @since 4.9.0 + * + * @param string $mysql_datetime MySQL datetime. + * + * @return string + */ + public function mysql_to_iso8601( $mysql_datetime ) { + + try { + $date = new DateTime( $mysql_datetime, new DateTimeZone( 'UTC' ) ); + } catch ( Exception $e ) { + return ''; + } + + return $this->to_iso8601( $date ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/GetEmailLogAbility.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/GetEmailLogAbility.php new file mode 100644 index 00000000..afff1cad --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/GetEmailLogAbility.php @@ -0,0 +1,149 @@ +<?php + +namespace WPMailSMTP\Pro\Abilities\EmailLogs; + +use WP_Error; +use WPMailSMTP\Pro\Abilities\AbstractEmailLogAbility; +use WPMailSMTP\Pro\Emails\Logs\Email; + +/** + * Ability: get the full details of a single logged email (Pro). + * + * @since 4.9.0 + */ +class GetEmailLogAbility extends AbstractEmailLogAbility { + + /** + * Ability slug, without the namespace prefix. + * + * @since 4.9.0 + * + * @return string + */ + public function get_name() { + + return 'get-email-log'; + } + + /** + * Human-readable label. + * + * @since 4.9.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Get Email Log', 'wp-mail-smtp-pro' ); + } + + /** + * Human-readable description. + * + * @since 4.9.0 + * + * @return string + */ + public function get_description() { + + return esc_html__( 'Get the full details of a single logged email, including its content.', 'wp-mail-smtp-pro' ); + } + + /** + * Input schema. + * + * @since 4.9.0 + * + * @return array + */ + public function get_input_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'log_id' => [ + 'description' => esc_html__( 'The ID of the logged email to retrieve.', 'wp-mail-smtp-pro' ), + 'type' => 'integer', + 'minimum' => 1, + ], + ], + 'required' => [ 'log_id' ], + ]; + } + + /** + * Output schema. + * + * @since 4.9.0 + * + * @return array + */ + public function get_output_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'id' => [ 'type' => 'integer' ], + 'subject' => [ 'type' => 'string' ], + 'from' => [ 'type' => 'string' ], + 'to' => [ 'type' => 'array' ], + 'cc' => [ 'type' => 'array' ], + 'bcc' => [ 'type' => 'array' ], + 'reply_to' => [ 'type' => 'array' ], + 'status' => [ + 'type' => 'string', + 'enum' => [ 'unsent', 'sent', 'waiting', 'delivered', 'blocked' ], + ], + 'date_sent' => [ 'type' => [ 'string', 'null' ] ], + 'mailer' => [ 'type' => 'string' ], + 'content_plain' => [ 'type' => 'string' ], + 'content_html' => [ 'type' => 'string' ], + 'headers' => [ 'type' => 'array' ], + 'attachment_count' => [ 'type' => 'integer' ], + 'error_message' => [ 'type' => [ 'string', 'null' ] ], + 'error_code' => [ 'type' => [ 'string', 'null' ] ], + ], + ]; + } + + /** + * Execute: get a single email log. + * + * @since 4.9.0 + * + * @param mixed $input Input data. + * + * @return array|WP_Error + */ + public function execute( $input = null ) { + + $available = $this->ensure_email_log_storage(); + + if ( is_wp_error( $available ) ) { + return $available; + } + + $args = $this->normalize_input( $input ); + $log_id = absint( $args['log_id'] ?? 0 ); + + if ( $log_id < 1 ) { + return new WP_Error( + 'wp_mail_smtp_invalid_log_id', + esc_html__( 'A valid log ID is required.', 'wp-mail-smtp-pro' ), + [ 'status' => 400 ] + ); + } + + $email = new Email( $log_id ); + + if ( ! $email->is_valid() ) { + return new WP_Error( + 'wp_mail_smtp_log_not_found', + esc_html__( 'Email log not found.', 'wp-mail-smtp-pro' ), + [ 'status' => 404 ] + ); + } + + return $this->email_log_formatter()->email_detail( $email ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/ListEmailLogsAbility.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/ListEmailLogsAbility.php new file mode 100644 index 00000000..30e20d52 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/EmailLogs/ListEmailLogsAbility.php @@ -0,0 +1,180 @@ +<?php + +namespace WPMailSMTP\Pro\Abilities\EmailLogs; + +use WP_Error; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement +use WPMailSMTP\Pro\Abilities\AbstractEmailLogAbility; +use WPMailSMTP\Pro\Emails\Logs\EmailsCollection; + +/** + * Ability: list logged emails with their summary metadata (Pro). + * + * Filters by recipient search term, status, mailer, and date range, with + * limit/offset pagination and configurable sorting. + * + * @since 4.9.0 + */ +class ListEmailLogsAbility extends AbstractEmailLogAbility { + + /** + * Ability slug, without the namespace prefix. + * + * @since 4.9.0 + * + * @return string + */ + public function get_name() { + + return 'list-email-logs'; + } + + /** + * Human-readable label. + * + * @since 4.9.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'List Email Logs', 'wp-mail-smtp-pro' ); + } + + /** + * Human-readable description. + * + * @since 4.9.0 + * + * @return string + */ + public function get_description() { + + return esc_html__( 'List logged emails with their summary metadata, filtered by recipient, status, mailer, and date range.', 'wp-mail-smtp-pro' ); + } + + /** + * Input schema. + * + * @since 4.9.0 + * + * @return array + */ + public function get_input_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'limit' => $this->limit_schema(), + 'offset' => $this->offset_schema(), + 'status' => $this->status_schema(), + 'mailer' => $this->mailer_schema(), + 'recipient' => [ + 'description' => esc_html__( 'Filter by a recipient email address.', 'wp-mail-smtp-pro' ), + 'type' => 'string', + ], + 'search' => [ + 'description' => esc_html__( 'Search term matched against recipient email addresses.', 'wp-mail-smtp-pro' ), + 'type' => 'string', + ], + 'date_from' => $this->date_schema( esc_html__( 'Only include emails sent on or after this date (Y-m-d).', 'wp-mail-smtp-pro' ) ), + 'date_to' => $this->date_schema( esc_html__( 'Only include emails sent on or before this date (Y-m-d).', 'wp-mail-smtp-pro' ) ), + 'orderby' => [ + 'description' => esc_html__( 'Sort field.', 'wp-mail-smtp-pro' ), + 'type' => 'string', + 'enum' => [ 'date_sent', 'subject', 'status' ], + 'default' => 'date_sent', + ], + 'order' => [ + 'description' => esc_html__( 'Sort direction.', 'wp-mail-smtp-pro' ), + 'type' => 'string', + 'enum' => [ 'asc', 'desc' ], + 'default' => 'desc', + ], + ], + ]; + } + + /** + * Output schema. + * + * @since 4.9.0 + * + * @return array + */ + public function get_output_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'logs' => [ + 'type' => 'array', + 'items' => $this->email_log_item_schema(), + ], + 'total' => [ 'type' => 'integer' ], + 'limit' => [ 'type' => 'integer' ], + 'offset' => [ 'type' => 'integer' ], + ], + ]; + } + + /** + * Execute: list email logs. + * + * @since 4.9.0 + * + * @param mixed $input Input data. + * + * @return array|WP_Error + */ + public function execute( $input = null ) { + + $available = $this->ensure_email_log_storage(); + + if ( is_wp_error( $available ) ) { + return $available; + } + + $args = $this->normalize_input( $input ); + $mailer = isset( $args['mailer'] ) ? sanitize_text_field( $args['mailer'] ) : ''; + + $validated = $this->validate_mailer( $mailer ); + + if ( is_wp_error( $validated ) ) { + return $validated; + } + + $pagination = $this->get_pagination( $args ); + $limit = $pagination['limit']; + $offset = $pagination['offset']; + + $params = $this->build_collection_params( $args, $limit, $offset ); + + if ( is_wp_error( $params ) ) { + return $params; + } + + if ( $mailer !== '' ) { + $params['mailer'] = $mailer; + } + + $params['orderby'] = $this->resolve_orderby( $args['orderby'] ?? '' ); + $params['order'] = $this->resolve_order( $args['order'] ?? '' ); + + $collection = new EmailsCollection( $params ); + $total = $collection->get_count(); + $formatter = $this->email_log_formatter(); + + $logs_out = []; + + foreach ( $collection->get() as $email ) { + $logs_out[] = $formatter->email_summary( $email ); + } + + return [ + 'logs' => $logs_out, + 'total' => $total, + 'limit' => $limit, + 'offset' => $offset, + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/Stats/GetEmailStatsAbility.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/Stats/GetEmailStatsAbility.php new file mode 100644 index 00000000..338ebbf3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Abilities/Stats/GetEmailStatsAbility.php @@ -0,0 +1,279 @@ +<?php + +namespace WPMailSMTP\Pro\Abilities\Stats; + +use WP_Error; // phpcs:ignore WPForms.PHP.UseStatement.UnusedUseStatement +use WPMailSMTP\Helpers\Helpers; +use WPMailSMTP\Pro\Abilities\AbstractEmailLogAbility; +use WPMailSMTP\Pro\Emails\Logs\DeliveryVerification\DeliveryVerification; +use WPMailSMTP\Pro\Emails\Logs\Reports\Report; + +/** + * Ability: aggregate email sending statistics for a period or date range (Pro). + * + * Mirrors the dashboard widget / Reports counts: delivered, sent, and unsent, + * excluding waiting and blocked emails. Mailers that cannot verify delivery omit + * the `delivered` field and fold any delivered count into `sent`. An optional + * mailer slug scopes the same shape to a single mailer. + * + * @since 4.9.0 + */ +class GetEmailStatsAbility extends AbstractEmailLogAbility { + + /** + * Ability slug, without the namespace prefix. + * + * @since 4.9.0 + * + * @return string + */ + public function get_name() { + + return 'get-email-stats'; + } + + /** + * Human-readable label. + * + * @since 4.9.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Get Email Stats', 'wp-mail-smtp-pro' ); + } + + /** + * Human-readable description. + * + * @since 4.9.0 + * + * @return string + */ + public function get_description() { + + return esc_html__( 'Get aggregate email sending statistics for a period or date range, optionally scoped to a single mailer.', 'wp-mail-smtp-pro' ); + } + + /** + * Input schema. + * + * @since 4.9.0 + * + * @return array + */ + public function get_input_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'period' => [ + 'description' => esc_html__( 'Predefined reporting period. Ignored when a custom date range is supplied.', 'wp-mail-smtp-pro' ), + 'type' => 'string', + 'enum' => [ 'today', 'week', 'month', 'year', 'all' ], + 'default' => 'week', + ], + 'date_from' => $this->date_schema( esc_html__( 'Custom range start date (Y-m-d). Overrides period.', 'wp-mail-smtp-pro' ) ), + 'date_to' => $this->date_schema( esc_html__( 'Custom range end date (Y-m-d). Overrides period.', 'wp-mail-smtp-pro' ) ), + 'mailer' => [ + 'description' => esc_html__( 'Optionally scope the statistics to a single mailer slug (e.g. smtp, gmail, sendlayer).', 'wp-mail-smtp-pro' ), + 'type' => 'string', + ], + ], + ]; + } + + /** + * Output schema. + * + * Same shape for overall and per-mailer modes. `delivered` is present only + * when the relevant mailer can verify delivery; otherwise its count is folded + * into `sent`. + * + * @since 4.9.0 + * + * @return array + */ + public function get_output_schema() { + + return [ + 'type' => 'object', + 'properties' => [ + 'delivered' => [ + 'description' => esc_html__( 'Count of confirmed-delivered emails. Present only for mailers that support delivery verification.', 'wp-mail-smtp-pro' ), + 'type' => 'integer', + ], + 'sent' => [ 'type' => 'integer' ], + 'unsent' => [ 'type' => 'integer' ], + 'total' => [ 'type' => 'integer' ], + 'success_rate' => [ + 'description' => esc_html__( 'Percentage of attempted emails that succeeded, computed over delivered plus sent; unsent emails are excluded from the numerator. Waiting and blocked emails are excluded entirely.', 'wp-mail-smtp-pro' ), + 'type' => 'number', + ], + 'message' => [ 'type' => 'string' ], + ], + ]; + } + + /** + * Execute: aggregate email stats, overall or scoped to a single mailer. + * + * @since 4.9.0 + * + * @param mixed $input Input data. + * + * @return array|WP_Error + */ + public function execute( $input = null ) { + + $available = $this->ensure_email_log_storage(); + + if ( is_wp_error( $available ) ) { + return $available; + } + + $args = $this->normalize_input( $input ); + $mailer = isset( $args['mailer'] ) ? sanitize_text_field( $args['mailer'] ) : ''; + + if ( $mailer !== '' ) { + $validated = $this->validate_mailer( $mailer ); + + if ( is_wp_error( $validated ) ) { + return $validated; + } + } + + return $this->build_stats( $args, $mailer ); + } + + /** + * Build the stats response, overall or scoped to a single mailer. + * + * @since 4.9.0 + * + * @param array $args Normalized input. + * @param string $mailer Mailer slug, or empty for overall stats. + * + * @return array + */ + protected function build_stats( array $args, $mailer ) { + + $range = $this->resolve_stats_range( $args ); + + $report_params = []; + + if ( $range !== null ) { + $report_params['date'] = $range; + } + + if ( $mailer !== '' ) { + $report_params['mailer'] = $mailer; + } + + $report = new Report( $report_params ); + $totals = $report->get_stats_totals(); + $can_verify = $this->is_verification_capable( $mailer ); + + $result = $this->shape_counts( $totals, $can_verify ); + + $logs = wp_mail_smtp()->get_pro()->get_logs(); + + if ( ! $logs->is_enabled() ) { + $result['message'] = esc_html__( 'Detailed statistics require the Email Log to be enabled. Totals reflect only currently logged emails.', 'wp-mail-smtp-pro' ); + } + + return $result; + } + + /** + * Shape a Reports counts row into the response counts. + * + * Non-verification mailers omit `delivered` and fold its count into `sent`. + * + * @since 4.9.0 + * + * @param array $counts Row with `delivered`, `sent`, `unsent` keys. + * @param bool $can_verify Whether the relevant mailer verifies delivery. + * + * @return array + */ + protected function shape_counts( array $counts, $can_verify ) { + + $delivered = (int) ( $counts['delivered'] ?? 0 ); + $sent = (int) ( $counts['sent'] ?? 0 ); + $unsent = (int) ( $counts['unsent'] ?? 0 ); + + if ( $can_verify ) { + $shaped = [ + 'delivered' => $delivered, + 'sent' => $sent, + 'unsent' => $unsent, + ]; + $success = $delivered + $sent; + } else { + $shaped = [ + 'sent' => $sent + $delivered, + 'unsent' => $unsent, + ]; + $success = $shaped['sent']; + } + + $shaped['total'] = $success + $unsent; + $shaped['success_rate'] = $this->success_rate( $success, $unsent ); + + return $shaped; + } + + /** + * Whether the relevant mailer can verify delivery. + * + * Overall mode keys off the active mailer; per-mailer mode keys off whether + * the slug has a delivery verifier. + * + * @since 4.9.0 + * + * @param string $mailer Mailer slug, or empty for overall stats. + * + * @return bool + */ + protected function is_verification_capable( $mailer ) { + + if ( $mailer === '' ) { + return ! Helpers::mailer_without_send_confirmation(); + } + + return array_key_exists( $mailer, DeliveryVerification::DELIVERY_VERIFIERS_PER_MAILER ); + } + + /** + * Resolve the stats date range from either a custom range or a period. + * + * @since 4.9.0 + * + * @param array $args Normalized input. + * + * @return array|null A [Y-m-d, Y-m-d] range, or null for all-time. + */ + protected function resolve_stats_range( array $args ) { + + $custom = $this->resolve_date_range( $args ); + + if ( $custom !== null ) { + return $custom; + } + + $period = $args['period'] ?? 'week'; + $today = gmdate( 'Y-m-d' ); + + $ranges = [ + 'today' => [ $today, $today ], + 'week' => [ gmdate( 'Y-m-d', strtotime( '-6 days' ) ), $today ], + 'month' => [ gmdate( 'Y-m-d', strtotime( '-1 month' ) ), $today ], + 'year' => [ gmdate( 'Y-m-d', strtotime( '-1 year' ) ), $today ], + ]; + + // 'all' (and any unrecognized period) means no date filter. + return $ranges[ $period ] ?? null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/AdditionalConnections.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/AdditionalConnections.php new file mode 100644 index 00000000..5ee96942 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/AdditionalConnections.php @@ -0,0 +1,266 @@ +<?php + +namespace WPMailSMTP\Pro\AdditionalConnections; + +use WPMailSMTP\ConnectionInterface; +use WPMailSMTP\Pro\AdditionalConnections\Admin\SettingsTab; +use WPMailSMTP\Pro\AdditionalConnections\Admin\TestTab; +use WPMailSMTP\WP; + +/** + * Class AdditionalConnections. + * + * @since 3.7.0 + */ +class AdditionalConnections { + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + // Add additional connections settings page. + add_filter( 'wp_mail_smtp_admin_get_pages', [ $this, 'init_settings_tab' ] ); + + // Maybe change connection object that should be used for OAuth authorization. + add_filter( 'wp_mail_smtp_admin_pages_auth_tab_process_auth_connection', [ $this, 'process_auth_connection' ] ); + + // Maybe change connection settings admin page url. + add_filter( + 'wp_mail_smtp_admin_connection_settings_get_admin_page_url', + function ( $admin_page_url, $connection ) { + return ! $connection->is_primary() ? $this->get_connection_admin_page_url( $connection ) : $admin_page_url; + }, + 10, + 2 + ); + + // Initialize test tab. + ( new TestTab() )->hooks(); + } + + /** + * Initialize settings tab. + * + * @since 3.7.0 + * + * @param array $tabs Tabs array. + */ + public function init_settings_tab( $tabs ) { + + static $settings_tab = null; + + if ( is_null( $settings_tab ) ) { + $settings_tab = new SettingsTab(); + } + + $tabs['connections'] = $settings_tab; + + return $tabs; + } + + /** + * Set connection object that should be used for OAuth authorization + * based on the connection ID retrieved from the OAuth "state" GET param. + * + * @since 3.7.0 + * + * @return ConnectionInterface + */ + public function process_auth_connection() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $state = isset( $_GET['state'] ) ? sanitize_key( $_GET['state'] ) : false; + + list( , $connection_id ) = array_pad( explode( '-', $state ), 2, false ); + + if ( empty( $state ) || 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; + } + + return $connection; + } + + /** + * Get additional connection settings admin page URL. + * + * @since 3.7.0 + * + * @param ConnectionInterface $connection The Connection object. + * + * @return string + */ + public function get_connection_admin_page_url( $connection ) { + + return add_query_arg( + [ + 'tab' => 'connections', + 'mode' => 'edit', + 'connection_id' => $connection->get_id(), + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ); + } + + /** + * Get connection by ID. + * + * @since 3.7.0 + * + * @param string $connection_id The connection ID. + * + * @return false|ConnectionInterface + */ + public function get_connection( $connection_id ) { + + if ( ! $this->connection_exists( $connection_id ) ) { + return false; + } + + return new Connection( $connection_id ); + } + + /** + * Get all connections. + * + * @since 3.7.0 + * + * @return ConnectionInterface[] + */ + public function get_connections() { + + return array_map( + function ( $connection_id ) { + return new Connection( $connection_id ); + }, + array_keys( $this->get_connections_raw() ) + ); + } + + /** + * Get all configured connections. + * + * @since 3.7.0 + * + * @return ConnectionInterface[] + */ + public function get_configured_connections() { + + return array_filter( + $this->get_connections(), + function ( $connection ) { + return $connection->get_mailer()->is_mailer_complete(); + } + ); + } + + /** + * Whether there are added additional connections. + * + * @since 3.7.0 + * + * @return bool + */ + public function has_connections() { + + return ! empty( $this->get_connections_raw() ); + } + + /** + * Remove connection. + * + * @since 3.7.0 + * + * @param string $connection_id The connection ID. + * + * @return bool + */ + public function remove_connection( $connection_id ) { + + $connections = $this->get_connections_raw(); + + if ( ! isset( $connections[ $connection_id ] ) ) { + return false; + } + + unset( $connections[ $connection_id ] ); + + if ( WP::use_global_plugin_settings() ) { + update_blog_option( get_main_site_id(), ConnectionOptions::META_KEY, $connections ); + } else { + update_option( ConnectionOptions::META_KEY, $connections, 'no' ); + } + + return true; + } + + /** + * Check whether the connection exists. + * + * @since 3.7.0 + * + * @param string $connection_id The connection ID. + * + * @return bool + */ + public function connection_exists( $connection_id ) { + + $additional_connections = $this->get_connections_raw(); + + return isset( $additional_connections[ $connection_id ] ); + } + + /** + * Get additional connections page manage capability. + * + * @since 3.7.0 + * + * @return string + */ + public function get_manage_capability() { + + /** + * Filter additional connections page manage capability. + * + * @since 3.7.0 + * + * @param string $capability Additional connections page manage capability. + */ + return apply_filters( + 'wp_mail_smtp_pro_additional_connections_get_manage_capability', + wp_mail_smtp()->get_capability_manage_global_options() + ); + } + + /** + * Get all connections raw data. + * + * @since 3.7.0 + * + * @return array + */ + private function get_connections_raw() { + + if ( WP::use_global_plugin_settings() ) { + $connections = get_blog_option( get_main_site_id(), ConnectionOptions::META_KEY, [] ); + } else { + $connections = get_option( ConnectionOptions::META_KEY, [] ); + } + + return is_array( $connections ) ? $connections : []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/SettingsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/SettingsTab.php new file mode 100644 index 00000000..ede70fec --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/SettingsTab.php @@ -0,0 +1,669 @@ +<?php + +namespace WPMailSMTP\Pro\AdditionalConnections\Admin; + +use WPMailSMTP\Admin\ConnectionSettings; +use WPMailSMTP\Admin\Pages\AdditionalConnectionsTab; +use WPMailSMTP\EmailSendingDebug; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\AdditionalConnections\AdditionalConnections; +use WPMailSMTP\Pro\AdditionalConnections\Connection; +use WPMailSMTP\Pro\Emails\Logs\Admin\PageAbstract; +use WPMailSMTP\WP; + +/** + * Class SettingsTab. + * + * Additional connections settings tab. + * + * @since 3.7.0 + */ +class SettingsTab extends AdditionalConnectionsTab { + + /** + * Additional Connections object. + * + * @since 3.7.0 + * + * @var AdditionalConnections + */ + private $additional_connections; + + /** + * Constructor. + * + * @since 3.7.0 + * + * @param PageAbstract $parent_page Parent page object. + */ + public function __construct( $parent_page = null ) { + + parent::__construct( $parent_page ); + + if ( wp_mail_smtp()->get_admin()->get_current_tab() === $this->slug ) { + $this->hooks(); + } + + $this->additional_connections = wp_mail_smtp()->get_pro()->get_additional_connections(); + } + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_action( 'admin_init', [ $this, 'process_actions' ] ); + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.7.0 + */ + public function enqueue_assets() { + + // Enqueue JS and CSS from education page. + if ( ! $this->additional_connections->has_connections() ) { + parent::enqueue_assets(); + } + } + + /** + * Process actions. + * + * @since 3.7.0 + */ + public function process_actions() { + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['mode'] ) && isset( $_REQUEST['connection_id'] ) ) { + $mode = sanitize_key( $_REQUEST['mode'] ); + + if ( $mode === 'delete' ) { + $this->process_connection_delete(); + } elseif ( $mode === 'switch_with_primary' ) { + $this->process_connection_switch_with_primary(); + } + } + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + $this->display_notices(); + } + + /** + * Delete the connection. + * + * @since 3.7.0 + */ + private function process_connection_delete() { + + // Nonce verification. + if ( + ! isset( $_REQUEST['_wpnonce'] ) || + ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'wp_mail_smtp_pro_additional_connection_delete' ) + ) { + wp_die( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) ); + } + + if ( ! current_user_can( $this->additional_connections->get_manage_capability() ) ) { + wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! isset( $_REQUEST['connection_id'] ) ) { + wp_die( esc_html__( 'Required parameters are missing.', 'wp-mail-smtp-pro' ) ); + } + + $connection_id = sanitize_key( $_REQUEST['connection_id'] ); + + $is_removed = $this->additional_connections->remove_connection( $connection_id ); + + if ( $is_removed ) { + $url = add_query_arg( 'message', 'deleted', $this->get_connections_list_url() ); + } else { + $url = add_query_arg( 'message', 'delete_failed', $this->get_connections_list_url() ); + } + + wp_safe_redirect( $url ); + exit; + } + + /** + * Switch additional connection to primary and vice versa. + * + * @since 3.10.0 + */ + private function process_connection_switch_with_primary() { + + // Nonce verification. + if ( + ! isset( $_REQUEST['_wpnonce'] ) || + ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'wp_mail_smtp_pro_additional_connection_switch_with_primary' ) + ) { + wp_die( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) ); + } + + if ( ! current_user_can( $this->additional_connections->get_manage_capability() ) ) { + wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! isset( $_REQUEST['connection_id'] ) ) { + wp_die( esc_html__( 'Required parameters are missing.', 'wp-mail-smtp-pro' ) ); + } + + $connection_id = sanitize_key( $_REQUEST['connection_id'] ); + $additional_connection = $this->additional_connections->get_connection( $connection_id ); + + if ( $additional_connection === false ) { + wp_die( esc_html__( 'Connection not found.', 'wp-mail-smtp-pro' ) ); + } + + $primary_connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + $primary_connection_mailer = $primary_connection->get_mailer_slug(); + $additional_connection_mailer = $additional_connection->get_mailer_slug(); + + $primary_connection_options_data = array_merge( + $primary_connection->get_options()->get_all_raw(), + [ + 'mail' => $additional_connection->get_options()->get_group( 'mail' ), + $additional_connection_mailer => $additional_connection->get_options()->get_group( $additional_connection_mailer ), + ] + ); + + $additional_connection_options_data = array_merge( + $additional_connection->get_options()->get_all_raw(), + [ + 'mail' => $primary_connection->get_options()->get_group( 'mail' ), + $primary_connection_mailer => $primary_connection->get_options()->get_group( $primary_connection_mailer ), + ] + ); + + $primary_connection->get_options()->set( $primary_connection_options_data ); + $additional_connection->get_options()->set( $additional_connection_options_data ); + + $url = add_query_arg( 'message', 'additional_connection_switched_with_primary', wp_mail_smtp()->get_admin()->get_admin_page_url() ); + + wp_safe_redirect( $url ); + exit; + } + + /** + * Display notices when needed. + * + * @since 3.7.0 + */ + private function display_notices() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $message = isset( $_GET['message'] ) ? sanitize_key( $_GET['message'] ) : ''; + + if ( + empty( $message ) || + ! current_user_can( $this->additional_connections->get_manage_capability() ) + ) { + return; + } + + switch ( $message ) { + case 'deleted': + WP::add_admin_notice( + esc_html__( 'Connection was successfully deleted.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + break; + + case 'delete_failed': + WP::add_admin_notice( + esc_html__( 'There was an error while processing your request, and connection were not deleted. Please try again.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_WARNING + ); + break; + + case 'saved': + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + break; + } + } + + /** + * Output HTML of the additional connections list or single additional connection. + * + * @since 3.7.0 + */ + public function display() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $mode = isset( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : false; + + if ( in_array( $mode, [ 'new', 'edit' ], true ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $connection_id = isset( $_GET['connection_id'] ) ? sanitize_key( $_GET['connection_id'] ) : false; + + $this->display_single_connection( $connection_id ); + } elseif ( $this->additional_connections->has_connections() ) { + $this->display_connections_list(); + } else { + $this->display_no_connections(); + } + } + + /** + * Output HTML of the additional connections settings header. + * + * @since 3.7.0 + * + * @param bool $is_singular Whether to display single or archive page header. + */ + private function display_header( $is_singular = false ) { + + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading wp-mail-smtp-additional-connections-header"> + <div class="wp-mail-smtp-setting-field"> + <h2 class="wp-mail-smtp-additional-connections-header__heading"> + <?php esc_html_e( 'Additional Connections', 'wp-mail-smtp-pro' ); ?> + <?php if ( ! $is_singular ) : ?> + <a href="<?php echo esc_url( $this->get_connection_url( 'new' ) ); ?>"> + <?php esc_html_e( 'Add New', 'wp-mail-smtp-pro' ); ?> + </a> + <?php endif; ?> + </h2> + + <p class="desc"> + <?php + echo wp_kses( + sprintf( /* translators: %s - Additional Connections documentation page URL. */ + __( 'Create additional connections to set a backup for your Primary Connection or to configure Smart Routing. <a href="%s" target="_blank" rel="noopener noreferrer">Learn More</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/configuring-additional-connections/', + [ + 'content' => 'Additional Connections description', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + ?> + </p> + + <?php if ( $is_singular ) : ?> + <a href="<?php echo esc_url( $this->get_connections_list_url() ); ?>" class="wp-mail-smtp-additional-connections-header__back-link"> + <span aria-hidden="true" class="wpms:icon-[fa6-solid--arrow-left-long] wpms:w-[13px] wpms:h-[12px] wpms:shrink-0"></span> + <?php esc_html_e( 'Back to All Connections', 'wp-mail-smtp-pro' ); ?> + </a> + <?php endif; ?> + </div> + </div> + <?php + } + + /** + * Output HTML of the additional connections list. + * + * @since 3.7.0 + */ + private function display_connections_list() { + + $this->display_header(); + + $connections = $this->additional_connections->get_connections(); + + if ( empty( $connections ) ) { + return; + } + ?> + <div class="wp-mail-smtp-additional-connections-list"> + <div class="wp-mail-smtp-additional-connections-list__header"> + <?php esc_html_e( 'Saved Connections', 'wp-mail-smtp-pro' ); ?> + </div> + + <ul class="wp-mail-smtp-additional-connections-list__items"> + <?php foreach ( array_values( $connections ) as $i => $connection ) : ?> + <li class="wp-mail-smtp-additional-connections-list__item"> + <?php echo esc_html( $i + 1 ); ?>. + <a href="<?php echo esc_url( $this->get_connection_url( 'edit', $connection->get_id() ) ); ?>" class="wp-mail-smtp-additional-connections-list__link"> + <?php echo esc_html( $connection->get_title() ); ?> + </a> + <?php $this->display_connection_error_icon( $connection->get_id() ); ?> + <a href="<?php echo esc_url( $this->get_connection_url( 'edit', $connection->get_id() ) ); ?>" class="wp-mail-smtp-additional-connections-list__btn wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey"> + <i class="dashicons dashicons-edit"></i> + </a> + </li> + <?php endforeach; ?> + </ul> + </div> + <?php + } + + /** + * Output the inline error icon next to a failing connection's name in the list. + * + * Renders nothing when the connection has no record in `EmailSendingDebug`. + * + * @since 4.9.0 + * + * @param string $connection_id Connection ID. + */ + private function display_connection_error_icon( $connection_id ) { + + $record = EmailSendingDebug::get( $connection_id ); + + if ( empty( $record ) ) { + return; + } + + $label = esc_html__( 'Last email send via this connection failed.', 'wp-mail-smtp-pro' ); + + printf( + '<span class="wp-mail-smtp-email-sending-errors-icon wpms:icon-[fa6-solid--circle-exclamation] wpms:ml-[10px] wpms:w-[16px] wpms:h-[16px] wpms:shrink-0 wpms:text-error" role="img" aria-label="%1$s" title="%1$s"></span>', + esc_attr( $label ) + ); + } + + /** + * Output HTML of the education elements when there are no additional connections. + * + * @since 3.7.0 + */ + private function display_no_connections() { + + ?> + <div class="wp-mail-smtp-additional-connections-no-items"> + <?php + $this->display_header(); + $this->display_education_screenshots(); + $this->display_education_features_list(); + ?> + + <p class="wp-mail-smtp-submit"> + <a href="<?php echo esc_url( $this->get_connection_url( 'new' ) ); ?>" class="wp-mail-smtp-btn wp-mail-smtp-btn-cta wp-mail-smtp-btn-orange"> + <?php esc_html_e( 'Get Started', 'wp-mail-smtp-pro' ); ?> + </a> + </p> + </div> + <?php + } + + /** + * Output HTML of the single additional connection. + * + * @since 3.7.0 + * + * @param string $connection_id Connection ID. + */ + private function display_single_connection( $connection_id ) { + + $is_new = $connection_id === false; + + if ( ! $is_new ) { + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); + + if ( $connection === false ) { + $this->display_header( true ); + $this->display_connection_not_found(); + + return; + } + } else { + $connection = new Connection( uniqid() ); + } + + $connection_relation = $this->get_connection_relation( $connection_id ); + $connection_settings = new ConnectionSettings( $connection ); + $mailer = $connection->get_mailer(); + ?> + + <form method="POST" action="" autocomplete="off" class="wp-mail-smtp-connection-settings-form"> + <?php $this->wp_nonce_field(); ?> + + <input type="hidden" name="is_new" value="<?php echo intval( $is_new ); ?>"> + <input type="hidden" name="connection_id" value="<?php echo esc_attr( $connection->get_id() ); ?>"> + + <?php $this->display_header( true ); ?> + + <?php if ( $connection_relation === 'backup' ) : ?> + <div class="wp-mail-smtp-notice notice-warning notice-inline wp-mail-smtp-additional-connections-notice-top"> + <p> + <?php + echo wp_kses( + __( '<b>Warning!</b> You’re editing your backup mailer.', 'wp-mail-smtp-pro' ), + [ + 'b' => [], + ] + ); + ?> + </p> + </div> + <?php endif; ?> + + <!-- Connection name --> + <div id="wp-mail-smtp-setting-row-from_email" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-email wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-from_email"><?php esc_html_e( 'Connection Nickname', 'wp-mail-smtp-pro' ); ?></label> + </div> + <div class="wp-mail-smtp-setting-field"> + <input name="wp-mail-smtp[connection][name]" type="text" + value="<?php echo esc_attr( $connection->get_options()->get( 'connection', 'name' ) ); ?>" + id="wp-mail-smtp-setting-from_email" spellcheck="false" + required + /> + + <?php if ( ! $is_new && $mailer->is_mailer_complete() ) : ?> + <a href="<?php echo esc_url( $this->get_connection_url( 'switch_with_primary', $connection_id ) ); ?>" class="js-wp-mail-smtp-switch-additional-connection-with-primary wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-blueish"> + <?php esc_html_e( 'Use as Primary Connection', 'wp-mail-smtp-pro' ); ?> + </a> + <?php endif; ?> + </div> + </div> + + <?php $connection_settings->display(); ?> + + <div class="wp-mail-smtp-additional-connection-actions"> + <?php $this->display_save_btn(); ?> + + <?php if ( ! $is_new ) : ?> + <a href="<?php echo esc_url( $this->get_connection_url( 'delete', $connection_id ) ); ?>" class="js-wp-mail-smtp-delete-additional-connection wp-mail-smtp-additional-connection-actions__delete-link" data-relation="<?php echo esc_attr( $connection_relation ); ?>"> + <?php esc_attr_e( 'Delete Connection', 'wp-mail-smtp-pro' ); ?> + </a> + <?php endif; ?> + </div> + </form> + <?php + } + + /** + * Display a connection not found notice. + * + * @since 3.7.0 + */ + private function display_connection_not_found() { + + ?> + <h2><?php esc_html_e( 'Something went wrong', 'wp-mail-smtp-pro' ); ?></h2> + <p> + <?php esc_html_e( 'You are trying to access an additional connection that is no longer available or never existed.', 'wp-mail-smtp-pro' ); ?> + </p> + <p> + <?php esc_html_e( 'Please use the "Back to All Connections" button to return to the list of all available connections.', 'wp-mail-smtp-pro' ); ?> + </p> + <?php + } + + /** + * Process tab form submission ($_POST). + * + * @since 3.7.0 + * + * @param array $data Post data specific for the plugin. + */ + public function process_post( $data ) { + + // phpcs:ignore WordPress.Security.NonceVerification.Missing + if ( ! isset( $_POST['connection_id'] ) ) { + wp_die( esc_html__( 'Something goes wrong. Missed connection ID parameter.', 'wp-mail-smtp-pro' ) ); + } + + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $is_new = ! empty( $_POST['is_new'] ); + + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $connection_id = sanitize_key( $_POST['connection_id'] ); + + $connection = new Connection( $connection_id ); + $connection_settings = new ConnectionSettings( $connection ); + + $old_data = $connection->get_options()->get_all(); + + $data = $connection_settings->process( $data, $old_data ); + + // Save connection settings. + $connection->get_options()->set( $data, false, false ); + + $connection_settings->post_process( $data, $old_data ); + + if ( $is_new || $connection_settings->get_scroll_to() !== false ) { + $redirect_url = $this->get_connection_url( 'edit', $connection->get_id() ); + + if ( $connection_settings->get_scroll_to() !== false ) { + $redirect_url .= $connection_settings->get_scroll_to(); + } + + $redirect_url = add_query_arg( 'message', 'saved', $redirect_url ); + + wp_safe_redirect( $redirect_url ); + exit; + } + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } + + /** + * Get the connections list URL. + * + * @since 3.7.0 + * + * @return string + */ + private function get_connections_list_url() { + + return add_query_arg( + [ + 'tab' => 'connections', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ); + } + + /** + * Get the connection URL. + * + * @since 3.7.0 + * + * @param string $mode URL type that should be returned. Acceptable values "new", "edit", "delete" + * and "switch_with_primary". + * @param string $connection_id Connection ID. + * + * @return string + */ + private function get_connection_url( $mode = 'edit', $connection_id = false ) { + + $url = ''; + + switch ( $mode ) { + case 'new': + $url = add_query_arg( + [ + 'tab' => 'connections', + 'mode' => 'new', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ); + break; + + case 'edit': + $url = add_query_arg( + [ + 'tab' => 'connections', + 'mode' => 'edit', + 'connection_id' => $connection_id, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ); + break; + + case 'delete': + $url = wp_nonce_url( + add_query_arg( + [ + 'tab' => 'connections', + 'mode' => 'delete', + 'connection_id' => $connection_id, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ), + 'wp_mail_smtp_pro_additional_connection_delete' + ); + break; + + case 'switch_with_primary': + $url = wp_nonce_url( + add_query_arg( + [ + 'tab' => 'connections', + 'mode' => 'switch_with_primary', + 'connection_id' => $connection_id, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ), + 'wp_mail_smtp_pro_additional_connection_switch_with_primary' + ); + break; + } + + return $url; + } + + /** + * Get connection relation/dependency. Backup relation prioritized. + * + * @since 3.7.0 + * + * @param string $connection_id Connection ID. + * + * @return string + */ + private function get_connection_relation( $connection_id ) { + + $backup_connection_id = Options::init()->get( 'backup_connection', 'connection_id' ); + + if ( ! empty( $backup_connection_id ) && $connection_id === $backup_connection_id ) { + return 'backup'; + } + + $routes = Options::init()->get( 'smart_routing', 'routes' ); + + if ( ! empty( $routes ) && in_array( $connection_id, array_column( $routes, 'connection_id' ), true ) ) { + return 'routing'; + } + + return 'none'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/TestTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/TestTab.php new file mode 100644 index 00000000..2619749c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Admin/TestTab.php @@ -0,0 +1,133 @@ +<?php + +namespace WPMailSMTP\Pro\AdditionalConnections\Admin; + +use WPMailSMTP\ConnectionInterface; +use WPMailSMTP\Pro\AdditionalConnections\AdditionalConnections; + +/** + * Class TestTab. + * + * @since 3.7.0 + */ +class TestTab { + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_action( + 'wp_mail_smtp_admin_pages_test_tab_display_form_send_to_after', + [ $this, 'display_connection_selector' ] + ); + + add_filter( + 'wp_mail_smtp_admin_pages_test_tab_process_post_connection', + [ $this, 'set_test_email_connection' ], + 10, + 2 + ); + } + + /** + * Display connection selector. + * + * @since 3.7.0 + */ + public function display_connection_selector() { + + $connections = ( new AdditionalConnections() )->get_configured_connections(); + + if ( empty( $connections ) ) { + return; + } + + $primary_connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + + // Bail if the primary connection is not configured. + if ( ! $primary_connection->get_mailer()->is_mailer_complete() ) { + return; + } + + // Add a primary connection to the list as a first item. + array_unshift( $connections, $primary_connection ); + + // Pre-select the connection from (in priority order): + // 1. The just-submitted POST value, so the page reload after a test keeps + // the connection the user picked. + // 2. ?connection_id=<id> from the URL — set by the EmailSendingErrors + // banner's "Send Test Email" CTA so the test runs through the + // connection that had the failure. + // Falls back to primary in every other case. Each candidate is validated + // against the list of configured connections before being honored. + // Read-only sticky-form lookahead during form render. Nonce verification fires on submit in TestTab::process_post(). + // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended + $posted_id = isset( $_POST['wp-mail-smtp']['test']['connection'] ) ? sanitize_key( wp_unslash( $_POST['wp-mail-smtp']['test']['connection'] ) ) : ''; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $requested_id = isset( $_GET['connection_id'] ) ? sanitize_key( wp_unslash( $_GET['connection_id'] ) ) : ''; + $selected_id = $primary_connection->get_id(); + + foreach ( [ $posted_id, $requested_id ] as $candidate ) { + if ( $candidate === '' ) { + continue; + } + + foreach ( $connections as $connection ) { + if ( $connection->get_id() === $candidate ) { + $selected_id = $candidate; + + break 2; + } + } + } + ?> + <div id="wp-mail-smtp-setting-row-test_email_connection" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-test_email_html"> + <?php esc_html_e( 'Connection', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <div class="wp-mail-smtp-connection-selector"> + <?php foreach ( $connections as $connection ) : ?> + <label for="wp-mail-smtp-setting-test_email_connection_<?php echo esc_attr( $connection->get_id() ); ?>"> + <input type="radio" id="wp-mail-smtp-setting-test_email_connection_<?php echo esc_attr( $connection->get_id() ); ?>" + name="wp-mail-smtp[test][connection]" value="<?php echo esc_attr( $connection->get_id() ); ?>" + <?php checked( $selected_id, $connection->get_id() ); ?> + /> + <span><?php echo esc_html( $connection->get_title() ); ?></span> + </label> + <?php endforeach; ?> + </div> + <p class="desc"> + <?php esc_html_e( 'Choose which connection you\'d like to use to send a test email.', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + </div> + <?php + } + + /** + * Set test email connection. + * + * @since 3.7.0 + * + * @param ConnectionInterface $connection The Connection object. + * @param array $data Post data. + */ + public function set_test_email_connection( $connection, $data ) { + + if ( ! empty( $data['test']['connection'] ) ) { + $connections_manager = wp_mail_smtp()->get_connections_manager(); + $connection_id = sanitize_key( $data['test']['connection'] ); + $connection = $connections_manager->get_connection( $connection_id ); + + $connections_manager->set_mail_connection( $connection ); + } + + return $connection; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Connection.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Connection.php new file mode 100644 index 00000000..be27687c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/Connection.php @@ -0,0 +1,86 @@ +<?php + +namespace WPMailSMTP\Pro\AdditionalConnections; + +use WPMailSMTP\AbstractConnection; + +/** + * Class Connection. + * + * Additional connection implementation. + * + * @since 3.7.0 + */ +class Connection extends AbstractConnection { + + /** + * The connection ID. + * + * @since 3.7.0 + * + * @var string + */ + private $connection_id; + + /** + * Connection Options object. + * + * @since 3.7.0 + * + * @var ConnectionOptions + */ + private $options = null; + + /** + * Constructor. + * + * @since 3.7.0 + * + * @param string $connection_id The connection ID. + */ + public function __construct( $connection_id ) { + + $this->connection_id = $connection_id; + } + + /** + * Get the connection identifier. + * + * @since 3.7.0 + * + * @return string + */ + public function get_id() { + + return $this->connection_id; + } + + /** + * Get the connection name. + * + * @since 3.7.0 + * + * @return string + */ + public function get_name() { + + return $this->get_options()->get( 'connection', 'name' ); + } + + /** + * Get connection Options object. + * + * @since 3.7.0 + * + * @return ConnectionOptions + */ + public function get_options() { + + if ( is_null( $this->options ) ) { + $this->options = new ConnectionOptions( $this->connection_id ); + } + + return $this->options; + } +} + diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/ConnectionOptions.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/ConnectionOptions.php new file mode 100644 index 00000000..fa58be42 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/AdditionalConnections/ConnectionOptions.php @@ -0,0 +1,140 @@ +<?php + +namespace WPMailSMTP\Pro\AdditionalConnections; + +use WPMailSMTP\Options; +use WPMailSMTP\WP; + +/** + * Class ConnectionOptions. + * + * @since 3.7.0 + */ +class ConnectionOptions extends Options { + + /** + * That's where additional connections options are saved in wp_options table. + * + * @since 3.7.0 + * + * @var string + */ + const META_KEY = 'wp_mail_smtp_additional_connections'; + + /** + * The connection ID. + * + * @since 3.7.0 + * + * @var string + */ + private $connection_id; + + /** + * All connections options. + * + * @since 3.7.0 + * + * @var array + */ + private $all_options = []; + + /** + * All instances of ConnectionOptions class that should be notified about options update. + * + * @since 3.7.0 + * + * @var ConnectionOptions[] + */ + protected static $update_observers; + + /** + * Constructor. + * + * @since 3.7.0 + * + * @param string $connection_id The connection ID. + */ + public function __construct( $connection_id ) { + + $this->connection_id = $connection_id; + + parent::__construct(); + } + + /** + * Whether current class is a main options. + * + * @since 3.7.0 + * + * @var bool + */ + protected function is_main_options() { + + return false; + } + + /** + * Retrieve connection options. + * + * @since 3.7.0 + */ + protected function populate_options() { + + if ( WP::use_global_plugin_settings() ) { + $this->all_options = get_blog_option( get_main_site_id(), static::META_KEY, [] ); + } else { + $this->all_options = get_option( static::META_KEY, [] ); + } + + // Populate connection options. + $this->options = isset( $this->all_options[ $this->connection_id ] ) ? $this->all_options[ $this->connection_id ] : []; + } + + /** + * Set connection options. + * + * @since 3.7.0 + * + * @param array $options Connection options to save. + * @param bool $once Whether to update existing options or to add these options only once. + * @param bool $overwrite_existing Whether to overwrite existing settings or merge these passed options with existing ones. + */ + public function set( $options, $once = false, $overwrite_existing = true ) { + + // Merge existing settings with new values. + if ( ! $overwrite_existing ) { + $options = self::array_merge_recursive( $this->get_all_raw(), $options ); + } + + // Filter out non-connection options. + $connection_options = array_merge( [ 'connection', 'mail', 'smtp' ], self::$mailers ); + $options = array_intersect_key( $options, array_flip( $connection_options ) ); + + // Sanitize options. + if ( isset( $options['connection']['name'] ) ) { + $options['connection']['name'] = sanitize_text_field( $options['connection']['name'] ); + } + + $options = $this->process_generic_options( $options ); + $options = $this->process_mailer_specific_options( $options ); + + $all_options = $this->all_options; + $all_options[ $this->connection_id ] = $options; + + $this->save_options( $all_options, $once ); + } + + /** + * Whether constants redefinition is enabled or not. + * + * @since 3.7.0 + * + * @return bool + */ + public function is_const_enabled() { + + // For now, additional connections do not support constants. + return false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Area.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Area.php new file mode 100644 index 00000000..4d44488d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Area.php @@ -0,0 +1,90 @@ +<?php + +namespace WPMailSMTP\Pro\Admin; + +use WPMailSMTP\Pro\Admin\Pages\MiscTab; +use WPMailSMTP\Pro\Admin\Pages\TestTab; + +/** + * Class Area registers and process all wp-admin display functionality. + * + * @since 4.0.0 + */ +class Area { + + /** + * Assign all hooks to proper places. + * + * @since 4.0.0 + */ + public function hooks() { + + // Admin pages. + add_filter( 'wp_mail_smtp_admin_get_pages', [ $this, 'admin_get_pages' ] ); + add_filter( 'wp_mail_smtp_admin_page_tools_tabs', [ $this, 'admin_page_tools_tabs' ] ); + + // Manage other admin notices. + add_action( 'admin_init', [ $this, 'manage_other_admin_notices' ] ); + } + + /** + * Replace Lite's Misc tab with the Pro version. + * + * @since 4.0.0 + * + * @param array $pages List of admin pages. + * + * @return array + */ + public function admin_get_pages( $pages ) { + + $pages['misc'] = new MiscTab(); + + return $pages; + } + + /** + * Replace Lite's Test sub-tab class string with the Pro version. + * + * @since 4.9.0 + * + * @param array $tabs Tools page sub-tabs (class-string map). + * + * @return array + */ + public function admin_page_tools_tabs( $tabs ) { + + $tabs['test'] = TestTab::class; + + return $tabs; + } + + /** + * Manage other admin notices. + * + * @since 4.5.0 + */ + public function manage_other_admin_notices() { + + $user_id = get_current_user_id(); + + if ( ! $user_id ) { + return; + } + + $meta_key = strrev( 'rotnemele' ) . '_admin_notices'; + $user_meta = get_user_meta( $user_id, $meta_key, true ); + + if ( is_array( $user_meta ) && isset( $user_meta['site_mailer_promotion'] ) ) { + return; + } + + if ( ! is_array( $user_meta ) ) { + $user_meta = []; + } + + $user_meta['site_mailer_promotion'] = 'true'; + + update_user_meta( $user_id, $meta_key, $user_meta ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/DashboardWidget.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/DashboardWidget.php new file mode 100644 index 00000000..ffdd0e2d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/DashboardWidget.php @@ -0,0 +1,1317 @@ +<?php + +namespace WPMailSMTP\Pro\Admin; + +use WPMailSMTP\Admin\Area; +use WPMailSMTP\Helpers\Helpers; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Emails\Logs\Email; +use WPMailSMTP\Pro\Emails\Logs\Logs; +use WPMailSMTP\WP; + +/** + * Dashboard Widget shows the email log chart and stats in WP Dashboard. + * + * @since 2.7.0 + */ +class DashboardWidget { + + /** + * Instance slug. + * + * @since 2.7.0 + * + * @const string + */ + const SLUG = 'dash_widget'; + + /** + * Widget settings. + * + * @since 2.7.0 + * + * @var array + */ + public $settings; + + /** + * Runtime values. + * + * @since 2.7.0 + * + * @var array + */ + public $runtime_data; + + /** + * Constructor. + * + * @since 2.7.0 + */ + public function __construct() {} + + /** + * Init class. + * + * @since 2.7.0 + */ + public function init() { + + // Prevent the class initialization, if the dashboard widget hidden setting is enabled. + if ( Options::init()->get( 'general', 'dashboard_widget_hidden' ) ) { + return; + } + + add_action( + 'admin_init', + function() { + // This widget should be displayed for certain high-level users only. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + return; + } + + /** + * Filters whether the initialization of the dashboard widget should be allowed. + * + * @since 2.7.0 + * + * @param bool $var If the dashboard widget should be initialized. + */ + if ( ! apply_filters( 'wp_mail_smtp_admin_dashboard_widget', '__return_true' ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName + return; + } + + $this->settings(); + $this->hooks(); + } + ); + } + + /** + * Filterable widget settings. + * + * @since 2.7.0 + */ + public function settings() { + + $this->settings = [ + + /** + * Filters whether the dashboard widget data should be cached. + * + * Allow results caching to reduce DB load. + * + * @since 2.7.0 + * + * @param bool $var If the dashboard widget data caching is enabled. + */ + 'allow_data_caching' => apply_filters( 'wp_mail_smtp_' . static::SLUG . '_allow_data_caching', true ), + + /** + * What the end date for the dashboard widget data should be. + * + * @since 2.7.0 + * + * @param string $var PHP DateTime supported string (http://php.net/manual/en/datetime.formats.php). + */ + 'date_end_str' => apply_filters( 'wp_mail_smtp_' . static::SLUG . '_date_end_str', 'yesterday' ), + + /** + * Number of seconds for the dashboard widget data to be cached (transient). + * + * @since 2.7.0 + * + * @param int $var Transient lifetime in seconds. Defaults to the end of a current day. + */ + 'transient_lifetime' => apply_filters( + 'wp_mail_smtp_' . static::SLUG . '_transient_lifetime', + strtotime( 'tomorrow' ) - time() + ), + ]; + } + + /** + * Widget hooks. + * + * @since 2.7.0 + */ + public function hooks() { + + add_action( 'admin_enqueue_scripts', [ $this, 'widget_scripts' ] ); + add_action( 'wp_dashboard_setup', [ $this, 'widget_register' ] ); + + add_action( 'wp_ajax_wp_mail_smtp_' . static::SLUG . '_get_chart_data', [ $this, 'get_chart_data_ajax' ] ); + add_action( 'wp_ajax_wp_mail_smtp_' . static::SLUG . '_get_email_stats', [ $this, 'get_email_stats_ajax' ] ); + add_action( 'wp_ajax_wp_mail_smtp_' . static::SLUG . '_save_widget_meta', [ $this, 'save_widget_meta_ajax' ] ); + } + + /** + * Load widget-specific scripts. + * Load them only on the admin dashboard page. + * + * @since 2.7.0 + */ + public function widget_scripts() { + + $screen = get_current_screen(); + + if ( ! isset( $screen->id ) || 'dashboard' !== $screen->id ) { + return; + } + + $min = WP::asset_min(); + + wp_enqueue_style( + 'wp-mail-smtp-dashboard-widget', + wp_mail_smtp()->assets_url . '/css/dashboard-widget.min.css', + [], + WPMS_PLUGIN_VER + ); + + wp_enqueue_script( + 'wp-mail-smtp-chart', + wp_mail_smtp()->assets_url . '/js/vendor/chart.min.js', + [ 'moment' ], + '4.4.9', + true + ); + + wp_enqueue_script( + 'wp-mail-smtp-chart-adapter', + wp_mail_smtp()->assets_url . '/js/vendor/chartjs-adapter-moment.min.js', + [ 'moment', 'wp-mail-smtp-chart' ], + '1.0.1', + true + ); + + wp_enqueue_script( + 'wp-mail-smtp-dashboard-widget', + wp_mail_smtp()->pro->assets_url . "/js/smtp-pro-dashboard-widget{$min}.js", + [ 'jquery', 'wp-mail-smtp-chart', 'wp-mail-smtp-chart-adapter' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-dashboard-widget', + 'wp_mail_smtp_dashboard_widget', + [ + 'nonce' => wp_create_nonce( 'wp_mail_smtp_' . static::SLUG . '_nonce' ), + 'slug' => static::SLUG, + 'empty_chart_html' => $this->get_empty_chart_html(), + 'chart_data' => $this->get_email_stats_count_by( + 'date', + $this->widget_meta( 'get', 'timespan' ) + ), + 'texts' => [ + 'confirmed_emails' => esc_html__( 'Confirmed sent emails', 'wp-mail-smtp-pro' ), + 'sent_emails' => esc_html__( 'Sent emails', 'wp-mail-smtp-pro' ), + 'unconfirmed_emails' => esc_html__( 'Unconfirmed sent emails', 'wp-mail-smtp-pro' ), + 'failed_emails' => esc_html__( 'Failed emails', 'wp-mail-smtp-pro' ), + ], + 'no_send_confirmations' => Helpers::mailer_without_send_confirmation(), + ] + ); + } + + /** + * Register the widget. + * + * @since 2.7.0 + */ + public function widget_register() { + + global $wp_meta_boxes; + + $widget_key = 'wp_mail_smtp_reports_widget_pro'; + + wp_add_dashboard_widget( + $widget_key, + esc_html__( 'WP Mail SMTP', 'wp-mail-smtp-pro' ), + [ $this, 'widget_content' ] + ); + + // Attempt to place the widget at the top. + $normal_dashboard = $wp_meta_boxes['dashboard']['normal']['core']; + + if ( isset( $normal_dashboard[ $widget_key ] ) ) { + $widget_instance = [ $widget_key => $normal_dashboard[ $widget_key ] ]; + + unset( $normal_dashboard[ $widget_key ] ); + + $sorted_dashboard = array_merge( $widget_instance, $normal_dashboard ); + + //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $wp_meta_boxes['dashboard']['normal']['core'] = $sorted_dashboard; + } + } + + /** + * Load widget content. + * + * @since 2.7.0 + */ + public function widget_content() { + + echo '<div class="wp-mail-smtp-dash-widget wp-mail-smtp-dash-widget--pro">'; + + if ( wp_mail_smtp()->pro->get_logs()->is_enabled() ) { + $this->widget_content_html(); + $this->display_after_widget_content_html(); + } else { + $this->widget_content_logs_disabled(); + } + + echo '</div><!-- .wp-mail-smtp-dash-widget -->'; + } + + /** + * Display the content after the email stats block. + * + * @since 3.9.0 + * + * @return void + */ + private function display_after_widget_content_html() { + + if ( + ! ( new Alerts() )->is_enabled() && + empty( $this->widget_meta( 'get', 'hide_email_alerts_banner' ) ) + ) { + $email_stats = $this->get_email_stats_count_by( 'total', $this->widget_meta( 'get', 'timespan' ) ); + + if ( ! empty( $email_stats['unsent'] ) ) { + $this->show_email_alerts_banner( $email_stats['unsent'] ); + + return; + } + } + + $plugins = get_plugins(); + $hide_recommended = $this->widget_meta( 'get', 'hide_recommended_block' ); + + if ( + ! array_key_exists( 'wpforms-lite/wpforms.php', $plugins ) && + ! array_key_exists( 'wpforms/wpforms.php', $plugins ) && + empty( $hide_recommended ) + ) { + $this->recommended_plugin_block_html(); + } + } + + /** + * Display the email alerts banner. + * + * @since 3.9.0 + * + * @param int $error_count The number of debug events error. + * + * @return void + */ + private function show_email_alerts_banner( $error_count ) { + + ?> + <div id="wp-mail-smtp-dash-widget-email-alerts-education" class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-email-alerts-education"> + <div class="wp-mail-smtp-dash-widget-email-alerts-education-error-icon"> + <?php + printf( + '<img src="%s" alt="%s"/>', + esc_url( wp_mail_smtp()->assets_url . '/images/dash-widget/error-icon.svg' ), + esc_attr__( 'Error icon', 'wp-mail-smtp-pro' ) + ); + ?> + </div> + <div class="wp-mail-smtp-dash-widget-email-alerts-education-content"> + <?php + $error_title = sprintf( + /* translators: 1: Number of failed emails, 2: Timespan. */ + _n( + 'We detected %1$d failed email in the last %2$d days.', + 'We detected %1$d failed emails in the last %2$d days.', + $error_count, + 'wp-mail-smtp-pro' + ), + $error_count, + $this->widget_meta( 'get', 'timespan' ) + ); + + $error_content = sprintf( + /* translators: %s - URL to WP Mail SMTP -> Settings -> Alerts Admin page.. */ + __( '<a href="%s">Enable Email Alerts</a> and get instant notifications when they fail.', 'wp-mail-smtp-pro' ), + esc_url( add_query_arg( 'tab', 'alerts', wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG ) ) ) + ); + ?> + <p> + <strong><?php echo esc_html( $error_title ); ?></strong><br /> + <?php + echo wp_kses( + $error_content, + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + ?> + </p> + </div> + + <button type="button" id="wp-mail-smtp-dash-widget-dismiss-email-alert-block" class="wp-mail-smtp-dash-widget-dismiss-email-alert-block" title="<?php esc_attr_e( 'Dismiss email alert block', 'wp-mail-smtp-pro' ); ?>"> + <span class="dashicons dashicons-no-alt"></span> + </button> + </div> + <?php + } + + /** + * Widget content HTML if the Email Logs are not enabled. + * + * @since 2.7.0 + */ + public function widget_content_logs_disabled() { + + $enable_logs_url = wp_mail_smtp()->pro->get_logs()->get_settings_url(); + + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + $learn_more_url = wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-set-up-email-logging/', [ 'medium' => 'link', 'content' => 'dashboardwidget' ] ); + + ?> + <div class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-block-logs-disabled"> + <img class="wp-mail-smtp-dash-widget-block-logo" src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/pattie.svg' ); ?>" alt="<?php esc_attr_e( 'Pattie the WP Mail SMTP mascot', 'wp-mail-smtp-pro' ); ?>"> + <h2><?php esc_html_e( 'Enable Email Logs to View Stats', 'wp-mail-smtp-pro' ); ?></h2> + <p><?php esc_html_e( 'Automatically keep track of every email sent from your WordPress site and view valuable statistics right here in your dashboard.', 'wp-mail-smtp-pro' ); ?></p> + <a href="<?php echo esc_url( $enable_logs_url ); ?>" class="button button-primary"> + <?php esc_html_e( 'Enable Email Logging', 'wp-mail-smtp-pro' ); ?> + </a> + <a href="<?php echo esc_url( $learn_more_url ); ?>" class="button" target="_blank" rel="noopener noreferrer"> + <?php esc_html_e( 'Learn More', 'wp-mail-smtp-pro' ); ?> + </a> + </div> + <?php + } + + /** + * Widget content HTML. + * + * @since 2.7.0 + */ + public function widget_content_html() { + + $timespan = $this->widget_meta( 'get', 'timespan' ); + ?> + + <div class="wp-mail-smtp-dash-widget-chart-block-container"> + <div class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-chart-block"> + <canvas id="wp-mail-smtp-dash-widget-chart" width="554" height="291"></canvas> + <div class="wp-mail-smtp-dash-widget-overlay"></div> + </div> + </div> + + <div class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-block-settings"> + <div> + <?php $this->email_types_select_html(); ?> + <a href="<?php echo esc_url( wp_mail_smtp()->pro->get_logs()->get_admin_page_url() ); ?>"> + <?php esc_html_e( 'View Full Log', 'wp-mail-smtp-pro' ); ?> + </a> + </div> + <div> + <?php + $this->timespan_select_html(); + $this->widget_settings_html(); + ?> + </div> + </div> + + <div id="wp-mail-smtp-dash-widget-email-stats-block" class="wp-mail-smtp-dash-widget-block wp-mail-smtp-dash-widget-email-stats-block"> + <?php $this->email_stats_block( $timespan ); ?> + </div> + <?php + } + + /** + * Timespan select HTML. + * + * @since 2.7.0 + */ + public function timespan_select_html() { + + $timespan = $this->widget_meta( 'get', 'timespan' ); + ?> + <select id="wp-mail-smtp-dash-widget-timespan" class="wp-mail-smtp-dash-widget-select-timespan" title="<?php esc_attr_e( 'Select timespan', 'wp-mail-smtp-pro' ); ?>"> + <?php foreach ( $this->get_timespan_options() as $option ) : ?> + <option value="<?php echo absint( $option ); ?>" <?php selected( $timespan, absint( $option ) ); ?>> + <?php /* translators: %d - Number of days. */ ?> + <?php echo esc_html( sprintf( _n( 'Last %d day', 'Last %d days', absint( $option ), 'wp-mail-smtp-pro' ), absint( $option ) ) ); ?> + </option> + <?php endforeach; ?> + </select> + + <?php + } + + /** + * Email types select HTML. + * + * @since 2.7.0 + */ + public function email_types_select_html() { + + $email_type = $this->widget_meta( 'get', 'email_type' ); + $options = [ + 'all' => esc_html__( 'All Emails', 'wp-mail-smtp-pro' ), + 'delivered' => esc_html__( 'Confirmed Emails', 'wp-mail-smtp-pro' ), + 'sent' => esc_html__( 'Unconfirmed Emails', 'wp-mail-smtp-pro' ), + 'unsent' => esc_html__( 'Failed Emails', 'wp-mail-smtp-pro' ), + ]; + + if ( Helpers::mailer_without_send_confirmation() ) { + unset( $options['sent'] ); + $options['delivered'] = esc_html__( 'Sent Emails', 'wp-mail-smtp-pro' ); + } + + ?> + <select id="wp-mail-smtp-dash-widget-email-type" class="wp-mail-smtp-dash-widget-select-email-type" title="<?php esc_attr_e( 'Select email type', 'wp-mail-smtp-pro' ); ?>"> + <?php foreach ( $options as $key => $title ) : ?> + <option value="<?php echo sanitize_key( $key ); ?>" <?php selected( $email_type, sanitize_key( $key ) ); ?>> + <?php echo esc_html( $title ); ?> + </option> + <?php endforeach; ?> + </select> + + <?php + } + + /** + * Widget settings HTML. + * + * @since 2.7.0 + */ + public function widget_settings_html() { + + $graph_style = $this->widget_meta( 'get', 'graph_style' ); + $color_scheme = $this->widget_meta( 'get', 'color_scheme' ); + ?> + <div class="wp-mail-smtp-dash-widget-settings-container"> + <button id="wp-mail-smtp-dash-widget-settings-button" class="wp-mail-smtp-dash-widget-settings-button button" type="button"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19"> + <path d="M18,11l-2.18,0c-0.17,0.7 -0.44,1.35 -0.81,1.93l1.54,1.54l-2.1,2.1l-1.54,-1.54c-0.58,0.36 -1.23,0.63 -1.91,0.79l0,2.18l-3,0l0,-2.18c-0.68,-0.16 -1.33,-0.43 -1.91,-0.79l-1.54,1.54l-2.12,-2.12l1.54,-1.54c-0.36,-0.58 -0.63,-1.23 -0.79,-1.91l-2.18,0l0,-2.97l2.17,0c0.16,-0.7 0.44,-1.35 0.8,-1.94l-1.54,-1.54l2.1,-2.1l1.54,1.54c0.58,-0.37 1.24,-0.64 1.93,-0.81l0,-2.18l3,0l0,2.18c0.68,0.16 1.33,0.43 1.91,0.79l1.54,-1.54l2.12,2.12l-1.54,1.54c0.36,0.59 0.64,1.24 0.8,1.94l2.17,0l0,2.97Zm-8.5,1.5c1.66,0 3,-1.34 3,-3c0,-1.66 -1.34,-3 -3,-3c-1.66,0 -3,1.34 -3,3c0,1.66 1.34,3 3,3Z"></path> + </svg> + </button> + <div class="wp-mail-smtp-dash-widget-settings-menu"> + <div class="wp-mail-smtp-dash-widget-settings-menu--style"> + <h4><?php esc_html_e( 'Graph Style', 'wp-mail-smtp-pro' ); ?></h4> + <div> + <div class="wp-mail-smtp-dash-widget-settings-menu-item"> + <input type="radio" id="wp-mail-smtp-dash-widget-settings-style-bar" name="style" value="bar" <?php checked( 'bar', $graph_style ); ?>> + <label for="wp-mail-smtp-dash-widget-settings-style-bar"><?php esc_html_e( 'Bar', 'wp-mail-smtp-pro' ); ?></label> + </div> + <div class="wp-mail-smtp-dash-widget-settings-menu-item"> + <input type="radio" id="wp-mail-smtp-dash-widget-settings-style-line" name="style" value="line" <?php checked( 'line', $graph_style ); ?>> + <label for="wp-mail-smtp-dash-widget-settings-style-line"><?php esc_html_e( 'Line', 'wp-mail-smtp-pro' ); ?></label> + </div> + </div> + </div> + <div class="wp-mail-smtp-dash-widget-settings-menu--color"> + <h4><?php esc_html_e( 'Color Scheme', 'wp-mail-smtp-pro' ); ?></h4> + <div> + <div class="wp-mail-smtp-dash-widget-settings-menu-item"> + <input type="radio" id="wp-mail-smtp-dash-widget-settings-color-smtp" name="color" value="smtp" <?php checked( 'smtp', $color_scheme ); ?>> + <label for="wp-mail-smtp-dash-widget-settings-color-smtp"><?php esc_html_e( 'WP Mail SMTP', 'wp-mail-smtp-pro' ); ?></label> + </div> + <div class="wp-mail-smtp-dash-widget-settings-menu-item"> + <input type="radio" id="wp-mail-smtp-dash-widget-settings-color-wp" name="color" value="wp" <?php checked( 'wp', $color_scheme ); ?>> + <label for="wp-mail-smtp-dash-widget-settings-color-wp"><?php esc_html_e( 'WordPress', 'wp-mail-smtp-pro' ); ?></label> + </div> + </div> + </div> + <button type="button" class="button wp-mail-smtp-dash-widget-settings-menu-save"><?php esc_html_e( 'Save Changes', 'wp-mail-smtp-pro' ); ?></button> + </div> + </div> + <?php + } + + /** + * Email statistics block. + * + * @since 2.7.0 + * + * @param int $days Timespan (in days) to fetch the data for. + * @param string $color_scheme The color scheme to fetch the data for. + */ + public function email_stats_block( $days, $color_scheme = false ) { + + $email_stats = $this->get_email_stats_count_by( 'total', $days ); + + if ( empty( $email_stats ) ) { + $this->email_stats_block_empty_html(); + } else { + $this->email_stats_block_html( $email_stats, $color_scheme ); + } + } + + /** + * Empty email statistics block HTML. + * + * @since 2.7.0 + */ + public function email_stats_block_empty_html() { + + ?> + <p class="wp-mail-smtp-error wp-mail-smtp-error-no-data-email-stats"> + <?php esc_html_e( 'No email logs for selected period.', 'wp-mail-smtp-pro' ); ?> + </p> + <?php + } + + /** + * Email statistics block HTML. + * + * @since 2.7.0 + * + * @param array $email_stats Stats for total, confirmed, unconfirmed and failed emails. + * @param string $color_scheme The color scheme to use for the output. + */ + public function email_stats_block_html( $email_stats, $color_scheme = false ) { + + $output_data = $this->get_email_stats_data( $email_stats, $color_scheme ); + ?> + + <table id="wp-mail-smtp-dash-widget-email-stats-table" cellspacing="0"> + <tr> + <?php + $count = 0; + $per_row = 2; + + foreach ( array_values( $output_data ) as $stats ) : + if ( ! is_array( $stats ) ) { + continue; + } + + if ( ! isset( $stats['icon'], $stats['title'] ) ) { + continue; + } + + // Make some exceptions for mailers without send confirmation functionality. + if ( Helpers::mailer_without_send_confirmation() ) { + $per_row = 3; + } + + // Create new row after every $per_row cells. + if ( $count !== 0 && $count % $per_row === 0 ) { + echo '</tr><tr>'; + } + + $count++; + ?> + <td class="wp-mail-smtp-dash-widget-email-stats-table-cell wp-mail-smtp-dash-widget-email-stats-table-cell--<?php echo esc_attr( $stats['type'] ); ?> wp-mail-smtp-dash-widget-email-stats-table-cell--<?php echo esc_attr( $per_row ); ?>"> + <div class="wp-mail-smtp-dash-widget-email-stats-table-cell-container"> + <img src="<?php echo esc_url( $stats['icon'] ); ?>" alt="<?php esc_attr_e( 'Table cell icon', 'wp-mail-smtp-pro' ); ?>"> + <span> + <?php echo esc_html( $stats['title'] ); ?> + </span> + </div> + </td> + <?php endforeach; ?> + </tr> + </table> + + <?php + } + + /** + * Recommended plugin block HTML. + * + * @since 2.7.0 + */ + public function recommended_plugin_block_html() { + + $install_wpforms_url = wp_nonce_url( + self_admin_url( 'update.php?action=install-plugin&plugin=wpforms-lite' ), + 'install-plugin_wpforms-lite' + ); + ?> + + <div class="wp-mail-smtp-dash-widget-recommended-plugin-block"> + <span class="wp-mail-smtp-dash-widget-recommended-plugin"> + <span class="recommended"><?php esc_html_e( 'Recommended Plugin:', 'wp-mail-smtp-pro' ); ?></span> + <span> + <b><?php esc_html_e( 'WPForms', 'wp-mail-smtp-pro' ); ?></b> <span class="sep">-</span> + <?php if ( current_user_can( 'install_plugins' ) ) : ?> + <a href="<?php echo esc_url( $install_wpforms_url ); ?>"><?php esc_html_e( 'Install', 'wp-mail-smtp-pro' ); ?></a> <span class="sep sep-vertical">|</span> + <?php endif; ?> + <a href="https://wpforms.com/?utm_source=wpmailsmtpplugin&utm_medium=link&utm_campaign=wpmailsmtpdashboardwidget" target="_blank"><?php esc_html_e( 'Learn More', 'wp-mail-smtp-pro' ); ?></a> + </span> + </span> + <button type="button" id="wp-mail-smtp-dash-widget-dismiss-recommended-plugin-block" class="wp-mail-smtp-dash-widget-dismiss-recommended-plugin-block" title="<?php esc_attr_e( 'Dismiss recommended plugin block', 'wp-mail-smtp-pro' ); ?>"> + <span class="dashicons dashicons-no-alt"></span> + </button> + </div> + <?php + } + + /** + * Get empty chart HTML. + * + * @since 2.7.0 + */ + public function get_empty_chart_html() { + + ob_start(); + ?> + + <div class="wp-mail-smtp-error wp-mail-smtp-error-no-data-chart"> + <div class="wp-mail-smtp-dash-widget-modal"> + <h2><?php esc_html_e( 'No email logs yet', 'wp-mail-smtp-pro' ); ?></h2> + <p><?php esc_html_e( 'Please check back tomorrow.', 'wp-mail-smtp-pro' ); ?></p> + </div> + </div> + + <?php + return ob_get_clean(); + } + + /** + * Get timespan options (in days). + * + * @since 2.7.0 + * + * @return array + */ + public function get_timespan_options() { + + $default = [ 7, 14, 30 ]; + + /** + * Define the timespan options, in days, to be available for the dashboard widget. + * + * @since 2.7.0 + * + * @param array $default Array of integers -> days. + */ + $options = apply_filters( 'wp_mail_smtp_' . static::SLUG . '_timespan_options', $default ); + + if ( ! is_array( $options ) ) { + return $default; + } + + $options = array_filter( $options, 'is_numeric' ); + + return empty( $options ) ? $default : $options; + } + + /** + * Get the default timespan option. + * + * @since 2.7.0 + * + * @return int|null + */ + public function get_timespan_default() { + + $options = $this->get_timespan_options(); + $default = reset( $options ); + + if ( ! is_numeric( $default ) ) { + return null; + } + + return $default; + } + + /** + * Get/set a widget meta. + * + * @since 2.7.0 + * + * @param string $action Possible value: 'get' or 'set'. + * @param string $meta Meta name. + * @param int $value Value to set. + * + * @return mixed + */ + public function widget_meta( $action, $meta, $value = 0 ) { + + $allowed_actions = array( 'get', 'set' ); + + if ( ! in_array( $action, $allowed_actions, true ) ) { + return false; + } + + if ( 'get' === $action ) { + return $this->get_widget_meta( $meta ); + } + + $meta_key = $this->get_widget_meta_key( $meta ); + $value = sanitize_key( $value ); + + if ( 'set' === $action && ! empty( $value ) ) { + return update_user_meta( get_current_user_id(), $meta_key, $value ); + } + + if ( 'set' === $action && empty( $value ) ) { + return delete_user_meta( get_current_user_id(), $meta_key ); + } + + return false; + } + + /** + * Get the widget meta value. + * + * @since 3.9.0 + * + * @param string $meta Meta name. + * + * @return mixed + */ + private function get_widget_meta( $meta ) { + + $defaults = [ + 'timespan' => $this->get_timespan_default(), + 'email_type' => 'all', + 'graph_style' => 'line', + 'color_scheme' => 'smtp', + 'hide_recommended_block' => 0, + 'hide_email_alerts_banner' => 0, + ]; + + $meta_value = get_user_meta( get_current_user_id(), $this->get_widget_meta_key( $meta ), true ); + + if ( ! empty( $meta_value ) ) { + return $meta_value; + } + + if ( isset( $defaults[ $meta ] ) ) { + return $defaults[ $meta ]; + } + + return null; + } + + /** + * Retrieve the meta key. + * + * @since 3.9.0 + * + * @param string $meta Meta name. + * + * @return string + */ + private function get_widget_meta_key( $meta ) { + + return 'wp_mail_smtp_' . static::SLUG . '_' . $meta; + } + + /** + * Converts number of days to day start and day end values. + * + * @since 2.7.0 + * + * @param integer $days Timespan days. + * + * @return mixed + */ + public function get_days_interval( $days = 0 ) { + + if ( empty( $days ) ) { + $days = $this->runtime_data['days']; + } else { + $this->runtime_data['days'] = $days; + } + + if ( ! empty( $this->runtime_data['days_interval'][ $days ] ) ) { + return $this->runtime_data['days_interval'][ $days ]; + } + + // PHP DateTime supported string (http://php.net/manual/en/datetime.formats.php). + $date_end_str = $this->settings['date_end_str']; + + try { + $interval['end'] = new \DateTime( $date_end_str ); + } catch ( \Exception $e ) { + return false; + } + + try { + $interval['start'] = new \DateTime( $date_end_str ); + } catch ( \Exception $e ) { + return false; + } + + $interval['end'] = $interval['end']->setTime( 23, 59, 59 ); + + $interval['start'] = $interval['start'] + ->modify( '-' . ( absint( $days ) - 1 ) . 'days' ) + ->setTime( 0, 0 ); + + $this->runtime_data['days_interval'][ $days ] = $interval; + + return $interval; + } + + + /** + * Get email stats count grouped by $param. + * Main point of entry to fetch email stats count data from DB. + * Caches the result. + * + * @since 2.7.0 + * + * @param string $param Possible value: 'date' or 'total'. + * @param int $days Timespan (in days) to fetch the data for. + * + * @return array + */ + public function get_email_stats_count_by( $param, $days = 0 ) { + + $allowed_params = [ 'date', 'total' ]; + + if ( ! in_array( $param, $allowed_params, true ) ) { + return []; + } + + $cache = false; + + // Allow results caching to reduce DB load. + $allow_caching = $this->settings['allow_data_caching']; + + if ( $allow_caching ) { + $transient_name = 'wp_mail_smtp_' . static::SLUG . '_pro_emails_by_' . $param . '_' . $days; + $cache = get_transient( $transient_name ); + + /** + * Filter the cached email count data, to clear or alter it. + * + * @since 2.7.0 + * + * @param array $cache The cached data. + * @param string $param The parameter to group the email counts by. Possible value: 'date' or 'total'. + * @param int $days The number of days in the past to collect the data for. + */ + $cache = apply_filters( 'wp_mail_smtp_' . static::SLUG . '_cached_data', $cache, $param, $days ); + } + + // is_array() detects cached empty searches. + if ( $allow_caching && is_array( $cache ) ) { + return $cache; + } + + $result = $this->get_email_stats_results( $param, $days ); + + if ( $allow_caching ) { + + // Transient lifetime in seconds. Defaults to the end of a current day. + $transient_lifetime = $this->settings['transient_lifetime']; + set_transient( $transient_name, $result, $transient_lifetime ); + } + + return $result; + } + + /** + * Get the email stats results from the DB. + * + * @since 2.7.0 + * + * @param string $param Possible value: 'date' or 'total'. + * @param int $days Timespan (in days) to fetch the data for. + * + * @return array + */ + private function get_email_stats_results( $param, $days ) { + + $result = []; + $dates = $this->get_days_interval( $days ); + + switch ( $param ) { + case 'date': + $result = $this->get_email_stats_count_by_date_sql( $dates['start'], $dates['end'] ); + break; + case 'total': + $result = $this->get_email_stats_count_by_total_sql( $dates['start'], $dates['end'] ); + break; + } + + return $result; + } + + /** + * Get email stats count grouped by date. + * In most cases it's better to use `get_email_stats_count_by( 'date' )` instead. + * Doesn't cache the result. + * + * @since 2.7.0 + * + * @param \DateTime $date_start Start date for the search. + * @param \DateTime $date_end End date for the search. + * + * @return array + */ + public function get_email_stats_count_by_date_sql( $date_start = null, $date_end = null ) { + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) || ! wp_mail_smtp()->pro->get_logs()->is_enabled() ) { + return []; + } + + global $wpdb; + + $table_name = Logs::get_table_name(); + $format = 'Y-m-d H:i:s'; + $placeholders = []; + + $sql = "SELECT CAST(date_sent AS DATE) as day, status, COUNT(status) as count + FROM {$table_name} + WHERE 1=1"; + + if ( ! empty( $date_start ) ) { + $sql .= ' AND date_sent >= %s'; + $placeholders[] = $date_start->format( $format ); + } + + if ( ! empty( $date_end ) ) { + $sql .= ' AND date_sent <= %s'; + $placeholders[] = $date_end->format( $format ); + } + + $sql .= ' GROUP BY day, status;'; + + if ( ! empty( $placeholders ) ) { + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $sql = $wpdb->prepare( $sql, $placeholders ); + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( $sql, ARRAY_A ); + + if ( empty( $results ) ) { + return []; + } + + $results = $this->process_row_data_email_stats_count_by_date( $results ); + + $results = $this->fill_chart_empty_days( $results, $date_start, $date_end ); + + return (array) $results; + } + + /** + * Get the total email stats count. + * In most cases it's better to use `get_email_stats_count_by( 'total' )` instead. + * Doesn't cache the result. + * + * @since 2.7.0 + * + * @param \DateTime $date_start Start date for the search. + * @param \DateTime $date_end End date for the search. + * + * @return array + */ + public function get_email_stats_count_by_total_sql( $date_start = null, $date_end = null ) { + + $data = [ + 'unsent' => 0, + 'sent' => 0, + 'waiting' => 0, + 'delivered' => 0, + ]; + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) || ! wp_mail_smtp()->pro->get_logs()->is_enabled() ) { + return $data; + } + + global $wpdb; + + $table_name = Logs::get_table_name(); + $format = 'Y-m-d H:i:s'; + $placeholders = []; + + $sql = "SELECT status, COUNT(*) as count + FROM {$table_name} + WHERE 1=1"; + + if ( ! empty( $date_start ) ) { + $sql .= ' AND date_sent >= %s'; + $placeholders[] = $date_start->format( $format ); + } + + if ( ! empty( $date_end ) ) { + $sql .= ' AND date_sent <= %s'; + $placeholders[] = $date_end->format( $format ); + } + + $sql .= ' GROUP BY status;'; + + if ( ! empty( $placeholders ) ) { + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $sql = $wpdb->prepare( $sql, $placeholders ); + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( $sql, \ARRAY_A ); + + if ( empty( $results ) ) { + return $data; + } + + return $this->filter_total_email_stats_results( $results, $data ); + } + + /** + * Fill DB results with empty entries where there's no data. + * Needed to correctly distribute labels and data on a chart. + * + * @since 2.7.0 + * + * @param array $results DB results from `$wpdb->get_results()`. + * @param \DateTime $date_start Start date for the search. + * @param \DateTime $date_end End date for the search. + * + * @return array + */ + public function fill_chart_empty_days( $results, $date_start, $date_end ) { + + if ( ! is_array( $results ) ) { + return []; + } + + $period = new \DatePeriod( + $date_start, + new \DateInterval( 'P1D' ), + $date_end + ); + + foreach ( $period as $key => $value ) { + /** The current DateTime object of the period. @var \DateTime $value */ + $date = $value->format( 'Y-m-d' ); + if ( ! array_key_exists( $date, $results ) ) { + $results[ $date ] = array( + 'day' => $date, + 'unsent' => 0, + 'sent' => 0, + 'waiting' => 0, + 'delivered' => 0, + ); + continue; + } + + // Mold an object to array to stay uniform. + $results[ $date ] = (array) $results[ $date ]; + } + + ksort( $results ); + + return $results; + } + + /** + * Get the data for a chart using AJAX. + * + * @since 2.7.0 + */ + public function get_chart_data_ajax() { + + check_admin_referer( 'wp_mail_smtp_' . static::SLUG . '_nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error(); + } + + $days = ! empty( $_POST['days'] ) ? absint( $_POST['days'] ) : 0; + + $data = $this->get_email_stats_count_by( 'date', $days ); + + wp_send_json( $data ); + } + + /** + * Get the data for a forms list using AJAX. + * + * @since 2.7.0 + */ + public function get_email_stats_ajax() { + + check_admin_referer( 'wp_mail_smtp_' . static::SLUG . '_nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error(); + } + + $days = ! empty( $_POST['days'] ) ? absint( $_POST['days'] ) : 0; + $color_scheme = ! empty( $_POST['color'] ) ? sanitize_key( $_POST['color'] ) : false; + + ob_start(); + $this->email_stats_block( $days, $color_scheme ); + wp_send_json( ob_get_clean() ); + } + + /** + * Save a widget meta for a current user using AJAX. + * + * @since 2.7.0 + */ + public function save_widget_meta_ajax() { + + check_admin_referer( 'wp_mail_smtp_' . static::SLUG . '_nonce' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + wp_send_json_error(); + } + + $meta = ! empty( $_POST['meta'] ) ? sanitize_key( $_POST['meta'] ) : ''; + $value = ! empty( $_POST['value'] ) ? sanitize_key( $_POST['value'] ) : 0; + + $this->widget_meta( 'set', $meta, $value ); + + exit(); + } + + /** + * Prepare the email stats data. + * The text and counts of the email stats. + * + * @since 2.7.0 + * + * @param array $email_stats Stats for total, confirmed, unconfirmed and failed emails. + * @param string $color_scheme The color scheme to use for the output. + * + * @return array[] + */ + private function get_email_stats_data( $email_stats, $color_scheme = false ) { + + $confirmed_sent = isset( $email_stats['delivered'] ) ? absint( $email_stats['delivered'] ) : 0; + $sent = isset( $email_stats['sent'] ) ? absint( $email_stats['sent'] ) : 0; + $failed_sent = isset( $email_stats['unsent'] ) ? absint( $email_stats['unsent'] ) : 0; + $total_sent = $confirmed_sent + $sent + $failed_sent; + + if ( empty( $color_scheme ) ) { + $color_scheme = $this->widget_meta( 'get', 'color_scheme' ); + } + + $output_data = [ + 'all' => [ + 'type' => 'all', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/' . $color_scheme . '/total.svg', + /* translators: %d number of total emails sent. */ + 'title' => esc_html( sprintf( esc_html__( '%d total', 'wp-mail-smtp-pro' ), $total_sent ) ), + 'count' => $total_sent, + ], + 'delivered' => [ + 'type' => 'delivered', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/' . $color_scheme . '/delivered.svg', + /* translators: %d number of confirmed emails sent. */ + 'title' => esc_html( sprintf( esc_html__( '%d confirmed', 'wp-mail-smtp-pro' ), $confirmed_sent ) ), + 'count' => $confirmed_sent, + ], + 'sent' => [ + 'type' => 'sent', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/' . $color_scheme . '/sent.svg', + /* translators: %d number of unconfirmed emails sent. */ + 'title' => esc_html( sprintf( esc_html__( '%d unconfirmed', 'wp-mail-smtp-pro' ), $sent ) ), + 'count' => $sent, + ], + 'unsent' => [ + 'type' => 'unsent', + 'icon' => wp_mail_smtp()->assets_url . '/images/dash-widget/' . $color_scheme . '/unsent.svg', + /* translators: %d number of failed email sent attempts. */ + 'title' => esc_html( sprintf( esc_html__( '%d failed', 'wp-mail-smtp-pro' ), $failed_sent ) ), + 'count' => $failed_sent, + ], + ]; + + if ( Helpers::mailer_without_send_confirmation() ) { + + // Skip the 'unconfirmed sent' section. + unset( $output_data['sent'] ); + + // Change the 'confirmed sent' section into a general 'sent' section. + $output_data['delivered']['title'] = esc_html( /* translators: %d number of sent emails. */ + sprintf( esc_html__( '%d sent', 'wp-mail-smtp-pro' ), $sent + $confirmed_sent ) + ); + $output_data['delivered']['count'] = $sent + $confirmed_sent; + } + + return $output_data; + } + + /** + * Process the DB row count data for email stats by date. + * + * @since 2.7.0 + * + * @param array $raw_data The data from the DB by rows. + * + * @return array + */ + private function process_row_data_email_stats_count_by_date( $raw_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( empty( $raw_data ) ) { + return []; + } + + $results = []; + + foreach ( $raw_data as $row ) { + if ( ! isset( $results[ $row['day'] ] ) ) { + $results[ $row['day'] ] = [ + 'day' => $row['day'], + 'unsent' => 0, + 'sent' => 0, + 'waiting' => 0, + 'delivered' => 0, + ]; + } + + switch ( absint( $row['status'] ) ) { + case Email::STATUS_UNSENT: + $results[ $row['day'] ]['unsent'] = absint( $row['count'] ); + break; + case Email::STATUS_SENT: + $results[ $row['day'] ]['sent'] = absint( $row['count'] ); + break; + case Email::STATUS_WAITING: + $results[ $row['day'] ]['waiting'] = absint( $row['count'] ); + break; + case Email::STATUS_DELIVERED: + $results[ $row['day'] ]['delivered'] = absint( $row['count'] ); + break; + } + } + + return $results; + } + + /** + * Filter the DB results for email stats by total param. + * + * @since 2.7.0 + * + * @param array $results The DB results for the email stats by total. + * @param array $data The current collected total data. + * + * @return array + */ + private function filter_total_email_stats_results( $results, $data ) { + + foreach ( $results as $row ) { + switch ( absint( $row['status'] ) ) { + case Email::STATUS_UNSENT: + $data['unsent'] = absint( $row['count'] ); + break; + case Email::STATUS_SENT: + $data['sent'] = absint( $row['count'] ); + break; + case Email::STATUS_WAITING: + $data['waiting'] = absint( $row['count'] ); + break; + case Email::STATUS_DELIVERED: + $data['delivered'] = absint( $row['count'] ); + break; + } + } + + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/MiscTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/MiscTab.php new file mode 100644 index 00000000..1aa1a5b4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/MiscTab.php @@ -0,0 +1,162 @@ +<?php + +namespace WPMailSMTP\Pro\Admin\Pages; + +use WPMailSMTP\Admin\Pages\MiscTab as MiscTabLite; +use WPMailSMTP\Helpers\UI; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Emails\RateLimiting\RateLimiting; + +/** + * Class MiscTab. + * + * @since 4.0.0 + */ +class MiscTab extends MiscTabLite { + + /** + * Display rate limit settings. + * + * @since 4.0.0 + */ + protected function display_rate_limit_settings() { + + $options = Options::init(); + + $rate_limit_periods = [ + 'minute' => esc_html__( 'Max emails per minute', 'wp-mail-smtp-pro' ), + 'hour' => esc_html__( 'Max emails per hour', 'wp-mail-smtp-pro' ), + 'day' => esc_html__( 'Max emails per day', 'wp-mail-smtp-pro' ), + 'week' => esc_html__( 'Max emails per week', 'wp-mail-smtp-pro' ), + 'month' => esc_html__( 'Max emails per month', 'wp-mail-smtp-pro' ), + ]; + ?> + <div class="wp-mail-smtp-setting-group"> + <div id="wp-mail-smtp-setting-row-rate_limit" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="<?php echo 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-rate_limit_enabled'; ?>"> + <?php esc_html_e( 'Email Rate Limiting', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + UI::toggle( + [ + 'name' => 'wp-mail-smtp[rate_limit][enabled]', + 'id' => 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-rate_limit_enabled', + 'value' => 'true', + 'checked' => RateLimiting::is_enabled(), + ] + ); + ?> + <p class="desc"> + <?php + printf( + wp_kses( /* translators: %1$s - Documentation URL. */ + __( '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. <a href="%1$s" target="_blank" rel="noopener noreferrer">Learn More</a>', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/a-complete-guide-to-miscellaneous-settings/#email-rate-limiting', + [ + 'medium' => 'misc-settings', + 'content' => 'Email Rate Limiting - support article', + ] + ) + ) + ); + ?> + </p> + </div> + </div> + <div id="wp-mail-smtp-setting-row-rate_limit_periods" style="display: <?php echo (bool) $options->get( 'rate_limit', 'enabled' ) ? 'block' : 'none'; ?>"> + <div class="wp-mail-smtp-setting-label"></div> + <div class="wp-mail-smtp-setting-field"> + <?php $this->display_enqueued_emails_count_notice(); ?> + <?php foreach ( $rate_limit_periods as $period => $label ) : ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-rate_limit_<?php echo esc_attr( $period ); ?>"><?php echo esc_html( $label ); ?></label> + </div> + <div class="wp-mail-smtp-setting-field"> + <input type="number" + name="wp-mail-smtp[rate_limit][<?php echo esc_attr( $period ); ?>]" + value="<?php echo esc_attr( $options->get( 'rate_limit', $period ) ); ?>" + min="0" + /> + </div> + </div> + <?php endforeach; ?> + </div> + </div> + </div> + <script> + jQuery( '#wp-mail-smtp-setting-misc-rate_limit_enabled' ).on( 'change', function() { + if ( jQuery( this ).is( ':checked' ) ) { + jQuery( '#wp-mail-smtp-setting-row-rate_limit_periods' ).show(); + } else { + jQuery( '#wp-mail-smtp-setting-row-rate_limit_periods' ).hide(); + } + } ); + </script> + <?php + } + + /** + * Process tab form submission ($_POST). + * + * @since 4.0.0 + * + * @param array $data Tab data specific for the plugin ($_POST). + */ + public function process_post( $data ) { + + if ( empty( $data['rate_limit']['enabled'] ) ) { + $data['rate_limit']['enabled'] = false; + } + + parent::process_post( $data ); + } + + /** + * Display a notice with the current count + * of enqueued emails. + * + * @since 4.0.0 + */ + private function display_enqueued_emails_count_notice() { + + $queue = wp_mail_smtp()->get_queue(); + + // We're checking for the queue being enabled explictly, + // just in case a 3rd party filter is turning it off. + if ( ! $queue->is_enabled() ) { + return; + } + + $enqueued_emails_count = $queue->count_queued_emails(); + + if ( $enqueued_emails_count === 0 ) { + return; + } + + $message = sprintf( /* translators: %s: enqueued email count. */ + _n( 'Currently there is %s email in the queue.', 'Currently there are %s emails in the queue.', $enqueued_emails_count, 'wp-mail-smtp-pro' ), + $enqueued_emails_count + ); + ?> + <div class="notice inline notice-inline wp-mail-smtp-notice notice-info" style="margin: 10px 0 0;"> + <p> + <?php echo esc_html( $message ); ?> + </p> + </div> + <?php + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/TestTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/TestTab.php new file mode 100644 index 00000000..0139f0f2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/Pages/TestTab.php @@ -0,0 +1,350 @@ +<?php + +namespace WPMailSMTP\Pro\Admin\Pages; + +use WPMailSMTP\Admin\Pages\TestTab as TestTabLite; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Loader as AlertsLoader; + +/** + * Pro variant of the Email Test page. + * + * @since 4.9.0 + */ +class TestTab extends TestTabLite { + + /** + * Render the Pro variant of the success banner. + * + * @since 4.9.0 + * + * @inheritdoc + */ + protected function display_success_banner() { + + if ( ! $this->is_setup_done() ) { + $this->display_success_banner_setup_pending(); + + return; + } + + $this->display_success_banner_setup_done(); + } + + /** + * Render the Pro success banner when one or more of {Email Log, + * Backup Connection, Alerts} are not yet configured. Each remaining + * feature is presented as a setup tile; configured ones render in a + * "Complete!" state with strikethrough text. + * + * @since 4.9.0 + */ + private function display_success_banner_setup_pending() { + + $assets_url = wp_mail_smtp()->pro->assets_url; + + $options = Options::init(); + + // When SendLayer is the primary mailer, recommending SendLayer as the + // backup undermines the redundancy goal — swap to a generic + // backup-mailer tile. + $is_sendlayer_primary = $options->get( 'mail', 'mailer' ) === 'sendlayer'; + + // Anchor matches the Backup Connection section id in the Pro Backup + // Connection settings (Settings → General). + $backup_settings_url = wp_mail_smtp()->get_admin()->get_admin_page_url() . '#wp-mail-smtp-setting-row-backup_connection'; + + $backup_tile = $this->build_backup_tile( $is_sendlayer_primary, $backup_settings_url, $assets_url ); + + $tiles = [ + $backup_tile, + [ + 'icon' => $assets_url . '/images/test-success/icon-alerts.svg', + 'title' => esc_html__( 'Get alerted for failed emails with Alerts', 'wp-mail-smtp-pro' ), + 'body' => esc_html__( 'Be the first to know when emails fail, with details to fix fast', 'wp-mail-smtp-pro' ), + 'url' => add_query_arg( 'tab', 'alerts', wp_mail_smtp()->get_admin()->get_admin_page_url() ), + 'complete' => $this->is_any_alert_provider_enabled(), + ], + [ + 'icon' => $assets_url . '/images/test-success/icon-email-log.svg', + 'title' => esc_html__( 'Never miss an email with Email Logging', 'wp-mail-smtp-pro' ), + 'body' => esc_html__( 'Keep a record of every email sent from your site for easy tracking', 'wp-mail-smtp-pro' ), + 'url' => add_query_arg( 'tab', 'logs', wp_mail_smtp()->get_admin()->get_admin_page_url() ), + 'complete' => (bool) $options->get( 'logs', 'enabled' ), + ], + ]; + ?> + <div class="wpms-test-email-success-banner wp-mail-smtp-test-success-banner wp-mail-smtp-test-success-banner--pro-setup-pending"> + <?php $this->display_success_banner_dismiss(); ?> + + <div class="wpms:flex wpms:flex-col wpms:gap-md wpms:p-md"> + <div class="wpms:flex wpms:flex-col wpms:gap-sm"> + <div class="wpms-test-email-success-banner__heading"> + <span aria-hidden="true" class="wpms:icon-[fa6-solid--circle-check] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> + <h2> + <?php esc_html_e( 'Test email sent successfully! Check your inbox to confirm delivery.', 'wp-mail-smtp-pro' ); ?> + </h2> + </div> + <p class="wpms:m-[0]! wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> + <?php esc_html_e( 'Setup these features already available in your WP Mail SMTP Pro plugin to get full value!', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + + <div class="wpms:flex wpms:flex-wrap wpms:items-stretch wpms:gap-md"> + <?php foreach ( $tiles as $tile ) : ?> + <div class="wpms-test-email-success-banner__card<?php echo $tile['complete'] ? ' wpms:opacity-70' : ''; ?>"> + <div class="wpms:flex wpms:items-start wpms:gap-[12px] wpms:w-full"> + <div class="wpms:flex wpms:items-center wpms:justify-center wpms:w-[60px] wpms:h-[60px] wpms:bg-[#f6f6f6] wpms:border wpms:border-surface-divider wpms:rounded-[4px] wpms:shrink-0 wpms:overflow-hidden"> + <img src="<?php echo esc_url( $tile['icon'] ); ?>" alt="" class="wpms:h-[32px] wpms:w-auto wpms:max-w-[40px]"> + </div> + <div class="wpms:flex wpms:flex-col wpms:flex-1 wpms:min-w-[0]"> + <h3 class="wpms:m-[0]! wpms:p-[0]! wpms:text-sm! wpms:leading-5! wpms:font-medium! wpms:text-primary<?php echo $tile['complete'] ? ' wpms:line-through' : ''; ?>"> + <?php echo esc_html( $tile['title'] ); ?> + </h3> + <p class="wpms:m-[0]! wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> + <?php echo esc_html( $tile['body'] ); ?> + </p> + </div> + </div> + <?php if ( $tile['complete'] ) : ?> + <button type="button" disabled class="wp-mail-smtp-btn wp-mail-smtp-btn-sm wp-mail-smtp-btn-light-grey wpms:inline-flex! wpms:items-center wpms:gap-[4px]! wpms:self-start wpms:mt-auto! wpms:opacity-50 wpms:cursor-not-allowed"> + <span><?php esc_html_e( 'Complete!', 'wp-mail-smtp-pro' ); ?></span> + <span aria-hidden="true" class="wpms:icon-[fa6-solid--circle-check] wpms:text-success wpms:w-[12px] wpms:h-[12px]"></span> + </button> + <?php elseif ( ! empty( $tile['quick_connect'] ) ) : ?> + <button type="button" + class="wp-mail-smtp-btn wp-mail-smtp-btn-sm wp-mail-smtp-btn-light-grey js-wp-mail-smtp-sendlayer-quick-connect-btn wpms:self-start wpms:mt-auto! wpms:focus:outline-none! wpms:focus:shadow-none!" + data-mode="backup_mailer" + data-utm-content="Test Email Success - Backup Mailer Tile"> + <?php esc_html_e( 'Setup Now', 'wp-mail-smtp-pro' ); ?> + </button> + <?php else : ?> + <a href="<?php echo esc_url( $tile['url'] ); ?>" class="wp-mail-smtp-btn wp-mail-smtp-btn-sm wp-mail-smtp-btn-light-grey wpms:self-start wpms:mt-auto!"> + <?php esc_html_e( 'Setup Now', 'wp-mail-smtp-pro' ); ?> + </a> + <?php endif; ?> + </div> + <?php endforeach; ?> + </div> + </div> + + <?php $this->display_success_pro_tip_strip(); ?> + </div> + <?php + } + + /** + * Render the Pro success banner when all of Email Log, Backup + * Connection, and Alerts are configured. Splits internally by + * cross-sell pool size: 1-3 candidates render the populated + * cross-sell tiles, an empty pool collapses to a slim 1-line strip. + * + * Covers two layouts that share identical markup and differ only in + * pool size: the "Pro Setup Done" layout with 3 tiles, and the "Pro + * One Installed" layout with 2 tiles (one catalog plugin already on + * the site → filtered out by get_cross_sell_recommendations()). + * + * @since 4.9.0 + */ + private function display_success_banner_setup_done() { + + $recommendations = $this->get_cross_sell_recommendations(); + + if ( empty( $recommendations ) ) { + $this->display_success_banner_slim(); + + return; + } + ?> + <div class="wpms-test-email-success-banner wp-mail-smtp-test-success-banner wp-mail-smtp-test-success-banner--pro-setup-done"> + <?php $this->display_success_banner_dismiss(); ?> + + <div class="wpms:flex wpms:flex-col wpms:gap-md wpms:p-md"> + <div class="wpms:flex wpms:flex-col wpms:gap-sm"> + <div class="wpms-test-email-success-banner__heading"> + <span aria-hidden="true" class="wpms:icon-[fa6-solid--circle-check] wpms:text-success wpms:w-[16px] wpms:h-[16px] wpms:shrink-0"></span> + <h2> + <?php esc_html_e( 'Test email sent successfully! Check your inbox to confirm delivery.', 'wp-mail-smtp-pro' ); ?> + </h2> + </div> + <p class="wpms:m-[0]! wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> + <?php esc_html_e( 'Add more power to your site with these tested and trusted WordPress plugins!', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + + <div class="wpms:flex wpms:flex-wrap wpms:items-stretch wpms:gap-md"> + <?php foreach ( $recommendations as $product ) : ?> + <div class="wpms-test-email-success-banner__card"> + <div class="wpms:flex wpms:items-start wpms:gap-[12px] wpms:w-full"> + <?php if ( ! empty( $product['framed_icon'] ) ) : ?> + <div class="wpms:flex wpms:items-center wpms:justify-center wpms:w-[60px] wpms:h-[60px] wpms:bg-surface-background-light wpms:border wpms:border-surface-divider wpms:rounded-[4px] wpms:shrink-0 wpms:overflow-hidden"> + <img src="<?php echo esc_url( $product['icon'] ); ?>" alt="" class="wpms:h-[32px] wpms:w-auto wpms:max-w-[40px]"> + </div> + <?php else : ?> + <img src="<?php echo esc_url( $product['icon'] ); ?>" alt="" class="wpms:w-[60px] wpms:h-[60px] wpms:shrink-0"> + <?php endif; ?> + <div class="wpms:flex wpms:flex-col wpms:flex-1 wpms:min-w-[0]"> + <h3 class="wpms:m-[0]! wpms:p-[0]! wpms:text-sm! wpms:leading-5! wpms:font-medium! wpms:text-primary"> + <?php echo esc_html( $product['title'] ); ?> + </h3> + <p class="wpms:m-[0]! wpms:text-sm! wpms:leading-5! wpms:text-tertiary"> + <?php echo esc_html( $product['desc'] ); ?> + </p> + </div> + </div> + <div class="wpms-test-email-success-banner__card-actions wpms:flex wpms:items-center wpms:flex-wrap wpms:gap-[12px] wpms:mt-auto!"> + <button type="button" + class="wp-mail-smtp-btn wp-mail-smtp-btn-sm wp-mail-smtp-btn-light-grey js-wp-mail-smtp-plugin-install-btn status-download" + data-plugin="<?php echo esc_attr( $product['install_url'] ); ?>" + data-settings-url="<?php echo esc_url( $product['settings_page_url'] ); ?>"> + <?php + printf( + /* translators: %s - product name (e.g. WPConsent). */ + esc_html__( 'Install %s', 'wp-mail-smtp-pro' ), + esc_html( $product['name'] ) + ); + ?> + </button> + </div> + </div> + <?php endforeach; ?> + </div> + </div> + </div> + <?php + } + + /** + * Render the no-cross-sell variant used when all Pro features are set + * up and every cross-sell candidate is already installed. Uses WP's + * default success-notice styling instead of a custom banner — there's + * nothing left to surface beyond the success acknowledgement itself. + * + * @since 4.9.0 + */ + private function display_success_banner_slim() { + ?> + <div class="notice notice-success is-dismissible"> + <p><?php esc_html_e( 'Test email sent successfully! Check your inbox to confirm delivery.', 'wp-mail-smtp-pro' ); ?></p> + </div> + <?php + } + + /** + * Whether the Pro setup checklist {Email Log, Backup Connection, + * at least one Alert provider} is fully satisfied. Drives the + * setup-pending vs setup-done branch in the banner resolver. + * + * @since 4.9.0 + * + * @return bool + */ + private function is_setup_done() { + + $options = Options::init(); + + if ( empty( $options->get( 'logs', 'enabled' ) ) ) { + return false; + } + + if ( ! $this->is_backup_connection_configured() ) { + return false; + } + + return $this->is_any_alert_provider_enabled(); + } + + /** + * Whether at least one Alert provider is enabled in settings. + * + * @since 4.9.0 + * + * @return bool + */ + private function is_any_alert_provider_enabled() { + + $options = Options::init(); + $alerts_loader = new AlertsLoader(); + + foreach ( array_keys( $alerts_loader->get_providers() ) as $provider_slug ) { + if ( $options->get( 'alert_' . $provider_slug, 'enabled' ) ) { + return true; + } + } + + return false; + } + + /** + * Whether a Backup Connection is selected in settings AND the connections + * manager can actually resolve it. + * + * @since 4.9.0 + * + * @return bool + */ + private function is_backup_connection_configured() { + + $connection_id = Options::init()->get( 'backup_connection', 'connection_id' ); + + if ( empty( $connection_id ) ) { + return false; + } + + $backup_connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id, false ); + + return ! empty( $backup_connection ); + } + + /** + * Build the backup-mailer tile shown on the Pro Setup Pending success banner. + * + * Three states drive icon / copy / CTA shape: + * + * - SendLayer is primary → generic "Setup a Backup Mailer" tile linking + * to the backup-connection settings anchor. + * - SendLayer is in additional connections (not yet backup) → SL-branded + * tile linking to the same anchor so the user picks SL from the radio + * list. + * - SendLayer is absent entirely → SL-branded tile whose CTA fires the + * SendLayer Quick Connect flow in `backup_mailer` mode. One OAuth + * round-trip provisions a new additional connection and assigns it as + * the backup connection. + * + * @since 4.9.0 + * + * @param bool $is_sendlayer_primary Whether SendLayer is the primary mailer. + * @param string $backup_settings_url Backup connection settings anchor URL. + * @param string $assets_url Pro assets URL. + * + * @return array + */ + private function build_backup_tile( $is_sendlayer_primary, $backup_settings_url, $assets_url ) { + + $is_complete = $this->is_backup_connection_configured(); + + if ( $is_sendlayer_primary || $is_complete ) { + return [ + 'icon' => $assets_url . '/images/test-success/icon-feature-backup-connection.svg', + 'title' => esc_html__( 'Setup a Backup Mailer', 'wp-mail-smtp-pro' ), + 'body' => esc_html__( 'Setup a secondary email provider in case your primary provider fails', 'wp-mail-smtp-pro' ), + 'url' => $backup_settings_url, + 'complete' => $is_complete, + 'quick_connect' => false, + ]; + } + + // SL is neither primary nor configured as backup → always offer Quick + // Connect's backup_mailer mode. An existing SL additional connection + // may be misconfigured or reserved for something else, so we create a + // dedicated "SendLayer (backup)" connection regardless. + return [ + 'icon' => $assets_url . '/images/test-success/icon-backup-connection.svg', + 'title' => esc_html__( 'Get a Free Backup mailer connection', 'wp-mail-smtp-pro' ), + 'body' => esc_html__( 'Setup SendLayer, our recommended mailer, as a backup for free', 'wp-mail-smtp-pro' ), + 'url' => $backup_settings_url, + 'complete' => $is_complete, + 'quick_connect' => true, + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/PluginsList.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/PluginsList.php new file mode 100644 index 00000000..e8a9480d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Admin/PluginsList.php @@ -0,0 +1,483 @@ +<?php + +namespace WPMailSMTP\Pro\Admin; + +use WPMailSMTP\Options; +use WPMailSMTP\Pro\License\Updater; +use WPMailSMTP\Pro\Pro; + +/** + * Class PluginsList. + * + * Handles the notice displayed in the Plugins page for Pro users. + * + * @since 3.8.0 + */ +class PluginsList { + + /** + * License Status: Empty. + * + * @since 3.8.0 + * + * @var int + */ + const LICENSE_STATUS_EMPTY = 1; + + /** + * License Status: Expired. + * + * @since 3.8.0 + * + * @var int + */ + const LICENSE_STATUS_EXPIRED = 2; + + /** + * License Status: Valid. + * + * @since 3.8.0 + * + * @var int + */ + const LICENSE_STATUS_VALID = 3; + + /** + * License Status: Site activations limit reached. + * + * @since 4.3.1 + * + * @var int + */ + const LICENSE_STATUS_LIMIT_REACHED = 4; + + /** + * The license status. + * + * @since 3.8.0 + * + * @var null|int + */ + private $license_status = null; + + /** + * Latest version fetched from remote source. + * + * @since 3.8.0 + * + * @var null|string + */ + private $remote_latest_version = null; + + /** + * Whether or not this site is using the latest version of WP Mail SMTP Pro. + * + * @since 3.8.0 + * + * @var bool + */ + private $is_using_latest_version = null; + + /** + * Register hooks. + * + * @since 3.8.0 + * + * @return void + */ + public function hooks() { + + add_action( 'admin_head', [ $this, 'add_style' ] ); + add_filter( 'site_transient_update_plugins', [ $this, 'site_transient_update_plugins' ] ); + add_action( 'after_plugin_row', [ $this, 'show_plugin_notice' ] ); + } + + /** + * Remove the border between the WP Mail SMTP Pro and custom notice. + * + * @since 3.8.0 + * + * @return void + */ + public function add_style() { + + $current_screen = get_current_screen(); + + if ( is_null( $current_screen ) || $current_screen->id !== 'plugins' || $this->get_license_status() === self::LICENSE_STATUS_VALID ) { + return; + } + + ?> + <style> + .plugins tr[data-slug="wp-mail-smtp-pro"] th, + .plugins tr[data-slug="wp-mail-smtp-pro"] td { + box-shadow: none; + } + + .plugins tr[data-slug="wp-mail-smtp-pro"] .second, + .plugins tr[data-slug="wp-mail-smtp-pro"] .row-actions { + padding-bottom: 0; + } + + @media screen and (max-width: 782px) { + .plugins tr[data-slug="wp-mail-smtp-pro"].plugin-update-tr.active:before { + background-color: #f0f6fc; + border-left: 4px solid #72aee6; + } + } + </style> + <?php + } + + /** + * Filters `update_plugins` transient in Admin Plugins page. + * + * This method removes the "update" of the Pro plugin IF the license key is not valid. + * Doing this fixes the edge case where both the default WP plugin update notice and our custom + * notice are displayed at the same time. + * + * @since 3.8.0 + * + * @param mixed $value Value of site transient. + * + * @return object $value Amended WordPress update object. + */ + public function site_transient_update_plugins( $value ) { + + global $current_screen; + + // We only want this filter in the Dashboard -> Plugins page. + if ( is_null( $current_screen ) || $current_screen->id !== 'plugins' ) { + return $value; + } + + $plugin_path = $this->get_pro_plugin_file_path(); + + if ( empty( $value ) || ! property_exists( $value, 'response' ) || empty( $value->response[ $plugin_path ] ) ) { + return $value; + } + + if ( $this->get_license_status() === self::LICENSE_STATUS_VALID ) { + return $value; + } + + unset( $value->response[ $plugin_path ] ); + + $value->no_update[ $plugin_path ] = Updater::get_no_update_object( + [ + 'plugin_path' => $plugin_path, + 'plugin_slug' => Pro::SLUG, + 'version' => WPMS_PLUGIN_VER, + ] + ); + + return $value; + } + + /** + * Returns the WP Mail SMTP Pro plugin file path relative to the plugins directory. + * + * @since 3.8.0 + * + * @return string + */ + private function get_pro_plugin_file_path() { + + return Pro::SLUG . '/wp_mail_smtp.php'; + } + + /** + * Get the license status. + * + * @since 3.8.0 + * + * @return int + */ + private function get_license_status() { + + if ( ! is_null( $this->license_status ) ) { + return $this->license_status; + } + + $this->license_status = self::LICENSE_STATUS_EMPTY; + + $license_option = Options::init()->get_group( 'license' ); + + // If there's a license, check if its expired. + if ( ! empty( $license_option['is_expired'] ) && $license_option['is_expired'] === true ) { + $this->license_status = self::LICENSE_STATUS_EXPIRED; + + return $this->license_status; + } + + // If there's a license, check if site activations limit reached. + if ( ! empty( $license_option['is_limit_reached'] ) && $license_option['is_limit_reached'] === true ) { + $this->license_status = self::LICENSE_STATUS_LIMIT_REACHED; + + return $this->license_status; + } + + if ( wp_mail_smtp()->get_pro()->get_license()->is_valid() ) { + $this->license_status = self::LICENSE_STATUS_VALID; + } + + return $this->license_status; + } + + /** + * Adds custom plugin notice for Pro users without a valid license. + * + * @since 3.8.0 + * + * @param string $plugin_file Path to the plugin file relative to the plugins directory. + * + * @return void + */ + public function show_plugin_notice( $plugin_file ) { + + if ( + $plugin_file !== $this->get_pro_plugin_file_path() || + $this->get_license_status() === self::LICENSE_STATUS_VALID + ) { + return; + } + + global $wp_list_table; + + $columns_count = 4; + + if ( ! empty( $wp_list_table ) ) { + $columns_count = $wp_list_table->get_column_count(); + } + ?> + <tr id="<?php echo esc_attr( Pro::SLUG ); ?>-update" class='plugin-update-tr active' + data-slug="<?php echo esc_attr( Pro::SLUG ); ?>" + data-plugin='<?php echo esc_attr( $plugin_file ); ?>' + > + <td colspan='<?php echo esc_attr( $columns_count ); ?>' class='plugin-update'> + <div class='update-message notice inline notice-warning notice-alt'> + <?php + echo '<p>' . wp_kses( + $this->get_update_notice(), + [ + 'a' => [ + 'href' => [], + 'target' => [], + ], + 'br' => [], + 'strong' => [ + 'style' => [], + ], + ] + ) . '</p>'; + ?> + </div> + </td> + </tr> + <?php + } + + /** + * Get the update notice. + * + * @since 3.8.0 + * + * @return string + */ + private function get_update_notice() { + + switch ( $this->get_license_status() ) { + case self::LICENSE_STATUS_EMPTY: + $message = $this->get_no_license_notice(); + break; + + case self::LICENSE_STATUS_EXPIRED: + $message = $this->get_expired_license_notice(); + break; + + case self::LICENSE_STATUS_LIMIT_REACHED: + $message = $this->get_limit_reached_license_notice(); + break; + + default: + $message = ''; + break; + } + + return $message; + } + + /** + * Get the notice for users without license key. + * + * @since 3.8.0 + * + * @return string + */ + private function get_no_license_notice() { + + $activate_url = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $purchase_url = wp_mail_smtp()->get_upgrade_link( + [ + 'medium' => 'all-plugins-license', + 'content' => 'Purchase one now', + ] + ); + + if ( $this->is_using_latest_version() ) { + return sprintf( /* translators: %1$s - WP Mail SMTP Pro URL; %2$s - WP Mail SMTP Pro purchase link. */ + __( + '<a href="%1$s">Activate WP Mail SMTP Pro</a> to receive features, updates, and support. Don\'t have a license? <a target="_blank" href="%2$s" rel="noopener noreferrer">Purchase one now</a>.', + 'wp-mail-smtp-pro' + ), + esc_url( $activate_url ), + esc_url( $purchase_url ) + ); + } + + return $this->get_new_version_available_notice() + . '<br />' + . sprintf( /* translators: %1$s - WP Mail SMTP Pro URL; %2$s - WP Mail SMTP Pro purchase link. */ + __( + '<a href="%1$s">Activate</a> your license to access this update, new features, and support. Don\'t have a license? <a target="_blank" href="%2$s" rel="noopener noreferrer">Purchase one now</a>.', + 'wp-mail-smtp-pro' + ), + esc_url( $activate_url ), + esc_url( $purchase_url ) + ); + } + + /** + * Get the notice for users with expired license key. + * + * @since 3.8.0 + * + * @return string + */ + private function get_expired_license_notice() { + + $message = $this->is_using_latest_version() ? '' : $this->get_new_version_available_notice() . '<br />'; + + return $message . sprintf( /* translators: %s - WP Mail SMTP Pro renew link. */ + __( + '<strong style="color: #e72f0a">Your WP Mail SMTP Pro license is expired.</strong> <a target="_blank" href="%s" rel="noopener noreferrer">Renew now</a> to receive new features, updates, and support.', + 'wp-mail-smtp-pro' + ), + esc_url( + wp_mail_smtp()->get_pro()->get_license()->get_renewal_link( + [ + 'medium' => 'all-plugins-license', + 'content' => 'Renew now', + ] + ) + ) + ); + } + + /** + * Get the notice for users with site activations limit reached. + * + * @since 4.3.1 + * + * @return string + */ + private function get_limit_reached_license_notice() { + + return sprintf( + wp_kses( /* translators: %1$s - WPMailSMTP.com account area URL; %2$s - WPMailSMTP.com pricing page URL. */ + __( '<strong style="color: #e72f0a">Your WP Mail SMTP Pro license has no site activations left.</strong> You can update the list of your sites or upgrade the license in the <a href="%1$s" target="_blank" rel="noopener noreferrer">Account area</a>. Or you can <a href="%2$s" target="_blank" rel="noopener noreferrer">purchase a new license key</a>.', 'wp-mail-smtp-pro' ), + [ + 'strong' => [ + 'style' => [], + ], + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/account/licenses/', + [ + 'medium' => 'all-plugins-license', + 'content' => 'license site limit reached', + ] + ) + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/pricing/', + [ + 'medium' => 'all-plugins-license', + 'content' => 'license site limit reached', + ] + ) + ) + ); + } + + /** + * Get the notice to show when a new version of WP Mail SMTP Pro is available. + * + * @since 3.8.0 + * + * @return string + */ + private function get_new_version_available_notice() { + + return sprintf( /* translators: %1$s - WP Mail SMTP Pro Changelog link; %2$s - WP Mail SMTP Pro latest version. */ + __( + 'There is a new version of WP Mail SMTP Pro available. <a target="_blank" href="%1$s" rel="noopener noreferrer">View version %2$s details</a>.', + 'wp-mail-smtp-pro' + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/how-to-view-recent-changes-to-the-wp-mail-smtp-plugin-changelog/', + [ + 'medium' => 'all-plugins-license', + 'content' => 'View version details', + ] + ) + ), + esc_html( $this->get_remote_latest_version() ) + ); + } + + /** + * Whether or not this site is using the latest version of WP Mail SMTP Pro. + * + * @since 3.8.0 + * + * @return bool + */ + private function is_using_latest_version() { + + if ( ! is_null( $this->is_using_latest_version ) ) { + return $this->is_using_latest_version; + } + + $this->is_using_latest_version = ! version_compare( WPMS_PLUGIN_VER, $this->get_remote_latest_version(), '<' ); + + return $this->is_using_latest_version; + } + + /** + * Get the remote latest version. + * + * @since 3.8.0 + * + * @return string + */ + private function get_remote_latest_version() { + + if ( ! is_null( $this->remote_latest_version ) ) { + return $this->remote_latest_version; + } + + $this->remote_latest_version = wp_mail_smtp()->get_pro()->get_license()->fetch_latest_plugin_version(); + + return $this->remote_latest_version; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/AbstractOptions.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/AbstractOptions.php new file mode 100644 index 00000000..27b9e701 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/AbstractOptions.php @@ -0,0 +1,171 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts; + +use WPMailSMTP\Options; + +/** + * Abstract Class OptionsAbstract. + * + * @since 3.5.0 + */ +abstract class AbstractOptions implements OptionsInterface { + + /** + * The provider slug. + * + * @since 3.5.0 + * + * @var string + */ + private $slug = ''; + + /** + * The provider title (or name). + * + * @since 3.5.0 + * + * @var string + */ + private $title = ''; + + /** + * The provider description. + * + * @since 3.5.0 + * + * @var string + */ + private $description = ''; + + /** + * The provider add connection button text. + * + * @since 3.5.0 + * + * @var string + */ + private $add_connection_text = ''; + + /** + * The provider maximum connections count. Unlimited by default. + * + * @since 3.5.0 + * + * @var int + */ + private $max_connections_count = - 1; + + /** + * Plugin options object. + * + * @since 3.5.0 + * + * @var Options + */ + protected $options; + + /** + * Constructor. + * + * @since 3.5.0 + * + * @param array $params The options parameters. + */ + public function __construct( $params ) { + + if ( ! empty( $params['slug'] ) ) { + $this->slug = sanitize_key( $params['slug'] ); + } + + if ( ! empty( $params['title'] ) ) { + $this->title = $params['title']; + } + + if ( ! empty( $params['description'] ) ) { + $this->description = $params['description']; + } + + if ( ! empty( $params['add_connection_text'] ) ) { + $this->add_connection_text = $params['add_connection_text']; + } + + if ( ! empty( $params['max_connections_count'] ) ) { + $this->max_connections_count = intval( $params['max_connections_count'] ); + } + + $this->options = Options::init(); + } + + /** + * Get the provider slug. + * + * @since 3.5.0 + * + * @return string + */ + public function get_slug() { + + return $this->slug; + } + + /** + * Get the provider title (or name). + * + * @since 3.5.0 + * + * @return string + */ + public function get_title() { + + return $this->title; + } + + /** + * Get the provider description. + * + * @since 3.5.0 + * + * @return string + */ + public function get_description() { + + return $this->description; + } + + /** + * Get the provider add connection button text. + * + * @since 3.5.0 + * + * @return string + */ + public function get_add_connection_text() { + + return $this->add_connection_text; + } + + /** + * Get the provider maximum connection count. + * + * @since 3.5.0 + * + * @return int + */ + public function get_max_connections_count() { + + return $this->max_connections_count; + } + + /** + * Get the provider options group. + * + * @since 3.5.0 + * + * @return string + */ + public function get_group() { + + return 'alert_' . $this->get_slug(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Admin/SettingsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Admin/SettingsTab.php new file mode 100644 index 00000000..0234e1e3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Admin/SettingsTab.php @@ -0,0 +1,519 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Admin; + +use WPMailSMTP\Admin\Area; +use WPMailSMTP\Admin\Pages\AlertsTab; +use WPMailSMTP\Helpers\Helpers; +use WPMailSMTP\Helpers\UI; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\AbstractOptions; +use WPMailSMTP\Pro\Alerts\Loader; +use WPMailSMTP\Pro\Alerts\Loader as AlertsLoader; +use WPMailSMTP\Pro\Alerts\Providers\Email\Handler as EmailAlertsHandler; +use WPMailSMTP\Pro\Alerts\Providers\Email\Options as EmailAlertsOptions; +use WPMailSMTP\Pro\Alerts\Providers\Push\Handler as PushAlertsHandler; +use WPMailSMTP\Pro\Alerts\Providers\Push\Options as PushAlertsOptions; +use WPMailSMTP\Pro\Emails\Logs\Admin\PageAbstract; +use WPMailSMTP\WP; + +/** + * Class SettingsTab. + * + * @since 3.5.0 + */ +class SettingsTab extends AlertsTab { + + /** + * Providers loader. + * + * @since 3.5.0 + * + * @var Loader + */ + private $loader; + + /** + * User meta for test alerts action notices. + * + * @since 3.9.0 + * + * @var string + */ + const NOTICE_USER_META = 'wp_mail_smtp_test_alerts_notice'; + + /** + * Plugin options. + * + * @since 3.10.0 + * + * @var Options + */ + private $options; + + /** + * Constructor. + * + * @since 3.5.0 + * + * @param PageAbstract $parent_page Parent page object. + */ + public function __construct( $parent_page = null ) { + + parent::__construct( $parent_page ); + + if ( wp_mail_smtp()->get_admin()->get_current_tab() === $this->slug ) { + $this->hooks(); + } + + $this->loader = new Loader(); + $this->options = Options::init(); + } + + /** + * Register hooks. + * + * @since 3.5.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + add_action( 'admin_init', [ $this, 'display_notices' ] ); + + add_filter( 'wp_mail_smtp_options_postprocess_key_defaults', [ $this, 'options_defaults' ], 10, 3 ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.5.0 + */ + public function enqueue_assets() { + + $min = WP::asset_min(); + + wp_enqueue_script( + 'wp-mail-smtp-alerts', + wp_mail_smtp()->plugin_url . "/assets/pro/js/smtp-pro-alerts{$min}.js", + [ 'jquery', 'wp-mail-smtp-admin' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-alerts', + 'wp_mail_smtp_alerts', + $this->get_localized_data() + ); + } + + /** + * Get localized data. + * + * @since 3.5.0 + * + * @return array + */ + private function get_localized_data() { + + return [ + 'providers' => array_map( + function ( AbstractOptions $option ) { + return [ + 'connection_options_tmpl' => $option->get_connection_options( [], '%%index%%' ), + 'max_connections_count' => $option->get_max_connections_count(), + ]; + }, + $this->loader->get_options_all() + ), + 'plugin_url' => wp_mail_smtp()->plugin_url, + 'texts' => [ + 'ok' => esc_html__( 'OK', 'wp-mail-smtp-pro' ), + 'alert_title' => esc_html__( 'Heads up!', 'wp-mail-smtp-pro' ), + 'alert_content' => esc_html__( 'Alerts settings have changed. Please save the settings before you can perform a test.', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Set options defaults. + * + * @since 3.5.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 ) { + + // Set admin email as default for Email channel. + if ( $group === 'alert_email' && $key === 'connections' ) { + $value = [ + [ + 'send_to' => get_option( 'admin_email' ), + ], + ]; + } elseif ( + $key === 'connections' && + substr( $group, 0, 6 ) === 'alert_' && + in_array( str_replace( 'alert_', '', $group ), array_keys( ( new AlertsLoader() )->get_providers() ), true ) + ) { + $value = []; + } + + return $value; + } + + /** + * Output HTML of the email controls settings. + * + * @since 3.5.0 + */ + public function display() { + + $options = Options::init(); + ?> + + <form method="POST" action=""> + <?php $this->wp_nonce_field(); ?> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading wp-mail-smtp-section-heading--has-divider"> + <div class="wp-mail-smtp-setting-field"> + <h2><?php esc_html_e( 'Alerts', 'wp-mail-smtp-pro' ); ?></h2> + <p class="desc"> + <?php esc_html_e( '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.', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + </div> + + <!-- Alert Events --> + <div id="wp-mail-smtp-setting-row-alert_event_types" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-debug_event_types"> + <?php esc_html_e( 'Notify when', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + UI::toggle( + [ + 'name' => '', + 'id' => 'wp-mail-smtp-setting-alert_events_email_failed', + 'value' => '', + 'label' => esc_html__( 'The initial email sending request fails', 'wp-mail-smtp-pro' ), + 'checked' => true, + 'disabled' => true, + ] + ); + ?> + + <p class="desc"> + <?php esc_html_e( 'This option is always enabled and will notify you about instant email sending failures.', 'wp-mail-smtp-pro' ); ?> + </p> + <hr class="wp-mail-smtp-setting-mid-row-sep"> + + <?php + // Whether logs are enabled. + $logs_enabled = wp_mail_smtp()->get_pro()->get_logs()->is_enabled(); + // Whether current mailer supports delivery verification. + $mailer_supports = ! Helpers::mailer_without_send_confirmation(); + + $this->display_unavailable_hard_bounce_alerts_notice( $logs_enabled, $mailer_supports ); + + UI::toggle( + [ + 'name' => 'wp-mail-smtp[alert_events][email_hard_bounced]', + 'id' => 'wp-mail-smtp-setting-alert_events_email_bounced', + 'value' => 'true', + 'label' => __( 'The deliverability verification process detects a hard bounce', 'wp-mail-smtp-pro' ), + 'checked' => (bool) $this->options->get( 'alert_events', 'email_hard_bounced' ) && $logs_enabled && $mailer_supports, + 'disabled' => ! $logs_enabled || ! $mailer_supports, + ] + ); + ?> + <p class="desc"> + <?php esc_html_e( '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.', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + </div> + + <?php foreach ( $this->loader->get_options_all() as $option ) : ?> + <?php + $is_enabled = (bool) $options->get( $option->get_group(), 'enabled' ); + $connections = (array) $options->get( $option->get_group(), 'connections' ); + ?> + + <div id="wp-mail-smtp-setting-row-alerts-<?php echo esc_attr( $option->get_slug() ); ?>" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading"> + <div class="wp-mail-smtp-setting-field"> + <h3><?php echo esc_html( $option->get_title() ); ?></h3> + <p class="desc"><?php echo wp_kses_post( $option->get_description() ); ?></p> + + <?php + if ( $option->get_slug() === EmailAlertsOptions::SLUG ) { + $this->display_email_alert_rate_limit_notice(); + } elseif ( $option->get_slug() === PushAlertsOptions::SLUG ) { + $this->display_push_notifications_site_notice(); + } + ?> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $option->get_slug() ); ?>-enabled"> + <?php + /* translators: %s - Alert title. */ + echo sprintf( esc_html__( '%s Alerts', 'wp-mail-smtp-pro' ), esc_html( $option->get_title() ) ); + ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + UI::toggle( + [ + 'class' => 'js-wp-mail-smtp-setting-alert-enabled', + 'name' => 'wp-mail-smtp[alert_' . $option->get_slug() . '][enabled]', + 'id' => 'wp-mail-smtp-setting-alert-' . $option->get_slug() . '-enabled', + 'checked' => $is_enabled, + ] + ); + ?> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-options"<?php echo ! $is_enabled ? ' style="display:none;"' : ''; ?>> + + <?php $option->display_options(); ?> + + <?php if ( $option->get_slug() !== PushAlertsOptions::SLUG ) : ?> + <div class="wp-mail-smtp-setting-row"> + <div class="wp-mail-smtp-setting-field"> + <button class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-blueish js-wp-mail-smtp-setting-alert-add-connection" data-provider="<?php echo esc_attr( $option->get_slug() ); ?>" <?php disabled( $option->get_max_connections_count() > 0 && count( $connections ) >= $option->get_max_connections_count() ); ?>> + <?php echo esc_html( $option->get_add_connection_text() ); ?> + </button> + </div> + </div> + <?php endif; ?> + </div> + </div> + <?php endforeach; ?> + + <?php $this->display_save_btn(); ?> + </form> + <?php + } + + /** + * Display the test alerts success notice, if present. + * + * @since 3.9.0 + */ + public function display_notices() { + + $current_user_id = get_current_user_id(); + + // Bail early if there is no notice. + if ( ! metadata_exists( 'user', $current_user_id, self::NOTICE_USER_META ) ) { + return; + } + + $notice_message = esc_html__( 'Alert Tests sent successfully.', 'wp-mail-smtp-pro' ); + $options = Options::init(); + $email_alerts_enabled = (bool) $options->get( 'alert_email', 'enabled' ); + + // If email alerts are enabled, add remaining rate limit time to the notice. + if ( $email_alerts_enabled ) { + $remaining_seconds = EmailAlertsHandler::get_remaining_rate_limit_seconds(); + // If rate limit just expired, or the alert handler + // hasn't run yet, default to the handler rate limit. + $remaining_seconds = $remaining_seconds === 0 ? EmailAlertsHandler::RATE_LIMIT : $remaining_seconds; + $remaining_minutes = round( $remaining_seconds / MINUTE_IN_SECONDS ); + $notice_message = sprintf( + /* translators: %1$s - Default success notice; %2$d. number of minutes until new email alerts can be sent. */ + esc_html__( '%1$s Any additional email alerts from this site are paused for %2$d minutes.', 'wp-mail-smtp-pro' ), + $notice_message, + $remaining_minutes + ); + } + + WP::add_admin_notice( $notice_message, WP::ADMIN_NOTICE_SUCCESS, false ); + + delete_user_meta( $current_user_id, self::NOTICE_USER_META ); + } + + /** + * Display a notice with the remaining minutes before + * email alerts rate limit expires. + * + * @since 3.9.0 + */ + private function display_email_alert_rate_limit_notice() { + + $remaining_seconds = EmailAlertsHandler::get_remaining_rate_limit_seconds(); + + // Bail early if rate limit expired. + if ( $remaining_seconds === 0 ) { + return; + } + + $remaining_minutes = round( $remaining_seconds / MINUTE_IN_SECONDS ); + ?> + <div class="notice inline notice-inline wp-mail-smtp-notice notice-warning"> + <p> + <?php + printf( + /* translators: %d - number of minutes until new email alerts can be sent. */ + esc_html__( 'Any additional email alerts from this site are paused for %d minutes. You can still test other types of alerts.', 'wp-mail-smtp-pro' ), + absint( $remaining_minutes ) + ); + ?> + </p> + </div> + <?php + } + + /** + * Output the notice container for push notification alerts. + * + * @since 4.4.0 + * + * @return void + */ + private function display_push_notifications_site_notice() { + + $remaining_seconds = PushAlertsHandler::get_remaining_rate_limit_seconds(); + + // Bail early if rate limit expired. + if ( $remaining_seconds > 0 ) { + $remaining_minutes = round( $remaining_seconds / MINUTE_IN_SECONDS ); + ?> + <div class="notice inline notice-inline wp-mail-smtp-notice notice-warning"> + <p> + <?php + printf( + /* translators: %d - number of minutes until new email alerts can be sent. */ + esc_html__( 'Any additional push notifications alerts from this site are paused for %d minutes. You can still test other types of alerts.', 'wp-mail-smtp-pro' ), + absint( $remaining_minutes ) + ); + ?> + </p> + </div> + <?php + } + ?> + <div class="notice inline notice-inline wp-mail-smtp-notice notice-warning wp-mail-smtp-push-notifications-notice" id="wp-mail-smtp-push-notifications-site-notice" style="display: none;"> + <p></p> + </div> + <?php + } + + /** + * Display a notice with the reasons hard-bounce alerts + * aren't available. + * + * @since 3.10.0 + * + * @param bool $logs_enabled Whether email logs are enabled. + * @param bool $mailer_supports Whether current mailer supports delivery verification. + */ + private function display_unavailable_hard_bounce_alerts_notice( $logs_enabled, $mailer_supports ) { + + if ( $logs_enabled && $mailer_supports ) { + return; + } + + $message = ''; + + if ( ! $logs_enabled ) { + $message = sprintf( + '%1$s <a href="%2$s">%3$s</a>.', + esc_html__( 'Email Log has to be enabled for hard bounce detection to work.', 'wp-mail-smtp-pro' ), + esc_url( + add_query_arg( + 'tab', + 'logs', + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG ) + ) + ), + esc_html__( 'Please enable Email Log', 'wp-mail-smtp-pro' ) + ); + } else { + $message = esc_html__( 'Your Primary Connection mailer doesn\'t support delivery verification. If you want to enable this option, please switch to a transactional mailer like SendLayer.', 'wp-mail-smtp-pro' ); + } + ?> + <div class="notice inline notice-inline wp-mail-smtp-notice notice-warning"> + <p> + <?php echo wp_kses( $message, [ 'a' => [ 'href' => [] ] ] ); ?> + </p> + </div> + <?php + } + + /** + * Display save and test buttons. + * + * @since 3.9.0 + */ + public function display_save_btn() { + + ?> + <p class="wp-mail-smtp-submit"> + <button type="submit" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-orange"> + <?php esc_html_e( 'Save Settings', 'wp-mail-smtp-pro' ); ?> + </button> + <button type="button" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-light-grey" id="js-wp-mail-smtp-btn-test-alerts" disabled> + <?php esc_html_e( 'Test Alerts', 'wp-mail-smtp-pro' ); ?> + </button> + </p> + <?php + $this->post_form_hidden_field(); + } + + /** + * Process tab form submission ($_POST). + * + * @since 3.5.0 + * + * @param array $data Post data specific for the plugin. + */ + public function process_post( $data ) { + + $options = Options::init(); + + foreach ( $this->loader->get_options_all() as $option ) { + $group = $option->get_group(); + $data[ $group ]['enabled'] = ! empty( $data[ $group ]['enabled'] ); + + if ( ! empty( $data[ $group ]['connections'] ) ) { + $data[ $group ]['connections'] = array_values( array_unique( $data[ $group ]['connections'], SORT_REGULAR ) ); + } + } + + $data['alert_events']['email_hard_bounced'] = ! empty( $data['alert_events']['email_hard_bounced'] ); + + $all = $options->get_all(); + + // Prevent connections array recursive merge. + $all = array_merge( $all, $data ); + + /** + * Fires before saving alerts settings. + * + * @since 4.5.0 + * + * @param array $all Options to save. + */ + do_action( 'wp_mail_smtp_pro_alerts_admin_settings_tab_before_save', $all ); + + // All the sanitization is done there. + $options->set( $all, false, true ); + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alert.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alert.php new file mode 100644 index 00000000..89f83c2a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alert.php @@ -0,0 +1,67 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts; + +/** + * Class Alert. + * + * @since 3.5.0 + */ +class Alert { + + /** + * Alert type. + * + * @since 3.5.0 + * + * @var string + */ + private $type; + + /** + * Alert data. + * + * @since 3.5.0 + * + * @var array + */ + private $data; + + /** + * Constructor. + * + * @since 3.5.0 + * + * @param string $type Alert type. + * @param array $data Alert data. + */ + public function __construct( $type, $data ) { + + $this->type = $type; + $this->data = $data; + } + + /** + * Get alert type. + * + * @since 3.5.0 + * + * @return string + */ + public function get_type() { + + return $this->type; + } + + /** + * Get alert data. + * + * @since 3.5.0 + * + * @return array + */ + public function get_data() { + + return $this->data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alerts.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alerts.php new file mode 100644 index 00000000..716c9ef7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Alerts.php @@ -0,0 +1,394 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts; + +use WPMailSMTP\Admin\Area; +use WPMailSMTP\ConnectionInterface; +use WPMailSMTP\MailCatcherInterface; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Admin\SettingsTab; +use WPMailSMTP\Pro\Alerts\Providers\Push\Provider as PushProvider; +use WPMailSMTP\Pro\Alerts\Providers\WhatsApp\Provider as WhatsAppProvider; +use WPMailSMTP\Pro\Emails\Logs\Email; +use WPMailSMTP\Pro\Tasks\NotifierTask; +use WPMailSMTP\WP; + +/** + * Class Alerts. + * + * @since 3.5.0 + */ +class Alerts { + + /** + * Failed email alert type slug. + * + * @since 3.5.0 + * + * @var string + */ + const FAILED_EMAIL = 'failed_email'; + + /** + * Failed primary email alert type slug. + * + * The primary connection failed to send an email, but the backup succeeded. + * + * @since 3.7.0 + * + * @var string + */ + const FAILED_PRIMARY_EMAIL = 'failed_primary_email'; + + /** + * Failed backup email alert type slug. + * + * The primary and backup connections failed to send an email. + * + * @since 3.7.0 + * + * @var string + */ + const FAILED_BACKUP_EMAIL = 'failed_backup_email'; + + /** + * Hard-bounced email alert type slug. + * + * @since 3.10.0 + * + * @var string + */ + const HARD_BOUNCED_EMAIL = 'hard_bounced_email'; + + /** + * Transient key for the global alert-dispatch throttle. While this transient + * is set, automatic alert dispatch via handle_failed_email() and + * handle_hard_bounced_email() is suppressed. + * + * @since 4.9.0 + * + * @var string + */ + const ALERT_THROTTLE_TRANSIENT = 'wp_mail_smtp_alert_throttle_lock'; + + /** + * Register hooks. + * + * @since 3.5.0 + */ + public function hooks() { + + add_filter( 'wp_mail_smtp_admin_get_pages', [ $this, 'init_settings_tab' ] ); + add_filter( 'wp_mail_smtp_admin_process_ajax_test_alerts_data', [ $this, 'process_ajax_test_alerts_data' ] ); + + add_action( 'wp_mail_smtp_options_set', [ $this, 'remove_empty_send_to_for_alert_email' ] ); + + ( new PushProvider() )->hooks(); + ( new WhatsAppProvider() )->hooks(); + } + + /** + * Removes the empty send_to values. + * + * @since 3.6.0 + * + * @param array $options The options array. + * + * @return array The options array after removing empty send_to values. + */ + public function remove_empty_send_to_for_alert_email( $options ) { + + if ( isset( $options['alert_email']['connections'] ) ) { + $valid_connections = []; + + foreach ( $options['alert_email']['connections'] as $connection ) { + if ( ! empty( $connection['send_to'] ) ) { + $valid_connections[] = [ + 'send_to' => $connection['send_to'], + ]; + } + } + + $options['alert_email']['connections'] = $valid_connections; + } + + return $options; + } + + /** + * Initialize settings tab. + * + * @since 3.5.0 + * + * @param array $tabs Tabs array. + */ + public function init_settings_tab( $tabs ) { + + $tabs['alerts'] = new SettingsTab(); + + return $tabs; + } + + /** + * Handle failed email. + * + * @since 3.5.0 + * @since 3.8.0 Included `mailer` array containing the primary and backup mailer slugs in the data + * passed to the notifier task. + * @since 4.9.0 Added the `$log_id` parameter. + * + * @param string $error_message Error message. + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + * @param string $mail_mailer Current mailer slug. + * @param string $failure_type Failure type. + * @param int|null $log_id Log row id to link to. Falls back to the current email id when null. + */ + public function handle_failed_email( $error_message, $mailcatcher, $mail_mailer, $failure_type = self::FAILED_EMAIL, $log_id = null ) { + + $allowed_types = [ + self::FAILED_EMAIL, + self::FAILED_PRIMARY_EMAIL, + self::FAILED_BACKUP_EMAIL, + ]; + + // Bail if any of alerts channels is not enabled, or it's a test email. + if ( + ! $this->is_enabled() || + $mail_mailer === 'mail' || + $mailcatcher->is_test_email() || + $mailcatcher->is_setup_wizard_test_email() || + ! in_array( $failure_type, $allowed_types, true ) + ) { + return; + } + + if ( ! $this->try_throttle_lock() ) { + return; + } + + $to_email_address = array_map( + function ( $address ) use ( $mailcatcher ) { + return $mailcatcher->addrFormat( $address ); + }, + $mailcatcher->getToAddresses() + ); + + $backup_connection = wp_mail_smtp()->get_pro()->get_backup_connections()->get_latest_backup_connection(); + + $data = [ + 'to_email_addresses' => implode( ',', $to_email_address ), + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + 'subject' => $mailcatcher->Subject, + 'error_message' => $error_message, + 'mailers' => [ + 'primary' => wp_mail_smtp()->get_connections_manager()->get_primary_connection()->get_mailer_slug(), + 'backup' => $backup_connection instanceof ConnectionInterface ? $backup_connection->get_mailer_slug() : null, + ], + ]; + + $debug_event_id = $mailcatcher->get_debug_event_id(); + + if ( ! empty( $debug_event_id ) ) { + $data['debug_event_id'] = $debug_event_id; + $data['debug_event_link'] = add_query_arg( + [ + 'tab' => 'debug-events', + 'debug_event_id' => $debug_event_id, + ], + WP::admin_url( 'admin.php?page=' . Area::SLUG . '-tools' ) + ); + } + + $log_id = $log_id ?? wp_mail_smtp()->get_pro()->get_logs()->get_current_email_id(); + + if ( ! empty( $log_id ) ) { + $data['log_id'] = $log_id; + $data['log_link'] = add_query_arg( + [ + 'email_id' => $log_id, + 'mode' => 'view', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ); + } + + ( new NotifierTask() ) + ->async() + ->params( $failure_type, $data ) + ->register(); + } + + /** + * Handle hard-bounced email. + * + * @since 3.10.0 + * + * @param string $error_message Error message. + * @param Email $email The Email object. + */ + public function handle_hard_bounced_email( $error_message, $email ) { + + // Bail if any of alerts channels is not enabled, + // or hard-bounce alerts are not enabled, + // or it's a test email. + if ( + ! $this->is_enabled() || + ! Options::init()->get( 'alert_events', 'email_hard_bounced' ) || + $email->is_test() + ) { + return; + } + + if ( ! $this->try_throttle_lock() ) { + return; + } + + $to_email_address = $email->get_people( 'to' ); + + $data = [ + 'to_email_addresses' => implode( ',', $to_email_address ), + 'subject' => $email->get_subject(), + 'error_message' => $error_message, + 'mailers' => [ + 'primary' => $email->get_mailer(), + 'backup' => null, + ], + ]; + + $current_email_id = $email->get_id(); + + if ( ! empty( $current_email_id ) ) { + $data['log_id'] = $current_email_id; + $data['log_link'] = add_query_arg( + [ + 'email_id' => $current_email_id, + 'mode' => 'view', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ); + } + + ( new NotifierTask() ) + ->async() + ->params( self::HARD_BOUNCED_EMAIL, $data ) + ->register(); + } + + /** + * Try to acquire the alert-dispatch throttle lock for the current window. + * + * Returns true exactly once per TTL window and false for the remainder. + * Both handle_failed_email() and handle_hard_bounced_email() gate on this + * before queueing a NotifierTask — during a flood, only the first failure + * in the window dispatches; subsequent failures are silently suppressed. + * + * When the filtered TTL is <= 0, the throttle is disabled: every call + * returns true and no transient is stored. + * + * @since 4.9.0 + * + * @return bool + */ + private function try_throttle_lock() { + + /** + * Filters the alert-dispatch throttle TTL, in seconds. + * + * Set to 0 or a negative value to disable throttling. + * + * @since 4.9.0 + * + * @param int $ttl Throttle window length in seconds. + */ + $ttl = (int) apply_filters( 'wp_mail_smtp_pro_alerts_try_throttle_lock_ttl', MINUTE_IN_SECONDS * 5 ); + + if ( $ttl <= 0 ) { + return true; + } + + if ( get_transient( self::ALERT_THROTTLE_TRANSIENT ) ) { + return false; + } + + set_transient( self::ALERT_THROTTLE_TRANSIENT, 1, $ttl ); + + return true; + } + + /** + * Handle the AJAX action for the test alerts button. + * + * @since 3.9.0 + * + * @param string $data Array of submitted data. + */ + public function process_ajax_test_alerts_data( $data ) { + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + return $data; + } + + // Bail if no alerts channel is enabled. + if ( ! $this->is_enabled() ) { + return; + } + + $this->send_test(); + + // Store a transient for next page refresh notice. + update_user_meta( get_current_user_id(), SettingsTab::NOTICE_USER_META, true ); + + return $data; + } + + /** + * Handle test run. + * + * @since 3.9.0 + */ + private function send_test() { + + $to_email_address = get_option( 'admin_email' ); + $subject = __( 'WP Mail SMTP Alerts Test', 'wp-mail-smtp-pro' ); + $error_message = __( 'This is a test error message triggered from the WP Mail SMTP Alerts settings', 'wp-mail-smtp-pro' ); + $backup_connection = wp_mail_smtp()->get_pro()->get_backup_connections()->get_latest_backup_connection(); + + $data = [ + 'to_email_addresses' => $to_email_address, + 'subject' => $subject, + 'error_message' => $error_message, + 'mailers' => [ + 'primary' => wp_mail_smtp()->get_connections_manager()->get_primary_connection()->get_mailer_slug(), + 'backup' => $backup_connection instanceof ConnectionInterface ? $backup_connection->get_mailer_slug() : null, + ], + ]; + + ( new NotifierTask() ) + ->async() + ->params( self::FAILED_EMAIL, $data ) + ->register(); + } + + /** + * Whether at least one alert channel is enabled. + * + * @since 3.5.0 + * @since 3.9.0 Change visibility from private to public. + * + * @return bool + */ + public function is_enabled() { + + $options = Options::init(); + $loader = new Loader(); + + $enabled_alerts = array_filter( + array_keys( $loader->get_providers() ), + function ( $provider_slug ) use ( $options ) { + return $options->get( 'alert_' . $provider_slug, 'enabled' ); + } + ); + + return count( $enabled_alerts ) > 0; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/CanValidateWebhookUrlTrait.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/CanValidateWebhookUrlTrait.php new file mode 100644 index 00000000..bb3ebbad --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/CanValidateWebhookUrlTrait.php @@ -0,0 +1,138 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Handlers; + +/** + * Webhook URL validation + safe HTTP request helper. + * + * Blocks link-local destinations (169.254.0.0/16, fe80::/10) — the cloud + * metadata range on AWS, GCP, and Azure that wp_http_validate_url() does + * not cover. Other internal ranges (loopback, RFC1918, IPv6 ULA) are left + * to wp_safe_remote_* and the http_request_host_is_external filter, so + * callers that need to reach internal receivers can opt in. + * + * @since 4.9.0 + */ +trait CanValidateWebhookUrlTrait { + + /** + * Validate a webhook URL and send a safe HTTP request, pinning curl's + * DNS resolution to the IP this trait validated. + * + * Returns false if the URL failed validation. Otherwise returns the + * wp_safe_remote_request() result (response array or WP_Error). + * + * @since 4.9.0 + * + * @param string $url URL to call. + * @param array $args Request args. Caller is responsible for setting `method`. + * + * @return array|\WP_Error|false + */ + protected function safe_webhook_request( $url, $args ) { + + $pin = $this->get_safe_webhook_request_pin( $url ); + + if ( ! $pin ) { + return false; + } + + $pin_curl = static function ( $handle ) use ( $pin ) { + curl_setopt( $handle, CURLOPT_RESOLVE, [ "{$pin['host']}:{$pin['port']}:{$pin['ip']}" ] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt + }; + + add_action( 'http_api_curl', $pin_curl ); + + try { + return wp_safe_remote_request( $url, $args ); + } finally { + remove_action( 'http_api_curl', $pin_curl ); + } + } + + /** + * Validate a webhook URL. Returns the host/IP/port to pin curl's + * resolution against, or false if the URL is unsafe. + * + * @since 4.9.0 + * + * @param string $url URL to validate. + * + * @return array|false ['host' => string, 'ip' => string, 'port' => int] + */ + protected function get_safe_webhook_request_pin( $url ) { + + if ( ! is_string( $url ) || $url === '' ) { + return false; + } + + $parts = wp_parse_url( $url ); + + if ( empty( $parts['host'] ) ) { + return false; + } + + $host = trim( $parts['host'], '[]' ); + $ip = filter_var( $host, FILTER_VALIDATE_IP ); + + if ( $ip === false ) { + $resolved = gethostbyname( $host ); + $ip = $resolved !== $host ? $resolved : ''; + } + + if ( $ip === '' ) { + return false; + } + + if ( $this->is_link_local_ip( $ip ) ) { + return false; + } + + if ( ! empty( $parts['port'] ) ) { + $port = (int) $parts['port']; + } else { + $scheme = isset( $parts['scheme'] ) ? strtolower( $parts['scheme'] ) : ''; + $port = $scheme === 'http' ? 80 : 443; + } + + return [ + 'host' => $parts['host'], + 'ip' => $ip, + 'port' => $port, + ]; + } + + /** + * Whether an IP literal belongs to the IPv4 (169.254.0.0/16) or IPv6 + * (fe80::/10) link-local range. + * + * @since 4.9.0 + * + * @param string $ip IP literal. + * + * @return bool + */ + private function is_link_local_ip( $ip ) { + + $packed = @inet_pton( $ip ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + + if ( $packed === false ) { + return true; + } + + // IPv4-mapped IPv6 (`::ffff:a.b.c.d`) → IPv4 — curl on dual-stack + // hosts connects to the embedded IPv4, so the IPv4 rule must apply. + if ( + strlen( $packed ) === 16 && + substr( $packed, 0, 12 ) === "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + ) { + $packed = substr( $packed, 12 ); + } + + if ( strlen( $packed ) === 4 ) { + return ord( $packed[0] ) === 169 && ord( $packed[1] ) === 254; + } + + return ord( $packed[0] ) === 0xFE && ( ord( $packed[1] ) & 0xC0 ) === 0x80; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/HandlerInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/HandlerInterface.php new file mode 100644 index 00000000..90527cb0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Handlers/HandlerInterface.php @@ -0,0 +1,35 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Handlers; + +use WPMailSMTP\Pro\Alerts\Alert; + +/** + * Interface HandlerInterface. + * + * @since 3.5.0 + */ +interface HandlerInterface { + + /** + * Whether current handler can handle provided alert. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ); + + /** + * Handle alert. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Loader.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Loader.php new file mode 100644 index 00000000..9306cafa --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Loader.php @@ -0,0 +1,178 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts; + +use Exception; +use ReflectionClass; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; + +/** + * Class Loader. + * + * @since 3.5.0 + */ +class Loader { + + /** + * Key is the provider slug, value is the path to its classes. + * + * @since 3.5.0 + * + * @var array + */ + protected $providers = [ + 'email' => 'WPMailSMTP\Pro\Alerts\Providers\Email\\', + 'slack_webhook' => 'WPMailSMTP\Pro\Alerts\Providers\SlackWebhook\\', + 'discord_webhook' => 'WPMailSMTP\Pro\Alerts\Providers\DiscordWebhook\\', + 'teams_webhook' => 'WPMailSMTP\Pro\Alerts\Providers\TeamsWebhook\\', + 'twilio_sms' => 'WPMailSMTP\Pro\Alerts\Providers\TwilioSMS\\', + 'custom_webhook' => 'WPMailSMTP\Pro\Alerts\Providers\CustomWebhook\\', + 'push_notifications' => 'WPMailSMTP\Pro\Alerts\Providers\Push\\', + 'whatsapp' => 'WPMailSMTP\Pro\Alerts\Providers\WhatsApp\\', + ]; + + /** + * Get all the supported providers. + * + * @since 3.5.0 + * + * @return array + */ + public function get_providers() { + + /** + * Filters supported providers. + * + * @since 3.5.0 + * + * @param array $providers Supported providers. + */ + return apply_filters( 'wp_mail_smtp_pro_alerts_loader_get_providers', $this->providers ); + } + + /** + * Get the provider handler, if exists. + * + * @since 3.5.0 + * + * @param string $provider Provider slug. + * + * @return HandlerInterface|null + */ + public function get_handler( $provider ) { + + return $this->get_entity( $provider, 'Handler' ); + } + + /** + * Get the provider options, if exists. + * + * @since 3.5.0 + * + * @param string $provider Provider slug. + * + * @return AbstractOptions|null + */ + public function get_options( $provider ) { + + return $this->get_entity( $provider, 'Options' ); + } + + /** + * Get handlers of all providers. + * + * @since 3.5.0 + * + * @return HandlerInterface[] + */ + public function get_handlers_all() { + + $handlers = []; + + foreach ( $this->get_providers() as $provider => $path ) { + $handler = $this->get_handler( $provider ); + + if ( ! $handler instanceof HandlerInterface ) { + continue; + } + + $handlers[ $provider ] = $handler; + } + + return $handlers; + } + + /** + * Get options of all providers. + * + * @since 3.5.0 + * + * @return AbstractOptions[] + */ + public function get_options_all() { + + $options = []; + + foreach ( $this->get_providers() as $provider => $path ) { + $option = $this->get_options( $provider ); + + if ( ! $option instanceof AbstractOptions ) { + continue; + } + + $options[ $provider ] = $option; + } + + return $options; + } + + /** + * Get a single provider FQN-path based on its name. + * + * @since 3.5.0 + * + * @param string $provider Provider slug. + * + * @return string|null + */ + private function get_provider_path( $provider ) { + + $providers = $this->get_providers(); + + return isset( $providers[ $provider ] ) ? $providers[ $provider ] : null; + } + + /** + * Get a generic entity based on the request. + * + * @since 3.5.0 + * + * @param string $provider Provider slug. + * @param string $request Entity class name. + * @param array $args Entity instantiation arguments. + * + * @return AbstractOptions|HandlerInterface|null + */ + private function get_entity( $provider, $request, $args = [] ) { + + $path = $this->get_provider_path( $provider ); + $entity = null; + + if ( empty( $path ) ) { + return $entity; + } + + try { + $reflection = new ReflectionClass( $path . $request ); + + if ( file_exists( $reflection->getFileName() ) ) { + $class = $path . $request; + $entity = new $class( ...$args ); + } + } catch ( Exception $e ) { + $entity = null; + } + + return $entity; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Notifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Notifier.php new file mode 100644 index 00000000..552554cf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Notifier.php @@ -0,0 +1,52 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts; + +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; + +/** + * Class Notifier. + * + * @since 3.5.0 + */ +class Notifier { + + /** + * Registered handlers. + * + * @since 3.5.0 + * + * @var HandlerInterface[] + */ + private $handlers = []; + + /** + * Register handler. + * + * @since 3.5.0 + * + * @param HandlerInterface $handler Handler object. + */ + public function push_handler( HandlerInterface $handler ) { + + $this->handlers[] = $handler; + } + + /** + * Send notification via registered handlers. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + */ + public function notify( Alert $alert ) { + + foreach ( $this->handlers as $handler ) { + if ( ! $handler->can_handle( $alert ) ) { + continue; + } + + $handler->handle( $alert ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/OptionsInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/OptionsInterface.php new file mode 100644 index 00000000..f4b4d85a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/OptionsInterface.php @@ -0,0 +1,57 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts; + +/** + * Interface OptionsInterface. + * + * @since 3.5.0 + */ +interface OptionsInterface { + + /** + * Get the mailer provider slug. + * + * @since 3.5.0 + * + * @return string + */ + public function get_slug(); + + /** + * Get the mailer provider title (or name). + * + * @since 3.5.0 + * + * @return string + */ + public function get_title(); + + /** + * Get the mailer provider description. + * + * @since 3.5.0 + * + * @return string + */ + public function get_description(); + + /** + * Output the mailer provider options. + * + * @since 3.5.0 + */ + public function display_options(); + + /** + * Get single connection options. + * + * @since 3.5.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Handler.php new file mode 100644 index 00000000..2361a7db --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Handler.php @@ -0,0 +1,191 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\CustomWebhook; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Options; +use WPMailSMTP\WP; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\CanValidateWebhookUrlTrait; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; + +/** + * Class Handler. Custom webhook alerts. + * + * @since 3.5.0 + */ +class Handler implements HandlerInterface { + + /** + * Webhook URL validation trait. + * + * @since 4.9.0 + */ + use CanValidateWebhookUrlTrait; + + /** + * Whether current handler can handle provided alert. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via custom webhook. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks, Generic.Metrics.CyclomaticComplexity.TooHigh + + $connections = (array) Options::init()->get( 'alert_custom_webhook', 'connections' ); + + $connections = array_unique( + array_filter( + $connections, + function ( $connection ) { + return isset( $connection['webhook_url'] ) && filter_var( $connection['webhook_url'], FILTER_VALIDATE_URL ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + $result = false; + $errors = []; + + foreach ( $connections as $connection ) { + $webhook_url = $connection['webhook_url']; + + $args = [ + 'method' => 'POST', + 'timeout' => MINUTE_IN_SECONDS, + 'redirection' => 0, + 'user-agent' => sprintf( 'wp-mail-smtp-alerts-webhooks/%s', WPMS_PLUGIN_VER ), + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( $this->get_message( $alert ) ), + ]; + + /** + * Filters custom webhook request arguments. + * + * @since 3.5.0 + * + * @param array $args Custom webhook request arguments. + * @param array $connection Connection settings. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_custom_webhook_handler_handle_request_args', $args, $connection, $alert ); + + $allow_internal_host = static function ( $is_external, $host, $url ) use ( $webhook_url ) { + return $url === $webhook_url ? true : $is_external; + }; + + $allow_configured_port = static function ( $ports, $host, $url ) use ( $webhook_url ) { + if ( $url !== $webhook_url ) { + return $ports; + } + + $port = wp_parse_url( $webhook_url, PHP_URL_PORT ); + + return $port ? array_merge( (array) $ports, [ (int) $port ] ) : $ports; + }; + + add_filter( 'http_request_host_is_external', $allow_internal_host, 10, 3 ); + add_filter( 'http_allowed_safe_ports', $allow_configured_port, 10, 3 ); + + $response = $this->safe_webhook_request( $webhook_url, $args ); + + remove_filter( 'http_request_host_is_external', $allow_internal_host, 10 ); + remove_filter( 'http_allowed_safe_ports', $allow_configured_port, 10 ); + + if ( $response === false ) { + $errors[] = esc_html__( 'Webhook URL points to an internal-network address and was rejected.', 'wp-mail-smtp-pro' ); + + continue; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( $response_code === 200 ) { + $result = true; + } else { + $errors[] = WP::wp_remote_get_response_error_message( $response ); + } + } + + DebugEvents::add_debug( esc_html__( 'Custom Webhook alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) ) { + DebugEvents::add( esc_html__( 'Alert: Custom Webhook.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + return $result; + } + + /** + * Build message array. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return array + */ + private function get_message( Alert $alert ) { + + $alert_message = ''; + + switch ( $alert->get_type() ) { + case Alerts::FAILED_EMAIL: + $alert_message = esc_html__( 'Your Site Failed to Send an Email', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_PRIMARY_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via the Primary connection, but the email was sent successfully via the Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_BACKUP_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via Primary and Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::HARD_BOUNCED_EMAIL: + $alert_message = esc_html__( 'An email failed to be delivered', 'wp-mail-smtp-pro' ); + break; + } + + return [ + 'event' => $alert->get_type(), + 'site_title' => get_bloginfo( 'name' ), + 'site_url' => home_url(), + 'general_message' => $alert_message, + 'email_data' => $alert->get_data(), + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Options.php new file mode 100644 index 00000000..78a6ee9d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/CustomWebhook/Options.php @@ -0,0 +1,139 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\CustomWebhook; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. Custom webhook settings. + * + * @since 3.5.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 3.5.0 + * + * @var string + */ + const SLUG = 'custom_webhook'; + + /** + * Constructor. + * + * @since 3.5.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'Paste in the webhook URL you’d like to use to receive alerts when email sending fails. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up webhook alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#webhooks', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Webhook Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'Webhook', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Webhook URL', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 3.5.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'webhook_url' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 3.5.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $webhook_url = isset( $connection['webhook_url'] ) ? $connection['webhook_url'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-webhook-url-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Webhook URL', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][webhook_url]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-webhook-url-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $webhook_url ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_CUSTOM_WEBHOOK_URL' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Handler.php new file mode 100644 index 00000000..bb922e9a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Handler.php @@ -0,0 +1,272 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\DiscordWebhook; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\CanValidateWebhookUrlTrait; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. Discord Incoming Webhook alerts. + * + * @since 4.2.0 + */ +class Handler implements HandlerInterface { + + /** + * Webhook URL validation trait. + * + * @since 4.9.0 + */ + use CanValidateWebhookUrlTrait; + + /** + * Whether current handler can handle provided alert. + * + * @since 4.2.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via Discord Incoming Webhook. + * + * @since 4.2.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { + + $connections = (array) Options::init()->get( 'alert_discord_webhook', 'connections' ); + + $connections = array_unique( + array_filter( + $connections, + function( $connection ) { + return isset( $connection['webhook_url'] ) && filter_var( $connection['webhook_url'], FILTER_VALIDATE_URL ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + $result = false; + $errors = []; + + foreach ( $connections as $connection ) { + $webhook_url = $connection['webhook_url']; + + $args = [ + 'method' => 'POST', + 'timeout' => MINUTE_IN_SECONDS, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'embeds' => $this->get_message( $alert ), + ] + ), + ]; + + /** + * Filters Discord Incoming Webhook request arguments. + * + * @since 4.2.0 + * + * @param array $args Discord Incoming Webhook request arguments. + * @param array $connection Connection settings. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_discord_webhook_handler_handle_request_args', $args, $connection, $alert ); + + $response = $this->safe_webhook_request( $webhook_url, $args ); + + if ( $response === false ) { + $errors[] = esc_html__( 'Webhook URL points to an internal-network address and was rejected.', 'wp-mail-smtp-pro' ); + + continue; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( in_array( $response_code, [ 200, 204 ], true ) ) { + $result = true; + } else { + $errors[] = WP::wp_remote_get_response_error_message( $response ); + } + } + + DebugEvents::add_debug( esc_html__( 'Discord Webhook alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) ) { + DebugEvents::add( esc_html__( 'Alert: Discord Webhook.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + return $result; + } + + /** + * Build message array. + * + * @since 4.2.0 + * + * @link https://discord.com/developers/docs/resources/webhook#execute-webhook + * + * @param Alert $alert Alert object. + * + * @return array + */ + private function get_message( Alert $alert ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $data = $alert->get_data(); + $site_title = get_bloginfo( 'name' ); + $settings_link = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $alert_message = ''; + + switch ( $alert->get_type() ) { + case Alerts::FAILED_EMAIL: + $alert_message = esc_html__( 'Your Site Failed to Send an Email', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_PRIMARY_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via the Primary connection, but the email was sent successfully via the Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_BACKUP_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via Primary and Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::HARD_BOUNCED_EMAIL: + $alert_message = esc_html__( 'An email failed to be delivered', 'wp-mail-smtp-pro' ); + break; + } + + // Compose Discord payload fields. + $message = []; + + $message_group = [ + 'title' => $alert_message, + 'fields' => [], + ]; + + $message_group_fields = [ + [ + 'name' => esc_html__( 'Website', 'wp-mail-smtp-pro' ), + 'value' => $site_title, + 'inline' => false, + ], + [ + 'name' => esc_html__( 'Website URL', 'wp-mail-smtp-pro' ), + 'value' => home_url(), + 'inline' => false, + ], + [ + 'name' => esc_html__( 'To email addresses', 'wp-mail-smtp-pro' ), + 'value' => $data['to_email_addresses'], + 'inline' => false, + ], + [ + 'name' => esc_html__( 'Subject', 'wp-mail-smtp-pro' ), + 'value' => $data['subject'], + 'inline' => false, + ], + ]; + + if ( ! empty( $data['error_message'] ) ) { + $message_group_fields[] = [ + 'name' => esc_html__( 'Error Message', 'wp-mail-smtp-pro' ), + 'value' => $data['error_message'], + 'inline' => false, + ]; + } + + // Check if quick links are available. + $quick_links = []; + + if ( ! empty( $data['log_link'] ) ) { + $quick_links[] = sprintf( + '[Email Log [#%s]](%s)', + $data['log_id'], + $data['log_link'] + ); + } + + if ( ! empty( $data['debug_event_link'] ) ) { + $quick_links[] = sprintf( + '[Debug Event [#%s]](%s)', + $data['debug_event_id'], + $data['debug_event_link'] + ); + } + + // Add quick links if available. + if ( ! empty( $quick_links ) ) { + $message_group_fields[] = [ + 'name' => '', + 'value' => implode( ' | ', $quick_links ), + 'inline' => false, + ]; + } + + // Add link to admin settings. + $message_group_fields[] = [ + 'name' => '', + 'value' => sprintf( + '[WP Mail SMTP Settings](%s)', + $settings_link + ), + 'inline' => false, + ]; + + // Add link to troubleshooting guide. + $message_group_fields[] = [ + 'name' => '', + 'value' => sprintf( + "%s\n[%s](%s)", + esc_html__( 'Need more help?', 'wp-mail-smtp-pro' ), + esc_html__( 'Read our troubleshooting guide', 'wp-mail-smtp-pro' ), + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/how-to-troubleshoot-wp-mail-smtp', + [ + 'medium' => 'Discord Alerts Notification', + 'content' => 'Read Our Troubleshooting Guide', + ] + ) + ), + 'inline' => false, + ]; + + // Add fields to payload. + $message_group['fields'] = $message_group_fields; + + $message[] = $message_group; + + return $message; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Options.php new file mode 100644 index 00000000..29eb0da9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/DiscordWebhook/Options.php @@ -0,0 +1,139 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\DiscordWebhook; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. Discord Incoming Webhook settings. + * + * @since 4.2.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 4.2.0 + * + * @var string + */ + const SLUG = 'discord_webhook'; + + /** + * Constructor. + * + * @since 4.2.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'Paste in the Discord webhook URL you\'d like to use to receive alerts when email sending fails. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up Discord alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#discord', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Discord Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'Discord', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Webhook', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 4.2.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'webhook_url' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 4.2.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $webhook_url = isset( $connection['webhook_url'] ) ? $connection['webhook_url'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-webhook-url-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Webhook URL', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][webhook_url]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-webhook-url-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $webhook_url ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_DISCORD_WEBHOOK_URL' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Handler.php new file mode 100644 index 00000000..2e5a8be2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Handler.php @@ -0,0 +1,166 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\Email; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Helpers\Helpers; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. Email API alerts. + * + * @since 3.5.0 + */ +class Handler implements HandlerInterface { + + /** + * The maximum rate at which alerts can be handled by this handler. + * + * @since 3.9.0 + * + * @var string + */ + const RATE_LIMIT = MINUTE_IN_SECONDS * 30; + + /** + * The last time an alert was handled by this handler. + * + * @since 3.9.0 + * + * @var string + */ + const LAST_EXECUTED_TRANSIENT = 'wp_mail_smtp_email_alert_handler_timestamp'; + + /** + * Whether current handler can handle provided alert. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via Email API. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { + + // Bail if license is not valid. + if ( ! wp_mail_smtp()->pro->get_license()->is_valid() ) { + return false; + } + + $connections = (array) Options::init()->get( 'alert_email', 'connections' ); + + $connections = array_unique( + array_filter( + $connections, + function ( $connection ) { + return isset( $connection['send_to'] ) && is_email( $connection['send_to'] ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + // Limit max 3 recipients. + $connections = array_slice( $connections, 0, 3 ); + + $url = 'https://connect.wpmailsmtp.com/email-api/v1/send'; + + $args = [ + 'headers' => [ + 'Content-Type' => 'application/json', + 'X-Licence-Key' => wp_mail_smtp()->get_license_key(), + 'X-Site-Domain' => wp_parse_url( WP::get_site_url(), PHP_URL_HOST ), + ], + 'user-agent' => Helpers::get_default_user_agent(), + 'body' => wp_json_encode( + [ + 'recipients_email_addresses' => array_column( $connections, 'send_to' ), + 'event' => $alert->get_type(), + 'plugin_version' => WPMS_PLUGIN_VER, + 'site_title' => get_bloginfo( 'name' ), + 'site_url' => home_url(), + 'settings_link' => wp_mail_smtp()->get_admin()->get_admin_page_url(), + 'email_data' => $alert->get_data(), + ] + ), + ]; + + /** + * Filters Email API request arguments. + * + * @since 3.5.0 + * + * @param array $args Email API request arguments. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_email_handler_handle_request_args', $args, $alert ); + + wp_remote_post( $url, $args ); + + DebugEvents::add_debug( esc_html__( 'An Email alert request was sent.', 'wp-mail-smtp-pro' ) ); + + // Start a new rate limit period, if previous one expired. + if ( self::get_remaining_rate_limit_seconds() === 0 ) { + set_transient( self::LAST_EXECUTED_TRANSIENT, time(), self::RATE_LIMIT ); + } + + return true; + } + + /** + * Return the number of seconds until rate limit expires. + * + * @since 3.9.0 + * + * @return int Number of seconds until rate limit expires. + */ + public static function get_remaining_rate_limit_seconds() { + + $last_executed_time = get_transient( self::LAST_EXECUTED_TRANSIENT ); + + // Bail early if rate limit already expired. + if ( $last_executed_time === false ) { + return 0; + } + + $remaining_time = self::RATE_LIMIT - ( time() - $last_executed_time ); + + // Bail early if rate limit just expired. + if ( $remaining_time <= 0 ) { + return 0; + } + + return $remaining_time; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Options.php new file mode 100644 index 00000000..e4ba1bb5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Email/Options.php @@ -0,0 +1,165 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\Email; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. Email API settings. + * + * @since 3.5.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 3.5.0 + * + * @var string + */ + const SLUG = 'email'; + + /** + * Constructor. + * + * @since 3.5.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'Enter the email addresses (3 max) you’d like to use to receive alerts when email sending fails. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up email alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#email', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Email Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + if ( ! wp_mail_smtp()->pro->get_license()->is_valid() ) { + ob_start(); + ?> + <div class="notice notice-error inline"> + <p> + <?php + echo wp_kses( + sprintf( + /* translators: %s - Plugin general settings page link. */ + __( 'This integration will not work without a valid WP Mail SMTP license key. <a href="%s">Please verify your license key</a>.', 'wp-mail-smtp-pro' ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ) + ), + [ + 'a' => [ + 'href' => [], + ], + ] + ); + ?> + </p> + </div> + <?php + $description .= ob_get_clean(); + } + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'Email', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Email Address', 'wp-mail-smtp-pro' ), + 'max_connections_count' => 3, + ] + ); + } + + /** + * Output the provider options. + * + * @since 3.5.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'send_to' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 3.5.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $send_to = isset( $connection['send_to'] ) ? $connection['send_to'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-send-to-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Send To', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][send_to]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-send-to-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $send_to ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_EMAIL_SEND_TO' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Handler.php new file mode 100644 index 00000000..0032ba26 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Handler.php @@ -0,0 +1,197 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\Push; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. Push notifications alerts. + * + * @since 4.4.0 + */ +class Handler implements HandlerInterface { + + /** + * The maximum rate at which alerts can be handled by this handler. + * + * @since 4.4.0 + * + * @var string + */ + const RATE_LIMIT = MINUTE_IN_SECONDS * 30; + + /** + * The last time an alert was handled by this handler. + * + * @since 4.4.0 + * + * @var string + */ + const LAST_EXECUTED_TRANSIENT = 'wp_mail_smtp_push_notifications_alert_handler_timestamp'; + + /** + * Whether current handler can handle provided alert. + * + * @since 4.4.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via push notifications. + * + * @since 4.4.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { + + $provider = new Provider(); + $result = false; + $errors = []; + + $args = [ + 'timeout' => MINUTE_IN_SECONDS, + 'json' => [ + 'urgency' => 'high', + 'payload' => wp_json_encode( $this->get_message( $alert ) ), + ], + ]; + + /** + * Filters push notifications alerts handler request arguments. + * + * @since 4.4.0 + * + * @param array $args Request arguments. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_push_handler_handle_request_args', $args, $alert ); + + $response = $provider->request( 'POST', 'push/v1/notify', $args ); + + if ( ! is_wp_error( $response ) ) { + $result = true; + } else { + $errors[] = $response->get_error_message(); + } + + DebugEvents::add_debug( esc_html__( 'Push notifications alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) && DebugEvents::is_debug_enabled() ) { + DebugEvents::add( esc_html__( 'Alert: Push notifications.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + // Start a new rate limit period, if previous one expired. + if ( self::get_remaining_rate_limit_seconds() === 0 ) { + set_transient( self::LAST_EXECUTED_TRANSIENT, time(), self::RATE_LIMIT ); + } + + return $result; + } + + /** + * Build message array. + * + * @since 4.4.0 + * + * @param Alert $alert Alert object. + * + * @return array + */ + private function get_message( Alert $alert ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $data = $alert->get_data(); + $alert_title = esc_html__( 'Email sending failed', 'wp-mail-smtp-pro' ); + $alert_message = ''; + + switch ( $alert->get_type() ) { + case Alerts::FAILED_EMAIL: + $alert_message = esc_html__( 'Your Site Failed to Send an Email', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_PRIMARY_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via the Primary connection, but the email was sent successfully via the Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_BACKUP_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via Primary and Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::HARD_BOUNCED_EMAIL: + $alert_message = esc_html__( 'An email failed to be delivered', 'wp-mail-smtp-pro' ); + break; + } + + // Check if quick links are available. + $quick_link = null; + + if ( ! empty( $data['log_link'] ) ) { + $quick_link = $data['log_link']; + } elseif ( ! empty( $data['debug_event_link'] ) ) { + $quick_link = $data['debug_event_link']; + } + + $message = [ + 'title' => $alert_title, + 'body' => $alert_message, + ]; + + // Add quick links if available. + if ( ! empty( $quick_link ) ) { + $message['data'] = [ + 'url' => $quick_link, + ]; + } + + return $message; + } + + /** + * Return the number of seconds until rate limit expires. + * + * @since 4.4.0 + * + * @return int Number of seconds until rate limit expires. + */ + public static function get_remaining_rate_limit_seconds() { + + $last_executed_time = get_transient( self::LAST_EXECUTED_TRANSIENT ); + + // Bail early if rate limit already expired. + if ( $last_executed_time === false ) { + return 0; + } + + $remaining_time = self::RATE_LIMIT - ( time() - $last_executed_time ); + + // Bail early if rate limit just expired. + if ( $remaining_time <= 0 ) { + return 0; + } + + return $remaining_time; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Options.php new file mode 100644 index 00000000..4d04e118 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Options.php @@ -0,0 +1,182 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\Push; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. + * + * @since 4.4.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 4.4.0 + * + * @var string + */ + const SLUG = 'push_notifications'; + + /** + * Constructor. + * + * @since 4.4.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'To receive push notifications on this device, you\'ll need to allow our plugin to send notifications via this browser. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up Push Notification alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#push-notifications', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Push notifications Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + if ( ! wp_mail_smtp()->pro->get_license()->is_valid() ) { + ob_start(); + ?> + <div class="notice notice-error inline"> + <p> + <?php + echo wp_kses( + sprintf( + /* translators: %s - Plugin general settings page link. */ + __( 'This integration will not work without a valid WP Mail SMTP license key. <a href="%s">Please verify your license key</a>.', 'wp-mail-smtp-pro' ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ) + ), + [ + 'a' => [ + 'href' => [], + ], + ] + ); + ?> + </p> + </div> + <?php + $description .= ob_get_clean(); + } + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'Push Notification', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Enable Push Notifications on This Device', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 4.4.0 + */ + public function display_options() { + + $slug = $this->get_slug(); + $public_key = $this->options->get( $this->get_group(), 'public_key' ); + ?> + <input type="hidden" + name="wp-mail-smtp[alert_<?php echo esc_attr( $slug ); ?>][public_key]" + id="wp-mail-smtp-push-notifications-public-key" + value="<?php echo esc_attr( $public_key ); ?>"> + + <div class="wp-mail-smtp-setting-row" id="wp-mail-smtp-setting-row-alert-push_notifications-subscriptions"> + <div class="wp-mail-smtp-setting-row"> + <div class="wp-mail-smtp-setting-field"> + <?php echo wp_mail_smtp()->prepare_loader(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + </div> + </div> + </div> + + <div class="wp-mail-smtp-setting-row"> + <div class="wp-mail-smtp-setting-field"> + <div class="notice inline notice-inline wp-mail-smtp-notice notice-error wp-mail-smtp-push-notifications-notice" + id="wp-mail-smtp-push-notifications-subscription-notice" + style="display: none;"> + <p></p> + </div> + + <button class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-blueish" + id="wp-mail-smtp-setting-alert-push_notifications-subscribe" + data-provider="<?php echo esc_attr( $this->get_slug() ); ?>" + disabled> + <span><?php echo esc_html( $this->get_add_connection_text() ); ?></span> + <?php echo wp_mail_smtp()->prepare_loader( 'white', 'sm' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + </button> + </div> + </div> + <?php + } + + /** + * Get single connection options. + * + * @since 4.4.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { + + $slug = $this->get_slug(); + $id = isset( $connection['id'] ) ? $connection['id'] : ''; + $label = isset( $connection['label'] ) ? $connection['label'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text" data-subscription-id="<?php echo esc_attr( $id ); ?>"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-user-agent-<?php echo esc_attr( $i ); ?>" data-current-label="<?php echo esc_attr__( 'current', 'wp-mail-smtp-pro' ); ?>"> + <?php esc_html_e( 'Connection Name', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][label]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-user-agent-%2$s" spellcheck="false"/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $label ) + ); + ?> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][id]" type="hidden" value="%3$s"/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $id ) + ); + ?> + <span class="js-wp-mail-smtp-setting-alert-push_notifications-remove-subscription"> + <i class="dashicons dashicons-trash"></i> + <?php echo wp_mail_smtp()->prepare_loader( '', 'sm' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + </span> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Provider.php new file mode 100644 index 00000000..30717536 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/Push/Provider.php @@ -0,0 +1,695 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\Push; + +use WPMailSMTP\Options as PluginOptions; +use WPMailSMTP\Pro\Alerts\Loader; +use WPMailSMTP\WP; + +/** + * Class Provider. + * + * @since 4.4.0 + */ +class Provider { + + /** + * Ajax action slug. + * + * @since 4.4.0 + */ + const AJAX_ACTION = 'wp_mail_smtp_admin_pro_push_notifications'; + + /** + * Service worker request parameter. + * + * @since 4.4.0 + */ + const SERVICE_WORKER_PARAMETER = 'wp-mail-smtp-pro-push-notifications-service-worker'; + + /** + * Manifest request parameter. + * + * @since 4.4.0 + */ + const MANIFEST_PARAMETER = 'wp-mail-smtp-pro-push-notifications-manifest'; + + /** + * Register hooks. + * + * @since 4.4.0 + * + * @return void + */ + public function hooks() { + + add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_assets' ], PHP_INT_MAX ); + add_action( 'wp_enqueue_scripts', [ $this, 'frontend_enqueue_assets' ] ); + add_action( 'admin_init', [ $this, 'maybe_output_service_worker' ] ); + add_action( 'init', [ $this, 'maybe_output_manifest' ] ); + add_action( 'admin_head', [ $this, 'enqueue_manifest' ] ); + + add_filter( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'process_ajax' ] ); + } + + /** + * Whether push notifications are enabled. + * + * @since 4.4.0 + * + * @return mixed|null + */ + private function is_enabled() { + + $options = PluginOptions::init(); + + return $options->get( 'alert_' . Options::SLUG, 'enabled' ); + } + + /** + * Get the public key. + * + * @since 4.4.0 + * + * @return mixed|null + */ + private function get_public_key() { + + $options = PluginOptions::init(); + + return $options->get( 'alert_' . Options::SLUG, 'public_key' ); + } + + /** + * Make a request to the remote API. + * + * @since 4.4.0 + * + * @param string $method Request method. + * @param string $endpoint Request endpoint. + * @param array $args Request arguments. + * + * @return array|WP_Error + */ + public function request( $method, $endpoint, $args = [] ) { + + $client = wp_mail_smtp()->get_pro()->get_product_api()->get_client(); + $response = $client->request( $method, $endpoint, $args ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + if ( ! $response->is_successful() ) { + return $response->get_errors(); + } + + $body = $response->get_body(); + + return ! empty( $body['data'] ) ? $body['data'] : []; + } + + /** + * Handle AJAX calls. + * + * @since 4.4.0 + * + * @return false|void|null + */ + public function process_ajax() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh,Generic.Metrics.CyclomaticComplexity.MaxExceeded + + if ( + ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) || + ! check_ajax_referer( self::AJAX_ACTION, false, false ) + ) { + wp_send_json_error(); + } + + $task = ! empty( $_REQUEST['task'] ) ? sanitize_key( $_REQUEST['task'] ) : ''; // phpcs:ignore WordPress.Security + + if ( empty( $task ) ) { + wp_send_json_error(); + } + + switch ( $task ) { + case 'enable_site': + $this->enable_site(); + break; + + case 'disable_site': + $this->disable_site(); + break; + + case 'get_subscriptions': + $this->get_subscriptions(); + break; + + case 'create_subscription': + $this->create_subscription(); + break; + + case 'update_subscription': + $this->update_subscription(); + break; + + case 'delete_subscription': + $this->delete_subscription(); + break; + + default: + wp_send_json_error(); + } + } + + /** + * Enable the current site on the remote API. + * + * @since 4.4.0 + * + * @return void + */ + private function enable_site() { + + $body = [ + 'email' => get_option( 'admin_email' ), + 'status' => 'active', + ]; + $site = $this->request( + 'POST', + 'push/v1/account', + [ + 'json' => $body, + ] + ); + + if ( is_wp_error( $site ) ) { + wp_send_json_error( + [ + 'message' => $site->get_error_message(), + ] + ); + } + + $plugin_options = PluginOptions::init(); + $all = $plugin_options->get_all(); + + $all['alert_push_notifications']['public_key'] = $site['public_key']; + + $plugin_options->set( $all, false, true ); + + wp_send_json_success( $site ); + } + + /** + * Disable the current site on the remote API. + * + * @since 4.4.0 + * + * @return void + */ + private function disable_site() { + + $body = [ + 'status' => 'inactive', + ]; + $site = $this->request( + 'POST', + 'push/v1/account', + [ + 'json' => $body, + ] + ); + + if ( is_wp_error( $site ) ) { + wp_send_json_error( + [ + 'message' => $site->get_error_message(), + ] + ); + } + + wp_send_json_success( $site ); + } + + /** + * Validate subscription details. + * + * @since 4.4.0 + * + * @return array|null + */ + private function validate_subscription_payload() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $details = ! empty( $_REQUEST['details'] ) ? $_REQUEST['details'] : ''; // phpcs:ignore WordPress.Security + + if ( empty( $details ) ) { + return null; + } + + $details = json_decode( wp_unslash( $details ), JSON_OBJECT_AS_ARRAY ); + + if ( empty( $details ) ) { + return null; + } + + if ( empty( $details['endpoint'] ) ) { + return null; + } + + $user_agent = ! empty( $_REQUEST['user_agent'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['user_agent'] ) ) : ''; // phpcs:disable WordPress.Security.NonceVerification.Recommended + + if ( empty( $user_agent ) ) { + return null; + } + + $body = [ + 'user_agent' => $user_agent, + 'details' => $details, + ]; + + return $body; + } + + /** + * Add a subscription to the remote API. + * + * @since 4.4.0 + * + * @return void + */ + private function create_subscription() { + + $body = $this->validate_subscription_payload(); + + if ( empty( $body ) ) { + wp_send_json_error(); + } + + $subscription = $this->request( + 'POST', + 'push/v1/subscriptions', + [ + 'json' => $body, + ] + ); + + if ( is_wp_error( $subscription ) ) { + wp_send_json_error( + [ + 'message' => $subscription->get_error_message(), + ] + ); + } + + wp_send_json_success( $subscription ); + } + + /** + * Update a subscription on the remote API. + * + * @since 4.4.0 + * + * @return void + */ + private function update_subscription() { + + $subscription_id = ! empty( $_REQUEST['subscription_id'] ) ? $_REQUEST['subscription_id'] : ''; // phpcs:ignore WordPress.Security + + if ( empty( $subscription_id ) ) { + wp_send_json_error(); + } + + $body = $this->validate_subscription_payload(); + + if ( empty( $body ) ) { + wp_send_json_error(); + } + + $subscription = $this->request( + 'PUT', + "push/v1/subscriptions/$subscription_id", + [ + 'json' => $body, + ] + ); + + if ( is_wp_error( $subscription ) ) { + wp_send_json_error( + [ + 'message' => $subscription->get_error_message(), + ] + ); + } + + wp_send_json_success( $subscription ); + } + + /** + * Delete a subscription from the remote API. + * + * @since 4.4.0 + * + * @return void + */ + private function delete_subscription() { + + $subscription_id = ! empty( $_REQUEST['subscription_id'] ) ? $_REQUEST['subscription_id'] : ''; // phpcs:ignore WordPress.Security + + if ( empty( $subscription_id ) ) { + wp_send_json_error(); + } + + $subscription = $this->request( + 'DELETE', + "push/v1/subscriptions/$subscription_id" + ); + + if ( is_wp_error( $subscription ) ) { + wp_send_json_error( + [ + 'message' => $subscription->get_error_message(), + ] + ); + } + + wp_send_json_success( $subscription ); + } + + /** + * Fetch subscriptions from the remote API. + * + * @since 4.4.0 + * + * @return void + */ + private function get_subscriptions() { + + $subscriptions = $this->request( 'GET', 'push/v1/subscriptions' ); + + if ( is_wp_error( $subscriptions ) ) { + wp_send_json_error( + [ + 'message' => $subscriptions->get_error_message(), + ] + ); + } + + $plugin_options = PluginOptions::init(); + $all = $plugin_options->get_all(); + $cached_connections = []; + + if ( ! empty( $all['alert_push_notifications']['connections'] ) ) { + $cached_connections = array_column( $all['alert_push_notifications']['connections'], null, 'id' ); + } + + $subscriptions = array_column( $subscriptions, null, 'id' ); + $connections = []; + + // Copy labels from previously saved connections. + foreach ( $subscriptions as $id => $subscription ) { + $connection = []; + $connection['id'] = $id; + + if ( ! empty( $cached_connections[ $id ]['label'] ) ) { + $connection['label'] = $cached_connections[ $id ]['label']; + } else { + $connection['label'] = $subscription['user_agent']; + } + + $connections[] = $connection; + } + + // Update stored connections. + $all['alert_push_notifications']['connections'] = $connections; + + $plugin_options->set( $all, false, true ); + + // Build connections HTML output. + $options_class = ( new Loader() )->get_options( Options::SLUG ); + $options = new $options_class(); + $output = []; + + foreach ( $connections as $i => $connection ) { + $output[] = $options->get_connection_options( $connection, $i ); + } + + $output = implode( '', $output ); + + wp_send_json_success( $output ); + } + + /** + * Return a list of all possible error notices. + * + * @since 4.4.0 + * + * @return array + */ + private function get_notices() { + + $notices = [ + 'unsupported' => esc_html__( 'This browser doesn\'t support push notifications', 'wp-mail-smtp-pro' ), + 'permission_denied' => wp_kses( + sprintf( + /* translators: %s - Plugin general settings page link. */ + __( 'Push notifications are disabled on this browser. <a href="%s" target="_blank" rel="noopener noreferrer">Follow this guide</a> to enable them', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#resetting-push-notifications-permissions', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Resetting push notification permissions', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ), + 'service_worker' => esc_html__( 'Error loading service worker', 'wp-mail-smtp-pro' ), + 'subscription' => esc_html__( 'Error fetching push notification subscription', 'wp-mail-smtp-pro' ), + 'subscribe' => esc_html__( 'Error subscribing to push notifications', 'wp-mail-smtp-pro' ), + 'unsubscribe' => esc_html__( 'Error unsubscribing from push notifications', 'wp-mail-smtp-pro' ), + 'request' => esc_html__( 'API request error', 'wp-mail-smtp-pro' ), + ]; + + return $notices; + } + + /** + * Enqueue admin assets. + * + * @since 4.4.0 + */ + public function admin_enqueue_assets() { + + // Bail if not admin. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + return; + } + + $admin = wp_mail_smtp()->get_admin(); + $is_alerts_tab = $admin->is_admin_page( 'general' ) && $admin->get_current_tab() === 'alerts'; + + // Bail if not in alerts tab + // and push notifications are disabled. + if ( ! $is_alerts_tab && ! $this->is_enabled() ) { + return; + } + + $this->enqueue_common_assets(); + + // Bail if we're not in Alerts tab. + if ( ! $is_alerts_tab ) { + return; + } + + wp_enqueue_script( + 'wp-mail-smtp-pro-alerts-push-notifications', + wp_mail_smtp()->assets_url . '/pro/js/smtp-pro-alerts-push-notifications' . WP::asset_min() . '.js', + [ 'wp-mail-smtp-alerts' ], + WPMS_PLUGIN_VER, + true + ); + } + + /** + * Enqueue frontend assets. + * + * @since 4.4.0 + * + * @return void + */ + public function frontend_enqueue_assets() { + + // Bail if not logged in, + // or not an admin, + // or push notifications are disabled. + if ( + ! is_user_logged_in() || + ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) || + ! $this->is_enabled() + ) { + return; + } + + $this->enqueue_common_assets(); + } + + /** + * Enqueue common assets. + * + * @since 4.4.0 + * + * @return void + */ + private function enqueue_common_assets() { + + wp_enqueue_script( + 'wp-mail-smtp-pro-push-notifications', + wp_mail_smtp()->assets_url . '/pro/js/smtp-pro-push-notifications' . WP::asset_min() . '.js', + [], + WPMS_PLUGIN_VER, + true + ); + + $ajax_url = add_query_arg( + 'action', + self::AJAX_ACTION, + wp_nonce_url( + admin_url( 'admin-ajax.php' ), + self::AJAX_ACTION + ) + ); + + $service_worker_url = add_query_arg( + [ + 'tab' => 'alerts', + self::SERVICE_WORKER_PARAMETER => 1, + 'v' => ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? time() : WPMS_PLUGIN_VER, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ); + + $script_settings = [ + 'apiUrl' => $ajax_url, + 'serviceWorkerUrl' => $service_worker_url, + 'publicKey' => $this->get_public_key(), + ]; + + $admin = wp_mail_smtp()->get_admin(); + + if ( + $admin->is_admin_page( 'general' ) && + $admin->get_current_tab() === 'alerts' + ) { + $script_settings['notices'] = $this->get_notices(); + } + + wp_add_inline_script( + 'wp-mail-smtp-pro-push-notifications', + 'var WPMailSMTPPushNotificationsSettings = ' . wp_json_encode( $script_settings ), + true + ); + } + + /** + * Output the application manifest. + * + * @since 4.4.0 + * + * @return void + */ + public function maybe_output_manifest() { + + $admin = wp_mail_smtp()->get_admin(); + + // Bail if it's not a manifest request, + // or we're not in Alerts tab. + if ( + ! isset( $_GET[ self::MANIFEST_PARAMETER ] ) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing + ! $admin->is_admin_page( 'general' ) || + $admin->get_current_tab() !== 'alerts' + ) { + return; + } + + $name = esc_html__( 'WP Mail SMTP Alerts', 'wp-mail-smtp-pro' ); + $manifest = [ + 'short_name' => $name, + 'name' => $name, + 'start_url' => '/wp-admin/admin.php?page=wp-mail-smtp&tab=alerts', + 'display' => 'standalone', + ]; + + header( 'Content-Type: application/json' ); + + echo json_encode( $manifest, JSON_PRETTY_PRINT ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode + + die(); + } + + /** + * Output the service worker. + * + * @since 4.4.0 + * + * @return void + */ + public function maybe_output_service_worker() { + + $admin = wp_mail_smtp()->get_admin(); + + // Bail if we're not in Alerts tab, + // or it's not a service worker request. + if ( + ! $admin->is_admin_page( 'general' ) || + $admin->get_current_tab() !== 'alerts' || + ! isset( $_GET[ self::SERVICE_WORKER_PARAMETER ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing + ) { + return; + } + + $url = wp_mail_smtp()->assets_url . '/pro/js/smtp-pro-push-notifications-service-worker' . WP::asset_min() . '.js'; + + header( 'Content-Type: application/javascript' ); + echo "importScripts('{$url}')"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + die(); + } + + /** + * Enqueue the application manifest. + * + * @since 4.4.0 + * + * @return void + */ + public function enqueue_manifest() { + + $admin = wp_mail_smtp()->get_admin(); + + // Bail if we're not in Alerts tab. + if ( + ! $admin->is_admin_page( 'general' ) || + $admin->get_current_tab() !== 'alerts' + ) { + return; + } + + $manifest_url = add_query_arg( + [ + 'tab' => 'alerts', + self::MANIFEST_PARAMETER => 1, + ], + $admin->get_admin_page_url() + ); + ?> + <link rel="manifest" href="<?php echo esc_attr( $manifest_url ); ?>"/> + <?php + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Handler.php new file mode 100644 index 00000000..02ed28d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Handler.php @@ -0,0 +1,331 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\SlackWebhook; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\CanValidateWebhookUrlTrait; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. Slack Incoming Webhook alerts. + * + * @since 3.5.0 + */ +class Handler implements HandlerInterface { + + /** + * Webhook URL validation trait. + * + * @since 4.9.0 + */ + use CanValidateWebhookUrlTrait; + + /** + * Whether current handler can handle provided alert. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via Slack Incoming Webhook. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { + + $connections = (array) Options::init()->get( 'alert_slack_webhook', 'connections' ); + + $connections = array_unique( + array_filter( + $connections, + function ( $connection ) { + return isset( $connection['webhook_url'] ) && filter_var( $connection['webhook_url'], FILTER_VALIDATE_URL ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + $result = false; + $errors = []; + + foreach ( $connections as $connection ) { + $webhook_url = $connection['webhook_url']; + + $args = [ + 'method' => 'POST', + 'timeout' => MINUTE_IN_SECONDS, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'blocks' => $this->get_message( $alert ), + ] + ), + ]; + + /** + * Filters Slack Incoming Webhook request arguments. + * + * @since 3.5.0 + * + * @param array $args Slack Incoming Webhook request arguments. + * @param array $connection Connection settings. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_slack_webhook_handler_handle_request_args', $args, $connection, $alert ); + + $response = $this->safe_webhook_request( $webhook_url, $args ); + + if ( $response === false ) { + $errors[] = esc_html__( 'Webhook URL points to an internal-network address and was rejected.', 'wp-mail-smtp-pro' ); + + continue; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( $response_code === 200 ) { + $result = true; + } else { + $errors[] = WP::wp_remote_get_response_error_message( $response ); + } + } + + DebugEvents::add_debug( esc_html__( 'Slack Webhook alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) ) { + DebugEvents::add( esc_html__( 'Alert: Slack Webhook.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + return $result; + } + + /** + * Build message array. + * + * @since 3.5.0 + * + * @link https://api.slack.com/messaging/interactivity + * + * @param Alert $alert Alert object. + * + * @return array + */ + private function get_message( Alert $alert ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $data = $alert->get_data(); + $site_title = get_bloginfo( 'name' ); + $settings_link = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $alert_message = ''; + + switch ( $alert->get_type() ) { + case Alerts::FAILED_EMAIL: + $alert_message = esc_html__( 'Your Site Failed to Send an Email', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_PRIMARY_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via the Primary connection, but the email was sent successfully via the Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_BACKUP_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via Primary and Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::HARD_BOUNCED_EMAIL: + $alert_message = esc_html__( 'An email failed to be delivered', 'wp-mail-smtp-pro' ); + break; + } + + $message = [ + [ + 'type' => 'header', + 'text' => [ + 'type' => 'plain_text', + 'text' => $alert_message, + ], + ], + [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => sprintf( + "*%s*:\n %s", + esc_html__( 'Website', 'wp-mail-smtp-pro' ), + $site_title + ), + ], + ], + ], + [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => sprintf( + "*%s*:\n %s", + esc_html__( 'Website URL', 'wp-mail-smtp-pro' ), + home_url() + ), + ], + ], + ], + [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => sprintf( + "*%s*:\n %s", + esc_html__( 'To email addresses', 'wp-mail-smtp-pro' ), + $data['to_email_addresses'] + ), + ], + ], + ], + [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => sprintf( + "*%s*:\n %s", + esc_html__( 'Subject', 'wp-mail-smtp-pro' ), + $data['subject'] + ), + ], + ], + ], + ]; + + if ( ! empty( $data['error_message'] ) ) { + $message[] = [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => sprintf( + "*%s*:\n %s", + esc_html__( 'Error message', 'wp-mail-smtp-pro' ), + $data['error_message'] + ), + ], + ], + ]; + } + + $quick_links = []; + + if ( ! empty( $data['log_link'] ) ) { + $quick_links[] = $this->build_link( + $data['log_link'], + /* translators: %s - Email Log ID. */ + sprintf( esc_html__( 'Email Log [#%s]', 'wp-mail-smtp-pro' ), $data['log_id'] ) + ); + } + + if ( ! empty( $data['debug_event_link'] ) ) { + $quick_links[] = $this->build_link( + $data['debug_event_link'], + /* translators: %s - Debug Event ID. */ + sprintf( esc_html__( 'Debug Event [#%s]', 'wp-mail-smtp-pro' ), $data['debug_event_id'] ) + ); + } + + if ( ! empty( $quick_links ) ) { + $message[] = [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => implode( ' | ', $quick_links ), + ], + ], + ]; + } + + $message[] = [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => $this->build_link( + $settings_link, + esc_html__( 'WP Mail SMTP settings', 'wp-mail-smtp-pro' ) + ), + ], + ], + ]; + + $message[] = [ + 'type' => 'section', + 'fields' => [ + [ + 'type' => 'mrkdwn', + 'text' => sprintf( + /* translators: %s - Troubleshooting guide link. */ + esc_html__( "Need more help?\n %s.", 'wp-mail-smtp-pro' ), + $this->build_link( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/how-to-troubleshoot-wp-mail-smtp', + [ + 'medium' => 'Slack Alerts Notification', + 'content' => 'Read Our Troubleshooting Guide', + ] + ), + esc_html__( 'Read our troubleshooting guide', 'wp-mail-smtp-pro' ) + ) + ), + ], + ], + ]; + + return $message; + } + + /** + * Build link. + * + * @since 3.5.0 + * + * @param string $url Link URL. + * @param string $text Link text. + * + * @return string + */ + private function build_link( $url, $text ) { + + return "<$url|$text>"; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Options.php new file mode 100644 index 00000000..eae0602f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/SlackWebhook/Options.php @@ -0,0 +1,139 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\SlackWebhook; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. Slack Incoming Webhook settings. + * + * @since 3.5.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 3.5.0 + * + * @var string + */ + const SLUG = 'slack_webhook'; + + /** + * Constructor. + * + * @since 3.5.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'Paste in the Slack webhook URL you’d like to use to receive alerts when email sending fails. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up Slack alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#slack', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Slack Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'Slack', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Webhook', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 3.5.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'webhook_url' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 3.5.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $webhook_url = isset( $connection['webhook_url'] ) ? $connection['webhook_url'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-webhook-url-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Webhook URL', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][webhook_url]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-webhook-url-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $webhook_url ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_SLACK_WEBHOOK_URL' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Handler.php new file mode 100644 index 00000000..95a3d79e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Handler.php @@ -0,0 +1,302 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\TeamsWebhook; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\CanValidateWebhookUrlTrait; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. Microsoft Teams Incoming Webhook alerts. + * + * @since 4.1.0 + */ +class Handler implements HandlerInterface { + + /** + * Webhook URL validation trait. + * + * @since 4.9.0 + */ + use CanValidateWebhookUrlTrait; + + /** + * Whether current handler can handle provided alert. + * + * @since 4.1.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via Microsoft Teams Incoming Webhook. + * + * @since 4.1.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { + + $connections = (array) Options::init()->get( 'alert_teams_webhook', 'connections' ); + + $connections = array_unique( + array_filter( + $connections, + function ( $connection ) { + return isset( $connection['webhook_url'] ) && filter_var( $connection['webhook_url'], FILTER_VALIDATE_URL ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + $result = false; + $errors = []; + + foreach ( $connections as $connection ) { + $webhook_url = $connection['webhook_url']; + + $args = [ + 'method' => 'POST', + 'timeout' => MINUTE_IN_SECONDS, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'type' => 'message', + 'attachments' => [ + [ + 'contentType' => 'application/vnd.microsoft.card.adaptive', + 'contentUrl' => null, + 'content' => [ + '$schema' => 'http://adaptivecards.io/schemas/adaptive-card.json', + 'type' => 'AdaptiveCard', + 'version' => '1.2', + 'body' => $this->get_message( $alert ), + ], + ], + ], + ] + ), + ]; + + /** + * Filters Microsoft Teams Incoming Webhook request arguments. + * + * @since 4.1.0 + * + * @param array $connection Connection settings. + * @param Alert $alert Alert object. + * + * @param array $args Microsoft Teams Incoming Webhook request arguments. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_teams_webhook_handler_handle_request_args', $args, $connection, $alert ); + + $response = $this->safe_webhook_request( $webhook_url, $args ); + + if ( $response === false ) { + $errors[] = esc_html__( 'Webhook URL points to an internal-network address and was rejected.', 'wp-mail-smtp-pro' ); + + continue; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( $response_code === 200 ) { + $result = true; + } else { + $errors[] = WP::wp_remote_get_response_error_message( $response ); + } + } + + DebugEvents::add_debug( esc_html__( 'Microsoft Teams Webhook alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) ) { + DebugEvents::add( esc_html__( 'Alert: Microsoft Teams Webhook.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + return $result; + } + + /** + * Build message array. + * + * @since 4.1.0 + * + * @param Alert $alert Alert object. + * + * @return array + * @link https://api.slack.com/messaging/interactivity + */ + private function get_message( Alert $alert ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $data = $alert->get_data(); + $site_title = get_bloginfo( 'name' ); + $settings_link = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $alert_message = ''; + + switch ( $alert->get_type() ) { + case Alerts::FAILED_EMAIL: + $alert_message = esc_html__( 'Your Site Failed to Send an Email', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_PRIMARY_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via the Primary connection, but the email was sent successfully via the Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_BACKUP_EMAIL: + $alert_message = esc_html__( 'Your Site failed to send an email via Primary and Backup connection', 'wp-mail-smtp-pro' ); + break; + + case Alerts::HARD_BOUNCED_EMAIL: + $alert_message = esc_html__( 'An email failed to be delivered', 'wp-mail-smtp-pro' ); + break; + } + + $message = [ + [ + 'type' => 'TextBlock', + 'text' => "[$site_title] $alert_message", + 'weight' => 'bolder', + 'size' => 'medium', + 'wrap' => true, + ], + [ + 'type' => 'TextBlock', + 'text' => sprintf( + "**%s**:\n\n %s", + esc_html__( 'Website URL', 'wp-mail-smtp-pro' ), + home_url() + ), + 'wrap' => true, + ], + [ + 'type' => 'TextBlock', + 'text' => sprintf( + "**%s**:\n\n %s", + esc_html__( 'To email addresses', 'wp-mail-smtp-pro' ), + $data['to_email_addresses'] + ), + 'wrap' => true, + ], + [ + 'type' => 'TextBlock', + 'text' => sprintf( + "**%s**:\n\n %s", + esc_html__( 'Subject', 'wp-mail-smtp-pro' ), + $data['subject'] + ), + 'wrap' => true, + ], + ]; + + if ( ! empty( $data['error_message'] ) ) { + $message[] = [ + 'type' => 'TextBlock', + 'text' => sprintf( + "**%s**:\n\n %s", + esc_html__( 'Error message', 'wp-mail-smtp-pro' ), + $data['error_message'] + ), + 'wrap' => true, + ]; + } + + $quick_links = []; + + if ( ! empty( $data['log_link'] ) ) { + $quick_links[] = $this->build_link( + $data['log_link'], + /* translators: %s - Email Log ID. */ + sprintf( esc_html__( 'Email Log [#%s]', 'wp-mail-smtp-pro' ), $data['log_id'] ) + ); + } + + if ( ! empty( $data['debug_event_link'] ) ) { + $quick_links[] = $this->build_link( + $data['debug_event_link'], + /* translators: %s - Debug Event ID. */ + sprintf( esc_html__( 'Debug Event [#%s]', 'wp-mail-smtp-pro' ), $data['debug_event_id'] ) + ); + } + + if ( ! empty( $quick_links ) ) { + $message[] = [ + 'type' => 'TextBlock', + 'text' => implode( ' | ', $quick_links ), + 'wrap' => true, + ]; + } + + $message[] = [ + 'type' => 'TextBlock', + 'text' => $this->build_link( + $settings_link, + esc_html__( 'WP Mail SMTP settings', 'wp-mail-smtp-pro' ) + ), + 'wrap' => true, + ]; + + $message[] = [ + 'type' => 'TextBlock', + 'text' => sprintf( + /* translators: %s - Troubleshooting guide link. */ + esc_html__( "Need more help?\n %s.", 'wp-mail-smtp-pro' ), + $this->build_link( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/how-to-troubleshoot-wp-mail-smtp', + [ + 'medium' => 'Microsoft Teams Alerts Notification', + 'content' => 'Read Our Troubleshooting Guide', + ] + ), + esc_html__( 'Read our troubleshooting guide', 'wp-mail-smtp-pro' ) + ) + ), + 'wrap' => true, + ]; + + return $message; + } + + /** + * Build link. + * + * @since 4.1.0 + * + * @param string $url Link URL. + * @param string $text Link text. + * + * @return string + */ + private function build_link( $url, $text ) { + + return "[$text]($url)"; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Options.php new file mode 100644 index 00000000..b7040a66 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TeamsWebhook/Options.php @@ -0,0 +1,139 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\TeamsWebhook; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. Microsoft Teams Incoming Webhook settings. + * + * @since 4.1.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 4.1.0 + * + * @var string + */ + const SLUG = 'teams_webhook'; + + /** + * Constructor. + * + * @since 4.1.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'Paste in the Microsoft Teams webhook URL you’d like to use to receive alerts when email sending fails. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up Microsoft Teams alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#msteams', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Microsoft Teams Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'Microsoft Teams', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Webhook', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 4.1.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'webhook_url' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 4.1.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $webhook_url = isset( $connection['webhook_url'] ) ? $connection['webhook_url'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-webhook-url-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Webhook URL', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][webhook_url]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-webhook-url-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $webhook_url ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_TEAMS_WEBHOOK_URL' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Handler.php new file mode 100644 index 00000000..53f61d1d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Handler.php @@ -0,0 +1,222 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\TwilioSMS; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. Twilio SMS alerts. + * + * @since 3.5.0 + */ +class Handler implements HandlerInterface { + + /** + * SMS message max length. + * + * @since 3.5.0 + * + * @var int + */ + const MESSAGE_MAX_LENGTH = 160; + + /** + * Site title max length. + * + * @since 3.5.0 + * + * @var int + */ + const SITE_TITLE_MAX_LENGTH = 15; + + /** + * Whether current handler can handle provided alert. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via Twilio SMS. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { + + $connections = (array) Options::init()->get( 'alert_twilio_sms', 'connections' ); + + $connections = array_unique( + array_filter( + $connections, + function ( $connection ) { + return ! empty( $connection['account_sid'] ) && + ! empty( $connection['auth_token'] ) && + ! empty( $connection['from_phone_number'] ) && + ! empty( $connection['to_phone_number'] ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + $result = false; + $errors = []; + + foreach ( $connections as $connection ) { + $account_sid = $connection['account_sid']; + $auth_token = $connection['auth_token']; + $from_phone_number = $connection['from_phone_number']; + $to_phone_number = $connection['to_phone_number']; + + $url = "https://api.twilio.com/2010-04-01/Accounts/$account_sid/Messages.json"; + + $args = [ + 'headers' => [ + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'Authorization' => 'Basic ' . base64_encode( "$account_sid:$auth_token" ), + ], + 'body' => [ + 'Body' => $this->get_message( $alert ), + 'From' => $from_phone_number, + 'To' => $to_phone_number, + ], + ]; + + /** + * Filters Twilio SMS request arguments. + * + * @since 3.5.0 + * + * @param array $args Twilio SMS request arguments. + * @param array $connection Connection settings. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_twilio_sms_handler_handle_request_args', $args, $connection, $alert ); + + $response = wp_remote_post( $url, $args ); + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( $response_code === 201 ) { + $result = true; + } else { + $errors[] = WP::wp_remote_get_response_error_message( $response ); + } + } + + DebugEvents::add_debug( esc_html__( 'Twilio SMS alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) && DebugEvents::is_debug_enabled() ) { + DebugEvents::add( esc_html__( 'Alert: Twilio SMS.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + return $result; + } + + /** + * Build message. + * + * @since 3.5.0 + * + * @param Alert $alert Alert object. + * + * @return string + */ + private function get_message( Alert $alert ) { + + $data = $alert->get_data(); + + // Truncate site title. + $site_title = $this->truncate_string( get_bloginfo( 'name' ), self::SITE_TITLE_MAX_LENGTH ); + + $message[] = sprintf( '[%s]', $site_title ); + + switch ( $alert->get_type() ) { + case Alerts::FAILED_EMAIL: + $message[] = esc_html__( 'Your Site Failed to Send an Email.', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_PRIMARY_EMAIL: + $message[] = esc_html__( 'Your Site failed to send an email via the Primary connection, but the email was sent successfully via the Backup connection.', 'wp-mail-smtp-pro' ); + break; + + case Alerts::FAILED_BACKUP_EMAIL: + $message[] = esc_html__( 'Your Site failed to send an email via Primary and Backup connection.', 'wp-mail-smtp-pro' ); + break; + + case Alerts::HARD_BOUNCED_EMAIL: + $message[] = esc_html__( 'An email failed to be delivered', 'wp-mail-smtp-pro' ); + break; + } + + $subject_placeholder = '{{subject}}'; + + $message[] = sprintf( + /* translators: %s - email subject. */ + esc_html__( 'Subject: “%s”.', 'wp-mail-smtp-pro' ), + $subject_placeholder + ); + + if ( ! empty( $data['log_id'] ) ) { + $message[] = sprintf( + /* translators: %s - Email Log ID. */ + esc_html__( 'Email Log [#%s].', 'wp-mail-smtp-pro' ), + $data['log_id'] + ); + } + + $message = implode( ' ', $message ); + + // Truncate subject to all available space. + $subject_max_length = self::MESSAGE_MAX_LENGTH - ( strlen( $message ) - strlen( $subject_placeholder ) ); + $subject = $this->truncate_string( $data['subject'], $subject_max_length ); + + $message = str_replace( $subject_placeholder, $subject, $message ); + + // Truncate the final message to make sure that it's not exceeded the max length limit. + return $this->truncate_string( $message, self::MESSAGE_MAX_LENGTH ); + } + + /** + * Truncate string. + * + * @since 3.5.0 + * + * @param string $string Input string. + * @param int $length String max length. + * + * @return string + */ + private function truncate_string( $string, $length ) { + + return substr( $string, 0, $length ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Options.php new file mode 100644 index 00000000..804e29db --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/TwilioSMS/Options.php @@ -0,0 +1,235 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\TwilioSMS; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. Twilio SMS settings. + * + * @since 3.5.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 3.5.0 + * + * @var string + */ + const SLUG = 'twilio_sms'; + + /** + * Constructor. + * + * @since 3.5.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %s - Documentation link. */ + __( 'To receive SMS alerts, you’ll need a Twilio account. <a href="%s" target="_blank" rel="noopener noreferrer">Read our documentation</a> to learn how to set up Twilio SMS, then enter your connection details below.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#sms', + [ + 'medium' => 'Alerts Settings', + 'content' => 'Twilio Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'SMS via Twilio', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Account', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 3.5.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'account_sid' => '', + 'auth_token' => '', + 'from_phone_number' => '', + 'to_phone_number' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 3.5.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $account_sid = isset( $connection['account_sid'] ) ? $connection['account_sid'] : ''; + $auth_token = isset( $connection['auth_token'] ) ? $connection['auth_token'] : ''; + $from_phone_number = isset( $connection['from_phone_number'] ) ? $connection['from_phone_number'] : ''; + $to_phone_number = isset( $connection['to_phone_number'] ) ? $connection['to_phone_number'] : ''; + + $is_enabled = $this->options->get( $group, 'enabled' ); + $is_connections_const_defined = $this->options->is_const_defined( $group, 'connections' ); + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-account-sid-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Twilio Account ID', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][account_sid]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-account-sid-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $account_sid ), + disabled( true, $is_connections_const_defined, false ), + $is_enabled ? 'required' : '' + ); + ?> + + <?php if ( $is_connections_const_defined ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_TWILIO_SMS_ACCOUNT_SID' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-auth-token-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Twilio Auth Token', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][auth_token]" type="password" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-auth-token-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $auth_token ), + disabled( true, $is_connections_const_defined, false ), + $is_enabled ? 'required' : '' + ); + ?> + + <?php if ( $is_connections_const_defined ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_TWILIO_SMS_AUTH_TOKEN' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-from-phone-number-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'From Phone Number', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][from_phone_number]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-from-phone-number-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $from_phone_number ), + disabled( true, $is_connections_const_defined, false ), + $is_enabled ? 'required' : '' + ); + ?> + + <?php if ( $is_connections_const_defined ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_TWILIO_SMS_FROM_PHONE_NUMBER' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-to-phone-number-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'To Phone Number', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][to_phone_number]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-to-phone-number-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $to_phone_number ), + disabled( true, $is_connections_const_defined, false ), + $is_enabled ? 'required' : '' + ); + ?> + + <?php if ( $is_connections_const_defined ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_TWILIO_SMS_TO_PHONE_NUMBER' ); + ?> + </p> + <?php endif; ?> + </div> + </div> + </div> + <?php + + return ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Handler.php new file mode 100644 index 00000000..6153573f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Handler.php @@ -0,0 +1,784 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\WhatsApp; + +use WPMailSMTP\Admin\DebugEvents\DebugEvents; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Alerts\Alert; +use WPMailSMTP\Pro\Alerts\Alerts; +use WPMailSMTP\Pro\Alerts\Handlers\HandlerInterface; +use WPMailSMTP\WP; + +/** + * Class Handler. WhatsApp alerts. + * + * @since 4.5.0 + */ +class Handler implements HandlerInterface { + + /** + * WhatsApp Cloud API base URL. + * + * @since 4.5.0 + * + * @var string + */ + const API_URL = 'https://graph.facebook.com/v22.0/'; + + /** + * WhatsApp message type for template messages. + * + * @since 4.5.0 + * + * @var string + */ + const MESSAGE_TYPE = 'template'; + + /** + * WhatsApp template name for alerts. + * + * @since 4.5.0 + * + * @var string + */ + const TEMPLATE_NAME = 'wp_mail_smtp_alert'; + + /** + * Transient name for caching template status. + * + * @since 4.5.0 + * + * @var string + */ + const TEMPLATE_STATUS_TRANSIENT = 'wp_mail_smtp_whatsapp_template_status'; + + /** + * Whether current handler can handle provided alert. + * + * @since 4.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function can_handle( Alert $alert ) { + + return in_array( + $alert->get_type(), + [ + Alerts::FAILED_EMAIL, + Alerts::FAILED_PRIMARY_EMAIL, + Alerts::FAILED_BACKUP_EMAIL, + Alerts::HARD_BOUNCED_EMAIL, + ], + true + ); + } + + /** + * Handle alert. + * Send alert notification via WhatsApp Cloud API. + * + * @since 4.5.0 + * + * @param Alert $alert Alert object. + * + * @return bool + */ + public function handle( Alert $alert ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $connections = (array) Options::init()->get( 'alert_whatsapp', 'connections' ); + $connections = array_unique( + array_filter( + $connections, + function ( $connection ) { + return ! empty( $connection['access_token'] ) && + ! empty( $connection['phone_number_id'] ) && + ! empty( $connection['to_phone_number'] ) && + ! empty( $connection['whatsapp_business_id'] ); + } + ), + SORT_REGULAR + ); + + if ( empty( $connections ) ) { + return false; + } + + $result = false; + $errors = []; + + foreach ( $connections as $connection ) { + // Check template status before sending - don't send if template is not approved. + $template_status = $this->check_template_status( $connection ); + + if ( empty( $template_status['exists'] ) || $template_status['status'] !== 'APPROVED' ) { + // Template doesn't exist or is not approved. + if ( empty( $template_status['exists'] ) ) { + // Template not found. + $errors[] = esc_html__( 'WhatsApp alert not sent: Template not found.', 'wp-mail-smtp-pro' ); + } else { + // Template exists but not approved. + $status_msg = sprintf( + /* translators: %s - Template status (e.g., PENDING, REJECTED). */ + esc_html__( 'Template status is %s.', 'wp-mail-smtp-pro' ), + $template_status['status'] + ); + $errors[] = esc_html__( 'WhatsApp alert not sent: ', 'wp-mail-smtp-pro' ) . $status_msg; + } + + // Skip to the next connection. + continue; + } + + // Template is approved, proceed with sending the alert. + $access_token = $connection['access_token']; + $phone_number_id = $connection['phone_number_id']; + $to_phone_number = $connection['to_phone_number']; + + // Format to WhatsApp number format (remove any non-numeric characters). + $to_phone_number = preg_replace( '/[^0-9]/', '', $to_phone_number ); + + $message_data = $this->get_message_data( $alert, $to_phone_number, $connection ); + + $args = [ + 'timeout' => MINUTE_IN_SECONDS, + 'headers' => [ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $access_token, + ], + 'body' => wp_json_encode( $message_data ), + ]; + + /** + * Filters WhatsApp API request arguments. + * + * @since 4.5.0 + * + * @param array $args WhatsApp API request arguments. + * @param array $connection Connection settings. + * @param Alert $alert Alert object. + */ + $args = apply_filters( 'wp_mail_smtp_pro_alerts_providers_whats_app_handler_handle_request_args', $args, $connection, $alert ); + + $endpoint = self::API_URL . $phone_number_id . '/messages'; + $response = wp_remote_post( $endpoint, $args ); + $response_code = wp_remote_retrieve_response_code( $response ); + + // 200 OK response means success. + if ( $response_code === 200 ) { + $result = true; + } else { + $errors[] = WP::wp_remote_get_response_error_message( $response ); + } + } + + DebugEvents::add_debug( esc_html__( 'WhatsApp alert request was sent.', 'wp-mail-smtp-pro' ) ); + + if ( ! empty( $errors ) && DebugEvents::is_debug_enabled() ) { + DebugEvents::add( esc_html__( 'Alert: WhatsApp.', 'wp-mail-smtp-pro' ) . WP::EOL . implode( WP::EOL, array_unique( $errors ) ) ); + } + + return $result; + } + + /** + * Build message data for WhatsApp Cloud API. + * + * @since 4.5.0 + * + * @link https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages + * + * @param Alert $alert Alert object. + * @param string $to_phone_number Recipient phone number. + * @param array $connection Connection settings. + * + * @return array + */ + private function get_message_data( Alert $alert, $to_phone_number, $connection = [] ) { + + $data = $alert->get_data(); + $settings_link = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $language_code = $this->get_language_code( $connection ); + + // Build message for template parameters. + $template_components = [ + 'components' => [ + [ + 'type' => 'body', + 'parameters' => [ + [ + 'type' => 'text', + 'text' => isset( $data['website_url'] ) ? esc_url_raw( $data['website_url'] ) : site_url(), // Website URL parameter. + ], + [ + 'type' => 'text', + 'text' => isset( $data['to_email_addresses'] ) ? $data['to_email_addresses'] : esc_html__( 'Unknown recipient', 'wp-mail-smtp-pro' ), // Recipient parameter. + ], + [ + 'type' => 'text', + 'text' => $data['subject'], // Subject parameter. + ], + [ + 'type' => 'text', + 'text' => ! empty( $data['error_message'] ) ? sanitize_text_field( $data['error_message'] ) : esc_html__( 'No error message provided', 'wp-mail-smtp-pro' ), // Error message parameter. + ], + [ + 'type' => 'text', + 'text' => esc_url_raw( $settings_link ), // Settings URL parameter. + ], + ], + ], + ], + ]; + + // Prepare template message data. + $message_data = [ + 'messaging_product' => 'whatsapp', + 'to' => $to_phone_number, + 'type' => self::MESSAGE_TYPE, + 'template' => array_merge( + [ + 'name' => $this->get_template_name(), + 'language' => [ + 'code' => $language_code, + ], + ], + $template_components + ), + ]; + + return $message_data; + } + + /** + * Check template status for a WhatsApp connection. + * + * @since 4.5.0 + * + * @param array $connection Connection settings with access_token and phone_number_id. + * @param bool $force Whether to force a fresh check bypassing cache. Default false. + * + * @return array Template status information. + */ + public function check_template_status( $connection, $force = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $status = [ + 'exists' => false, + 'status' => '', + 'error' => '', + ]; + + if ( empty( $connection['access_token'] ) || empty( $connection['phone_number_id'] ) || empty( $connection['whatsapp_business_id'] ) ) { + $status['error'] = esc_html__( 'Missing connection details', 'wp-mail-smtp-pro' ); + + return $status; + } + + $transient_key = $this->get_template_status_cache_key( $connection ); + + // Check if we have a cached result and we're not forcing a fresh check. + if ( ! $force ) { + $cached_status = get_transient( $transient_key ); + + if ( $cached_status !== false ) { + return $cached_status; + } + } + + $waba_id = $connection['whatsapp_business_id']; + + // Direct check for template status using the WABA ID. + $api_url = self::API_URL . $waba_id . '/message_templates?fields=name,status,language&name=' . $this->get_template_name(); + + $response = wp_remote_get( + $api_url, + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $connection['access_token'], + ], + ] + ); + + if ( is_wp_error( $response ) ) { + $status['error'] = $response->get_error_message(); + + DebugEvents::add( + esc_html__( 'Alert: WhatsApp.', 'wp-mail-smtp-pro' ) . WP::EOL . + sprintf( + /* translators: %s: Error message from WhatsApp API request. */ + esc_html__( 'WhatsApp template status check failed: %s', 'wp-mail-smtp-pro' ), + $response->get_error_message() + ) + ); + + return $status; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( $response_code !== 200 ) { + $status['error'] = WP::wp_remote_get_response_error_message( $response ); + + DebugEvents::add( + esc_html__( 'Alert: WhatsApp.', 'wp-mail-smtp-pro' ) . WP::EOL . + sprintf( + /* translators: %s: Error message from WhatsApp API request. */ + esc_html__( 'WhatsApp template status check failed: %s', 'wp-mail-smtp-pro' ), + $status['error'] + ) + ); + + return $status; + } + + $template_data = json_decode( wp_remote_retrieve_body( $response ), true ); + $cache_duration = 2 * MINUTE_IN_SECONDS; + $expected_language = $this->get_language_code( $connection ); + + // Check for our template in the retrieved data. + if ( ! empty( $template_data['data'] ) && is_array( $template_data['data'] ) ) { + $status = $this->find_template_by_language( $template_data['data'], $expected_language, $status ); + } + + // If no template exists for the current language, create one. + if ( ! $status['exists'] ) { + $response = $this->create_template( $connection ); + + if ( $response['success'] ) { + $status['exists'] = true; + $status['status'] = $response['template']['status']; + } else { + $status['exists'] = false; + $status['status'] = 'NOT_FOUND'; + } + + set_transient( $transient_key, $status, $cache_duration ); + + return $status; + } + + // Use longer cache duration (1 hour) for all statuses except pending ones. + if ( $status['exists'] && $status['status'] !== 'PENDING' && $status['status'] !== 'IN_APPEAL' ) { + $cache_duration = HOUR_IN_SECONDS; + } + + set_transient( $transient_key, $status, $cache_duration ); + + return $status; + } + + /** + * Create a WhatsApp message template for alerts. + * + * When a template is successfully created, the template_language should be saved + * to the connection settings to ensure all future API calls use the same language. + * + * @since 4.5.0 + * + * @param array $connection Connection settings with access_token and whatsapp_business_id. + * + * @return array Template creation response with success status and details. + */ + public function create_template( $connection ) { + + // Check if we have the required fields. + if ( empty( $connection['access_token'] ) || empty( $connection['whatsapp_business_id'] ) ) { + return [ + 'success' => false, + 'error' => esc_html__( 'Missing connection details', 'wp-mail-smtp-pro' ), + ]; + } + + /** + * Filter the template data before creating a WhatsApp template. + * + * @since 4.5.0 + * + * @param array $template_data WhatsApp template data. + */ + $template_data = apply_filters( + 'wp_mail_smtp_pro_alerts_providers_whats_app_handler_template_data', + [ + 'name' => $this->get_template_name(), + 'category' => 'UTILITY', + 'language' => $this->get_language_code( $connection ), + 'components' => [ + [ + 'type' => 'HEADER', + 'format' => 'TEXT', + 'text' => esc_html__( 'Your Site Failed to Send an Email', 'wp-mail-smtp-pro' ), + ], + [ + 'type' => 'BODY', + 'text' => '*' . esc_html__( 'Website', 'wp-mail-smtp-pro' ) . ':* {{1}}' . "\n\n" . + '*' . esc_html__( 'To email', 'wp-mail-smtp-pro' ) . ':* {{2}}' . "\n\n" . + '*' . esc_html__( 'Subject', 'wp-mail-smtp-pro' ) . ':* {{3}}' . "\n\n" . + '*' . esc_html__( 'Error message', 'wp-mail-smtp-pro' ) . ':* {{4}}' . "\n\n" . + '*' . esc_html__( 'Check your WP Mail SMTP settings', 'wp-mail-smtp-pro' ) . ':* {{5}}' . "\n\n" . + esc_html__( 'Need more help? Read our troubleshooting guide', 'wp-mail-smtp-pro' ) . ': https://wpmailsmtp.com/docs/troubleshooting', + 'example' => [ + 'body_text' => [ + [ + 'https://example.com', + 'pattie@example.com', + 'WP Mail SMTP Alerts Test', + 'This is a test error message triggered from the WP Mail SMTP Alerts settings', + 'https://example.com/wp-admin/admin.php?page=wp-mail-smtp', + ], + ], + ], + ], + [ + 'type' => 'FOOTER', + 'text' => esc_html__( 'Powered by WP Mail SMTP', 'wp-mail-smtp-pro' ), + ], + ], + ] + ); + + // API endpoint for creating templates. + $waba_id = $connection['whatsapp_business_id']; + $api_url = self::API_URL . $waba_id . '/message_templates'; + + // Make the API request. + $response = wp_remote_post( + $api_url, + [ + 'headers' => [ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $connection['access_token'], + ], + 'body' => wp_json_encode( $template_data ), + 'timeout' => MINUTE_IN_SECONDS, + ] + ); + + // Handle the response. + if ( is_wp_error( $response ) ) { + DebugEvents::add( + esc_html__( 'Alert: WhatsApp.', 'wp-mail-smtp-pro' ) . WP::EOL . + sprintf( + /* translators: %s: Error message from WhatsApp API request. */ + esc_html__( 'WhatsApp template creation failed: %s', 'wp-mail-smtp-pro' ), + $response->get_error_message() + ) + ); + + return [ + 'success' => false, + 'error' => $response->get_error_message(), + ]; + } + + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( $response_code !== 200 ) { + $error_message = WP::wp_remote_get_response_error_message( $response ); + + DebugEvents::add( + esc_html__( 'Alert: WhatsApp.', 'wp-mail-smtp-pro' ) . WP::EOL . + sprintf( + /* translators: %s: Error message from WhatsApp API request. */ + esc_html__( 'WhatsApp template creation failed: %s', 'wp-mail-smtp-pro' ), + $error_message + ) + ); + + return [ + 'success' => false, + 'error' => $error_message, + 'code' => $response_code, + ]; + } + + delete_transient( $this->get_template_status_cache_key( $connection ) ); + + return [ + 'success' => true, + 'template' => $response_body, + 'template_language' => $this->get_language_code( $connection ), + ]; + } + + /** + * Find template by language in the retrieved template data. + * + * @since 4.5.0 + * + * @param array $templates_data Array of template data from API. + * @param string $expected_language Expected language code. + * @param array $status Current status array to update. + * + * @return array Updated status array. + */ + private function find_template_by_language( $templates_data, $expected_language, $status ) { + + foreach ( $templates_data as $template ) { + if ( isset( $template['name'] ) && $template['name'] === $this->get_template_name() ) { + // Check if language matches (if language field is available). + $template_language = isset( $template['language'] ) ? $template['language'] : $expected_language; + + if ( $template_language === $expected_language ) { + $status['exists'] = true; + $status['status'] = $template['status']; + + break; + } + } + } + + return $status; + } + + /** + * Get the template name with filter applied. + * + * @since 4.5.0 + * + * @return string Template name. + */ + private function get_template_name() { + + /** + * Filter the WhatsApp template name. + * + * @since 4.5.0 + * + * @param string $template_name The template name to use for WhatsApp alerts. + */ + return apply_filters( 'wp_mail_smtp_pro_alerts_providers_whats_app_handler_template_name', self::TEMPLATE_NAME ); + } + + /** + * Get the cache key for the template status. + * + * @since 4.5.0 + * + * @param array $connection Connection settings. + * + * @return string Cache key. + */ + private function get_template_status_cache_key( $connection ) { + + $cache_key = $connection['whatsapp_business_id'] . '_' . $this->get_language_code( $connection ); + + return self::TEMPLATE_STATUS_TRANSIENT . '_' . $cache_key; + } + + /** + * Get the language code for the template. + * + * @since 4.5.0 + * + * @param array $connection Optional. Connection settings to get stored language from. + * + * @return string Language code. + */ + private function get_language_code( $connection = [] ) { + + // If we have a stored template language in the connection, use it. + if ( ! empty( $connection['template_language'] ) ) { + return $connection['template_language']; + } + + // Otherwise, determine language from user locale (for initial setup). + $wp_locale = get_user_locale(); + $language_code = 'en_US'; + + $language_mapper = [ + 'af' => 'af', + 'sq' => 'sq', + 'ar' => 'ar', + 'ar_EG' => 'ar_EG', + 'ar_AE' => 'ar_AE', + 'ar_LB' => 'ar_LB', + 'ar_MA' => 'ar_MA', + 'ar_QA' => 'ar_QA', + 'az' => 'az', + 'be_BY' => 'be_BY', + 'bn' => 'bn', + 'bn_IN' => 'bn_IN', + 'bg' => 'bg', + 'ca' => 'ca', + 'zh_CN' => 'zh_CN', + 'zh_HK' => 'zh_HK', + 'zh_TW' => 'zh_TW', + 'hr' => 'hr', + 'cs' => 'cs', + 'da' => 'da', + 'prs_AF' => 'prs_AF', + 'nl' => 'nl', + 'nl_BE' => 'nl_BE', + 'en' => 'en', + 'en_GB' => 'en_GB', + 'en_US' => 'en_US', + 'en_AE' => 'en_AE', + 'en_AU' => 'en_AU', + 'en_CA' => 'en_CA', + 'en_GH' => 'en_GH', + 'en_IE' => 'en_IE', + 'en_IN' => 'en_IN', + 'en_JM' => 'en_JM', + 'en_MY' => 'en_MY', + 'en_NZ' => 'en_NZ', + 'en_QA' => 'en_QA', + 'en_SG' => 'en_SG', + 'en_UG' => 'en_UG', + 'en_ZA' => 'en_ZA', + 'et' => 'et', + 'fil' => 'fil', + 'fi' => 'fi', + 'fr' => 'fr', + 'fr_BE' => 'fr_BE', + 'fr_CA' => 'fr_CA', + 'fr_CH' => 'fr_CH', + 'fr_CI' => 'fr_CI', + 'fr_MA' => 'fr_MA', + 'ka' => 'ka', + 'de' => 'de', + 'de_AT' => 'de_AT', + 'de_CH' => 'de_CH', + 'el' => 'el', + 'gu' => 'gu', + 'ha' => 'ha', + 'he' => 'he', + 'hi' => 'hi', + 'hu' => 'hu', + 'id' => 'id', + 'ga' => 'ga', + 'it' => 'it', + 'ja' => 'ja', + 'kn' => 'kn', + 'kk' => 'kk', + 'rw_RW' => 'rw_RW', + 'ko' => 'ko', + 'ky_KG' => 'ky_KG', + 'lo' => 'lo', + 'lv' => 'lv', + 'lt' => 'lt', + 'mk' => 'mk', + 'ms' => 'ms', + 'ml' => 'ml', + 'mr' => 'mr', + 'nb' => 'nb', + 'ps_AF' => 'ps_AF', + 'fa' => 'fa', + 'pl' => 'pl', + 'pt_BR' => 'pt_BR', + 'pt_PT' => 'pt_PT', + 'pa' => 'pa', + 'ro' => 'ro', + 'ru' => 'ru', + 'sr' => 'sr', + 'si_LK' => 'si_LK', + 'sk' => 'sk', + 'sl' => 'sl', + 'es' => 'es', + 'es_AR' => 'es_AR', + 'es_CL' => 'es_CL', + 'es_CO' => 'es_CO', + 'es_CR' => 'es_CR', + 'es_DO' => 'es_DO', + 'es_EC' => 'es_EC', + 'es_HN' => 'es_HN', + 'es_MX' => 'es_MX', + 'es_PA' => 'es_PA', + 'es_PE' => 'es_PE', + 'es_ES' => 'es_ES', + 'es_UY' => 'es_UY', + 'sw' => 'sw', + 'sv' => 'sv', + 'ta' => 'ta', + 'te' => 'te', + 'th' => 'th', + 'tr' => 'tr', + 'uk' => 'uk', + 'ur' => 'ur', + 'uz' => 'uz', + 'vi' => 'vi', + 'zu' => 'zu', + 'ar_SA' => 'ar', + 'az_AZ' => 'az', + 'bg_BG' => 'bg', + 'bn_BD' => 'bn', + 'bs_BA' => 'sr', + 'ca_ES' => 'ca', + 'cs_CZ' => 'cs', + 'da_DK' => 'da', + 'de_DE' => 'de', + 'el_GR' => 'el', + 'fa_IR' => 'fa', + 'fi_FI' => 'fi', + 'fr_FR' => 'fr', + 'gu_IN' => 'gu', + 'he_IL' => 'he', + 'hi_IN' => 'hi', + 'hr_HR' => 'hr', + 'hu_HU' => 'hu', + 'id_ID' => 'id', + 'is_IS' => 'en', + 'it_IT' => 'it', + 'ja_JP' => 'ja', + 'ko_KR' => 'ko', + 'lt_LT' => 'lt', + 'lv_LV' => 'lv', + 'mk_MK' => 'mk', + 'mr_IN' => 'mr', + 'ms_MY' => 'ms', + 'nb_NO' => 'nb', + 'nl_NL' => 'nl', + 'nn_NO' => 'nb', + 'pl_PL' => 'pl', + 'ro_RO' => 'ro', + 'ru_RU' => 'ru', + 'sk_SK' => 'sk', + 'sl_SI' => 'sl', + 'sq_AL' => 'sq', + 'sr_RS' => 'sr', + 'sv_SE' => 'sv', + 'ta_IN' => 'ta', + 'th_TH' => 'th', + 'tr_TR' => 'tr', + 'uk_UA' => 'uk', + 'vi_VN' => 'vi', + ]; + + if ( isset( $language_mapper[ $wp_locale ] ) ) { + $language_code = $language_mapper[ $wp_locale ]; + } else { + $language_part = substr( $wp_locale, 0, 2 ); + + if ( isset( $language_mapper[ $language_part ] ) ) { + $language_code = $language_mapper[ $language_part ]; + } + } + + /** + * Filter the language code used for WhatsApp templates. + * + * @since 4.5.0 + * + * @param string $language_code Language code to use for the WhatsApp template. + * @param string $wp_locale Current WordPress locale. + */ + return apply_filters( 'wp_mail_smtp_pro_alerts_providers_whats_app_handler_language_code', $language_code, $wp_locale ); + } + + /** + * Get the user's template language for new connections. + * + * This method is used when creating new WhatsApp connections to determine + * what language should be stored for the template. + * + * @since 4.5.0 + * + * @return string Language code based on current user locale. + */ + public function get_user_template_language() { + + // Use the same logic as get_language_code but without connection parameter. + return $this->get_language_code(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Options.php new file mode 100644 index 00000000..016eca00 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Options.php @@ -0,0 +1,407 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\WhatsApp; + +use WPMailSMTP\Pro\Alerts\AbstractOptions; + +/** + * Class Options. WhatsApp alert settings. + * + * @since 4.5.0 + */ +class Options extends AbstractOptions { + + /** + * Provider slug. + * + * @since 4.5.0 + * + * @var string + */ + const SLUG = 'whatsapp'; + + /** + * Constructor. + * + * @since 4.5.0 + */ + public function __construct() { + + $description = wp_kses( + sprintf( + /* translators: %1$s - Documentation link. */ + __( 'Enter your WhatsApp Cloud API credentials to receive alerts when email sending fails. You\'ll need to create a Meta developer account, set up a WhatsApp Business Platform account, and register the "wp_mail_smtp_alert" template in the Meta developer portal. <a href="%1$s" target="_blank" rel="noopener noreferrer">Read our documentation on setting up WhatsApp alerts</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-email-alerts/#whatsapp', + [ + 'medium' => 'Alerts Settings', + 'content' => 'WhatsApp Documentation', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + + parent::__construct( + [ + 'slug' => self::SLUG, + 'title' => esc_html__( 'WhatsApp', 'wp-mail-smtp-pro' ), + 'description' => $description, + 'add_connection_text' => esc_html__( 'Add Another Connection', 'wp-mail-smtp-pro' ), + ] + ); + } + + /** + * Output the provider options. + * + * @since 4.5.0 + */ + public function display_options() { + + $connections = $this->options->get( $this->get_group(), 'connections' ); + + if ( empty( $connections ) ) { + $connections = [ + [ + 'access_token' => '', + 'phone_number_id' => '', + 'to_phone_number' => '', + 'whatsapp_business_id' => '', + ], + ]; + } + + foreach ( $connections as $i => $connection ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->get_connection_options( $connection, $i ); + } + } + + /** + * Get single connection options. + * + * @since 4.5.0 + * + * @param array $connection Connection settings. + * @param string $i Connection index. + * + * @return string + */ + public function get_connection_options( $connection, $i ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $slug = $this->get_slug(); + $group = $this->get_group(); + + $access_token = isset( $connection['access_token'] ) ? $connection['access_token'] : ''; + $phone_number_id = isset( $connection['phone_number_id'] ) ? $connection['phone_number_id'] : ''; + $to_phone_number = isset( $connection['to_phone_number'] ) ? $connection['to_phone_number'] : ''; + $whatsapp_business_id = isset( $connection['whatsapp_business_id'] ) ? $connection['whatsapp_business_id'] : ''; + + ob_start(); + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-alert-connection-options"> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-access-token-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Access Token', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][access_token]" type="password" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-access-token-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $access_token ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_WHATSAPP_ACCESS_TOKEN' ); + ?> + </p> + <?php endif; ?> + <p class="desc"> + <?php + printf( + /* translators: %s - URL to Facebook Developers page. */ + esc_html__( 'Your permanent access token with WhatsApp Business Messaging permission. Generate it in the %1$sMeta App Dashboard%2$s under "User Token Generator" or "System User" section.', 'wp-mail-smtp-pro' ), + '<a href="https://developers.facebook.com/apps/" target="_blank" rel="noopener noreferrer">', + '</a>' + ); + ?> + </p> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-whatsapp-business-id-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'WhatsApp Business Account ID', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][whatsapp_business_id]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-whatsapp-business-id-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $whatsapp_business_id ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_WHATSAPP_BUSINESS_ID' ); + ?> + </p> + <?php endif; ?> + <p class="desc"> + <?php + printf( + /* translators: %1$s - opening link tag, %2$s - closing link tag. */ + esc_html__( 'Your WhatsApp Business Account ID from the Meta Business Platform. Find it in the %1$sWhatsApp Accounts section%2$s of your Meta Business Manager.', 'wp-mail-smtp-pro' ), + '<a href="https://business.facebook.com/settings/whatsapp-business-accounts/" target="_blank" rel="noopener noreferrer">', + '</a>' + ); + ?> + </p> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-phone-number-id-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'Phone Number ID', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][phone_number_id]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-phone-number-id-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $phone_number_id ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_WHATSAPP_PHONE_NUMBER_ID' ); + ?> + </p> + <?php endif; ?> + <p class="desc"> + <?php + printf( + /* translators: %1$s - opening link tag, %2$s - closing link tag. */ + esc_html__( 'Your business phone number ID from the WhatsApp Cloud API. This is a numeric value found in the %1$sWhatsApp Manager → API Setup screen%2$s when you click on your business phone number.', 'wp-mail-smtp-pro' ), + '<a href="https://business.facebook.com/wa/manage/phone-numbers/" target="_blank" rel="noopener noreferrer">', + '</a>' + ); + ?> + </p> + </div> + </div> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-to-phone-number-<?php echo esc_attr( $i ); ?>"> + <?php esc_html_e( 'To Phone Number', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + printf( + '<input name="wp-mail-smtp[alert_%1$s][connections][%2$s][to_phone_number]" type="text" value="%3$s" id="wp-mail-smtp-setting-alert-%1$s-to-phone-number-%2$s" spellcheck="false" %4$s %5$s/>', + esc_attr( $slug ), + esc_attr( $i ), + esc_attr( $to_phone_number ), + disabled( true, $this->options->is_const_defined( $group, 'connections' ), false ), + $this->options->get( $group, 'enabled' ) ? 'required' : '' + ); + ?> + + <?php if ( $this->options->is_const_defined( $group, 'connections' ) ) : ?> + <p class="desc"> + <?php + //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $this->options->get_const_set_message( 'WPMS_ALERT_WHATSAPP_TO_PHONE_NUMBER' ); + ?> + </p> + <?php endif; ?> + <p class="desc"> + <?php esc_html_e( 'The phone number where you want to receive alerts. Should be numeric only without any spaces or special characters (e.g., 12345678901).', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + </div> + + <?php if ( ! empty( $connection['access_token'] ) && ! empty( $connection['phone_number_id'] ) && ! empty( $connection['whatsapp_business_id'] ) ) : ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-text" style="min-height: 47px;"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-alert-<?php echo esc_attr( $slug ); ?>-template-status-<?php echo esc_attr( $i ); ?>" class="wp-mail-smtp-setting-label-with-tooltip"> + <?php esc_html_e( 'WhatsApp Message Template Status', 'wp-mail-smtp-pro' ); ?> + + <span class="wp-mail-smtp-tooltip wp-mail-smtp-tooltip-with-icon"> + <img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/font-awesome/info-circle.svg' ); ?>" width="15" height="15" alt="info"/> + <span class="wp-mail-smtp-tooltip-text wp-mail-smtp-tooltip-small-text"> + <?php esc_html_e( 'To send WhatsApp alerts, a message template must be created. We generate it automatically for you, so you don\'t need to do anything, but it must be approved by the WhatsApp team before it can be used. This review process can take up to 24–48 hours, though it\'s usually completed sooner.', 'wp-mail-smtp-pro' ); ?> + </span> + </span> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php $this->display_template_status( $connection ); ?> + </div> + </div> + <?php endif; ?> + </div> + <?php + + return ob_get_clean(); + } + + /** + * Display template status with appropriate UI. + * + * @since 4.5.0 + * + * @param array $connection WhatsApp connection settings. + * @param bool $force Whether to force a fresh check bypassing cache. Default false. + */ + public function display_template_status( $connection, $force = false ) { //phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Only check status if we have connection details. + if ( empty( $connection['access_token'] ) || empty( $connection['phone_number_id'] ) || empty( $connection['whatsapp_business_id'] ) ) { + return; + } + + $handler = new Handler(); + $template_status = $handler->check_template_status( $connection, $force ); + + if ( ! empty( $template_status['error'] ) ) { + $this->display_status_message( + esc_html__( 'Status Check Failed', 'wp-mail-smtp-pro' ), + 'cancel-red' + ); + $this->display_status_notice( + esc_html__( 'The template status check failed. Please try again later.', 'wp-mail-smtp-pro' ), + 'error' + ); + + return; + } + + if ( $template_status['exists'] ) { + $status_text = $template_status['status']; + + if ( $status_text === 'APPROVED' ) { + $this->display_status_message( + esc_html__( 'Approved', 'wp-mail-smtp-pro' ), + 'check-circle-dark-green' + ); + $this->display_recheck_status( $connection ); + } elseif ( $status_text === 'REJECTED' ) { + $this->display_status_message( + esc_html__( 'Rejected', 'wp-mail-smtp-pro' ), + 'cancel-red' + ); + $this->display_recheck_status( $connection ); + $this->display_status_notice( + esc_html__( 'The template has been rejected. Please review the template and resubmit it.', 'wp-mail-smtp-pro' ), + 'error' + ); + } elseif ( $status_text === 'PENDING' ) { + $this->display_status_message( + esc_html__( 'Pending', 'wp-mail-smtp-pro' ), + 'clock-orange', + ); + $this->display_recheck_status( $connection ); + $this->display_status_notice( + esc_html__( 'The template is pending approval. Please check back later.', 'wp-mail-smtp-pro' ), + 'warning' + ); + } else { + $this->display_status_message( $status_text ); + $this->display_recheck_status( $connection ); + } + + return; + } else { + $this->display_status_message( + esc_html__( 'Not Found', 'wp-mail-smtp-pro' ), + 'cancel-red' + ); + $this->display_recheck_status( $connection ); + } + } + + /** + * Display status message with appropriate styling and icon. + * + * @since 4.5.0 + * + * @param string $status_text The text to display for the status. + * @param string $icon_slug The slug of the icon to display. + */ + private function display_status_message( $status_text, $icon_slug = 'clock-orange' ) { + + echo '<div class="wp-mail-smtp-setting-status-label wp-mail-smtp-setting-status-label-' . esc_attr( str_replace( ' ', '-', strtolower( $status_text ) ) ) . '">'; + echo esc_html( $status_text ); + echo '<img src="' . esc_url( wp_mail_smtp()->assets_url . '/images/font-awesome/' . $icon_slug . '.svg' ) . '" alt="" /> '; + echo '</div>'; + } + + /** + * Display recheck status button. + * + * @since 4.5.0 + * + * @param array $connection WhatsApp connection settings. + */ + private function display_recheck_status( $connection ) { + + echo '<a href="#" class="wp-mail-smtp-whatsapp-recheck-status" data-connection-id="' . + esc_attr( hash( 'sha256', wp_json_encode( $connection ) ) ) . '">' . + '<span>' . esc_html__( 'Recheck Status', 'wp-mail-smtp-pro' ) . '</span>' . + '<img src="' . esc_url( wp_mail_smtp()->assets_url . '/images/font-awesome/arrow-rotate-right-purple.svg' ) . '" alt="arrow-rotate-right-purple" />' . + '</a>'; + } + + /** + * Display status notice. + * + * @since 4.5.0 + * + * @param string $message The message to display. + * @param string $notice_type The type of notice to display (error, warning, info, etc.). + */ + private function display_status_notice( $message, $notice_type = 'warning' ) { + + echo '<div class="notice inline notice-inline wp-mail-smtp-notice notice-' . esc_attr( $notice_type ) . '">'; + echo '<p>' . esc_html( $message ) . '</p>'; + echo '</div>'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Provider.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Provider.php new file mode 100644 index 00000000..6d66cfbc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Alerts/Providers/WhatsApp/Provider.php @@ -0,0 +1,167 @@ +<?php + +namespace WPMailSMTP\Pro\Alerts\Providers\WhatsApp; + +use WPMailSMTP\Options as PluginOptions; + +/** + * Class Provider. + * + * @since 4.5.0 + */ +class Provider { + + /** + * Ajax action slug. + * + * @since 4.5.0 + */ + const AJAX_ACTION = 'wp_mail_smtp_whatsapp_recheck_status'; + + /** + * Register hooks. + * + * @since 4.5.0 + * + * @return void + */ + public function hooks() { + + add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'recheck_connection_status' ] ); + add_action( 'wp_mail_smtp_pro_alerts_admin_settings_before_save', [ $this, 'maybe_create_template_on_settings_save' ] ); + } + + /** + * Recheck WhatsApp connection status via AJAX. + * + * @since 4.5.0 + */ + public function recheck_connection_status() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + check_ajax_referer( 'wp-mail-smtp-pro-admin', 'nonce' ); + + // Check nonce and capabilities. + 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-pro' ) ] + ); + } + + // Get connection data. + $connection_id = isset( $_POST['connection_id'] ) ? sanitize_text_field( wp_unslash( $_POST['connection_id'] ) ) : ''; + $options = PluginOptions::init(); + $connections = $options->get( 'alert_whatsapp', 'connections' ); + + if ( empty( $connections ) || empty( $connection_id ) ) { + wp_send_json_error( + [ 'message' => esc_html__( 'Connection not found.', 'wp-mail-smtp-pro' ) ] + ); + } + + // Find the connection. + $connection = null; + + foreach ( $connections as $conn ) { + if ( hash_equals( hash( 'sha256', wp_json_encode( $conn ) ), $connection_id ) ) { + $connection = $conn; + + break; + } + } + + if ( empty( $connection ) ) { + wp_send_json_error( + [ 'message' => esc_html__( 'Connection not found.', 'wp-mail-smtp-pro' ) ] + ); + } + + // Generate HTML for the response. + ob_start(); + + $options_instance = new Options(); + + $options_instance->display_template_status( $connection, true ); + $html = ob_get_clean(); + + wp_send_json_success( + [ + 'html' => wp_kses_post( $html ), + ] + ); + } + + /** + * Check if WhatsApp settings have changed and create template if needed. + * + * @since 4.5.0 + * + * @param array $new_options New options that will be saved. + * + * @return array + */ + public function maybe_create_template_on_settings_save( $new_options ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Get current WhatsApp options to compare. + $old_whatsapp_options = PluginOptions::init()->get_group( 'alert_whatsapp' ); + $new_whatsapp_options = isset( $new_options['alert_whatsapp'] ) ? $new_options['alert_whatsapp'] : []; + + // Check if WhatsApp options have changed or if they were previously empty. + $options_changed = $this->connections_have_changed( $old_whatsapp_options, $new_whatsapp_options ); + $was_empty = empty( $old_whatsapp_options ) || empty( $old_whatsapp_options['connections'] ); + + // Only proceed if options changed or were previously empty. + if ( ! $options_changed && ! $was_empty ) { + return $new_options; + } + + // Check if we have valid new WhatsApp alert options. + if ( empty( $new_whatsapp_options['connections'] ) ) { + return $new_options; + } + + $handler = new Handler(); + + // Process each connection. + foreach ( $new_whatsapp_options['connections'] as $index => $connection ) { + // Skip if missing required fields. + if ( empty( $connection['access_token'] ) || + empty( $connection['whatsapp_business_id'] ) || + empty( $connection['phone_number_id'] ) ) { + continue; + } + + // Add template language if not already set (for new connections). + if ( empty( $connection['template_language'] ) ) { + $new_options['alert_whatsapp']['connections'][ $index ]['template_language'] = $handler->get_user_template_language(); + } + + // Ensure template exists for this connection. + $handler->check_template_status( $new_options['alert_whatsapp']['connections'][ $index ], true ); + } + + return $new_options; + } + + /** + * Check if WhatsApp connections have changed. + * + * @since 4.5.0 + * + * @param array $old_options Old WhatsApp options. + * @param array $new_options New WhatsApp options. + * + * @return bool True if connections have changed, false otherwise. + */ + private function connections_have_changed( $old_options, $new_options ) { + + // Remove template_language from old options for comparison (since it's not in form data). + if ( ! empty( $old_options['connections'] ) ) { + foreach ( $old_options['connections'] as $index => $connection ) { + unset( $old_options['connections'][ $index ]['template_language'] ); + } + } + + // Compare the cleaned old options with the new options. + return $old_options !== $new_options; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/Admin/SettingsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/Admin/SettingsTab.php new file mode 100644 index 00000000..f2e0e9f7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/Admin/SettingsTab.php @@ -0,0 +1,135 @@ +<?php + +namespace WPMailSMTP\Pro\BackupConnections\Admin; + +use WPMailSMTP\Options; +use WPMailSMTP\Pro\AdditionalConnections\AdditionalConnections; + +/** + * Class SettingsTab. + * + * Backup connection settings. + * + * @since 3.7.0 + */ +class SettingsTab { + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_filter( 'wp_mail_smtp_admin_settings_tab_display', [ $this, 'display' ] ); + } + + /** + * Display backup connection settings. + * + * @since 3.7.0 + * + * @param string $settings_html Settings page HTML. + * + * @return string + */ + public function display( $settings_html ) { + + $additional_connections = new AdditionalConnections(); + $connections = $additional_connections->get_configured_connections(); + + $connection_id = Options::init()->get( 'backup_connection', 'connection_id' ); + + if ( ! $additional_connections->connection_exists( $connection_id ) ) { + $connection_id = false; + } + + ob_start(); + ?> + <!-- Backup Connection Section Title --> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading"> + <div class="wp-mail-smtp-setting-field"> + <h2><?php esc_html_e( 'Backup Connection', 'wp-mail-smtp-pro' ); ?></h2> + <p class="desc"> + <?php + echo wp_kses( + sprintf( /* translators: %s - Backup Connections documentation page URL. */ + __( '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. <a href="%s" target="_blank" rel="noopener noreferrer">Learn More</a>.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/configuring-backup-connection/', + [ + 'content' => 'Backup Connection Setting', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + ] + ); + ?> + </p> + </div> + </div> + + <!-- Backup Connection Selector --> + <div id="wp-mail-smtp-setting-row-backup_connection" class="wp-mail-smtp-setting-row wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-test_email_html"> + <?php esc_html_e( 'Backup Connection', 'wp-mail-smtp-pro' ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <div class="wp-mail-smtp-connection-selector"> + <label for="wp-mail-smtp-setting-row-backup_connection_none"> + <input type="radio" id="wp-mail-smtp-setting-row-backup_connection_none" name="wp-mail-smtp[backup_connection][connection_id]" value="" <?php checked( empty( $connection_id ) ); ?>/> + <span><?php esc_attr_e( 'None', 'wp-mail-smtp-pro' ); ?></span> + </label> + <?php foreach ( $connections as $connection ) : ?> + <label for="wp-mail-smtp-setting-row-backup_connection_<?php echo esc_attr( $connection->get_id() ); ?>"> + <input type="radio" id="wp-mail-smtp-setting-row-backup_connection_<?php echo esc_attr( $connection->get_id() ); ?>" + name="wp-mail-smtp[backup_connection][connection_id]" value="<?php echo esc_attr( $connection->get_id() ); ?>" + <?php checked( $connection_id, $connection->get_id() ); ?> + /> + <span><?php echo esc_html( $connection->get_title() ); ?></span> + </label> + <?php endforeach; ?> + </div> + <p class="desc"> + <?php + if ( empty( $connections ) ) { + echo wp_kses( + sprintf( /* translators: %s - Additional connections settings page url. */ + __( 'Once you add an <a href="%s">additional connection</a>, you can select it here.', 'wp-mail-smtp-pro' ), + add_query_arg( + [ + 'tab' => 'connections', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + } else { + esc_html_e( 'Choose which connection you’d like to use as your Backup Connection.', 'wp-mail-smtp-pro' ); + } + ?> + </p> + </div> + </div> + <?php + + return $settings_html . ob_get_clean(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/BackupConnections.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/BackupConnections.php new file mode 100644 index 00000000..d9649dbe --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/BackupConnections/BackupConnections.php @@ -0,0 +1,219 @@ +<?php + +namespace WPMailSMTP\Pro\BackupConnections; + +use WPMailSMTP\ConnectionInterface; +use WPMailSMTP\Options; +use WPMailSMTP\Pro\BackupConnections\Admin\SettingsTab; +use WPMailSMTP\Pro\WPMailArgs; + +/** + * Class BackupConnections. + * + * @since 3.7.0 + */ +class BackupConnections { + + /** + * The `wp_mail` function arguments that were used for current email sending. + * + * @since 3.7.0 + * + * @var WPMailArgs + */ + private $current_wp_mail_args; + + /** + * Latest backup connection that was used for current email sending. + * + * @since 3.8.0 + * + * @var ConnectionInterface + */ + private $latest_backup_connection; + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + // Init settings. + ( new SettingsTab() )->hooks(); + + // Filter options save process. + add_filter( 'wp_mail_smtp_options_set', [ $this, 'filter_options_set' ] ); + + // Capture `wp_mail` function call. + add_action( 'wp_mail_smtp_processor_capture_wp_mail_call', [ $this, 'capture_wp_mail_call' ] ); + } + + /** + * Capture `wp_mail` function call. + * + * @since 4.0.0 + */ + public function capture_wp_mail_call() { + /* + * We need to use original arguments that were passed to the `wp_mail` function without modifications, + * since `wp_mail` filter will be applied again in the backup email. + */ + $args = wp_mail_smtp()->get_processor()->get_original_wp_mail_args(); + + if ( ! empty( $args ) ) { + $this->set_current_backup_connection( $args ); + } + } + + /** + * Set backup connection and capture `wp_mail` function arguments. + * + * @since 3.7.0 + * + * @param array $args Array of the `wp_mail` function arguments. + * + * @return array + */ + public function set_current_backup_connection( $args ) { + + $this->current_wp_mail_args = new WPMailArgs( $args ); + + $backup_connection_id = Options::init()->get( 'backup_connection', 'connection_id' ); + + // Bail if the backup connection is not selected. + if ( empty( $backup_connection_id ) ) { + return $args; + } + + $connections_manager = wp_mail_smtp()->get_connections_manager(); + $backup_connection = $connections_manager->get_mail_backup_connection(); + + // Check if the backup connection was not set previously. + if ( is_null( $backup_connection ) ) { + $backup_connection = $connections_manager->get_connection( $backup_connection_id, false ); + + if ( ! empty( $backup_connection ) ) { + $connections_manager->set_mail_backup_connection( $backup_connection ); + } + } + + $this->latest_backup_connection = $backup_connection; + + return $args; + } + + /** + * Get the latest backup connection. + * + * @since 3.8.0 + * + * @return ConnectionInterface|null + */ + public function get_latest_backup_connection() { + + return $this->latest_backup_connection; + } + + /** + * Whether the backup connection is defined and can be used for sending an email. + * + * @since 3.7.0 + * + * @return bool + */ + public function is_ready() { + + $backup_connection = wp_mail_smtp()->get_connections_manager()->get_mail_backup_connection(); + + return ! empty( $this->current_wp_mail_args ) && ! empty( $backup_connection ); + } + + /** + * Send email via the backup connection. + * + * @since 3.7.0 + * + * @return bool + */ + public function send_email() { + + if ( empty( $this->current_wp_mail_args ) ) { + return false; + } + + $connections_manager = wp_mail_smtp()->get_connections_manager(); + $backup_connection = $connections_manager->get_mail_backup_connection(); + + if ( empty( $backup_connection ) ) { + return false; + } + + $args = array_merge( + [ + 'to' => '', + 'subject' => '', + 'message' => '', + 'headers' => '', + 'attachments' => [], + ], + $this->current_wp_mail_args->get_args() + ); + + $connections_manager->reset_mail_connection(); + + // Set backup connection as current mail connection. + $connections_manager->set_mail_connection( $backup_connection ); + + /** + * Fires before email sending via the backup connection. + * + * @since 3.7.0 + * + * @param array $args Array of the `wp_mail` function arguments. + */ + do_action( 'wp_mail_smtp_pro_backup_connections_send_email_before', $args ); + + $is_sent = wp_mail( $args['to'], $args['subject'], $args['message'], $args['headers'], $args['attachments'] ); + + /** + * Fires after email sending via the backup connection. + * + * @since 3.7.0 + * + * @param bool $is_sent Whether the email was sent successfully or not. + * @param array $args Array of the `wp_mail` function arguments. + */ + do_action( 'wp_mail_smtp_pro_backup_connections_send_email_after', $is_sent, $args ); + + return $is_sent; + } + + /** + * Sanitize options. + * + * @since 3.7.0 + * + * @param array $options Currently processed options passed to a filter hook. + * + * @return array + */ + public function filter_options_set( $options ) { + + if ( ! isset( $options['backup_connection'] ) ) { + $options['backup_connection'] = [ + 'connection_id' => false, + ]; + + return $options; + } + + foreach ( $options['backup_connection'] as $key => $value ) { + if ( $key === 'connection_id' ) { + $options['backup_connection'][ $key ] = sanitize_key( $value ); + } + } + + return $options; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/CanProcessConditionalLogicTrait.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/CanProcessConditionalLogicTrait.php new file mode 100644 index 00000000..29a0118e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/CanProcessConditionalLogicTrait.php @@ -0,0 +1,121 @@ +<?php + +namespace WPMailSMTP\Pro\ConditionalLogic; + +/** + * Trait CanProcessConditionalLogicTrait. + * + * @since 3.7.0 + */ +trait CanProcessConditionalLogicTrait { + + /** + * Process conditional rules. + * + * @since 3.7.0 + * + * @param array $conditionals List of conditionals. + * @param array $values List of values. + * + * @return bool + */ + public function process_conditionals( $conditionals, $values ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded + + if ( empty( $conditionals ) ) { + return true; + } + + $pass = false; + + foreach ( $conditionals as $group ) { + $pass_group = true; + + if ( ! empty( $group ) ) { + foreach ( $group as $rule ) { + if ( empty( $rule['property'] ) || empty( $rule['operator'] ) ) { + continue; + } + + $rule_property = $rule['property']; + $rule_operator = $rule['operator']; + $rule_value = isset( $rule['value'] ) ? strtolower( trim( $rule['value'] ) ) : ''; + + $actual_values = []; + + if ( isset( $values[ $rule_property ] ) ) { + $actual_values = array_map( 'strtolower', array_map( 'trim', (array) $values[ $rule_property ] ) ); + } + + $pass_rule = false; + + foreach ( $actual_values as $actual_value ) { + if ( $this->process_rule( $actual_value, $rule_value, $rule_operator ) ) { + $pass_rule = true; + + break; + } + } + + if ( ! $pass_rule ) { + $pass_group = false; + + break; + } + } + } + + if ( $pass_group ) { + $pass = true; + } + } + + return $pass; + } + + /** + * Process conditional rule. + * + * @since 3.7.0 + * + * @param string $left Actual value. + * @param string $right Rule value. + * @param string $operator Rule operator. + * + * @return bool + */ + private function process_rule( $left, $right, $operator ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + switch ( $operator ) { + case '==': + $pass_rule = $left === $right; + break; + + case '!=': + $pass_rule = $left !== $right; + break; + + case 'c': + $pass_rule = ( strpos( $left, $right ) !== false ); + break; + + case '!c': + $pass_rule = ( strpos( $left, $right ) === false ); + break; + + case '^': + $pass_rule = ( strrpos( $left, $right, - strlen( $left ) ) !== false ); + break; + + case '~': + // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found + $pass_rule = ( ( $temp = strlen( $left ) - strlen( $right ) ) >= 0 && strpos( $left, $right, $temp ) !== false ); + break; + + default: + $pass_rule = false; + break; + } + + return $pass_rule; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/ConditionalLogicSettings.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/ConditionalLogicSettings.php new file mode 100644 index 00000000..469bd88f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConditionalLogic/ConditionalLogicSettings.php @@ -0,0 +1,265 @@ +<?php + +namespace WPMailSMTP\Pro\ConditionalLogic; + +use WPMailSMTP\WP; + +/** + * Class ConditionalLogicSettings. + * + * @since 3.7.0 + */ +class ConditionalLogicSettings { + + /** + * Conditions properties. + * + * @since 3.7.0 + * + * @var array + */ + private $properties; + + /** + * Constructor. + * + * @since 3.7.0 + * + * @param array $properties Conditions properties. + */ + public function __construct( $properties = [] ) { + + $this->properties = $properties; + } + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ], 5 ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.7.0 + */ + public function enqueue_assets() { + + $min = WP::asset_min(); + + wp_enqueue_script( + 'wp-mail-smtp-conditional-logic', + wp_mail_smtp()->plugin_url . "/assets/pro/js/smtp-pro-conditional-logic{$min}.js", + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-conditional-logic', + 'wp_mail_smtp_conditional_logic', + [ + 'properties' => $this->properties, + ] + ); + } + + /** + * Build the conditional logic settings. + * + * @since 3.7.0 + * + * @param array $args Data is needed for a block to be generated properly. + * @param bool $echo Whether to return or print. Default: print. + * + * @return string|void + */ + public function block( $args = [], $echo = true ) { // phpcs:ignore Generic.Arrays.DisallowLongArraySyntax.Found, Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded + + $field_name = ! empty( $args['field_name'] ) ? $args['field_name'] : ''; + $conditionals = ! empty( $args['conditionals'] ) ? $args['conditionals'] : [ 'group-0' => [ 'rule-0' => [] ] ]; + $properties = $this->properties; + $rule_data_attrs = 'data-input-name="' . esc_attr( $field_name ) . '"'; + + ob_start(); + + // Block open markup. + echo '<div class="wp-mail-smtp-conditional">'; + + // Go through each conditional logic group. + foreach ( $conditionals as $group_id => $group ) : + // Individual group open markup. + echo '<div class="wp-mail-smtp-conditional__group">'; + + echo '<table><tbody>'; + + foreach ( $group as $rule_id => $rule ) : + // Set default value as first property from the list. + if ( empty( $rule['property'] ) ) { + $rule['property'] = array_keys( $properties )[0]; + } + + $property_type = ! empty( $properties[ $rule['property'] ]['type'] ) ? $properties[ $rule['property'] ]['type'] : 'text'; + + // Individual rule table row. + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '<tr class="wp-mail-smtp-conditional__row" ' . $rule_data_attrs . '>'; + + echo '<td class="wp-mail-smtp-conditional__property-col">'; + + printf( + '<select name="%1$s[conditionals][%2$s][%3$s][property]" class="wp-mail-smtp-conditional__property" data-groupid="%2$s" data-ruleid="%3$s">', + esc_attr( $field_name ), + esc_attr( $group_id ), + esc_attr( $rule_id ) + ); + + if ( ! empty( $properties ) ) { + foreach ( $properties as $property_value => $property ) { + if ( isset( $rule['property'] ) ) { + $selected = $rule['property']; + } else { + $selected = false; + } + + $selected = selected( $selected, $property_value, false ); + + printf( '<option value="%s" %s>%s</option>', esc_attr( $property_value ), esc_attr( $selected ), esc_html( $property['label'] ) ); + } + } + + echo '</select>'; + + echo '</td>'; + + // Rule operator - allows the user to determine the comparison operator used for processing. + echo '<td class="wp-mail-smtp-conditional__operator-col">'; + + printf( + '<select name="%s[conditionals][%s][%s][operator]" class="wp-mail-smtp-conditional__operator">', + esc_attr( $field_name ), + esc_attr( $group_id ), + esc_attr( $rule_id ) + ); + + $selected_operator = ! empty( $rule['operator'] ) ? $rule['operator'] : false; + + $operators = [ + [ + 'value' => 'c', + 'label' => esc_html__( 'Contains', 'wp-mail-smtp-pro' ), + ], + [ + 'value' => '!c', + 'label' => esc_html__( 'Does not contain', 'wp-mail-smtp-pro' ), + ], + [ + 'value' => '==', + 'label' => esc_html__( 'Is', 'wp-mail-smtp-pro' ), + ], + [ + 'value' => '!=', + 'label' => esc_html__( 'Is not', 'wp-mail-smtp-pro' ), + ], + [ + 'value' => '^', + 'label' => esc_html__( 'Starts with', 'wp-mail-smtp-pro' ), + ], + [ + 'value' => '~', + 'label' => esc_html__( 'Ends with', 'wp-mail-smtp-pro' ), + ], + ]; + + foreach ( $operators as $operator ) { + printf( + '<option value="%s" %s %s>%s</option>', + esc_attr( $operator['value'] ), + selected( $selected_operator, $operator['value'] ), + disabled( $property_type === 'select' && ! in_array( $operator['value'], [ '==', '!=' ], true ) ), + esc_html( $operator['label'] ) + ); + } + + echo '</select>'; + + echo '</td>'; + + // Rule value - allows the user to determine the value we are using for comparison. + echo '<td class="wp-mail-smtp-conditional__value-col">'; + + if ( ! empty( $rule['property'] ) ) { + $rule_value = ! empty( $rule['value'] ) ? $rule['value'] : ''; + + if ( $property_type === 'select' ) { + printf( + '<select name="%1$s[conditionals][%2$s][%3$s][value]" class="wp-mail-smtp-conditional__value">', + esc_attr( $field_name ), + esc_attr( $group_id ), + esc_attr( $rule_id ) + ); + + if ( ! empty( $properties[ $rule['property'] ]['choices'] ) ) { + foreach ( $properties[ $rule['property'] ]['choices'] as $option_value => $option_label ) { + printf( + '<option value="%1$s" %2$s>%3$s</option>', + esc_attr( $option_value ), + selected( $option_value, $rule_value, false ), + esc_html( trim( $option_label ) ) + ); + } + } + + echo '</select>'; + } else { + printf( + '<input type="text" name="%s[conditionals][%s][%s][value]" value="%s" class="wp-mail-smtp-conditional__value">', + esc_attr( $field_name ), + esc_attr( $group_id ), + esc_attr( $rule_id ), + esc_attr( $rule_value ) + ); + } + } else { + echo '<select></select>'; + } + + echo '</td>'; + + // Rule actions. + echo '<td class="wp-mail-smtp-conditional__actions">'; + echo '<button class="wp-mail-smtp-conditional__add-rule wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey" title="' . esc_attr__( 'Create new rule', 'wp-mail-smtp-pro' ) . '">' . esc_html_x( 'And', 'Conditional Logic: new rule logic.', 'wp-mail-smtp-pro' ) . '</button>'; + + echo '<button class="wp-mail-smtp-conditional__delete-rule" title="' . esc_attr__( 'Delete rule', 'wp-mail-smtp-pro' ) . '"><i class="dashicons dashicons-trash" aria-hidden="true"></i></button>'; + echo '</td>'; + + echo '</tr>'; // Close individual rule table row. + + endforeach; // End foreach() for individual rules. + + echo '</tbody></table>'; + + echo '<div class="wp-mail-smtp-conditional__group-delimiter">' . esc_html_x( 'or', 'Conditional Logic: new rule logic.', 'wp-mail-smtp-pro' ) . '</div>'; + + echo '</div>'; // Close individual group markup. + + endforeach; // End foreach() for conditional logic groups. + + echo '<button class="wp-mail-smtp-conditional__add-group wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-grey">' . esc_html__( 'Add New Group', 'wp-mail-smtp-pro' ) . '</button>'; + + echo '</div>'; // Close block markup. + + $output = ob_get_clean(); + + if ( $echo ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo $output; + } else { + return $output; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConnectionsManager.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConnectionsManager.php new file mode 100644 index 00000000..d9fb4185 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/ConnectionsManager.php @@ -0,0 +1,169 @@ +<?php + +namespace WPMailSMTP\Pro; + +use WPMailSMTP\ConnectionInterface; +use WPMailSMTP\ConnectionsManager as ConnectionsManagerLite; +use WPMailSMTP\Pro\AdditionalConnections\AdditionalConnections; + +/** + * Class ConnectionsManager. + * + * This class allows to define a primary and backup connection per each email. + * + * @since 3.7.0 + */ +class ConnectionsManager extends ConnectionsManagerLite { + + /** + * Hold all created connections objects. + * + * @since 3.7.0 + * + * @var ConnectionInterface[] + */ + private $connections = []; + + /** + * The current connection object that is using for email sending. + * + * @since 3.7.0 + * + * @var ConnectionInterface + */ + private $mail_connection = null; + + /** + * The backup connection object that will be used for email sending if the primary connection fails. + * + * @since 3.7.0 + * + * @var ConnectionInterface + */ + private $mail_backup_connection = null; + + /** + * Get connection object by ID. + * + * @since 3.7.0 + * + * @param string $connection_id Connection ID. + * @param bool $default Whether to return the default connection if the connection by ID was not found or not. + * + * @return false|ConnectionInterface + */ + public function get_connection( $connection_id, $default = true ) { + + if ( $connection_id === 'primary' ) { + return $this->get_primary_connection(); + } + + // Return already created connection from storage. + if ( isset( $this->connections[ $connection_id ] ) ) { + return $this->connections[ $connection_id ]; + } + + // Get additional connection. + $connection = $this->get_additional_connection( $connection_id ); + + if ( $connection ) { + return $connection; + } + + return $default ? $this->get_primary_connection() : false; + } + + /** + * Set connection object that will be used for email sending. + * + * @since 3.7.0 + * + * @param ConnectionInterface $connection The connection object. + */ + public function set_mail_connection( ConnectionInterface $connection ) { + + $this->mail_connection = $connection; + } + + /** + * Get connection object that should be used for email sending. + * + * If connection object is not set, then primary connection will be returned. + * + * @since 3.7.0 + * + * @param bool $default Whether to return the default mail connection if the connection was not set. + * + * @return ConnectionInterface + */ + public function get_mail_connection( $default = true ) { + + if ( ! empty( $this->mail_connection ) || ! $default ) { + return $this->mail_connection; + } + + return $this->get_primary_connection(); + } + + /** + * Set a backup connection object. + * + * This object will be used for email sending if the primary connection fails. + * + * @since 3.7.0 + * + * @param ConnectionInterface|false $connection The connection object. + */ + public function set_mail_backup_connection( $connection ) { + + $this->mail_backup_connection = $connection; + } + + /** + * Get a backup connection. + * + * This object will be used for email sending if the primary connection fails. + * + * @since 3.7.0 + * + * @return ConnectionInterface|false|null + */ + public function get_mail_backup_connection() { + + return $this->mail_backup_connection; + } + + /** + * Reset all mail related variables. + * + * All variables are set before each particular email is sent and reset after. + * + * @since 3.7.0 + */ + public function reset_mail_connection() { + + $this->mail_connection = null; + $this->mail_backup_connection = null; + } + + /** + * Get additional connection object by ID. + * + * @since 3.7.0 + * + * @param string $connection_id Connection ID. + * + * @return false|ConnectionInterface + */ + private function get_additional_connection( $connection_id ) { + + $additional_connections = new AdditionalConnections(); + $connection = $additional_connections->get_connection( $connection_id ); + + if ( $connection && ! isset( $this->connections[ $connection_id ] ) ) { + $this->connections[ $connection_id ] = $connection; + } + + return $connection; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/DBRepair.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/DBRepair.php new file mode 100644 index 00000000..b0d2630a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/DBRepair.php @@ -0,0 +1,83 @@ +<?php + +namespace WPMailSMTP\Pro; + +use WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments; +use WPMailSMTP\Pro\Emails\Logs\Attachments\Migration as AttachmentsMigration; +use WPMailSMTP\Pro\Emails\Logs\Logs; +use WPMailSMTP\Pro\Emails\Logs\Migration as EmailLogMigration; +use WPMailSMTP\Pro\Emails\Logs\Tracking\Migration as TrackingMigration; +use WPMailSMTP\DBRepair as LiteDBRepair; +use WPMailSMTP\Pro\Emails\Logs\Tracking\Tracking; + +/** + * Class DBRepair to fix the DB related issues. + * + * @since 3.6.0 + */ +class DBRepair extends LiteDBRepair { + + /** + * Update the Migration option to fix the missing table. + * + * @since 3.6.0 + * + * @param string $missing_table The name of the table. + */ + protected function fix_missing_db_table( $missing_table ) { + + parent::fix_missing_db_table( $missing_table ); + + if ( $missing_table === Logs::get_table_name() ) { + update_option( EmailLogMigration::OPTION_NAME, 0 ); + } elseif ( $missing_table === Tracking::get_events_table_name() ) { + update_option( TrackingMigration::OPTION_NAME, 0 ); + } elseif ( $missing_table === Tracking::get_links_table_name() ) { + update_option( TrackingMigration::OPTION_NAME, 0 ); + } elseif ( $missing_table === Attachments::get_attachment_files_table_name() ) { + update_option( AttachmentsMigration::OPTION_NAME, 0 ); + } elseif ( $missing_table === Attachments::get_email_attachments_table_name() ) { + update_option( AttachmentsMigration::OPTION_NAME, 0 ); + } + } + + /** + * Get the error message (Reason) if the table is missing. + * + * @since 3.6.0 + * + * @param string $missing_table The table name that we are checking. + * @param array $reasons The array that holds all the error messages or reason. + */ + protected function get_error_message_for_missing_table( $missing_table, &$reasons ) { + + parent::get_error_message_for_missing_table( $missing_table, $reasons ); + + $reason = ''; + + if ( $missing_table === Logs::get_table_name() ) { + $reason .= $this->get_reason_output_message( + $missing_table, + get_option( EmailLogMigration::ERROR_OPTION_NAME, $this->get_missing_table_default_error_message() ) + ); + } elseif ( + $missing_table === Tracking::get_events_table_name() || + $missing_table === Tracking::get_links_table_name() + ) { + $reason .= $this->get_reason_output_message( + $missing_table, + get_option( TrackingMigration::ERROR_OPTION_NAME, $this->get_missing_table_default_error_message() ) + ); + } elseif ( + $missing_table === Attachments::get_attachment_files_table_name() || + $missing_table === Attachments::get_email_attachments_table_name() + ) { + $reason .= $this->get_reason_output_message( + $missing_table, + get_option( AttachmentsMigration::ERROR_OPTION_NAME, $this->get_missing_table_default_error_message() ) + ); + } + + $reasons[] = $reason; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Admin/SettingsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Admin/SettingsTab.php new file mode 100644 index 00000000..f445f840 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Admin/SettingsTab.php @@ -0,0 +1,139 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Control\Admin; + +use WPMailSMTP\Admin\Pages\ControlTab; +use WPMailSMTP\Helpers\UI; +use WPMailSMTP\Options; +use WPMailSMTP\WP; + +/** + * Class SettingsTab. + * + * @since 1.5.0 + */ +class SettingsTab extends ControlTab { + + /** + * Output HTML of the email controls settings. + * + * @since 1.5.0 + */ + public function display() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $options = Options::init(); + $controls = wp_mail_smtp()->pro->get_control()->get_controls(); + ?> + + <form method="POST" action=""> + <?php $this->wp_nonce_field(); ?> + + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content section-heading wp-mail-smtp-section-heading--has-divider"> + <div class="wp-mail-smtp-setting-field"> + <h2><?php esc_html_e( 'Email Controls', 'wp-mail-smtp-pro' ); ?></h2> + <p class="desc"> + <?php esc_html_e( 'WordPress, by default, will send out emails for many events on your site. Using the toggles below, you can decide exactly which emails you\'d like enabled.', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + </div> + + <?php + foreach ( $controls as $section_id => $section ) : + if ( empty( $section['emails'] ) ) { + continue; + } + + if ( $this->is_it_for_multisite( sanitize_key( $section_id ) ) && ! WP::use_global_plugin_settings() ) { + continue; + } + ?> + <div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading no-desc" id="wp-mail-smtp-setting-row-email-heading-<?php echo esc_attr( $section_id ); ?>"> + <div class="wp-mail-smtp-setting-field"> + <h2><?php echo esc_html( $section['title'] ); ?></h2> + </div> + </div> + <div class="wp-mail-smtp-setting-group"> + <?php + foreach ( $section['emails'] as $email_id => $email ) : + $email_id = sanitize_key( $email_id ); + + if ( empty( $email_id ) || empty( $email['label'] ) ) { + continue; + } + + if ( $this->is_it_for_multisite( sanitize_key( $email_id ) ) && ! WP::use_global_plugin_settings() ) { + continue; + } + ?> + <div id="wp-mail-smtp-setting-row-control_<?php echo esc_attr( $email_id ); ?>" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-checkbox-toggle wp-mail-smtp-clear"> + <div class="wp-mail-smtp-setting-label"> + <label for="wp-mail-smtp-setting-<?php echo esc_attr( $email_id ); ?>"> + <?php echo esc_html( $email['label'] ); ?> + </label> + </div> + <div class="wp-mail-smtp-setting-field"> + <?php + UI::toggle( + [ + 'name' => 'wp-mail-smtp[control][' . $email_id . ']', + 'id' => 'wp-mail-smtp-setting-' . $email_id, + 'checked' => ! $options->get( 'control', $email_id ), + ] + ); + ?> + <?php if ( ! empty( $email['desc'] ) ) : ?> + <p class="desc"> + <?php echo $email['desc']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> + </p> + <?php endif; ?> + </div> + </div> + <?php endforeach; ?> + </div> + <?php endforeach; ?> + + <?php $this->display_save_btn(); ?> + + </form> + <?php + } + + /** + * Process tab form submission ($_POST). + * + * @since 1.5.0 + * + * @param array $data Post data specific for the plugin. + */ + public function process_post( $data ) { + + $options = Options::init(); + $is_multisite = is_multisite(); + + $controls = wp_mail_smtp()->pro->get_control()->get_controls( true ); + + foreach ( $controls as $control ) { + // In MS we have all options listed - so just preserve everything and convert to boolean. + if ( $is_multisite ) { + // Those that are not in $data - user unchecked - which means disabled. + $data['control'][ $control ] = empty( $data['control'][ $control ] ); + } else { + // Process MS specific separately with on by default (enabled). + if ( strpos( $control, 'network' ) !== false ) { + $data['control'][ $control ] = false; + } else { + // Those that are not in $data - user unchecked - which means disabled. + $data['control'][ $control ] = empty( $data['control'][ $control ] ); + } + } + } + + // All the sanitization is done there. + $options->set( $data, false, false ); + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Control.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Control.php new file mode 100644 index 00000000..f920a388 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Control.php @@ -0,0 +1,105 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Control; + +use WPMailSMTP\Pro\Emails\Control\Admin\SettingsTab; + +/** + * Class Control. + * + * @since 1.5.0 + */ +class Control { + + /** + * Control constructor. + * + * @since 1.5.0 + */ + public function __construct() {} + + /** + * Initialize the Control functionality. + * + * @since 1.5.0 + */ + public function init() { + + // Add a new Email Controls tab under General. + add_filter( + 'wp_mail_smtp_admin_get_pages', + function ( $pages ) { + + $misc = $pages['misc']; + unset( $pages['misc'] ); + + $pages['control'] = new SettingsTab(); + $pages['misc'] = $misc; + + return $pages; + }, + 1 + ); + + // Filter admin area options save process. + add_filter( 'wp_mail_smtp_options_set', [ $this, 'filter_options_set' ] ); + + new Switcher(); + } + + /** + * Sanitize admin area options. + * + * @since 1.5.0 + * + * @param array $options The options array. + * + * @return array + */ + public function filter_options_set( $options ) { + + if ( isset( $options['control'] ) ) { + foreach ( $options['control'] as $key => $value ) { + $options['control'][ $key ] = (bool) $value; + } + } else { + $controls = $this->get_controls( true ); + + // All emails are on by default (not disabled). + foreach ( $controls as $control ) { + $options['control'][ $control ] = false; + } + } + + return $options; + } + + /** + * Get the list of all available emails that we can manage. + * + * @since 1.5.0 + * + * @param bool $keys_only Whether to return the list of emails keys only (no sections/descriptions). + * + * @return array + */ + public function get_controls( $keys_only = false ) { + + $data = SettingsTab::get_controls(); + + if ( $keys_only === true ) { + // Create an array of arrays per each section of all the keys. + $update_data = array_map( + function ( $leaf ) { + return array_keys( $leaf ); + }, + array_column( $data, 'emails' ) + ); + + // Unpack to flatten it the array. + $data = array_merge( ...$update_data ); + } + + return apply_filters( 'wp_mail_smtp_pro_emails_control_get_controls', $data, $keys_only ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Reload.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Reload.php new file mode 100644 index 00000000..059584ab --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Reload.php @@ -0,0 +1,185 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Control; + +use WPMailSMTP\Options; + +/** + * Class Reload contains methods that have a copied from WP-core functionality. + * + * @since 1.5.0 + */ +class Reload { + + /** + * Email login credentials to a newly-registered user. + * + * A new user registration notification is also sent to admin email. + * + * @since 1.5.0 Method content copied from \wp_new_user_notification() in WP 5.1.1. + * @since 3.1.0 Method content copied from \wp_new_user_notification() in WP 5.8.1. + * @since 3.9.0 Method content copied from \wp_new_user_notification() in WP 6.2.2. + * + * @param int $user_id User ID. + * @param null $deprecated Not used (argument deprecated). + * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' or an empty + * string (admin only), 'user', or 'both' (admin and user). Default empty. + */ + public static function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Accepts only 'user', 'admin', 'both' or default '' as $notify. + if ( ! in_array( $notify, array( 'user', 'admin', 'both', '' ), true ) ) { + return; + } + + $user = get_userdata( $user_id ); + + // The blogname option is escaped with esc_html() on the way into the database in sanitize_option(). + // We want to reverse this for the plain text arena of emails. + $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); + + /** + * Filters whether the admin is notified of a new user registration. + * + * @param bool $send Whether to send the email. Default true. + * @param WP_User $user User object for new user. + */ + $send_notification_to_admin = apply_filters( 'wp_send_new_user_notification_to_admin', true, $user ); + + if ( 'user' !== $notify && true === $send_notification_to_admin ) { + $switched_locale = switch_to_locale( get_locale() ); + + /* translators: %s: Site title. */ + $message = sprintf( __( 'New user registration on your site %s:' ), $blogname ) . "\r\n\r\n"; + /* translators: %s: User login. */ + $message .= sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n"; + /* translators: %s: User email address. */ + $message .= sprintf( __( 'Email: %s' ), $user->user_email ) . "\r\n"; + + $wp_new_user_notification_email_admin = array( + 'to' => get_option( 'admin_email' ), + /* translators: New user registration notification email subject. %s: Site title. */ + 'subject' => __( '[%s] New User Registration' ), + 'message' => $message, + 'headers' => '', + ); + + /** + * Filters the contents of the new user notification email sent to the site admin. + * + * @param array $wp_new_user_notification_email_admin { + * Used to build wp_mail(). + * + * @type string $to The intended recipient - site admin email address. + * @type string $subject The subject of the email. + * @type string $message The body of the email. + * @type string $headers The headers of the email. + * } + * + * @param \WP_User $user User object for new user. + * @param string $blogname The site title. + */ + $wp_new_user_notification_email_admin = apply_filters( 'wp_new_user_notification_email_admin', $wp_new_user_notification_email_admin, $user, $blogname ); + + wp_mail( + $wp_new_user_notification_email_admin['to'], + wp_specialchars_decode( sprintf( $wp_new_user_notification_email_admin['subject'], $blogname ) ), + $wp_new_user_notification_email_admin['message'], + $wp_new_user_notification_email_admin['headers'] + ); + + if ( $switched_locale ) { + restore_previous_locale(); + } + } + + /** + * Filters whether the user is notified of their new user registration. + * + * @param bool $send Whether to send the email. Default true. + * @param WP_User $user User object for new user. + */ + $send_notification_to_user = apply_filters( 'wp_send_new_user_notification_to_user', true, $user ); + + // `$deprecated` was pre-4.3 `$plaintext_pass`. An empty `$plaintext_pass` didn't sent a user notification. + if ( 'admin' === $notify || true !== $send_notification_to_user || ( empty( $deprecated ) && empty( $notify ) ) ) { + return; + } + + $key = get_password_reset_key( $user ); + if ( is_wp_error( $key ) ) { + return; + } + + $switched_locale = switch_to_locale( get_user_locale( $user ) ); + + /* translators: %s: User login. */ + $message = sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n"; + $message .= __( 'To set your password, visit the following address:' ) . "\r\n\r\n"; + $message .= network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user->user_login ), 'login' ) . "\r\n\r\n"; + + $message .= wp_login_url() . "\r\n"; + + $wp_new_user_notification_email = array( + 'to' => $user->user_email, + /* translators: Login details notification email subject. %s: Site title. */ + 'subject' => __( '[%s] Login Details' ), + 'message' => $message, + 'headers' => '', + ); + + /** + * Filters the contents of the new user notification email sent to the new user. + * + * @param array $wp_new_user_notification_email { + * Used to build wp_mail(). + * + * @type string $to The intended recipient - New user email address. + * @type string $subject The subject of the email. + * @type string $message The body of the email. + * @type string $headers The headers of the email. + * } + * + * @param \WP_User $user User object for new user. + * @param string $blogname The site title. + */ + $wp_new_user_notification_email = apply_filters( 'wp_new_user_notification_email', $wp_new_user_notification_email, $user, $blogname ); + + wp_mail( + $wp_new_user_notification_email['to'], + wp_specialchars_decode( sprintf( $wp_new_user_notification_email['subject'], $blogname ) ), + $wp_new_user_notification_email['message'], + $wp_new_user_notification_email['headers'] + ); + + if ( $switched_locale ) { + restore_previous_locale(); + } + } + + /** + * Redefine the $send_as_email param, to not actually send an email. + * + * @since 1.5.0 Method overloaded copied from \wp_privacy_process_personal_data_export_page() in WP 5.1.1. + * + * @param array $response The response from the personal data exporter for the given page. + * @param int $exporter_index The index of the personal data exporter. Begins at 1. + * @param string $email_address The email address of the user whose personal data this is. + * @param int $page The page of personal data for this exporter. Begins at 1. + * @param int $request_id The request ID for this personal data export. + * @param bool $send_as_email Whether the final results of the export should be emailed to the user. + * @param string $exporter_key The slug (key) of the exporter. + * + * @return array The filtered response. + */ + public static function wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ) { + + $is_email_disabled = Options::init()->get( 'control', 'dis_personal_data_sent_export_link' ); + + if ( $is_email_disabled && $send_as_email ) { + $send_as_email = false; + } + + return \wp_privacy_process_personal_data_export_page( $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Switcher.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Switcher.php new file mode 100644 index 00000000..c33951ae --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/Switcher.php @@ -0,0 +1,158 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Control; + +use WPMailSMTP\Options; + +/** + * Class Switcher uses DB settings to define which emails should be switched off. + * + * @since 1.5.0 + */ +class Switcher { + + /** + * @since + * + * @var Options + */ + private $options; + + /** + * Disable those emails that were saved as such on options page. + * + * @since 1.5.0 + */ + public function __construct() { + + $this->options = Options::init()->get_group( 'control' ); + + // Get only those that are truthy - thus, disabled. + $disabled = array_filter( $this->options ); + + foreach ( $disabled as $key => $value ) { + switch ( $key ) { + // Comments. + case 'dis_comments_awaiting_moderation': + remove_action( 'comment_post', 'wp_new_comment_notify_moderator' ); + break; + case 'dis_comments_published': + remove_action( 'comment_post', 'wp_new_comment_notify_postauthor' ); + add_filter( 'notify_post_author', '__return_false', PHP_INT_MAX ); + break; + + // Change of Admin Email. + case 'dis_admin_email_attempt': + add_action( + 'admin_init', + function() { + remove_action( 'add_option_new_admin_email', 'update_option_new_admin_email' ); + remove_action( 'update_option_new_admin_email', 'update_option_new_admin_email' ); + } + ); + break; + case 'dis_admin_email_changed': + add_filter( 'send_site_admin_email_change_email', '__return_false', PHP_INT_MAX, 4 ); + break; + case 'dis_admin_email_network_attempt': + add_action( + 'admin_init', + function() { + remove_action( 'add_site_option_new_admin_email', 'update_network_option_new_admin_email' ); + remove_action( 'update_site_option_new_admin_email', 'update_network_option_new_admin_email' ); + } + ); + break; + case 'dis_admin_email_network_changed': + add_filter( 'send_network_admin_email_change_email', '__return_false', PHP_INT_MAX, 4 ); + break; + + // Change of User Email or Password. + case 'dis_user_details_password_reset_request': + add_filter( 'retrieve_password_message', '__return_false', PHP_INT_MAX, 4 ); + break; + case 'dis_user_details_password_reset': + remove_action( 'after_password_reset', 'wp_password_change_notification' ); + break; + case 'dis_user_details_password_changed': + add_filter( 'send_password_change_email', '__return_false', PHP_INT_MAX, 3 ); + break; + case 'dis_user_details_email_change_attempt': + add_action( + 'admin_init', + function() { + remove_action( 'personal_options_update', 'send_confirmation_on_profile_email' ); + } + ); + break; + case 'dis_user_details_email_changed': + add_filter( 'send_email_change_email', '__return_false', PHP_INT_MAX, 3 ); + break; + + // Personal Data Requests. + case 'dis_personal_data_user_confirmed': + remove_action( 'user_request_action_confirmed', '_wp_privacy_send_request_confirmation_notification', 12 ); + break; + case 'dis_personal_data_sent_export_link': + add_action( + 'admin_init', + function() { + remove_filter( 'wp_privacy_personal_data_export_page', 'wp_privacy_process_personal_data_export_page', 10 ); + } + ); + break; + case 'dis_personal_data_erased_data': + add_action( + 'admin_init', + function() { + remove_action( 'wp_privacy_personal_data_erased', '_wp_privacy_send_erasure_fulfillment_notification' ); + } + ); + break; + + // Automatic Updates. + case 'dis_auto_updates_plugin_status': + add_filter( 'auto_plugin_update_send_email', '__return_false' ); + break; + case 'dis_auto_updates_theme_status': + add_filter( 'auto_theme_update_send_email', '__return_false' ); + break; + case 'dis_auto_updates_status': + add_filter( 'auto_core_update_send_email', '__return_false' ); + add_filter( 'send_core_update_notification_email', '__return_false' ); + break; + case 'dis_auto_updates_full_log': + add_filter( 'automatic_updates_send_debug_email ', '__return_false', 1 ); + break; + + // New User. + case 'dis_new_user_created_to_admin': + // Processed in Reload class. + break; + case 'dis_new_user_created_to_user': + // Processed in Reload class. + break; + case 'dis_new_user_invited_to_site_network': + add_filter( 'wpmu_signup_user_notification', '__return_false' ); + break; + case 'dis_new_user_created_network': + remove_action( 'wpmu_new_user', 'newuser_notify_siteadmin' ); + break; + case 'dis_new_user_added_activated_network': + add_filter( 'wpmu_welcome_user_notification', '__return_false' ); + break; + + // New Site. + case 'dis_new_site_user_registered_site_network': + add_filter( 'wpmu_signup_blog_notification', '__return_false' ); + break; + case 'dis_new_site_user_added_activated_site_in_network_to_admin': + remove_action( 'wp_initialize_site', 'newblog_notify_siteadmin', 100 ); + break; + case 'dis_new_site_user_added_activated_site_in_network_to_site': + add_filter( 'wpmu_welcome_notification', '__return_false' ); + break; + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/functions.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/functions.php new file mode 100644 index 00000000..e12cc4a3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Control/functions.php @@ -0,0 +1,56 @@ +<?php + +use WPMailSMTP\Options; +use WPMailSMTP\Pro\Emails\Control\Reload; + +if ( ! function_exists( 'wp_new_user_notification' ) ) { + /** + * Add additional logic to the way \wp_new_user_notification() function works. + * Allow to switch notifications dynamically for each recipient. + * + * @since 1.5.0 + * + * @param int $user_id User ID. + * @param null $deprecated Not used (argument deprecated). + * @param string $notify Optional. Type of notification that should happen. Accepts 'admin' or an empty + * string (admin only), 'user', or 'both' (admin and user). Default empty. + */ + function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) { + + // Accepts only 'user', 'admin' , 'both' or default '' as $notify. + if ( ! in_array( $notify, array( 'user', 'admin', 'both', '' ), true ) ) { + return; + } + + $options = Options::init(); + + $is_admin_disabled = $options->get( 'control', 'dis_new_user_created_to_admin' ); + $is_user_disabled = $options->get( 'control', 'dis_new_user_created_to_user' ); + + switch ( $notify ) { + case 'user': + if ( ! $is_user_disabled ) { + Reload::wp_new_user_notification( $user_id, $deprecated, 'user' ); + } + break; + case 'both': + if ( ! $is_admin_disabled ) { + Reload::wp_new_user_notification( $user_id, $deprecated, 'admin' ); + } + if ( ! $is_user_disabled ) { + Reload::wp_new_user_notification( $user_id, $deprecated, 'user' ); + } + break; + case 'admin': + case '': + if ( ! $is_admin_disabled ) { + Reload::wp_new_user_notification( $user_id, $deprecated, 'admin' ); + } + break; + + default: + // Do not interfere with the unknown behavior. + Reload::wp_new_user_notification( $user_id, $deprecated, $notify ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/ArchivePage.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/ArchivePage.php new file mode 100644 index 00000000..81657a7a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/ArchivePage.php @@ -0,0 +1,305 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Logs\Admin; + +use WPMailSMTP\Admin\Area; + +/** + * Class ArchivePage displays a Email Log page content. + * + * @since 1.5.0 + */ +class ArchivePage extends PageAbstract { + + /** + * Email logs list table. + * + * @since 2.8.0 + * + * @var Table + */ + protected $table = null; + + /** + * ArchivePage class constructor. + * + * @since 2.8.0 + */ + public function __construct() { + parent::__construct(); + + // Remove unnecessary $_GET parameters and prevent url duplications in _wp_http_referer input. + $this->remove_get_parameters(); + } + + /** + * Link label of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_label() { + return esc_html__( 'Email Log', 'wp-mail-smtp-pro' ); + } + + /** + * Tab content. + * + * @since 1.5.0 + * @since 1.7.0 Added search functionality. + */ + public function display() { + + $page_url = wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ); + ?> + + <div class="wp-mail-smtp-page-title"> + <span class="page-title"> + <?php echo esc_html( $this->get_label() ); ?> + </span> + + <?php + /** + * Fires after email logs archive page title. + * + * @since 2.9.0 + */ + do_action( 'wp_mail_smtp_pro_emails_logs_admin_archive_page_display_header' ); + ?> + </div> + + <h1 class="screen-reader-text"> + <?php echo esc_html( $this->get_label() ); ?> + </h1> + + <div class="wp-mail-smtp-page-content"> + <?php + /** + * Fires before email logs archive page content. + * + * @since 2.3.1 + */ + do_action( 'wp_mail_smtp_admin_pages_before_content' ); + ?> + <form action="<?php echo esc_url( $page_url ); ?>" method="get"> + <input type="hidden" name="page" value="<?php echo esc_attr( Area::SLUG . '-logs' ); ?>" /> + + <?php + if ( wp_mail_smtp()->pro->get_logs()->is_enabled() ) { + if ( ! wp_mail_smtp()->pro->get_logs()->is_valid_db() ) { + $this->display_logging_not_installed(); + } else { + $table = $this->get_table(); + $table->prepare_items(); + + // State of status filter for submission with other filters. + if ( $table->get_filtered_status() !== false ) { + printf( '<input type="hidden" name="status" value="%s">', esc_attr( $table->get_filtered_status() ) ); + } + + if ( $this->get_filters_html() ) { + ?> + <div id="wp-mail-smtp-reset-filter"> + <?php + $status = $table->get_filtered_status(); + echo wp_kses( + sprintf( /* translators: %1$s - number of email logs found; %2$s - filtered status. */ + _n( + 'Found <strong>%1$s %2$s email log</strong>', + 'Found <strong>%1$s %2$s email logs</strong>', + absint( $table->get_pagination_arg( 'total_items' ) ), + 'wp-mail-smtp-pro' + ), + absint( $table->get_pagination_arg( 'total_items' ) ), + $status !== false && isset( $table->get_statuses()[ $status ] ) ? $table->get_statuses()[ $status ] : '' + ), + [ + 'strong' => [], + ] + ); + ?> + + <?php foreach ( $this->get_filters_html() as $id => $html ) : ?> + <?php + echo wp_kses( + $html, + [ 'em' => [] ] + ); + ?> + <i class="reset dashicons dashicons-dismiss" data-scope="<?php echo esc_attr( $id ); ?>"></i> + <?php endforeach; ?> + </div> + <?php + } + + $table->search_box( + esc_html__( 'Search Emails', 'wp-mail-smtp-pro' ), + Area::SLUG . '-logs-archive-search-input' + ); + + $table->views(); + $table->display(); + } + } else { + $this->display_logging_disabled(); + } + ?> + </form> + </div> + + <?php + } + + /** + * Get email logs list table. + * + * @since 2.8.0 + * + * @return Table + */ + public function get_table() { + + if ( $this->table === null ) { + $this->table = new Table(); + } + + return $this->table; + } + + /** + * Return an array with information (HTML and id) for each filter for this current view. + * + * @since 2.8.0 + * + * @return array + */ + public function get_filters_html() { + + $filters = [ + '.search-box' => $this->get_filter_search_html(), + '.wp-mail-smtp-filter-date' => $this->get_filter_date_html(), + ]; + + return array_filter( $filters ); + } + + /** + * Return HTML with information about the search filter. + * + * @since 2.8.0 + * + * @return string + */ + protected function get_filter_search_html() { + + $table = $this->get_table(); + $search_parts = $table->get_filtered_search_parts(); + + if ( $search_parts === false ) { + return ''; + } + + $place = ''; + $verb = ''; + $term = $search_parts['term']; + + switch ( $search_parts['place'] ) { + case 'people': + $place = esc_html__( 'Email Address', 'wp-mail-smtp-pro' ); + $verb = esc_html__( 'is', 'wp-mail-smtp-pro' ); + break; + + case 'headers': + $place = esc_html__( 'Subject or Headers', 'wp-mail-smtp-pro' ); + $verb = esc_html__( 'contains', 'wp-mail-smtp-pro' ); + break; + + case 'content': + $place = esc_html__( 'Content', 'wp-mail-smtp-pro' ); + $verb = esc_html__( 'contains', 'wp-mail-smtp-pro' ); + break; + + case 'error_text': + $place = esc_html__( 'Error Message', 'wp-mail-smtp-pro' ); + $verb = esc_html__( 'contains', 'wp-mail-smtp-pro' ); + break; + + case 'source': + $place = esc_html__( 'Source', 'wp-mail-smtp-pro' ); + $verb = esc_html__( 'contains', 'wp-mail-smtp-pro' ); + break; + } + + return sprintf( /* translators: %1$s - field name; %2$s - verb; %3$s - term. */ + __( 'where %1$s %2$s "%3$s"', 'wp-mail-smtp-pro' ), + '<em>' . $place . '</em>', + $verb, + '<em>' . esc_html( $term ) . '</em>' + ); + } + + /** + * Return HTML with information about the date filter. + * + * @since 2.8.0 + * + * @return string + */ + public function get_filter_date_html() { + + $table = $this->get_table(); + $dates = $table->get_filtered_dates(); + + if ( $dates === false ) { + return ''; + } + + $dates = array_map( + function ( $date ) { + + return date_i18n( 'M j, Y', strtotime( $date ) ); + }, + $dates + ); + + $html = ''; + + switch ( count( $dates ) ) { + case 1: + $html = sprintf( /* translators: %s - Date. */ + esc_html__( 'on %s', 'wp-mail-smtp-pro' ), + '<em>' . $dates[0] . '</em>' + ); + break; + case 2: + $html = sprintf( /* translators: %1$s - Date. %2$s - Date. */ + esc_html__( 'between %1$s and %2$s', 'wp-mail-smtp-pro' ), + '<em>' . $dates[0] . '</em>', + '<em>' . $dates[1] . '</em>' + ); + break; + } + + return $html; + } + + /** + * Remove unnecessary $_GET parameters for shorter URL. + * + * @since 2.8.0 + */ + protected function remove_get_parameters() { + + if ( isset( $_SERVER['REQUEST_URI'] ) ) { + $_SERVER['REQUEST_URI'] = remove_query_arg( + [ + '_wp_http_referer', + '_wpnonce', + 'wp-mail-smtp-delete-log-entries-nonce', + ], + $_SERVER['REQUEST_URI'] // phpcs:ignore WordPress.Security.ValidatedSanitizedInput + ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PageAbstract.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PageAbstract.php new file mode 100644 index 00000000..647528a7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PageAbstract.php @@ -0,0 +1,147 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Logs\Admin; + +use WPMailSMTP\Admin\Area; +use WPMailSMTP\Pro\Emails\Logs\Migration; +use WPMailSMTP\WP; + +/** + * Class PageAbstract to handle Logs pages specific needs. + * + * @since 1.5.0 + */ +abstract class PageAbstract extends \WPMailSMTP\Admin\PageAbstract { + + /** + * @since 1.5.0 + * + * @var string Slug of a page. + */ + protected $slug = 'logs'; + + /** + * Title of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_title() { + return $this->get_label(); + } + + /** + * Get the page/tab link. + * + * @since 1.5.0 + * + * @return string + */ + public function get_link() { + + return add_query_arg( + 'page', + Area::SLUG . '-' . $this->slug, + WP::admin_url( 'admin.php' ) + ); + } + + /** + * Notify user that email logging is disabled. + * + * @since 1.5.0 + */ + public function display_logging_disabled() { + + ?> + <div class="wp-mail-smtp-logs-note"> + <h2><?php esc_html_e( 'Email Log is Not Enabled', 'wp-mail-smtp-pro' ); ?></h2> + <p> + <?php + esc_html_e( 'Emails sent when logging is disabled are not stored in the database and will not display when enabled. ', 'wp-mail-smtp-pro' ); + ?> + </p> + <a href="<?php echo esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '&tab=logs' ) ); ?>" class="wp-mail-smtp-btn wp-mail-smtp-btn-orange wp-mail-smtp-btn-md"> + <?php esc_html_e( 'Enable Email Log', 'wp-mail-smtp-pro' ); ?> + </a> + </div> + <?php + } + + /** + * Notify user that email logging is not installed correctly. + * + * @since 1.7.0 + * @since 2.2.0 Display the error logged for the Email log table creation. + */ + public function display_logging_not_installed() { + + $error_message = get_option( Migration::ERROR_OPTION_NAME ); + + $create_missing_tables_url = wp_nonce_url( + add_query_arg( + [ + 'create-missing-db-tables' => 1, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ), + Area::SLUG . '-create-missing-db-tables' + ); + + $contact_support_url = wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/account/support/', + [ + 'medium' => 'email-logs', + 'content' => 'Email Log not installed correctly', + ] + ); + ?> + + <div class="wp-mail-smtp-logs-note errored"> + <h2><?php esc_html_e( 'Email Logging is Not Installed Correctly', 'wp-mail-smtp-pro' ); ?></h2> + + <p> + <?php + if ( ! empty( $error_message ) ) { + echo wp_kses( + sprintf( /* translators: %1$s - create missing tables link; %2$s - contact support link. */ + __( '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 <a href="%1$s">create the missing DB tables by clicking on this link</a>. If this issue persists, please <a href="%2$s" target="_blank" rel="noopener noreferrer">contact our support</a> and provide the error message below:', 'wp-mail-smtp-pro' ), + esc_url( $create_missing_tables_url ), + esc_url( $contact_support_url ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + echo '<br><br>'; + echo '<code>' . esc_html( $error_message ) . '</code>'; + } else { + echo wp_kses( + sprintf( /* translators: %1$s - create missing tables link; %2$s - contact support link. */ + __( '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 <a href="%1$s">create the missing DB tables by clicking on this link</a>. If this issue persists, please <a href="%2$s" target="_blank" rel="noopener noreferrer">contact our support</a>.', 'wp-mail-smtp-pro' ), + esc_url( $create_missing_tables_url ), + esc_url( $contact_support_url ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + } + ?> + <br><br> + <?php esc_html_e( 'Right now all sent emails are not logged.', 'wp-mail-smtp-pro' ); ?> + </p> + </div> + + <?php + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PrintPreview.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PrintPreview.php new file mode 100644 index 00000000..062f9b20 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/PrintPreview.php @@ -0,0 +1,333 @@ +<?php + +namespace WPMailSMTP\Pro\Emails\Logs\Admin; + +use WPMailSMTP\WP; +use WPMailSMTP\Admin\Area; +use WPMailSMTP\Pro\Emails\Logs\Email; + +/** + * Print preview for email. + * + * @since 2.8.0 + */ +class PrintPreview { + + /** + * Email object. + * + * @since 2.8.0 + * + * @var Email + */ + protected $email; + + /** + * Hooks. + * + * @since 2.8.0 + */ + public function hooks() { + + add_action( 'admin_init', [ $this, 'print_html' ], 1 ); + } + + /** + * Check if current page request meets requirements for email print preview page. + * + * @since 2.8.0 + * + * @return bool + */ + public function is_print_page() { + + $mode = isset( $_GET['mode'] ) ? sanitize_key( $_GET['mode'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $email_id = isset( $_GET['email_id'] ) ? absint( $_GET['email_id'] ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) || $mode !== 'print' || ! $email_id ) { + return false; + } + + $this->email = new Email( $email_id ); + + // Check is valid email was found. + if ( $this->email->get_id() === 0 ) { + return false; + } + + return true; + } + + /** + * Output HTML markup for the email print preview page. + * + * @since 2.8.0 + */ + public function print_html() { + + if ( ! $this->is_print_page() ) { + return; + } + + if ( ! current_user_can( wp_mail_smtp()->get_admin()->get_logs_access_capability() ) ) { + wp_die( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) ); + } + + ?> + <!doctype html> + <html> + <head> + <meta charset="<?php bloginfo( 'charset' ); ?>"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + <title><?php esc_html_e( 'WP Mail SMTP - Email Details', 'wp-mail-smtp-pro' ); ?> + + + + + + + + + + + + email ); + ?> + + + +
    + + email ); + ?> + +

      +
    + + +
    +

    +
    + +
    + + email ); + ?> + +
    + +
    +

    +

    + email->get_date_sent()->format( WP::datetime_mysql_format() ) ) ) + ) + ); + ?> +

    +
    + + +
    +

    +

    + email->get_people( 'from' ); + + if ( ! empty( $data ) ) { + $people_from = $data; + } else { + $people_from = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + echo esc_html( $people_from ); + ?> +

    +
    + + +
    +

    +

    + email->get_people( 'to' ); + + if ( ! empty( $data ) ) { + $people_to = implode( ', ', $data ); + } else { + $people_to = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + echo esc_html( $people_to ); + ?> +

    +
    + + + email->get_people( 'cc' ); + + if ( ! empty( $data ) ) : + ?> +
    +

    +

    +
    + + + + email->get_people( 'bcc' ); + + if ( ! empty( $data ) ) : + ?> +
    +

    +

    +
    + + + +
    +

    +

    email->get_subject() ); ?>

    +
    + + +
    +

    +

    email->get_status_name() ); ?>

    +
    + + + email->get_mailer() ) ) : + $provider = wp_mail_smtp()->get_providers()->get_options( $this->email->get_mailer() ); + ?> +
    +

    +

    + get_title(); + } else { + $mailer_name = $this->email->get_mailer(); + } + + if ( $this->email->get_header( 'X-WP-Mail-SMTP-Connection-Type' ) === 'backup' ) { + $mailer_name .= ' ' . esc_html__( '(backup)', 'wp-mail-smtp-pro' ); + } + + echo esc_html( $mailer_name ); + ?> +

    +
    + + +
    + + email ); + ?> + + email->get_content() ) ) : + $preview_url = add_query_arg( + [ + 'email_id' => $this->email->get_id(), + 'mode' => 'preview', + ], + wp_nonce_url( + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ), + 'wp_mail_smtp_pro_logs_log_preview' + ) + ); + ?> + + + + email ); + ?> + +
    +

    + +

    + + + options = Options::init(); + } + + /** + * Link label of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'Email Log', 'wp-mail-smtp-pro' ); + } + + /** + * Title of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_title() { + + return $this->get_label(); + } + + /** + * Tab content. + * + * @since 1.5.0 + */ + public function display() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + ?> +
    + wp_nonce_field(); ?> + + +
    +
    +

    get_title(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

    +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[logs][enabled]', + 'id' => 'wp-mail-smtp-setting-logs_enabled', + 'value' => 'true', + 'checked' => (bool) $this->options->get( 'logs', 'enabled' ), + 'disabled' => $this->options->is_const_defined( 'logs', 'enabled' ), + ] + ); + ?> +

    + +

    +

    + options->is_const_defined( 'logs', 'enabled' ) ) { + echo '
    ' . $this->options->get_const_set_message( 'WPMS_LOGS_ENABLED' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> +

    +
    +
    + + + + + + + + + + + + + + + webhooks_status(); ?> + + +
    +
    + +
    +
    + +

    + options->is_const_defined( 'logs', 'log_retention_period' ) ) { + echo '
    ' . $this->options->get_const_set_message( 'WPMS_LOGS_LOG_RETENTION_PERIOD' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> +

    +
    +
    + + + + + display_save_btn(); ?> + +
    + + get_pro()->get_logs()->is_enabled() || ! Webhooks::is_allowed() ) { + return; + } + + $provider = wp_mail_smtp()->get_pro()->get_logs()->get_webhooks()->get_active_provider(); + + if ( $provider === false ) { + return; + } + + // Verify subscription before display status. + $provider->verify_subscription(); + + ?> +
    +
    + +
    +
    + get_setup_status() === Webhooks::SUCCESS_SETUP ) : ?> + + +

    + + options->is_const_enabled() ) : ?> +

    + + + + +

    +
    + + get_setup_status() === Webhooks::BROKEN_SETUP ) : ?> + +

    + +
      +
    1. +
    2. + get_providers()->get_options( $provider->get_mailer_name() )->get_title() ) + ); + ?> +
    3. +
    +
    + + get_setup_status() === Webhooks::FAILED_SETUP ) : ?> + +

    + +

    +
    + +

    + +

    + + get_setup_status() === Webhooks::MANUAL_SETUP ) : ?> + +

    + +

    +
    + + +
    +
    + options->set( $data, false, false ); + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } + + /** + * Get log retention period options. + * + * @since 2.8.0 + * + * @return array + */ + public function get_log_retention_period_options() { + + $options = [ + 86400 => esc_html__( '1 Day', 'wp-mail-smtp-pro' ), + 604800 => esc_html__( '1 Week', 'wp-mail-smtp-pro' ), + 2628000 => esc_html__( '1 Month', 'wp-mail-smtp-pro' ), + 15770000 => esc_html__( '6 Months', 'wp-mail-smtp-pro' ), + 31540000 => esc_html__( '1 Year', 'wp-mail-smtp-pro' ), + ]; + + $log_retention_period = $this->options->get( 'logs', 'log_retention_period' ); + + // Check if defined value already in list and add it if not. + if ( + ! empty( $log_retention_period ) && + ! isset( $options[ $log_retention_period ] ) + ) { + $log_retention_period_days = floor( $log_retention_period / DAY_IN_SECONDS ); + + $options[ $log_retention_period ] = sprintf( + /* translators: %d - days count. */ + _n( '%d Day', '%d Days', $log_retention_period_days, 'wp-mail-smtp-pro' ), + $log_retention_period_days + ); + + ksort( $options ); + } + + /** + * Filter log retention period options. + * + * @since 2.8.0 + * + * @param array $options Log retention period options. + * Option key in seconds and value in human readable time period. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_admin_settings_tab_get_log_retention_period_options', + $options + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/SinglePage.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/SinglePage.php new file mode 100644 index 00000000..a07d5ceb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/SinglePage.php @@ -0,0 +1,779 @@ +hooks(); + } + + /** + * Register hooks. + * + * @since 2.8.0 + */ + public function hooks() { + + // Output single Email Log content and sidebar metabox. + add_action( 'wp_mail_smtp_pro_emails_logs_admin_single_page_display_content', [ $this, 'email_details' ], 10 ); + add_action( 'wp_mail_smtp_pro_emails_logs_admin_single_page_display_content', [ $this, 'email_extra_details' ], 20 ); + add_action( 'wp_mail_smtp_pro_emails_logs_admin_single_page_display_sidebar', [ $this, 'email_meta' ], 10 ); + add_action( 'wp_mail_smtp_pro_emails_logs_admin_single_page_display_sidebar', [ $this, 'email_actions' ], 20 ); + add_action( 'wp_mail_smtp_pro_emails_logs_admin_single_page_display_sidebar', [ $this, 'email_attachments' ], 30 ); + } + + /** + * Link label of a tab. + * + * @since 1.5.0 + * + * @return string + */ + public function get_label() { + return esc_html__( 'View Email', 'wp-mail-smtp-pro' ); + } + + /** + * Tab content. + * + * @since 1.5.0 + */ + public function display() { + ?> + +
    + + get_label() ); ?> + + + + + +
    + +

    + get_label() ); ?> +

    + +
    + + pro->get_logs()->is_enabled() ) { + if ( ! wp_mail_smtp()->pro->get_logs()->is_valid_db() ) { + $this->display_logging_not_installed(); + } else { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $this->email = new Email( intval( $_GET['email_id'] ) ); + + if ( $this->email->is_valid() ) { + ?> +
    + +
    + + +
    + email, $this ); + ?> +
    + + +
    + email, $this ); + ?> +
    + +
    + +
    + display_error_happened(); + } + } + } else { + $this->display_logging_disabled(); + } + ?> + +
    + + +
    + +
    +

    +
    + +
    +
      + +
    • +
    • + get_date_sent()->format( WP::datetime_mysql_format() ) ) ) + ) + ); + ?> +
    • + + +
    • +
    • + get_people( 'from' ); + + if ( ! empty( $data ) ) { + $people_from = $data; + } else { + $people_from = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + echo esc_html( $people_from ); + ?> +
    • + + +
    • +
    • + get_people( 'to' ); + + if ( ! empty( $data ) ) { + $people_to = implode( ', ', $data ); + } else { + $people_to = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + echo esc_html( $people_to ); + ?> +
    • + + + get_people( 'cc' ); + + if ( ! empty( $data ) ) : + ?> +
    • +
    • + + + + get_people( 'bcc' ); + + if ( ! empty( $data ) ) : + ?> +
    • +
    • + + + +
    • +
    • + get_subject() ); ?> +
    • + +
    +
    + +
    + +
    + +
    +

    + + + has_error() ) : ?> + <?php esc_attr_e( 'Error icon', 'wp-mail-smtp' ); ?> + + +

    +
    + +
    +
    + +
    + + has_error() ) { + $error_code = $email->get_error_code(); + $mailer_slug = $email->get_mailer(); + + if ( ! empty( $error_code ) && ! empty( $mailer_slug ) ) { + $troubleshoot_url = (string) ( new Registry() )->doc_url_for( $mailer_slug, $error_code ); + } + } + ?> + + + + + + + + +

    +
    +					get_headers() ) ? implode( "\r\n", (array) json_decode( $email->get_headers() ) ) : '';
    +					echo esc_html( trim( $tech ) );
    +					?>
    +				
    + + get_initiator_file() ) ) : ?> +

    +
    +						get_initiator_name() ); ?> get_initiator_file() ); ?>
    +					
    + + + has_error() ) : ?> + + + +
    +
    + +
    + +
    +

    + +

    +
    + +
    + +
      +
    • + get_status() ) { + $label = '' . esc_html__( 'Delivered', 'wp-mail-smtp-pro' ) . ''; + $icon_classes = 'wp-mail-smtp-dashicons-yes-alt-green status delivered'; + } elseif ( Email::STATUS_SENT === $email->get_status() ) { + $label = '' . esc_html__( 'Sent', 'wp-mail-smtp-pro' ) . ''; + $icon_classes = 'status dot sent'; + } elseif ( Email::STATUS_WAITING === $email->get_status() ) { + $label = '' . esc_html__( 'Waiting for confirmation', 'wp-mail-smtp-pro' ) . ''; + $icon_classes = 'status circle waiting'; + } else { + $label = '' . esc_html__( 'Not Sent', 'wp-mail-smtp-pro' ) . ''; + $icon_classes = 'status dot notsent'; + } + ?> + + + + +
    • + + get_mailer() ) ) { + $provider = wp_mail_smtp()->get_providers()->get_options( $email->get_mailer() ); + + ?> +
    • + + ' . esc_html( $provider->get_title() ) . ''; + } else { + $mailer_name = '' . esc_html( $email->get_mailer() ) . ''; + } + + if ( $email->get_header( 'X-WP-Mail-SMTP-Connection-Type' ) === 'backup' ) { + $mailer_name .= ' ' . esc_html__( '(backup)', 'wp-mail-smtp-pro' ); + } + + printf( + /* translators: %s - name of the mailer. */ + esc_html__( 'Mailer: %s', 'wp-mail-smtp-pro' ), + $mailer_name // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + ?> +
    • + + +
    • + + ' . (int) $email->get_attachments() . '' + ); + ?> +
    • +
    • + + ' . (int) $email->get_id() . '' + ); + ?> +
    • + get_pro()->get_logs()->is_enabled_open_email_tracking() ) : ?> +
    • + + ' . $this->display_was_email_already_opened() . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + ?> +
    • + + get_pro()->get_logs()->is_enabled_click_link_tracking() ) : ?> +
    • + + ' . $this->display_was_email_link_already_clicked() . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + ?> +
    • + + get_initiator_name() ) ) : ?> +
    • + + ' . esc_html( $email->get_initiator_name() ) . '' + ); + ?> +
    • + +
    + +
    + + get_pro()->get_logs()->get_manage_capability() ) ) : ?> +
    + $email->get_id(), + 'mode' => 'delete', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ), + 'wp_mail_smtp_pro_logs_log_delete' + ); + ?> + +
    + + +
    + get_content() ) ) { + // Register WP built-in Thickbox for popup. + add_thickbox(); + + $preview_url = add_query_arg( + [ + 'email_id' => $email->get_id(), + 'mode' => 'preview', + 'TB_iframe' => true, + 'width' => 600, + 'height' => '', + ], + wp_nonce_url( wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ), 'wp_mail_smtp_pro_logs_log_preview' ) + ); + ?> + + +
    + +
    +
    + +
    + +
    + 'print', + 'email_id' => $email->get_id(), + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ); + + $action_links = [ + 'print' => [ + 'url' => $print_url, + 'target' => 'blank', + 'icon' => 'dashicons-media-text', + 'label' => esc_html__( 'Print', 'wp-mail-smtp-pro' ), + ], + 'export' => [ + 'url' => $this->get_export_url( $email->get_id(), 'csv' ), + 'icon' => 'dashicons-migrate', + 'label' => esc_html__( 'Export (CSV)', 'wp-mail-smtp-pro' ), + ], + 'export_xlsx' => [ + 'url' => $this->get_export_url( $email->get_id(), 'xlsx' ), + 'icon' => 'dashicons-media-spreadsheet', + 'label' => esc_html__( 'Export (XLSX)', 'wp-mail-smtp-pro' ), + ], + 'export_eml' => [ + 'url' => $this->get_export_url( $email->get_id(), 'eml' ), + 'icon' => 'dashicons-email', + 'label' => esc_html__( 'Export (EML)', 'wp-mail-smtp-pro' ), + ], + ]; + + if ( ! empty( $email->get_content() ) ) { + $action_links['resend'] = [ + 'url' => '#', + 'icon' => 'dashicons-update', + 'label' => esc_html__( 'Resend', 'wp-mail-smtp-pro' ), + ]; + } + + if ( + $email->is_waiting_for_delivery_verification() && + ! Helpers::mailer_without_send_confirmation() + ) { + $recheck_email_status_link = add_query_arg( + [ + 'mode' => 'view', + 'email_id' => $email->get_id(), + 'recheck_email_status' => wp_create_nonce( RecheckDeliveryStatus::SINGLE_LOG_NONCE_ACTION ), + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ); + + $action_links['recheck_email_status'] = [ + 'url' => $recheck_email_status_link, + 'icon' => 'dashicons-yes', + 'label' => esc_html__( 'Re-check email status', 'wp-mail-smtp-pro' ), + ]; + } + + /** + * Filters single email log actions. + * + * @since 2.8.0 + * + * @param array $action_links Actions. + * @param Email $email Email. + */ + $action_links = apply_filters( 'wp_mail_smtp_pro_emails_logs_admin_single_page_email_actions', $action_links, $email ); + ?> +
    +
    +

    +
    +
    + +
    +
    + get_attachments( $email->get_id() ); + + if ( empty( $attachments ) ) { + return false; + } + + /** + * Filters single email log attachments. + * + * @since 2.9.0 + * + * @param array $attachments The array of Attachment objects. + * @param Email $email Email. + */ + $attachments = apply_filters( 'wp_mail_smtp_pro_emails_logs_admin_single_page_attachments', $attachments, $email ); + ?> + + email_details( $this->email ); + $this->email_extra_details( $this->email ); + } + + /** + * Display the sidebar content of the page. + * + * @deprecated 2.8.0 Use wp_mail_smtp_pro_emails_logs_admin_single_page_sidebar action. + * + * @since 1.5.0 + */ + public function display_content_side() { + _deprecated_function( __METHOD__, '2.8.0' ); + + $this->email_meta( $this->email ); + } + + /** + * Display a generic error message that something went wrong. + * + * @since 1.5.0 + */ + public function display_error_happened() { + ?> + +
    +

    +

    + +

    +

    + +

    +
    + + 'export', + 'action' => 'wp_mail_smtp_tools_export_single_email_log', + 'email_id' => $email_id, + 'export_type' => $type, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) + ), + 'wp-mail-smtp-tools-export-single-email-log-nonce', + 'nonce' + ); + } + + /** + * Display whether email was opened. + * + * @since 2.9.0 + * + * @return string + */ + private function display_was_email_already_opened() { + + if ( ! $this->email->is_content_type_html_based() ) { + return esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return ( new OpenEmailEvent( $this->email->get_id() ) )->was_event_already_triggered() ? + esc_html__( 'Yes', 'wp-mail-smtp-pro' ) : + esc_html__( 'No', 'wp-mail-smtp-pro' ); + } + + /** + * Display whether one of email links was clicked. + * + * @since 2.9.0 + * + * @return string + */ + private function display_was_email_link_already_clicked() { + + if ( ! $this->email->is_content_type_html_based() ) { + return esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return ( new ClickLinkEvent( $this->email->get_id() ) )->was_event_already_triggered() ? + esc_html__( 'Yes', 'wp-mail-smtp-pro' ) : + esc_html__( 'No', 'wp-mail-smtp-pro' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/Table.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/Table.php new file mode 100644 index 00000000..ffd76ade --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Admin/Table.php @@ -0,0 +1,1023 @@ +options = Options::init(); + + // Set parent defaults. + parent::__construct( + array( + 'singular' => 'email', + 'plural' => 'emails', + 'ajax' => false, + 'screen' => 'wp-mail-smtp_page_wp-mail-smtp-logs', + ) + ); + } + + /** + * Get the email log statuses for filtering purpose. + * + * @since 2.7.0 + * + * @return array Associative array of email log statuses StatusCode=>Name. + */ + public function get_statuses() { + + // In this order statuses will appear in filters bar. + $statuses = [ + Email::STATUS_DELIVERED => __( 'Delivered', 'wp-mail-smtp-pro' ), + Email::STATUS_SENT => __( 'Sent', 'wp-mail-smtp-pro' ), + Email::STATUS_WAITING => __( 'Awaiting Confirmation', 'wp-mail-smtp-pro' ), + Email::STATUS_UNSENT => __( 'Failed', 'wp-mail-smtp-pro' ), + ]; + + // Add the Blocked status only if the "Log Blocked Emails" option is enabled. + if ( Options::init()->get( 'logs', 'log_blocked_emails' ) ) { + $statuses[ Email::STATUS_BLOCKED ] = __( 'Blocked', 'wp-mail-smtp-pro' ); + } + + // Exclude Delivered and Pending statuses for mailers without verification API. + if ( Helpers::mailer_without_send_confirmation() ) { + unset( $statuses[ Email::STATUS_DELIVERED ] ); + unset( $statuses[ Email::STATUS_WAITING ] ); + } + + return $statuses; + } + + /** + * Get the items counts for various statuses of email log. + * + * @since 2.7.0 + */ + public function get_counts() { + + $this->counts = []; + + // Base params with applied filters. + $base_params = $this->get_filters_query_params(); + + $total_params = $base_params; + unset( $total_params['status'] ); + $this->counts['total'] = ( new EmailsCollection( $total_params ) )->get_count(); + + foreach ( $this->get_statuses() as $status => $name ) { + $collection = new EmailsCollection( array_merge( $base_params, [ 'status' => $status ] ) ); + + $this->counts[ 'status_' . $status ] = $collection->get_count(); + } + + /** + * Filters items counts by various statuses of email log. + * + * @since 2.7.0 + * + * @param array $counts { + * Items counts by statuses. + * + * @type integer $total Total items count. + * @type integer $status_{$status_key} Items count by status. + * } + */ + $this->counts = apply_filters( 'wp_mail_smtp_pro_emails_logs_admin_table_get_counts', $this->counts ); + } + + /** + * Retrieve the view statuses. + * + * @since 2.7.0 + */ + public function get_views() { + + $base_url = $this->get_filters_base_url(); + $current_status = $this->get_filtered_status(); + + $views = []; + + $views['all'] = sprintf( + '%3$s (%4$d)', + esc_url( remove_query_arg( 'status', $base_url ) ), + $current_status === false ? 'class="current"' : '', + esc_html__( 'All', 'wp-mail-smtp-pro' ), + intval( $this->counts['total'] ) + ); + + foreach ( $this->get_statuses() as $status => $status_label ) { + + $count = intval( $this->counts[ 'status_' . $status ] ); + + // Skipping status with no emails. + if ( $count === 0 && $current_status !== $status ) { + continue; + } + + $views[ $status ] = sprintf( + '%3$s (%4$d)', + esc_url( add_query_arg( 'status', $status, $base_url ) ), + $current_status === $status ? 'class="current"' : '', + esc_html( $status_label ), + $count + ); + + } + + /** + * Filters items views. + * + * @since 2.7.0 + * + * @param array $views { + * Items views by statuses. + * + * @type string $all Total items view. + * @type integer $status_key Items views by status. + * } + * @param array $counts { + * Items counts by statuses. + * + * @type integer $total Total items count. + * @type integer $status_{$status_key} Items count by status. + * } + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_admin_table_get_views', $views, $this->counts ); + } + + /** + * Define the table columns. + * + * @since 1.5.0 + * + * @return array Associative array of slug=>Name columns data. + */ + public function get_columns() { + + $columns = []; + + $columns['cb'] = ''; + $columns['status'] = ''; + $columns['subject'] = esc_html__( 'Subject', 'wp-mail-smtp-pro' ); + $columns['from'] = esc_html__( 'From', 'wp-mail-smtp-pro' ); + $columns['to'] = esc_html__( 'To', 'wp-mail-smtp-pro' ); + $columns['initiator'] = esc_html__( 'Source', 'wp-mail-smtp-pro' ); + $columns['mailer'] = esc_html__( 'Mailer', 'wp-mail-smtp-pro' ); + $columns['cc'] = esc_html__( 'CC', 'wp-mail-smtp-pro' ); + $columns['bcc'] = esc_html__( 'BCC', 'wp-mail-smtp-pro' ); + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_open_email_tracking() ) { + $columns['opened'] = esc_html__( 'Opened', 'wp-mail-smtp-pro' ); + } + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_click_link_tracking() ) { + $columns['clicked'] = esc_html__( 'Clicked', 'wp-mail-smtp-pro' ); + } + + $columns['date_sent'] = esc_html__( 'Date Sent', 'wp-mail-smtp-pro' ); + + return $columns; + } + + /** + * Allow users to select multiple emails at once (to perform a bulk action, for example). + * + * @since 1.5.0 + * + * @param Email $item Email object. + * + * @return string Checkbox for bulk selection. + */ + protected function column_cb( $item ) { + + return sprintf( + '', + $item->get_id() + ); + } + + /** + * Display a nice email status: sent or not. + * + * @since 1.5.0 + * + * @param Email $item Email object. + * + * @return string Email status as a dot. + */ + public function column_status( $item ) { + + switch ( $item->get_status() ) { + case Email::STATUS_DELIVERED: + return ''; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement + + case Email::STATUS_SENT: + return ''; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement + + case Email::STATUS_WAITING: + return ''; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement + + case Email::STATUS_BLOCKED: + return ''; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement + + default: + return ''; // phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.AddEmptyLineBeforeReturnStatement + } + } + + /** + * Display Email subject. + * + * @since 1.5.0 + * + * @param Email $item Email object. + * + * @return string Email subject. + */ + public function column_subject( $item ) { + + $subject = '' . + '' . + esc_html( $item->get_subject() ) . + '' . + ''; + + // View log action. + $actions[] = ' + ' . + esc_html__( 'View Log', 'wp-mail-smtp-pro' ) . + ' + '; + + // View email action. + if ( ! empty( $item->get_content() ) ) { + $actions[] = ' + + '; + } + + // Delete action. + if ( current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) ) { + $actions[] = ' + ' . + esc_html__( 'Delete', 'wp-mail-smtp-pro' ) . + ' + '; + } + + return $subject . '
    ' . implode( ' | ', $actions ) . '
    '; + } + + /** + * Get the link to a certain action: "edit" or "delete" for now. + * + * @since 1.5.0 + * + * @param Email $item Email object. + * @param string $link The link type to create. + * + * @return string + */ + protected function get_item_link( $item, $link = 'edit' ) { + + $url = ''; + $link = sanitize_key( $link ); + + switch ( $link ) { + case 'edit': + $url = add_query_arg( + [ + 'email_id' => $item->get_id(), + 'mode' => 'view', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ); + break; + + case 'view': + $url = add_query_arg( + [ + 'email_id' => $item->get_id(), + 'mode' => 'preview', + 'TB_iframe' => true, + 'width' => 600, + 'height' => '', + ], + wp_nonce_url( wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ), 'wp_mail_smtp_pro_logs_log_preview' ) + ); + break; + + case 'delete': + $url = wp_nonce_url( + add_query_arg( + [ + 'email_id' => $item->get_id(), + 'mode' => 'delete', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ), + 'wp_mail_smtp_pro_logs_log_delete' + ); + break; + } + + /** + * Filters email log link. + * + * @since 2.9.0 + * + * @param string $url Item link. + * @param Email $item Email instance. + * @param string $link Link type. + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_admin_table_get_item_link', $url, $item, $link ); + } + + /** + * Display FROM email address. + * + * @since 1.5.0 + * @since 1.7.1 Added special processing for Gmail/Outlook mailers. + * + * @param Email $item Email object. + * + * @return string Email recipient(s). + */ + public function column_from( $item ) { + + $from_email = $this->generate_email_search_link( $item->get_people( 'from' ) ); + + if ( empty( $from_email ) ) { + $from_email = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return $from_email; + } + + /** + * Display TO email addresses. + * + * @since 1.5.0 + * + * @param Email $item Email object. + * + * @return string Email recipient(s), comma separated. + */ + public function column_to( $item ) { + + $to_emails = $item->get_people( 'to' ); + + foreach ( $to_emails as $key => $email ) { + $to_emails[ $key ] = $this->generate_email_search_link( $email ); + } + + if ( ! empty( $to_emails ) ) { + $to_emails = implode( ', ', $to_emails ); + } else { + $to_emails = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return $to_emails; + } + + /** + * Display name of the plugin/theme (or WP core) that initiated/called the `wp_mail` function. + * + * @since 3.0.0 + * + * @param Email $item Email object. + * + * @return string + */ + public function column_initiator( $item ) { + + return esc_html( $item->get_initiator_name() ); + } + + /** + * Display the mailer name. + * + * @since 3.7.0 + * + * @param Email $item Email object. + * + * @return string + */ + public function column_mailer( $item ) { + + static $providers = []; + + $mailer_slug = $item->get_mailer(); + + if ( ! isset( $providers[ $mailer_slug ] ) ) { + $providers[ $mailer_slug ] = wp_mail_smtp()->get_providers()->get_options( $mailer_slug ); + } + + $provider = $providers[ $mailer_slug ]; + + if ( $provider !== null ) { + $mailer_name = $provider->get_title(); + } else { + $mailer_name = $mailer_slug; + } + + if ( $item->get_header( 'X-WP-Mail-SMTP-Connection-Type' ) === 'backup' ) { + $mailer_name .= ' ' . esc_html__( '(backup)', 'wp-mail-smtp-pro' ); + } + + return esc_html( $mailer_name ); + } + + /** + * Display CC email addresses. + * + * @since 3.1.0 + * + * @param Email $item Email object. + * + * @return string Email CC recipient(s), comma separated. + */ + public function column_cc( $item ) { + + $cc_emails = $item->get_people( 'cc' ); + + foreach ( $cc_emails as $key => $email ) { + $cc_emails[ $key ] = $this->generate_email_search_link( $email ); + } + + if ( ! empty( $cc_emails ) ) { + $cc_emails = implode( ', ', $cc_emails ); + } else { + $cc_emails = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return $cc_emails; + } + + /** + * Display BCC email addresses. + * + * @since 3.1.0 + * + * @param Email $item Email object. + * + * @return string Email BCC recipient(s), comma separated. + */ + public function column_bcc( $item ) { + + $bcc_emails = $item->get_people( 'bcc' ); + + foreach ( $bcc_emails as $key => $email ) { + $bcc_emails[ $key ] = $this->generate_email_search_link( $email ); + } + + if ( ! empty( $bcc_emails ) ) { + $bcc_emails = implode( ', ', $bcc_emails ); + } else { + $bcc_emails = esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return $bcc_emails; + } + + /** + * Display whether email was opened. + * + * @since 2.9.0 + * + * @param Email $item Email object. + * + * @return string + */ + public function column_opened( $item ) { + + if ( ! $item->is_content_type_html_based() ) { + return esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return ( new OpenEmailEvent( $item->get_id() ) )->was_event_already_triggered() ? + esc_html__( 'Yes', 'wp-mail-smtp-pro' ) : + esc_html__( 'No', 'wp-mail-smtp-pro' ); + } + + /** + * Display whether one of email links was clicked. + * + * @since 2.9.0 + * + * @param Email $item Email object. + * + * @return string + */ + public function column_clicked( $item ) { + + if ( ! $item->is_content_type_html_based() ) { + return esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return ( new ClickLinkEvent( $item->get_id() ) )->was_event_already_triggered() ? + esc_html__( 'Yes', 'wp-mail-smtp-pro' ) : + esc_html__( 'No', 'wp-mail-smtp-pro' ); + } + + /** + * Display Email date sent. + * + * @since 1.5.0 + * + * @param Email $item Email object. + * + * @return string + * @throws \Exception Date manipulation can throw an exception. + */ + public function column_date_sent( $item ) { + + $date = null; + + try { + $date = $item->get_date_sent(); + } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // We don't handle this exception as we define a default value above. + } + + if ( empty( $date ) ) { + return esc_html__( 'N/A', 'wp-mail-smtp-pro' ); + } + + return esc_html( date_i18n( WP::datetime_format(), strtotime( get_date_from_gmt( $date->format( WP::datetime_mysql_format() ) ) ) ) ); + } + + /** + * Define columns that are sortable. + * + * @since 1.5.0 + * + * @return array List of columns that should be sortable. + */ + protected function get_sortable_columns() { + + return array( + 'subject' => array( 'subject', false ), + 'date_sent' => array( 'date_sent', false ), + ); + } + + /** + * Define a list of available bulk actions. + * + * @since 1.5.0 + * + * @return array List of actions: slug=>Name. + */ + protected function get_bulk_actions() { + + $actions = []; + + if ( current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) ) { + $actions['delete'] = esc_html__( 'Delete', 'wp-mail-smtp-pro' ); + $actions['resend'] = esc_html__( 'Resend', 'wp-mail-smtp-pro' ); + $actions['recheck_email_status'] = esc_html__( 'Re-check status', 'wp-mail-smtp-pro' ); + } + + return $actions; + } + + /** + * Process the bulk actions. + * + * @since 1.5.0 + * + * @see $this->prepare_items() + */ + public function process_bulk_action() { + + switch ( $this->current_action() ) { + case 'delete': + // This case is handled in \WPMailSMTP\Pro\Emails\Logs\Logs::process_email_delete(). + break; + } + } + + /** + * Return status filter value or FALSE. + * + * @since 2.8.0 + * + * @return bool|integer + */ + public function get_filtered_status() { + + if ( ! isset( $_REQUEST['status'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + return intval( $_REQUEST['status'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + /** + * Return date filter value or FALSE. + * + * @since 2.8.0 + * + * @return bool|array + */ + public function get_filtered_dates() { + + if ( empty( $_REQUEST['date'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return false; + } + + $dates = (array) explode( ' - ', sanitize_text_field( wp_unslash( $_REQUEST['date'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + return array_map( 'sanitize_text_field', $dates ); + } + + /** + * Return search filter values or FALSE. + * + * @since 2.8.0 + * + * @return bool|array + */ + public function get_filtered_search_parts() { + + if ( empty( $_REQUEST['search']['place'] ) || empty( $_REQUEST['search']['term'] ) ) { + return false; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + return array_map( 'sanitize_text_field', array_map( 'wp_unslash', $_REQUEST['search'] ) ); + } + + /** + * Whether the emails log is filtered or not. + * + * @since 2.8.0 + * + * @return bool + */ + public function is_filtered() { + + $is_filtered = false; + + if ( + $this->get_filtered_search_parts() !== false || + $this->get_filtered_dates() !== false || + $this->get_filtered_status() !== false + ) { + $is_filtered = true; + } + + return $is_filtered; + } + + /** + * Get current filters query parameters. + * + * @since 2.7.0 + * + * @return array + */ + public function get_filters_query_params() { + + $params = [ + 'search' => $this->get_filtered_search_parts(), + 'status' => $this->get_filtered_status(), + 'date' => $this->get_filtered_dates(), + ]; + + return array_filter( + $params, + function ( $v ) { + + return $v !== false; + } + ); + } + + /** + * Get current filters base url. + * + * @since 2.7.0 + * + * @return string + */ + public function get_filters_base_url() { + + $base_url = wp_mail_smtp()->pro->get_logs()->get_admin_page_url(); + $filters_params = $this->get_filters_query_params(); + + if ( isset( $filters_params['search'] ) ) { + $base_url = add_query_arg( + [ + 'search' => [ + 'place' => $filters_params['search']['place'], + 'term' => $filters_params['search']['term'], + ], + ], + $base_url + ); + } + + if ( isset( $filters_params['status'] ) ) { + $base_url = add_query_arg( 'status', $filters_params['status'], $base_url ); + } + + if ( isset( $filters_params['date'] ) ) { + $base_url = add_query_arg( 'date', implode( ' - ', $filters_params['date'] ), $base_url ); + } + + return $base_url; + } + + /** + * Get the data, prepare pagination, process bulk actions. + * Prepare columns for display. + * + * @since 1.5.0 + * @since 1.7.0 Added search support. + */ + public function prepare_items() { + + // Retrieve count. + $this->get_counts(); + + /** + * TODO: implement. + */ + $this->process_bulk_action(); + + /* + * Prepare all the params to pass to our Collection. + * All sanitization is done in that class. + */ + $params = $this->get_filters_query_params(); + + // Total amount for pagination with WHERE clause - super quick count DB request. + $total_items = ( new EmailsCollection( $params ) )->get_count(); + + if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'subject', 'date_sent' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $params['orderby'] = sanitize_key( $_REQUEST['orderby'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + if ( ! empty( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $params['order'] = sanitize_key( $_REQUEST['order'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + $params['offset'] = ( $this->get_pagenum() - 1 ) * EmailsCollection::$per_page; + + // Get the data from the DB using parameters defined above. + $collection = new EmailsCollection( $params ); + $this->items = $collection->get(); + + /* + * Register our pagination options & calculations. + */ + $this->set_pagination_args( + [ + 'total_items' => $total_items, + 'per_page' => EmailsCollection::$per_page, + ] + ); + } + + /** + * Display the search box. + * + * @since 1.7.0 + * + * @param string $text The 'submit' button label. + * @param string $input_id ID attribute value for the search input field. + */ + public function search_box( $text, $input_id ) { + + if ( ! $this->is_filtered() && ! $this->has_items() ) { + return; + } + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + $search_place = ! empty( $_REQUEST['search']['place'] ) ? sanitize_key( $_REQUEST['search']['place'] ) : 'people'; + $search_term = ! empty( $_REQUEST['search']['term'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['search']['term'] ) ) : ''; + + if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'subject', 'date_sent' ], true ) ) { + echo ''; + } + + if ( ! empty( $_REQUEST['order'] ) ) { + $order = strtoupper( sanitize_key( $_REQUEST['order'] ) ); + $order = $order === 'ASC' ? 'ASC' : 'DESC'; + echo ''; + } + + // phpcs:enable + ?> + + + + items ) > 0; + } + + /** + * Message to be displayed when there are no items. + * + * @since 1.5.0 + * @since 1.7.0 Added a custom message for empty search results. + */ + public function no_items() { + + if ( $this->is_filtered() ) { + esc_html_e( 'No emails found.', 'wp-mail-smtp-pro' ); + } else { + esc_html_e( 'No emails have been logged for now.', 'wp-mail-smtp-pro' ); + } + } + + /** + * Displays the table and register the WP Thickbox. + * + * @since 2.3.0 + */ + public function display() { + + // Register WP built-in Thickbox for popup. + add_thickbox(); + + parent::display(); + } + + /** + * Extra controls to be displayed between bulk actions and pagination. + * + * @since 2.5.0 + * + * @param string $which Which tablenav: top or bottom. + */ + protected function extra_tablenav( $which ) { + + if ( $which !== 'top' ) { + return; + } + + $date = $this->get_filtered_dates() !== false ? implode( ' - ', $this->get_filtered_dates() ) : ''; + ?> +
    + + + + + +
    + has_items() && + current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) + ) { + wp_nonce_field( 'wp_mail_smtp_pro_delete_log_entries', 'wp-mail-smtp-delete-log-entries-nonce', false ); + printf( + '', + esc_html__( 'Delete All Logs', 'wp-mail-smtp-pro' ) + ); + } + + if ( + $this->has_items() && + $this->get_filtered_status() === Email::STATUS_WAITING && + current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) + ) { + wp_nonce_field( RecheckDeliveryStatus::ARCHIVE_NONCE_ACTION, 'wp-mail-smtp-recheck-all-email-logs-status-nonce', false ); + printf( + '', + esc_html__( 'Re-check All Email Statuses', 'wp-mail-smtp-pro' ) + ); + } + } + + /** + * Get the name of the primary column. + * Important for the mobile view. + * + * @since 2.5.0 + * + * @return string The name of the primary column. + */ + protected function get_primary_column_name() { + + return 'subject'; + } + + /** + * Generate a HTML link for searching/filtering table items by provided email. + * + * @since 1.9.0 + * + * @param string $email The email address for which to search for. + * + * @return string A HTML link with the href pointing to the table email search for the provided email. + * Or an empty string, if $email is not defined. + */ + private function generate_email_search_link( $email ) { + + if ( empty( $email ) ) { + return ''; + } + + $url = add_query_arg( + array( + 'search' => array( + 'place' => 'people', + 'term' => rawurlencode( $email ), + ), + ), + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ) + ); + + return '' . esc_html( $email ) . ''; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachment.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachment.php new file mode 100644 index 00000000..cd6984cb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachment.php @@ -0,0 +1,461 @@ +prepare( $args ); + } + + /** + * Add the attachment: + * - copy file to the plugin's uploads folder, if the attachment file is new/unique, + * - save the file details to the DB, if the attachment file is new/unique, + * - connect the attachment file to the email log. + * + * @since 2.9.0 + * + * @param string $original_attachment The original attachment file path or content. + * @param int $email_log_id The email log ID. + * @param string $original_attachment_name The original attachment file name. + * @param boolean $is_string_attachment Whether string attachment or not. + * + * @return bool + */ + public function add( $original_attachment, $email_log_id, $original_attachment_name = '', $is_string_attachment = false ) { + + $original_attachment_content = $this->get_attachment_file_content( $original_attachment, $is_string_attachment ); + + if ( $original_attachment_content === false ) { + return false; + } + + $this->hash = sanitize_key( md5( $original_attachment_content ) ); + + if ( empty( $this->hash ) ) { + return false; + } + + if ( ! $is_string_attachment && $original_attachment_name === '' ) { + $original_attachment_name = wp_basename( $original_attachment ); + } + + $original_attachment_name = sanitize_file_name( $original_attachment_name ); + $this->original_filename = $original_attachment_name; + + $existing_attachment = $this->attachment_exists( $this->hash ); + + if ( empty( $existing_attachment ) ) { + $this->path = $this->store_file( $original_attachment_content, $original_attachment_name ); + $this->id = $this->create( $this->path, $this->hash ); + } else { + $this->path = $existing_attachment['path']; + $this->id = $existing_attachment['id']; + } + + if ( empty( $this->path ) || empty( $this->id ) ) { + return false; + } + + return $this->connect_to_email_log( $email_log_id, $this->id, $original_attachment_name ); + } + + /** + * Get the attachment ID. + * + * @since 2.9.0 + * + * @return string + */ + public function get_id() { + + return $this->id; + } + + /** + * Get the attachment URL. + * + * @since 2.9.0 + * + * @return string + */ + public function get_url() { + + return $this->url; + } + + /** + * Get the attachment file path. + * + * @since 2.9.0 + * + * @return string + */ + public function get_path() { + + return $this->path; + } + + /** + * Get the attachment filename. + * + * @since 2.9.0 + * + * @return string + */ + public function get_filename() { + + return ! empty( $this->original_filename ) ? $this->original_filename : $this->filename; + } + + /** + * Get the dashicon icon for the attachment, based on the extension. + * + * @since 2.9.0 + * + * @return string + */ + public function get_icon() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + if ( empty( $this->path ) ) { + return 'dashicons-media-default'; + } + + $extension = pathinfo( $this->path, PATHINFO_EXTENSION ); + + switch ( $extension ) { + case 'jpeg': + case 'jpg': + case 'png': + case 'gif': + case 'tiff': + return 'dashicons-format-image'; + + case 'pdf': + return 'wp-mail-smtp-dashicons-pdf-gray'; + + case 'zip': + case 'tar': + case 'gz': + case 'rar': + case '7z': + return 'dashicons-media-archive'; + + default: + return 'dashicons-media-default'; + } + } + + /** + * Get the attachment's file content. + * + * @since 3.1.0 + * + * @param string $attachment The attachment file path or content. + * @param boolean $is_string_attachment Whether string attachment or not. + * + * @return string|false + */ + protected function get_attachment_file_content( $attachment, $is_string_attachment ) { + + if ( ! $is_string_attachment ) { + if ( ! file_exists( $attachment ) ) { + return false; + } + + $attachment = file_get_contents( $attachment ); + } + + return $attachment; + } + + /** + * Store the original attachment to the plugin's uploads folder. + * + * @since 2.9.0 + * + * @param string $original_file_content The attachment's original file content. + * @param string $original_filename The attachment's original file name. + * + * @return false|string + */ + protected function store_file( $original_file_content, $original_filename ) { + + $this->folder_name = sanitize_key( uniqid() ); + + $upload_folder = Attachments::get_root_uploads_directory() . $this->folder_name; + + if ( ! is_dir( $upload_folder ) ) { + wp_mkdir_p( $upload_folder ); + + // 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( Attachments::get_root_uploads_directory() ); + } + + $file_extension = pathinfo( $original_filename, PATHINFO_EXTENSION ); + $this->filename = wp_unique_filename( $upload_folder, wp_generate_password( 32, false, false ) . '.' . $file_extension ); + $upload_path = trailingslashit( $upload_folder ) . $this->filename; + + if ( file_put_contents( $upload_path, $original_file_content ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents + $this->path = $upload_path; + + return $upload_path; + } + + return false; + } + + /** + * Check if the attachment file already exists. + * + * @since 2.9.0 + * + * @param string $file_hash The md5 hash of the file content. + * + * @return array|false + */ + protected function attachment_exists( $file_hash ) { + + global $wpdb; + + $file_db_table = Attachments::get_attachment_files_table_name(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + $result = $wpdb->get_row( + $wpdb->prepare( + "SELECT id, folder, filename FROM {$file_db_table} WHERE hash = %s", + $file_hash + ), + ARRAY_A + ); + // phpcs:enable + + if ( empty( $result ) ) { + return false; + } + + $result['path'] = $this->build_attachment_path( $result['folder'], $result['filename'] ); + $result['url'] = $this->build_attachment_url( $result['folder'], $result['filename'] ); + + return $result; + } + + /** + * Create the attachment file in the DB. + * + * @since 2.9.0 + * + * @param string $attachment_path The attachment file path. + * @param string $file_hash The md5 hash of the attachment file content. + * + * @return int|false + */ + protected function create( $attachment_path, $file_hash ) { + + global $wpdb; + + $file_db_table = Attachments::get_attachment_files_table_name(); + + $result = $wpdb->insert( + $file_db_table, + [ + 'hash' => sanitize_key( $file_hash ), + 'folder' => sanitize_key( $this->folder_name ), + 'filename' => sanitize_file_name( wp_basename( $attachment_path ) ), + ], + '%s' + ); + + if ( empty( $result ) ) { + return false; + } + + return $wpdb->insert_id; + } + + /** + * Attach the attachment to the email log. + * + * @since 2.9.0 + * + * @param int $email_log_id The Email Log ID. + * @param int $attachment_id The Attachment ID. + * @param string $original_attachment_name The original attachment file name. + * + * @return bool + */ + protected function connect_to_email_log( $email_log_id, $attachment_id, $original_attachment_name ) { + + global $wpdb; + + $attachments_db_table = Attachments::get_email_attachments_table_name(); + + $result = $wpdb->insert( + $attachments_db_table, + [ + 'email_log_id' => intval( $email_log_id ), + 'attachment_id' => intval( $attachment_id ), + 'filename' => sanitize_file_name( $original_attachment_name ), + ], + [ + '%d', + '%d', + '%s', + ] + ); + + return (bool) $result; + } + + /** + * Prepare the Attachment object with passed parameters. + * + * @since 2.9.0 + * + * @param array $args The array of attachment arguments. + */ + protected function prepare( $args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( ! empty( $args['id'] ) ) { + $this->id = $args['id']; + } + + if ( ! empty( $args['hash'] ) ) { + $this->hash = $args['hash']; + } + + if ( ! empty( $args['folder'] ) ) { + $this->folder_name = $args['folder']; + } + + if ( ! empty( $args['filename'] ) ) { + $this->filename = $args['filename']; + } + + if ( ! empty( $args['original_filename'] ) ) { + $this->original_filename = $args['original_filename']; + } + + if ( ! empty( $this->folder_name ) && ! empty( $this->filename ) ) { + $this->path = $this->build_attachment_path( $this->folder_name, $this->filename ); + $this->url = $this->build_attachment_url( $this->folder_name, $this->filename ); + } + } + + /** + * Build the stored attachment file path from the folder and filename. + * + * @since 2.9.0 + * + * @param string $folder The folder where the attachment is stored in. + * @param string $filename The filename of the file. + * + * @return string + */ + private function build_attachment_path( $folder, $filename ) { + + $upload_folder = Attachments::get_root_uploads_directory(); + + return trailingslashit( $upload_folder . sanitize_key( $folder ) ) . sanitize_file_name( $filename ); + } + + /** + * Build the stored attachment file URL from the folder and filename. + * + * @since 2.9.0 + * + * @param string $folder The folder where the attachment is stored in. + * @param string $filename The filename of the file. + * + * @return string + */ + private function build_attachment_url( $folder, $filename ) { + + $upload_folder_url = Attachments::get_root_uploads_url(); + + return trailingslashit( $upload_folder_url . sanitize_key( $folder ) ) . sanitize_file_name( $filename ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachments.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachments.php new file mode 100644 index 00000000..7e717a1a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Attachments.php @@ -0,0 +1,334 @@ +is_valid_db() ) { + return []; + } + + $email_attachments_db_table = self::get_email_attachments_table_name(); + $attachment_files_db_table = self::get_attachment_files_table_name(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + $attachments_data = $wpdb->get_results( + $wpdb->prepare( + "SELECT attachment_files.id AS id, attachment_files.folder AS folder, attachment_files.filename AS filename, email_attachments.filename AS original_filename + FROM {$email_attachments_db_table} email_attachments + INNER JOIN {$attachment_files_db_table} attachment_files ON email_attachments.attachment_id = attachment_files.id + WHERE email_attachments.email_log_id = %d;", + $email_log_id + ), + ARRAY_A + ); + // phpcs:enable + + if ( empty( $attachments_data ) ) { + return []; + } + + $attachments = []; + + foreach ( $attachments_data as $attachment_args ) { + $attachments[] = new Attachment( $attachment_args ); + } + + return $attachments; + } + + /** + * Delete attachments connected to the provided email log id list. + * And also delete any attachment files from FS and DB if they are no longer connected to at least one email log. + * + * @since 2.9.0 + * + * @param string $email_log_ids A string of Email Log IDs to be deleted. + */ + public function delete_attachments( $email_log_ids ) { + + global $wpdb; + + if ( ! $this->is_valid_db() ) { + return; + } + + $log_ids_array = explode( ',', (string) $email_log_ids ); + $log_ids_array = array_map( 'intval', $log_ids_array ); + $placeholders = implode( ', ', array_fill( 0, count( $log_ids_array ), '%d' ) ); + + $email_attachments_db_table = self::get_email_attachments_table_name(); + + // Delete the DB rows of attachments connected to the provided email log ids list. + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + $number_deleted_rows = $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$email_attachments_db_table} WHERE email_log_id IN ( {$placeholders} )", + $log_ids_array + ) + ); + // phpcs:enable + + if ( ! empty( $number_deleted_rows ) ) { + $this->delete_unconnected_attachments(); + } + } + + /** + * Process the attachments of the sending email. + * + * @since 2.9.0 + * + * @param int $email_log_id The Email Log ID. + * @param array $attachments The array of attachments and their data (the attachment file path is in index 0). + * + * @return void|false + */ + public function process_attachments( $email_log_id, $attachments ) { + + if ( empty( $email_log_id ) || empty( $attachments ) ) { + return false; + } + + if ( ! wp_mail_smtp()->pro->get_logs()->is_enabled_save_attachments() || ! $this->is_valid_db() ) { + return false; + } + + foreach ( $attachments as $attachment_item ) { + + if ( empty( $attachment_item[0] ) ) { + continue; + } + + $attachment = new Attachment(); + $attachment->add( $attachment_item[0], $email_log_id, $attachment_item[2], $attachment_item[5] ); + } + } + + /** + * Delete all attachments from the DB and FS. + * + * @since 2.9.0 + */ + public function delete_all_attachments() { + + global $wpdb; + + // Truncate attachment DB tables. + if ( $this->is_valid_db() ) { + $attachment_files_table = self::get_attachment_files_table_name(); + $email_attachments_table = self::get_email_attachments_table_name(); + + $wpdb->query( "TRUNCATE TABLE `$attachment_files_table`;" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( "TRUNCATE TABLE `$email_attachments_table`;" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } + + // Remove all attachment files from the FS. + $this->remove_all_attachment_files(); + } + + /** + * Whether the attachments DB tables exist. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_valid_db() { + + global $wpdb; + + static $is_valid = null; + + // Return cached value only if tables already exists. + if ( $is_valid === true ) { + return true; + } + + $attachment_files_table = self::get_attachment_files_table_name(); + $email_attachments_table = self::get_email_attachments_table_name(); + + $files_exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $attachment_files_table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $attach_exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $email_attachments_table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + + $is_valid = $files_exists && $attach_exists; + + return $is_valid; + } + + /** + * Get the root uploads directory. + * + * @since 2.9.0 + * + * @return string + */ + public static function get_root_uploads_directory() { + + $upload_directory = Uploads::upload_dir(); + + return trailingslashit( trailingslashit( $upload_directory['path'] ) . 'attachments' ); + } + + /** + * Get the root uploads URL. + * + * @since 2.9.0 + * + * @return string + */ + public static function get_root_uploads_url() { + + $upload_directory = Uploads::upload_dir(); + + return trailingslashit( trailingslashit( $upload_directory['url'] ) . 'attachments' ); + } + + /** + * Get the attachment files DB table name. + * + * @since 2.9.0 + * + * @return string Attachment files DB table name, prefixed. + */ + public static function get_attachment_files_table_name() { + + global $wpdb; + + return $wpdb->prefix . self::BASE_ATTACHMENT_FILES_DB_NAME; + } + + /** + * Get the email attachments (connecting email logs with attachment files) DB table name. + * + * @since 2.9.0 + * + * @return string Email attachments DB table name, prefixed. + */ + public static function get_email_attachments_table_name() { + + global $wpdb; + + return $wpdb->prefix . self::BASE_EMAIL_ATTACHMENTS_DB_NAME; + } + + /** + * Delete any attachment files from FS and DB if they are no longer connected to at least one email log. + * + * @since 2.9.0 + */ + private function delete_unconnected_attachments() { + + global $wpdb; + + $email_attachments_db_table = self::get_email_attachments_table_name(); + $attachment_files_db_table = self::get_attachment_files_table_name(); + + // Get the list of attachment files without connected email logs. + $query = "SELECT attachment_files.id, attachment_files.folder, attachment_files.filename + FROM {$attachment_files_db_table} attachment_files + LEFT JOIN {$email_attachments_db_table} email_attachments ON email_attachments.attachment_id = attachment_files.id + WHERE email_attachments.attachment_id IS NULL"; + + $attachments = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + + if ( empty( $attachments ) ) { + return; + } + + // Delete attachment files from the FS and DB, without connected email logs. + foreach ( $attachments as $attachment_args ) { + $attachment = new Attachment( $attachment_args ); + $file = $attachment->get_path(); + $deleted = false; + + if ( file_exists( $file ) ) { + $deleted = unlink( $file ); + } + + if ( $deleted ) { + rmdir( dirname( $file ) ); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query( + $wpdb->prepare( "DELETE FROM {$attachment_files_db_table} WHERE id = %d", $attachment->get_id() ) + ); + // phpcs:enable + } + } + } + + /** + * Remove all attachment files and folders in our attachments folder. + * + * @since 2.9.0 + * + * @return bool + */ + private function remove_all_attachment_files() { + + $attachments_directory = self::get_root_uploads_directory(); + + if ( false === file_exists( $attachments_directory ) ) { + return false; + } + + /** Array of file objects. @var SplFileInfo[] $files */ + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( $attachments_directory, RecursiveDirectoryIterator::SKIP_DOTS ), + RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ( $files as $fileinfo ) { + if ( $fileinfo->isDir() ) { + rmdir( $fileinfo->getRealPath() ); + } else { + unlink( $fileinfo->getRealPath() ); + } + } + + return rmdir( $attachments_directory ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Cleanup.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Cleanup.php new file mode 100644 index 00000000..07ab874e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Cleanup.php @@ -0,0 +1,151 @@ +is_valid_db() ) { + return true; + } + + $wpdb = WP::wpdb(); + + // Delete orphaned email attachments. + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + $delete_email_attachment_query = $wpdb->prepare( + 'DELETE email_attachments + FROM `%1$s` email_attachments + LEFT JOIN `%2$s` email_logs + ON email_logs.id = email_attachments.email_log_id + WHERE email_logs.id IS NULL', + Attachments::get_email_attachments_table_name(), + Logs::get_table_name() + ); + // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( $delete_email_attachment_query ); + + return $this->cleanup_attachment_files(); + } + + /** + * Cleanup all orphaned attachment files. Both the DB records and physical files. + * + * @since 3.8.0 + * + * @return bool Returns `true` if all orphaned attachments files are deleted data are removed or + * if attachments DB is not valid. + */ + private function cleanup_attachment_files() { + + if ( ! ( new Attachments() )->is_valid_db() ) { + return true; + } + + $wpdb = WP::wpdb(); + $attachment_files_table = Attachments::get_attachment_files_table_name(); + + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + $get_attachments_query = $wpdb->prepare( + 'SELECT attachment_files.id, attachment_files.folder, attachment_files.filename + FROM `%1$s` attachment_files + LEFT JOIN `%2$s` email_attachments + ON email_attachments.attachment_id = attachment_files.id + WHERE email_attachments.attachment_id IS NULL + LIMIT %3$d', + $attachment_files_table, + Attachments::get_email_attachments_table_name(), + $this->get_batch_size() + ); + // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $attachments_results = $wpdb->get_results( $get_attachments_query, ARRAY_A ); + + if ( empty( $attachments_results ) ) { + return true; + } + + foreach ( $attachments_results as $att ) { + $attachment = new Attachment( $att ); + $file = $attachment->get_path(); + $should_delete_db_record = false; + + // If the file no longer exist, we can safely delete it's DB record. + if ( ! file_exists( $file ) ) { + $should_delete_db_record = true; + } elseif ( unlink( $file ) ) { + // Delete the file folder. + rmdir( dirname( $file ) ); + + // If the file exist, try to delete it. + // If we successfully delete it, we can remove the DB record. + $should_delete_db_record = true; + } + + if ( $should_delete_db_record ) { + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + $wpdb->query( + $wpdb->prepare( + 'DELETE FROM `%1$s` WHERE id = %2$d', + $attachment_files_table, + $attachment->get_id() + ) + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + } + } + + return false; + } + + /** + * Returns the number of attachment files to cleanup per batch. + * + * @since 3.8.0 + * + * @return int + */ + private function get_batch_size() { + + /** + * Filter the number of attachment files to cleanup per batch. + * + * @since 3.8.0 + * + * @param int $batch_size Number of attachment files to cleanup per batch. + */ + $batch_size = absint( apply_filters( 'wp_mail_smtp_pro_emails_logs_attachments_cleanup_get_batch_size', self::CLEANUP_ATTACHMENT_FILES_BATCH_SIZE ) ); + + return empty( $batch_size ) ? self::CLEANUP_ATTACHMENT_FILES_BATCH_SIZE : $batch_size; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Migration.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Migration.php new file mode 100644 index 00000000..fe4fd811 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Attachments/Migration.php @@ -0,0 +1,118 @@ +get_pro()->get_logs()->is_enabled_save_attachments(); + } + + /** + * Create the attachment files DB table structure. + * + * @since 2.9.0 + */ + protected function migrate_to_1() { + + global $wpdb; + + $table = Attachments::get_attachment_files_table_name(); + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE IF NOT EXISTS `$table` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `hash` VARCHAR(128) NOT NULL, + `folder` VARCHAR(64) NOT NULL, + `filename` TEXT NOT NULL, + PRIMARY KEY (id), + INDEX hash (hash) + ) + ENGINE='InnoDB' + {$charset_collate};"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + + 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 attachments DB table structure. + * + * @since 2.9.0 + */ + protected function migrate_to_2() { + + $this->maybe_required_older_migrations( 2 ); + + global $wpdb; + + $table = Attachments::get_email_attachments_table_name(); + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE IF NOT EXISTS `$table` ( + `email_log_id` INT UNSIGNED NOT NULL, + `attachment_id` INT UNSIGNED, + `filename` TEXT, + UNIQUE KEY uq_email_attachments (email_log_id, attachment_id, filename(190)), + INDEX email_log (email_log_id) + ) + ENGINE='InnoDB' + {$charset_collate};"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + + 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/CanResendEmailTrait.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/CanResendEmailTrait.php new file mode 100644 index 00000000..2495b9f5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/CanResendEmailTrait.php @@ -0,0 +1,166 @@ +get_content() ) ) { + return new WP_Error( 'empty_email_body', esc_html__( 'Email can\'t be resent with empty content.', 'wp-mail-smtp-pro' ) ); + } + + $this->processing_email = $email; + + if ( $to === null ) { + $to = $email->get_people( 'to' ); + } + + $connections_manager = wp_mail_smtp()->get_connections_manager(); + + if ( ! $connection instanceof ConnectionInterface ) { + $connection = $connections_manager->get_primary_connection(); + } + + // These headers will be set by PHPMailer and our mailer. + $exclude_headers = [ + 'To', + 'Date', + 'Message-ID', + 'X-Mailer', + 'X-Mailer-Type', + 'X-Msg-ID', + 'Subject', + 'Content-Type', + 'Content-Transfer-Encoding', // Fix SendGrid error. + 'MIME-Version', + 'X-WP-Mail-SMTP-Connection', + 'X-WP-Mail-SMTP-Connection-Type', + ]; + + $headers = array_filter( + json_decode( $email->get_headers() ), + function ( $header ) use ( $exclude_headers ) { + return ! in_array( trim( explode( ':', $header )[0] ), $exclude_headers, true ); + } + ); + + // Set content type. + $headers[] = 'Content-Type: ' . $email->get_content_type(); + + $connections_manager->set_mail_connection( $connection ); + $connections_manager->set_mail_backup_connection( false ); + + add_action( 'phpmailer_init', [ $this, 'set_attachments' ] ); + $is_sent = wp_mail( $to, $email->get_subject(), $email->get_content(), $headers ); + + remove_action( 'phpmailer_init', [ $this, 'set_attachments' ] ); + + $this->processing_email = null; + + if ( $is_sent === false ) { + $debug_message = EmailSendingDebug::get_message( $connection->get_id() ); + $error_message = ! empty( $debug_message ) + ? $debug_message + : esc_html__( 'Email failed to send.', 'wp-mail-smtp-pro' ); + + return new WP_Error( 'email_send_error', $error_message ); + } + + return $is_sent; + } + + /** + * Set email attachments. + * + * @since 2.9.0 + * + * @param \PHPMailer $phpmailer PHPMailer instance. + */ + public function set_attachments( &$phpmailer ) { + + if ( is_null( $this->processing_email ) ) { + return; + } + + $attachments = ( new Attachments() )->get_attachments( $this->processing_email->get_id() ); + + if ( empty( $attachments ) ) { + return; + } + + foreach ( $attachments as $attachment ) { + try { + $phpmailer->addAttachment( $attachment->get_path(), $attachment->get_filename() ); + } catch ( \Exception $e ) { + continue; + } + } + } + + /** + * Schedule emails resend. + * + * @since 2.9.0 + * + * @param array $email_ids Email ids. + * @param string $connection_id Connection ID. + */ + public function schedule_emails_send( $email_ids, $connection_id = 'primary' ) { + + if ( wp_mail_smtp()->get_queue()->is_enabled() ) { + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id ); + + foreach ( array_chunk( $email_ids, 30 ) as $chunk ) { + $emails_collection = new EmailsCollection( + [ + 'ids' => $chunk, + 'per_page' => count( $chunk ), + ] + ); + + foreach ( $emails_collection->get() as $email ) { + $this->send_email( $email, null, $connection ); + } + } + } else { + $resend_task = new ResendTask(); + + // Batch emails sending because it can take some time on slower web hostings or slow SMTP servers. Especially emails with attachments. + foreach ( array_chunk( $email_ids, ResendTask::EMAILS_PER_BATCH ) as $chunk ) { + $resend_task->schedule( $chunk, $connection_id ); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/AbstractDeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/AbstractDeliveryVerifier.php new file mode 100644 index 00000000..47314fe1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/AbstractDeliveryVerifier.php @@ -0,0 +1,281 @@ +get_pro()->get_logs()->get_email_connection( $email ); + + if ( empty( $connection ) ) { + throw new Exception( esc_html__( 'Unable to find email connection.', 'wp-mail-smtp-pro' ) ); + } + + $mailer = $email->get_mailer(); + $mailer_options = $connection->get_options()->get_group( $mailer ); + + if ( empty( $mailer_options ) ) { + throw new Exception( esc_html__( 'Unable to find mailer options.', 'wp-mail-smtp-pro' ) ); + } + + $this->mailer = $mailer; + $this->mailer_options = $mailer_options; + $this->email = $email; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + abstract protected function get_delivery_status() : DeliveryStatus; + + /** + * Get events from the API response. + * + * @since 3.9.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + abstract protected function get_events(); + + /** + * Verify the email delivery status. + * + * @since 3.9.0 + * @since 3.10.0 Updated to return the delivery status, or error in case of failure. + * + * @return DeliveryStatus|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an instance of DeliveryStatus. + */ + public function verify() { + + $events = $this->get_events(); + + if ( is_wp_error( $events ) ) { + $this->unable_to_verify( $events ); + + return $events; + } + + $this->events = $events; + $this->delivery_status = $this->get_delivery_status(); + + if ( $this->delivery_status->is_delivered() ) { + $this->save_email_delivered(); + } elseif ( $this->delivery_status->is_failed() ) { + $this->save_email_not_delivered( $this->delivery_status->get_fail_reason() ); + } else { + $this->unable_to_verify( + new WP_Error( + $this->mailer . '_verification_error', + esc_html__( 'Unable to verify email delivery status.', 'wp-mail-smtp-pro' ) + ) + ); + } + + return $this->delivery_status; + } + + /** + * Set the email as not verified. + * + * @since 3.9.0 + * + * @param WP_Error $error WP Error object. + * + * @return void + */ + protected function unable_to_verify( $error ) { + + $this->error = $error; + } + + /** + * Save email status as delivered. + * + * @since 3.9.0 + * + * @return void + */ + protected function save_email_delivered() { + + $this->get_email()->set_status( Email::STATUS_DELIVERED ); + $this->get_email()->save(); + } + + /** + * Save email status as not delivered. + * + * @since 3.9.0 + * + * @param string $fail_reason Fail reason. + * + * @return void + */ + protected function save_email_not_delivered( $fail_reason ) { + + $this->get_email()->set_status( Email::STATUS_UNSENT ); + + if ( ! empty( $fail_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' ), $fail_reason ); + } else { + $error_text = esc_html__( 'The email failed to be delivered. No specific reason was provided by the API.', 'wp-mail-smtp-pro' ); + } + + $this->get_email()->set_error_text( $error_text ); + $this->get_email()->save(); + } + + /** + * Validate the response from the API. + * + * @since 3.9.0 + * + * @param array|WP_Error $response Response from WP remote get. + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns the JSON decoded response body. + */ + protected function validate_response( $response ) { + + if ( is_wp_error( $response ) ) { + return $response; + } + + if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { + return new WP_Error( $this->get_mailer() . '_delivery_verifier_invalid_response_code', WP::wp_remote_get_response_error_message( $response ) ); + } + + return json_decode( wp_remote_retrieve_body( $response ), true ); + } + + /** + * Get the mailer slug. + * + * @since 3.9.0 + * + * @return string + */ + protected function get_mailer() { + + return $this->mailer; + } + + /** + * Get the mailer plugin settings. + * + * @since 3.9.0 + * + * @return array + */ + public function get_mailer_options() { + + return $this->mailer_options; + } + + /** + * Get the Email object. + * + * @since 3.9.0 + * + * @return Email + */ + public function get_email() { + + return $this->email; + } + + /** + * Whether the email is verified or not. + * + * @since 3.9.0 + * + * @return bool + */ + public function is_verified() { + + if ( ! $this->delivery_status instanceof DeliveryStatus ) { + $this->delivery_status = new DeliveryStatus(); + } + + return $this->delivery_status->is_verified(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryStatus.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryStatus.php new file mode 100644 index 00000000..0a89d329 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryStatus.php @@ -0,0 +1,165 @@ +status === self::STATUS_DELIVERED; + } + + /** + * Whether the delivery status is failed. + * + * @since 3.9.0 + * + * @return bool + */ + public function is_failed() { + + return $this->status === self::STATUS_FAILED; + } + + /** + * Whether we are able to get the delivery status. + * + * @since 3.9.0 + * + * @return bool + */ + public function is_verified() { + + return $this->status !== self::STATUS_UNKNOWN; + } + + /** + * Set the delivery status. + * + * @since 3.9.0 + * + * @param string $status Deliver status. + */ + public function set_status( $status ) { + + if ( in_array( $status, [ self::STATUS_DELIVERED, self::STATUS_FAILED ], true ) ) { + $this->status = $status; + } + } + + /** + * Get the reason why the delivery failed. + * + * @since 3.9.0 + * + * @return string + */ + public function get_fail_reason() { + + return $this->fail_reason; + } + + /** + * Get the error code. + * + * @since 4.8.0 + * + * @return string + */ + public function get_error_code() { + + return $this->error_code; + } + + /** + * Set the reason why the delivery failed. + * + * @since 3.9.0 + * + * @param string $reason Reason why the delivery failed. + * + * @return void + */ + public function set_fail_reason( $reason ) { + + $this->fail_reason = $reason; + } + + /** + * Set the error code. + * + * @since 4.8.0 + * + * @param string $code Error code. + */ + public function set_error_code( $code ) { + + $this->error_code = $code; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryVerification.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryVerification.php new file mode 100644 index 00000000..f2fe0d1b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/DeliveryVerification.php @@ -0,0 +1,172 @@ + MailgunDeliveryVerifier::class, + 'mandrill' => MandrillDeliveryVerifier::class, + 'postmark' => PostmarkDeliveryVerifier::class, + 'sendlayer' => SendlayerDeliveryVerifier::class, + 'smtpcom' => SMTPcomDeliveryVerifier::class, + 'sparkpost' => SparkPostDeliveryVerifier::class, + 'sendinblue' => SendinblueDeliveryVerifier::class, + 'smtp2go' => SMTP2GODeliveryVerifier::class, + 'mailjet' => MailjetDeliveryVerifier::class, + 'elasticemail' => ElasticEmailDeliveryVerifier::class, + 'mailersend' => MailerSendDeliveryVerifier::class, + 'resend' => ResendDeliveryVerifier::class, + ]; + + /** + * Verify sent status tasks classes. + * + * @since 3.9.0 + * + * @var VerifySentStatusTaskAbstract[] + */ + const VERIFY_TASKS_PER_MAILER = [ + 'mailgun' => MailgunVerifySentStatusTask::class, + 'mandrill' => MandrillVerifySentStatusTask::class, + 'postmark' => PostmarkVerifySentStatusTask::class, + 'sendlayer' => SendlayerVerifySentStatusTask::class, + 'smtpcom' => SMTPcomVerifySentStatusTask::class, + 'sparkpost' => SparkPostVerifySentStatusTask::class, + 'sendinblue' => SendinblueVerifySentStatusTask::class, + 'smtp2go' => SMTP2GOVerifySentStatusTask::class, + 'mailjet' => MailjetVerifySentStatusTask::class, + 'elasticemail' => ElasticEmailVerifySentStatusTask::class, + 'mailersend' => MailerSendVerifySentStatusTask::class, + 'resend' => ResendVerifySentStatusTask::class, + ]; + + /** + * Get the verifier for a given email log ID. + * + * @since 3.9.0 + * + * @param int $email_id Email Log ID. + * + * @return WP_Error|AbstractDeliveryVerifier Returns the DeliveryVerifier instance or false if not found. + */ + public function get_verifier( $email_id ) { + + $email = new Email( $email_id ); + + // Check if email exists (was not deleted). + if ( $email->get_id() === 0 ) { + return new WP_Error( + 'wp_mail_smtp_pro_emails_logs_delivery_verification_get_verifier_no_email', + esc_html__( 'Unable to find email.', 'wp-mail-smtp-pro' ) + ); + } + + $mailer = $email->get_mailer(); + + if ( empty( $mailer ) || ! isset( self::DELIVERY_VERIFIERS_PER_MAILER[ $mailer ] ) ) { + return new WP_Error( + 'wp_mail_smtp_pro_emails_logs_delivery_verification_get_verifier_invalid_mailer', + esc_html__( 'Invalid mailer.', 'wp-mail-smtp-pro' ) + ); + } + + $mailer_class = self::DELIVERY_VERIFIERS_PER_MAILER[ $mailer ]; + + try { + $verifier = new $mailer_class( $email ); + } catch ( Exception $e ) { + return new WP_Error( + 'wp_mail_smtp_pro_emails_logs_delivery_verification_get_verifier_invalid_mailer', + $e->getMessage() + ); + } + + if ( ! is_a( $verifier, AbstractDeliveryVerifier::class ) ) { + return new WP_Error( + 'wp_mail_smtp_pro_emails_logs_delivery_verification_get_verifier_invalid_verifier', + esc_html__( 'Invalid verifier.', 'wp-mail-smtp-pro' ) + ); + } + + return $verifier; + } + + /** + * Create AS task that will verify the email delivery status. + * + * @since 3.9.0 + * + * @param int $email_log_id Email Log ID. + * + * @return void + */ + public function schedule_verification( $email_log_id ) { + + // Get the email. + $email = new Email( $email_log_id ); + + // Check if email exists (was not deleted). + if ( $email->get_id() === 0 ) { + return; + } + + $mailer = $email->get_mailer(); + + if ( ! isset( self::VERIFY_TASKS_PER_MAILER[ $mailer ] ) ) { + return; + } + + $as_class = self::VERIFY_TASKS_PER_MAILER[ $mailer ]; + $as_task = new $as_class(); + + if ( ! is_a( $as_task, VerifySentStatusTaskAbstract::class ) ) { + return; + } + + $as_task->params( $email_log_id, 1 ) + ->once( time() + $as_task::SCHEDULE_TASK_IN ) + ->register(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/ElasticEmail/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/ElasticEmail/DeliveryVerifier.php new file mode 100644 index 00000000..b6c5e29c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/ElasticEmail/DeliveryVerifier.php @@ -0,0 +1,112 @@ +events['Delivered'] ) ? $this->events['Delivered'] : []; + // Failed is an array of arrays. + $failed = ! empty( $this->events['Failed'] ) ? $this->events['Failed'] : []; + + // Only consider the first "TO" address. + $to = $this->get_email()->get_people( 'to' ); + $to = is_array( $to ) ? $to[0] : $to; + + foreach ( $delivered as $event ) { + if ( $event === $to ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + + return $delivery_status; + } + } + + foreach ( $failed as $event ) { + if ( ! empty( $event['Address'] ) && $event['Address'] === $to ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $event['Error'] ) ) { + $delivery_status->set_fail_reason( $event['Error'] ); + } + + if ( ! empty( $event['ErrorCode'] ) ) { + $error_code = ! empty( $event['Category'] ) ? $event['ErrorCode'] . '_' . $event['Category'] : $event['ErrorCode']; + + $delivery_status->set_error_code( $error_code ); + } + + return $delivery_status; + } + } + + return $delivery_status; + } + + /** + * Get events from the API response. + * + * @since 4.3.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + protected function get_events() { + + $mailer_options = $this->get_mailer_options(); + $transaction_id = $this->get_email()->get_message_id(); + $url = add_query_arg( + [ + 'showDelivered' => true, + 'showFailed' => true, + ], + "https://api.elasticemail.com/v4/emails/{$transaction_id}/status" + ); + $response = wp_safe_remote_get( + $url, + [ + 'headers' => [ + 'X-ElasticEmail-ApiKey' => $mailer_options['api_key'], + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( + ! isset( $validated_response['Delivered'] ) || + ! isset( $validated_response['Failed'] ) + ) { + return new WP_Error( 'elasticemail_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/MailerSend/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/MailerSend/DeliveryVerifier.php new file mode 100644 index 00000000..8caef9d5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/MailerSend/DeliveryVerifier.php @@ -0,0 +1,104 @@ +events; + + if ( ! is_array( $events ) || empty( $events[0] ) || ! is_array( $events[0] ) ) { + return $delivery_status; + } + + // Only consider first TO email. + $message_data = $events[0]; + + if ( empty( $message_data['status'] ) ) { + return $delivery_status; + } + + if ( $message_data['status'] === 'delivered' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + } + + if ( $message_data['status'] === 'rejected' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + $delivery_status->set_error_code( $message_data['status'] ); + } + + return $delivery_status; + } + + /** + * Get events from the API response. + * + * @since 4.5.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + protected function get_events() { + + $mailer_options = $this->get_mailer_options(); + $message_id = $this->get_email()->get_header( 'X-Msg-ID' ); + + if ( empty( $message_id ) ) { + return new WP_Error( 'mailersend_delivery_verifier_missing_message_id', esc_html__( 'Message ID is missing.', 'wp-mail-smtp-pro' ) ); + } + + $url = 'https://api.mailersend.com/v1/messages/' . $message_id; + + $response = wp_safe_remote_get( + $url, + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $mailer_options['api_key'], + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + // Validate response structure and extract emails array. + if ( + empty( $validated_response['data'] ) || + ! is_array( $validated_response['data'] ) || + empty( $validated_response['data']['emails'] ) || + ! is_array( $validated_response['data']['emails'] ) + ) { + return new WP_Error( + 'mailersend_delivery_verifier_invalid_response_structure', + esc_html__( 'Invalid response structure from MailerSend API.', 'wp-mail-smtp-pro' ) + ); + } + + return $validated_response['data']['emails']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailgun/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailgun/DeliveryVerifier.php new file mode 100644 index 00000000..accaaae6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailgun/DeliveryVerifier.php @@ -0,0 +1,117 @@ +get_email()->get_date_sent()->getTimestamp(); + } catch ( Exception $exception ) { + $begin_timestamp = time(); + } + + $options = $this->get_mailer_options(); + + // Prepare the API endpoint. + $endpoint = Mailer::API_BASE_US; + + if ( ! empty( $options ) && $options['region'] === 'EU' ) { + $endpoint = Mailer::API_BASE_EU; + } + + $endpoint .= sanitize_text_field( $options['domain'] ) . '/events'; + + $response = wp_safe_remote_get( + add_query_arg( + [ + 'begin' => $begin_timestamp - ( 45 * MINUTE_IN_SECONDS ), + 'ascending' => 'yes', + 'limit' => 10, + 'pretty' => 'yes', + 'message-id' => trim( $this->get_email()->get_header( 'Message-ID' ), '<>' ), + ], + $endpoint + ), + [ + 'headers' => [ + 'Authorization' => 'Basic ' . base64_encode( 'api:' . $options['api_key'] ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + ], + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['items'] ) ) { + return new WP_Error( 'mailgun_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['items']; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + protected function get_delivery_status(): DeliveryStatus { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $delivery_status = new DeliveryStatus(); + + foreach ( $this->events as $event ) { + if ( empty( $event['event'] ) ) { + continue; + } + + if ( $event['event'] === 'delivered' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( $event['event'] === 'failed' && $event['severity'] === 'permanent' ) { // Hard bounce. + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $event['delivery-status']['enhanced-code'] ) ) { + $delivery_status->set_error_code( $event['delivery-status']['enhanced-code'] ); + } + + if ( ! empty( $event['delivery-status']['description'] ) ) { + $delivery_status->set_fail_reason( $event['delivery-status']['description'] ); + } elseif ( ! empty( $event['delivery-status']['message'] ) ) { + $delivery_status->set_fail_reason( $event['delivery-status']['message'] ); + } + break; + } + } + + return $delivery_status; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailjet/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailjet/DeliveryVerifier.php new file mode 100644 index 00000000..c96de48b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mailjet/DeliveryVerifier.php @@ -0,0 +1,103 @@ +events as $event ) { + $status = strtolower( $event['Status'] ); + + if ( in_array( $status, $succeeded_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( in_array( $status, $failed_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $event['StateID'] ) ) { + $delivery_status->set_fail_reason( $event['StateID'] ); + } + + if ( ! empty( $event['Status'] ) ) { + $delivery_status->set_error_code( $event['Status'] ); + } + + break; + } + } + + return $delivery_status; + } + + /** + * Get events from the API response. + * + * @since 4.2.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + protected function get_events() { + + $mailer_options = $this->get_mailer_options(); + $user = $mailer_options['api_key']; + $pass = $mailer_options['secret_key']; + $message_id = $this->get_email()->get_message_id(); + + $response = wp_safe_remote_get( + "https://api.mailjet.com/v3/REST/message/{$message_id}", + [ + 'headers' => [ + 'Authorization' => 'Basic ' . base64_encode( "$user:$pass" ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['Data'] ) ) { + return new WP_Error( 'mailjet_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['Data']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mandrill/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mandrill/DeliveryVerifier.php new file mode 100644 index 00000000..ef55ea92 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Mandrill/DeliveryVerifier.php @@ -0,0 +1,104 @@ +events as $event ) { + if ( empty( $event['state'] ) ) { + continue; + } + + if ( $event['state'] === 'sent' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( in_array( $event['state'], $failed_states, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + $delivery_status->set_error_code( $event['state'] ); + + if ( ! empty( $event['reject'] ) ) { + $delivery_status->set_fail_reason( $event['reject']['reason'] ); + } elseif ( ! empty( $event['bounce_description'] ) ) { + $delivery_status->set_fail_reason( $event['bounce_description'] ); + } + break; + } + } + + return $delivery_status; + } + + /** + * Get events from the API response. + * + * @since 4.6.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + protected function get_events() { + + $mailer_options = $this->get_mailer_options(); + $message_id = $this->get_email()->get_header( 'X-Msg-ID' ); + + if ( empty( $message_id ) ) { + return new WP_Error( 'mandrill_delivery_verifier_missing_message_id', esc_html__( 'Message ID is missing.', 'wp-mail-smtp-pro' ) ); + } + + $response = wp_safe_remote_post( + 'https://mandrillapp.com/api/1.0/messages/info', + [ + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'key' => $mailer_options['api_key'], + 'id' => $message_id, + ] + ), + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['_id'] ) ) { + return new WP_Error( 'mandrill_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return [ $validated_response ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Postmark/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Postmark/DeliveryVerifier.php new file mode 100644 index 00000000..80565ad5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Postmark/DeliveryVerifier.php @@ -0,0 +1,163 @@ +api_base_url . '/messages/outbound/' . $this->get_email()->get_message_id() . '/details', + $this->get_request_args() + ); + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['MessageEvents'] ) ) { + return new WP_Error( 'postmark_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['MessageEvents']; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + protected function get_delivery_status(): DeliveryStatus { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded + + $delivery_status = new DeliveryStatus(); + + // Process the event items and check event types. + foreach ( $this->events as $event ) { + if ( empty( $event['Type'] ) ) { + continue; + } + + if ( $event['Type'] === 'Delivered' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( $event['Type'] === 'Bounced' ) { + $bounced_event = $this->handle_bounced( $event ); + + if ( + is_array( $bounced_event ) && + isset( $bounced_event['failed'] ) && + $bounced_event['failed'] + ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $bounced_event['reason'] ) ) { + $delivery_status->set_fail_reason( esc_html( $bounced_event['reason'] ) ); + } + + if ( ! empty( $bounced_event['bounce_name'] ) ) { + $delivery_status->set_error_code( esc_html( $bounced_event['bounce_name'] ) ); + } + + break; + } + } + } + + return $delivery_status; + } + + /** + * Get the request arguments. + * + * @since 3.9.0 + * + * @return array + */ + private function get_request_args() { + + $mailer_options = $this->get_mailer_options(); + + return [ + 'headers' => [ + 'X-Postmark-Server-Token' => $mailer_options['server_api_token'], + 'Content-Type' => 'application/json', + ], + ]; + } + + /** + * Handle bounced event. + * + * @since 3.9.0 + * + * @param array $event Array containing the event details. + * + * @return array|null Returns `null` if the email was not hard bounced, otherwise returns an array with the reason. + */ + private function handle_bounced( $event ) { + + if ( empty( $event['Details']['BounceID'] ) ) { + return [ + 'failed' => true, + ]; + } + + // Get bounce details. + $response = wp_safe_remote_get( + $this->api_base_url . '/bounces/' . $event['Details']['BounceID'], + $this->get_request_args() + ); + + if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { + return [ + 'failed' => true, + ]; + } + + $bounce = json_decode( wp_remote_retrieve_body( $response ), true ); + + // Try later if email was not hard bounced. + if ( isset( $bounce['Type'] ) && $bounce['Type'] !== 'HardBounce' ) { + return null; + } + + return [ + 'failed' => true, + 'reason' => ! empty( $bounce['Description'] ) ? $bounce['Description'] : '', + 'bounce_name' => ! empty( $bounce['Name'] ) ? $bounce['Name'] : '', + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Resend/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Resend/DeliveryVerifier.php new file mode 100644 index 00000000..d77832a4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Resend/DeliveryVerifier.php @@ -0,0 +1,93 @@ +events as $event ) { + $status = strtolower( $event['last_event'] ); + + if ( in_array( $status, $succeeded_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + + break; + } + + if ( in_array( $status, $failed_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + $delivery_status->set_error_code( $status ); + + break; + } + } + + return $delivery_status; + } + + /** + * Get events from the API response. + * + * @since 4.7.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + protected function get_events() { + + $mailer_options = $this->get_mailer_options(); + $api_key = $mailer_options['api_key']; + $message_id = $this->get_email()->get_message_id(); + + $response = wp_safe_remote_get( + "https://api.resend.com/emails/$message_id", + [ + 'headers' => [ + 'Authorization' => "Bearer $api_key", // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response ) ) { + return new WP_Error( 'resend_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return [ $validated_response ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTP2GO/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTP2GO/DeliveryVerifier.php new file mode 100644 index 00000000..245cd0fa --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTP2GO/DeliveryVerifier.php @@ -0,0 +1,114 @@ +get_email()->get_people( 'to' ); + $to = is_array( $to ) ? $to[0] : $to; + + // Only consider events related to the first "TO" address. + $events = array_filter( + $this->events, + function ( $event ) use ( $to ) { + return $event['recipient'] === $to; + } + ); + + foreach ( $events as $event ) { + $status = trim( strtolower( $event['status'] ) ); + $response = $event['response']; + + if ( in_array( $status, $succeeded_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( in_array( $status, $failed_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + $delivery_status->set_fail_reason( $response ); + $delivery_status->set_error_code( $status ); + break; + } + } + + return $delivery_status; + } + + /** + * Get events from the API response. + * + * @since 4.1.0 + * + * @return mixed|WP_Error Returns `WP_Error` if unable to fetch a valid response from the API. + * Otherwise, returns an array containing the events. + */ + protected function get_events() { + + $mailer_options = $this->get_mailer_options(); + $response = wp_safe_remote_post( + 'https://api.smtp2go.com/v3/email/search', + [ + 'headers' => [ + 'X-Smtp2go-Api-Key' => $mailer_options['api_key'], + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + 'body' => wp_json_encode( + [ + 'email_id' => [ $this->get_email()->get_message_id() ], + ] + ), + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['data'] ) || empty( $validated_response['data']['emails'] ) ) { + return new WP_Error( 'smtp2go_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['data']['emails']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTPcom/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTPcom/DeliveryVerifier.php new file mode 100644 index 00000000..6366c51a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SMTPcom/DeliveryVerifier.php @@ -0,0 +1,112 @@ +get_mailer_options(); + + try { + $begin_timestamp = $this->get_email()->get_date_sent()->getTimestamp(); + } catch ( \Exception $exception ) { + $begin_timestamp = time(); + } + + $response = wp_safe_remote_get( + add_query_arg( + [ + 'channel' => $mailer_options['channel'], + 'start' => $begin_timestamp - ( 45 * MINUTE_IN_SECONDS ), + 'limit' => 10, + 'offset' => 0, + 'msg_id' => $this->get_email()->get_header( 'X-Msg-ID' ), + ], + 'https://api.smtp.com/v4/messages' + ), + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $mailer_options['api_key'], + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ] + ); + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['data']['items'] ) ) { + return new WP_Error( 'smtpcom_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['data']['items']; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + protected function get_delivery_status(): DeliveryStatus { + + $delivery_status = new DeliveryStatus(); + $failed_event_types = [ + 'failed', + 'bounced', + ]; + + foreach ( $this->events as $item ) { + if ( empty( $item['details']['delivery']['event'] ) ) { + continue; + } + + $event = $item['details']['delivery']['event']; + + if ( $event === 'delivered' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( in_array( $event, $failed_event_types, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $item['details']['delivery']['status'] ) ) { + $delivery_status->set_fail_reason( $item['details']['delivery']['status'] ); + } + + if ( ! empty( $item['details']['delivery']['code'] ) ) { + $delivery_status->set_error_code( $item['details']['delivery']['code'] ); + } + break; + } + } + + return $delivery_status; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendinblue/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendinblue/DeliveryVerifier.php new file mode 100644 index 00000000..2152d8c5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendinblue/DeliveryVerifier.php @@ -0,0 +1,99 @@ +get_mailer_options(); + $response = wp_safe_remote_get( + 'https://api.brevo.com/v3/smtp/statistics/events', + [ + 'body' => [ + 'limit' => 10, + 'messageId' => $this->get_email()->get_header( 'Message-ID' ), + ], + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'api-key' => $mailer_options['api_key'], + ], + ] + ); + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['events'] ) ) { + return new WP_Error( 'sendinblue_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['events']; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + protected function get_delivery_status(): DeliveryStatus { + + $delivery_status = new DeliveryStatus(); + $failed_event_types = [ + 'hardBounces', + 'softBounces', + 'blocked', + 'invalid', + ]; + + foreach ( $this->events as $event ) { + if ( empty( $event['event'] ) ) { + continue; + } + + if ( $event['event'] === 'delivered' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( in_array( $event['event'], $failed_event_types, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $event['reason'] ) ) { + $delivery_status->set_fail_reason( $event['reason'] ); + } + + $delivery_status->set_error_code( $event['event'] ); + + break; + } + } + + return $delivery_status; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendlayer/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendlayer/DeliveryVerifier.php new file mode 100644 index 00000000..e9adb74f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/Sendlayer/DeliveryVerifier.php @@ -0,0 +1,99 @@ +get_mailer_options(); + $response = wp_safe_remote_get( + add_query_arg( + [ + 'MessageID' => $this->get_email()->get_message_id(), + ], + 'https://console.sendlayer.com/api/v1/events' + ), + [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $mailer_options['api_key'], + 'Content-Type' => 'application/json', + ], + ] + ); + + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['Events'] ) ) { + return new WP_Error( 'sendlayer_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['Events']; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + protected function get_delivery_status(): DeliveryStatus { + + $delivery_status = new DeliveryStatus(); + $failed_delivery_events = [ + 'failed', + 'rejected-by-system', + 'rejected-by-mta', + ]; + + foreach ( $this->events as $event ) { + if ( empty( $event['Event'] ) ) { + continue; + } + + if ( $event['Event'] === 'delivered' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( in_array( $event['Event'], $failed_delivery_events, true ) ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $event['Reason'] ) ) { + $delivery_status->set_fail_reason( $event['Reason'] ); + } + + $delivery_status->set_error_code( $event['Event'] ); + + break; + } + } + + return $delivery_status; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SparkPost/DeliveryVerifier.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SparkPost/DeliveryVerifier.php new file mode 100644 index 00000000..48ccd25f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/DeliveryVerification/SparkPost/DeliveryVerifier.php @@ -0,0 +1,117 @@ +get_email()->get_date_sent()->format( 'Y-m-d\TH:i:s\Z' ); + } catch ( Exception $e ) { + $from_date = gmdate( 'Y-m-d\TH:i:s\Z', strtotime( '-1 day' ) ); + } + + $mailer_options = $this->get_mailer_options(); + $endpoint = ( $mailer_options['region'] === 'EU' ? Mailer::API_BASE_EU : Mailer::API_BASE_US ) . '/events/message'; + $response = wp_safe_remote_get( + add_query_arg( + [ + 'from' => $from_date, + 'events' => 'delivery,bounce', + 'transmissions' => $this->get_email()->get_message_id(), + ], + $endpoint + ), + [ + 'headers' => [ + 'Authorization' => $mailer_options['api_key'], + 'Content-Type' => 'application/json', + ], + ] + ); + $validated_response = $this->validate_response( $response ); + + if ( is_wp_error( $validated_response ) ) { + return $validated_response; + } + + if ( empty( $validated_response['results'] ) ) { + return new WP_Error( 'sparkpost_delivery_verifier_missing_response', WP::wp_remote_get_response_error_message( $response ) ); + } + + return $validated_response['results']; + } + + /** + * Get the DeliveryStatus of the email. + * + * @since 3.9.0 + * + * @return DeliveryStatus + */ + protected function get_delivery_status(): DeliveryStatus { + + $delivery_status = new DeliveryStatus(); + $failed_delivery_events = [ + 'out_of_band', + 'policy_rejection', + 'generation_failure', + 'generation_rejection', + ]; + + foreach ( $this->events as $event ) { + if ( empty( $event['type'] ) ) { + continue; + } + + if ( $event['type'] === 'delivery' ) { + $delivery_status->set_status( DeliveryStatus::STATUS_DELIVERED ); + break; + } + + if ( + ( // Hard bounce. + $event['type'] === 'bounce' && + in_array( (int) $event['bounce_class'], [ 1, 10, 25, 30, 80, 90 ], true ) + ) || + in_array( $event['type'], $failed_delivery_events, true ) + ) { + $delivery_status->set_status( DeliveryStatus::STATUS_FAILED ); + + if ( ! empty( $event['raw_reason'] ) ) { + $delivery_status->set_fail_reason( $event['raw_reason'] ); + } + + if ( ! empty( $event['bounce_class'] ) ) { + $delivery_status->set_error_code( 'bounce_class_' . $event['bounce_class'] ); + } + + break; + } + } + + return $delivery_status; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Email.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Email.php new file mode 100644 index 00000000..7574c5a2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Email.php @@ -0,0 +1,1312 @@ +populate_email( $id_or_row ); + } + + /** + * Deep-clone object-typed properties so the cache slot stays isolated from + * subsequent mutations on the original instance. + * + * @since 4.9.0 + */ + public function __clone() { + + if ( $this->date_sent instanceof DateTime ) { + $this->date_sent = clone $this->date_sent; + } + } + + /** + * Get and prepare the email data. + * + * @since 1.5.0 + * + * @param int|object $id_or_row + */ + private function populate_email( $id_or_row ) { + + $email = null; + + if ( is_numeric( $id_or_row ) && $id_or_row > 0 ) { + // Get by ID. + $collection = new EmailsCollection( [ 'id' => (int) $id_or_row ] ); + $emails = $collection->get(); + + if ( $emails->valid() ) { + $email = $emails->current(); + } + } elseif ( + is_object( $id_or_row ) && + isset( + $id_or_row->id, + $id_or_row->subject, + $id_or_row->people, + $id_or_row->headers, + $id_or_row->content_plain, + $id_or_row->content_html, + $id_or_row->status, + $id_or_row->date_sent, + $id_or_row->mailer, + $id_or_row->attachments + ) + ) { + $email = $id_or_row; + } + + if ( $email !== null ) { + foreach ( get_object_vars( $email ) as $key => $value ) { + $this->{$key} = $value; + } + } + + // Stale-replica fallback: if we were asked to load by id, the DB returned + // nothing, but the in-memory slot holds a snapshot of that id from earlier + // in this request, populate from the snapshot. Keeps post-write reads + // reliable on hosts where read replicas haven't caught up with the writer. + if ( + (int) $this->id === 0 + && is_numeric( $id_or_row ) + && (int) $id_or_row > 0 + && self::$last_saved !== null + && (int) self::$last_saved->id === (int) $id_or_row + ) { + foreach ( get_object_vars( self::$last_saved ) as $prop => $value ) { + $this->{$prop} = $value; + } + } + } + + /** + * Get email by message ID. + * + * @since 3.3.0 + * + * @param string $message_id Message ID. + * + * @return Email|null + */ + public static function get_by_message_id( $message_id ) { + + // Get by message ID. + $collection = new EmailsCollection( + [ + 'message_id' => $message_id, + 'per_page' => 1, + ] + ); + $emails = $collection->get(); + + return $emails->current(); + } + + /** + * Email ID as per our DB table. + * + * @since 1.5.0 + * + * @return int + */ + public function get_id() { + + return (int) $this->id; + } + + /** + * Message ID (mainly external ID from email provider). + * + * @since 3.3.0 + * + * @return string + */ + public function get_message_id() { + + return $this->message_id; + } + + /** + * Email subject. + * + * @since 1.5.0 + * + * @return string + */ + public function get_subject() { + + return $this->subject; + } + + /** + * Render-safe subject for HTML attribute output. + * + * Double-encodes any entities in the supplied subject so values that + * survived the storage-side wp_kses() strip (which leaves entities + * intact) cannot smuggle live markup through esc_attr() into data-* or + * title attributes that JavaScript later reads and writes via innerHTML + * sinks. + * + * Static so call sites that hold a plain subject string (the Reports + * stats aggregation groups by subject and has no Email instance per row) + * can use it directly. + * + * @since 4.9.0 + * + * @param string $subject Subject string as stored. + * + * @return string + */ + public static function esc_subject_attr( $subject ) { + + return htmlspecialchars( + (string) $subject, + ENT_QUOTES, + get_bloginfo( 'charset' ), + true /* $double_encode */ + ); + } + + /** + * All the people that participated in this email: sent to, from or copied. + * + * @since 1.5.0 + * + * @param string $type Who exactly to retrieve. Supported values: to, from, cc, bcc. If empty - get everyone. + * + * @return array|string List of people. Empty array if type is incorrect or everyone are requested. + * String for some specific type. + */ + public function get_people( $type = '' ) { + + $people = WP::is_json( $this->people ) ? json_decode( $this->people ) : array(); + $type = sanitize_key( $type ); + + // Get all. + if ( empty( $type ) ) { + $data = $people; + } elseif ( isset( $people->{$type} ) ) { + $data = $people->{$type}; + } else { + $data = array(); + } + + return $data; + } + + /** + * Email from name. + * + * @since 2.8.0 + * + * @return string + */ + public function get_from_name() { + + $from_header = $this->get_header( 'From' ); + + if ( ! empty( $from_header ) ) { + + preg_match( '/\s*(.*[^\s])\s*<\s*(.*[^\s])\s*>/', $from_header, $output ); + + if ( ! empty( $output[1] ) ) { + return trim( $output[1] ); + } + } + + return ''; + } + + /** + * All the headers, JSON string. + * + * @since 1.5.0 + * + * @return string + */ + public function get_headers() { + + return WP::is_json( $this->headers ) ? $this->headers : '[]'; + } + + /** + * Get a specific header by its name if it exists. + * + * @since 2.5.0 + * + * @param string $name The name of the header to get. + * + * @return string + */ + public function get_header( $name ) { + + if ( empty( $name ) ) { + return ''; + } + + $headers = json_decode( $this->get_headers(), true ); + + foreach ( $headers as $header ) { + preg_match( '/^' . $name . ': (.*)/', $header, $output ); + if ( ! empty( $output[1] ) ) { + return $output[1]; + } + } + + return ''; + } + + /** + * Error recorded when email was attempted to be sent. + * + * @since 2.5.0 + * + * @return string + */ + public function get_error_text() { + + return $this->error_text; + } + + /** + * Structured error code captured when an email failed to send. + * + * @since 4.9.0 + * + * @return string + */ + public function get_error_code() { + + return $this->error_code; + } + + /** + * Composite error key captured when an email failed to send. + * + * @since 4.9.0 + * + * @return string + */ + public function get_error_key() { + + return $this->error_key; + } + + /** + * Get the content of the email. + * + * @since 1.5.0 + * + * @return string Return HTML content by default. Fallback to plain. + */ + public function get_content() { + + $html = $this->get_content_html(); + + if ( empty( $html ) ) { + return $this->get_content_plain(); + } + + return $html; + } + + /** + * Get the plain content of the email. + * + * @since 1.5.0 + * + * @return string + */ + public function get_content_plain() { + + return $this->content_plain; + } + + /** + * Get the HTML content of the email. + * + * @since 1.5.0 + * + * @return string + */ + public function get_content_html() { + + return $this->content_html; + } + + /** + * 1 means sent, 0 means not sent. + * + * @since 1.5.0 + * + * @return int + */ + public function get_status() { + + return (int) $this->status; + } + + /** + * Get human readable status name. + * + * @since 2.8.0 + * + * @return string + */ + public function get_status_name() { + + $statuses = [ + self::STATUS_UNSENT => __( 'Not Sent', 'wp-mail-smtp-pro' ), + self::STATUS_SENT => __( 'Sent', 'wp-mail-smtp-pro' ), + self::STATUS_WAITING => __( 'Waiting for confirmation', 'wp-mail-smtp-pro' ), + self::STATUS_DELIVERED => __( 'Delivered', 'wp-mail-smtp-pro' ), + self::STATUS_BLOCKED => __( 'Blocked', 'wp-mail-smtp-pro' ), + ]; + + return isset( $statuses[ $this->status ] ) ? $statuses[ $this->status ] : ''; + } + + /** + * Get the date/time when this email was sent. + * + * @since 1.5.0 + * @since 2.6.0 Added UTC timezone. + * @since 3.8.0 Handle cases where `$this->date_sent` is a `DateTime` object. + * + * @return DateTime + * @throws \Exception Emits exception on incorrect date. + */ + public function get_date_sent() { + + $timezone = new \DateTimeZone( 'UTC' ); + $date = false; + + if ( ! empty( $this->date_sent ) ) { + $date_sent_string = is_a( $this->date_sent, DateTime::class ) ? $this->date_sent->format( WP::datetime_mysql_format() ) : $this->date_sent; + $date = DateTime::createFromFormat( WP::datetime_mysql_format(), $date_sent_string, $timezone ); + } + + if ( $date === false ) { + $date = new \DateTime( 'now', $timezone ); + } + + return $date; + } + + /** + * @return string + */ + public function get_mailer() { + + return $this->mailer; + } + + /** + * Whether email had attachments on not. + * + * @since 1.5.0 + * + * @return int 0 when no attachments, number if any. + */ + public function get_attachments() { + + return (int) $this->attachments; + } + + /** + * Get name of the plugin/theme (or WP core) that initiated/called the `wp_mail` function. + * + * @since 3.0.0 + * + * @return string + */ + public function get_initiator_name() { + + return $this->initiator_name; + } + + /** + * Get file path and line number of the plugin/theme (or WP core) that initiated/called the `wp_mail` function. + * + * @since 3.0.0 + * + * @return string + */ + public function get_initiator_file() { + + return $this->initiator_file; + } + + /** + * Get the parent's ID. + * + * @since 3.7.0 + * + * @return int + */ + public function get_parent_id() { + + return $this->parent_id; + } + + /** + * Get email content type. + * + * @since 2.9.0 + * + * @return string + */ + public function get_content_type() { + + // Check if HTML content is available. + if ( ! empty( $this->get_content_html() ) ) { + return 'text/html'; + } + + // Check the content-type header. + $content_type_header = $this->get_header( 'Content-Type' ); + $content_type_data = explode( ';', $content_type_header ); + + if ( ! empty( $content_type_data[0] ) ) { + return strtolower( trim( $content_type_data[0] ) ); + } + + return 'text/plain'; + } + + /** + * Get email charset. + * + * @since 2.9.0 + * + * @return string + */ + public function get_charset() { + + $charset = get_bloginfo( 'charset' ); + + $content_type_header = $this->get_header( 'Content-Type' ); + + list( , $charset_content ) = explode( ';', $content_type_header ); + if ( false !== stripos( $charset_content, 'charset=' ) ) { + $charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) ); + } + + return $charset; + } + + /** + * Get email connection ID. + * If an email was sent via the primary connection, then it returns an empty string. + * + * @since 3.7.0 + * + * @return string + */ + public function get_connection_id() { + + return $this->get_header( 'X-WP-Mail-SMTP-Connection' ); + } + + /** + * Is content type HTML based. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_content_type_html_based() { + + $html_content_types = [ + 'text/html', + 'multipart/alternative', + 'multipart/mixed', + ]; + + return in_array( $this->get_content_type(), $html_content_types, true ); + } + + /** + * Set message ID (mainly external ID from email provider). + * + * @since 3.3.0 + * + * @param string $message_id Message ID. + * + * @return Email + */ + public function set_message_id( $message_id ) { + + $this->message_id = $message_id; + + return $this; + } + + /** + * Set the email subject. + * + * @since 1.5.0 + * @since 2.2.0 Limit the length of the subject to 191 chars because the `subject` DB column is `varchar(191)`. + * + * @param string $subject + * + * @return Email + */ + public function set_subject( $subject ) { + + if ( ! function_exists( 'mb_substr' ) ) { + Helpers::include_mbstring_polyfill(); + } + + $this->subject = mb_substr( wp_kses( $subject, [] ), 0, 191 ); + + return $this; + } + + /** + * Set the email people. Contains to/cc/bcc/from. + * We require array here. + * + * @since 1.5.0 + * + * @param array $people + * + * @return Email + */ + public function set_people( $people ) { + + if ( ! is_array( $people ) ) { + $people = array(); + } + + $this->people = wp_json_encode( $people ); + + return $this; + } + + /** + * Set the email headers. + * We require array here. + * + * @since 1.5.0 + * + * @param array $headers + * + * @return Email + */ + public function set_headers( $headers ) { + + if ( ! is_array( $headers ) ) { + $headers = []; + } + + $filtered_headers = array_filter( + $headers, + function ( $header ) { + return strpos( $header, ':' ) !== false; + } + ); + + $this->headers = wp_json_encode( array_values( $filtered_headers ) ); + + return $this; + } + + /** + * Set the email error text. + * + * @since 2.5.0 + * + * @param string $error_text The email error text to be set. + * + * @return Email + */ + public function set_error_text( $error_text ) { + + $this->error_text = sanitize_textarea_field( $error_text ); + + return $this; + } + + /** + * Set the structured error code captured at send-failure time. + * + * @since 4.9.0 + * + * @param string $error_code The structured mailer error code. + * + * @return Email + */ + public function set_error_code( $error_code ) { + + $this->error_code = sanitize_text_field( $error_code ); + + return $this; + } + + /** + * Set the composite error key captured at send-failure time. + * + * @since 4.9.0 + * + * @param string $error_key The composite error key. + * + * @return Email + */ + public function set_error_key( $error_key ) { + + $this->error_key = sanitize_text_field( $error_key ); + + return $this; + } + + /** + * Set the email content and try to define whether it has HTML or not. + * + * @since 1.5.0 + * + * @param string $content The email content. + * + * @return Email + */ + public function set_content( $content ) { + + // TODO: this check is NOT reliable. + if ( $content === wp_strip_all_tags( $content ) ) { + $this->set_content_plain( wp_strip_all_tags( $content ) ); + } else { + $this->set_content_html( $content ); + } + + return $this; + } + + /** + * Set the email plain text content. + * + * @since 1.5.0 + * + * @param string $plain + * + * @return Email + */ + public function set_content_plain( $plain ) { + + $this->content_plain = $plain; + + return $this; + } + + /** + * Set the email HTML content. + * + * @since 1.5.0 + * + * @param string $html + * + * @return Email + */ + public function set_content_html( $html ) { + + $this->content_html = $html; + + return $this; + } + + /** + * Set the date when the email was sent. + * Date validation is taken from wp_insert_post(). + * Date should be set in this format: Y-m-d H:i:s. + * DateTime object will be set in property, on save it will be converted to string. + * + * @since 1.5.0 + * @since 2.6.0 Added UTC timezone. + * + * @param string $date_sent + * + * @return Email + * + * @throws \Exception When date is incorrectly generated on PHP side. + */ + public function set_date_sent( $date_sent ) { + + // Validate the date. Time is ignored. + $mm = substr( $date_sent, 5, 2 ); + $jj = substr( $date_sent, 8, 2 ); + $aa = substr( $date_sent, 0, 4 ); + + $valid_date = wp_checkdate( $mm, $jj, $aa, $date_sent ); + $timezone = new \DateTimeZone( 'UTC' ); + + if ( $valid_date ) { + $date_sent = \DateTime::createFromFormat( WP::datetime_mysql_format(), $date_sent, $timezone ); + } else { + $date_sent = new \DateTime( 'now', $timezone ); + } + + $this->date_sent = $date_sent; + + return $this; + } + + /** + * Set whether the email had attachments. + * + * @since 1.5.0 + * + * @param int $attachments + * + * @return Email + */ + public function set_attachments( $attachments ) { + + $this->attachments = (int) $attachments; + + return $this; + } + + /** + * Set the mailer key that was used to send this email. + * + * @since 1.5.0 + * + * @param string $mailer + * + * @return Email + */ + public function set_mailer( $mailer ) { + + $this->mailer = sanitize_key( $mailer ); + + return $this; + } + + /** + * Set the status of the email: was it successfully sent or not? + * We do not insist on exact default statuses, as developers may want to change that. + * We do insist on an integer. + * + * @since 1.5.0 + * + * @param int $status + * + * @return Email + */ + public function set_status( $status ) { + + $this->status = (int) $status; + + return $this; + } + + /** + * Set the initiator by checking the backtrace for the wp_mail function call. + * + * @since 3.0.0 + * + * @return Email + */ + public function set_initiator() { + + $initiator = wp_mail_smtp()->get_wp_mail_initiator(); + + if ( empty( $initiator->get_file() ) ) { + return $this; + } + + $this->initiator_file = $initiator->get_file(); + + if ( ! empty( $initiator->get_line() ) ) { + $this->initiator_file .= ':' . $initiator->get_line(); + } + + $this->initiator_name = $initiator->get_name(); + + return $this; + } + + /** + * Set the initiator name. + * + * @since 3.8.0 + * + * @param string $name Initiator name. + * + * @return Email + */ + public function set_initiator_name( $name ) { + + $this->initiator_name = $name; + + return $this; + } + + /** + * Set parent ID. + * + * @since 3.7.0 + * + * @param int $parent_id Parent ID. + * + * @return Email + */ + public function set_parent_id( $parent_id ) { + + $this->parent_id = $parent_id; + + return $this; + } + + /** + * Save a new or modified email in DB. + * + * @since 1.5.0 + * + * @return Email New or updated email class instance. + * @throws \Exception When email init fails. + */ + public function save() { + + global $wpdb; + + $table = Logs::get_table_name(); + + if ( (bool) $this->get_id() ) { + // Update the existing DB table record. + $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $table, + [ + 'message_id' => $this->message_id, + 'subject' => $this->subject, + 'people' => $this->people, + 'headers' => $this->headers, + 'error_text' => $this->error_text, + 'error_code' => $this->error_code, + 'error_key' => $this->error_key, + 'content_plain' => $this->content_plain, + 'content_html' => $this->content_html, + 'status' => $this->status, + 'date_sent' => $this->get_date_sent()->format( WP::datetime_mysql_format() ), + 'mailer' => $this->mailer, + 'attachments' => $this->attachments, + 'initiator_name' => $this->initiator_name, + 'initiator_file' => $this->initiator_file, + 'parent_id' => $this->parent_id, + ], + [ + 'id' => $this->get_id(), + ], + [ + '%s', // message ID. + '%s', // subject. + '%s', // people. + '%s', // headers. + '%s', // error_text. + '%s', // error_code. + '%s', // error_key. + '%s', // content_plain. + '%s', // content_html. + '%s', // status. + '%s', // date_sent. + '%s', // mailer. + '%d', // attachments. + '%s', // initiator_name. + '%s', // initiator_file. + '%d', // parent ID. + ], + [ + '%d', + ] + ); + + $email_id = $this->get_id(); + } else { + // Create a new DB table record. + $wpdb->insert( + $table, + [ + 'message_id' => $this->message_id, + 'subject' => $this->subject, + 'people' => $this->people, + 'headers' => $this->headers, + 'error_text' => $this->error_text, + 'error_code' => $this->error_code, + 'error_key' => $this->error_key, + 'content_plain' => $this->content_plain, + 'content_html' => $this->content_html, + 'status' => $this->status, + 'date_sent' => $this->get_date_sent()->format( WP::datetime_mysql_format() ), + 'mailer' => $this->mailer, + 'attachments' => $this->attachments, + 'initiator_name' => $this->initiator_name, + 'initiator_file' => $this->initiator_file, + 'parent_id' => $this->parent_id, + ], + [ + '%s', // message ID. + '%s', // subject. + '%s', // people. + '%s', // headers. + '%s', // error_text. + '%s', // error_code. + '%s', // error_key. + '%s', // content_plain. + '%s', // content_html. + '%s', // status. + '%s', // date_sent. + '%s', // mailer. + '%d', // attachments. + '%s', // initiator_name. + '%s', // initiator_file. + '%d', // parent ID. + ] + ); + + $email_id = $wpdb->insert_id; + } + + // Snapshot the just-saved state. populate_email() falls back to this slot + // when a subsequent DB read for the same id returns nothing (stale read + // replica). Bounded to one entry per request — only the latest save's + // data is kept. The clone keeps the cached snapshot isolated from any + // further mutations to $this. + if ( (int) $email_id > 0 ) { + $snapshot = clone $this; + $snapshot->id = (int) $email_id; + self::$last_saved = $snapshot; + } + + try { + $email = new Email( $email_id ); + } catch ( \Exception $e ) { + $email = new Email(); + } + + return $email; + } + + /** + * Delete the current email. + * Usage: + * $email = new Email( $email_id ); + * $email->delete(); + * + * @since 1.5.0 + * + * @return int|false Integer on successful deletion (number of rows), false on failure. + */ + public function delete() { + + global $wpdb; + + return $wpdb->delete( + Logs::get_table_name(), + array( + 'id' => $this->get_id(), + ), + array( + '%d', + ) + ); + } + + /** + * Whether the email instance is a valid entity to work with. + * + * @since 1.5.0 + */ + public function is_valid() { + + if ( + empty( $this->id ) || + empty( $this->headers ) || + empty( $this->date_sent ) + ) { + return false; + } + + return true; + } + + /** + * Whether the email was successfully sent or not. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_sent() { + + return self::STATUS_SENT === $this->get_status(); + } + + /** + * Whether the email is waiting for delivery verification. + * + * @since 3.9.0 + * + * @return bool + */ + public function is_waiting_for_delivery_verification() { + + return $this->get_status() === self::STATUS_WAITING; + } + + /** + * Whether the email sending has failed. + * + * @since 2.5.0 + * + * @return bool + */ + public function has_failed() { + + return self::STATUS_UNSENT === $this->get_status(); + } + + /** + * Whether the email content has HTML or not. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_html() { + + return ! empty( $this->get_content_html() ); + } + + /** + * If the email has an error text. + * + * @since 2.5.0 + * + * @return bool + */ + public function has_error() { + + return ! empty( $this->get_error_text() ); + } + + /** + * If the email is a test. + * + * @since 3.10.0 + * + * @return bool + */ + public function is_test() { + + $mailer_type = $this->get_header( 'X-Mailer-Type' ); + + return in_array( + $mailer_type, + [ + 'WPMailSMTP/Admin/Test', + 'WPMailSMTP/Admin/SetupWizard/Test', + ], + true + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/EmailsCollection.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/EmailsCollection.php new file mode 100644 index 00000000..acd47c17 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/EmailsCollection.php @@ -0,0 +1,614 @@ + true ] ) + * + * @since 1.5.0 + * + * @param array $params + */ + public function __construct( array $params = array() ) { + + $this->set_per_page(); + $this->params = $this->process_params( $params ); + } + + /** + * Returns an array of allowed search conditions for email logs. + * + * @since 3.8.0 + * + * @return array + */ + public static function get_search_conditions() { + + return [ + 'people' => esc_html__( 'Email Addresses', 'wp-mail-smtp-pro' ), + 'headers' => esc_html__( 'Subject & Headers', 'wp-mail-smtp-pro' ), + 'content' => esc_html__( 'Content', 'wp-mail-smtp-pro' ), + 'error_text' => esc_html__( 'Error Message', 'wp-mail-smtp-pro' ), + 'source' => esc_html__( 'Source', 'wp-mail-smtp-pro' ), + ]; + } + + /** + * Set the per page attribute to the screen options value. + * + * @since 1.9.0 + */ + protected function set_per_page() { + + $per_page = (int) get_user_meta( + get_current_user_id(), + 'wp_mail_smtp_log_entries_per_page', + true + ); + + if ( $per_page < 1 ) { + $per_page = self::PER_PAGE; + } + + self::$per_page = $per_page; + } + + /** + * Verify, sanitize, and populate with default values + * all the passed parameters, which participate in DB queries. + * + * @since 1.5.0 + * @since 1.7.0 Added search processing. + * + * @param array $params + * + * @return array + */ + public function process_params( $params ) { + + $params = (array) $params; + $processed = []; + + /* + * WHERE. + */ + // Single ID. + if ( ! empty( $params['id'] ) ) { + $processed['id'] = intval( $params['id'] ); + } + + // Single message ID. + if ( ! empty( $params['message_id'] ) ) { + $processed['message_id'] = sanitize_text_field( $params['message_id'] ); + } + + // Multiple IDs. + if ( + ! empty( $params['ids'] ) && + is_array( $params['ids'] ) + ) { + $processed['ids'] = array_unique( array_filter( array_map( 'intval', array_values( $params['ids'] ) ) ) ); + } + + // Status. Accepts a single int, or an array of ints for a multi-status + // match (e.g. "sent" spanning STATUS_SENT and STATUS_DELIVERED). + if ( isset( $params['status'] ) ) { + if ( is_array( $params['status'] ) ) { + $statuses = array_values( + array_filter( + array_map( 'intval', $params['status'] ), + static function ( $status ) { + + return in_array( $status, self::STATUSES, true ); + } + ) + ); + + if ( ! empty( $statuses ) ) { + $processed['status'] = $statuses; + } + } elseif ( in_array( $params['status'], self::STATUSES, true ) ) { + $processed['status'] = intval( $params['status'] ); + } + } + + // Mailer. + if ( ! empty( $params['mailer'] ) && is_string( $params['mailer'] ) ) { + $processed['mailer'] = sanitize_text_field( $params['mailer'] ); + } + + // Search. + if ( + ! empty( $params['search']['place'] ) && + is_string( $params['search']['place'] ) && + in_array( strtolower( $params['search']['place'] ), self::SEARCHABLE, true ) + ) { + $processed['search']['place'] = strtolower( sanitize_key( $params['search']['place'] ) ); + } + + if ( ! empty( $params['search']['term'] ) ) { + $processed['search']['term'] = sanitize_text_field( wp_unslash( $params['search']['term'] ) ); + } + + /* + * LIMIT. + */ + if ( ! empty( $params['offset'] ) ) { + $processed['offset'] = intval( $params['offset'] ); + } + + if ( ! empty( $params['per_page'] ) ) { + $processed['per_page'] = intval( $params['per_page'] ); + } + + /* + * ORDER. + */ + if ( + ! empty( $params['order'] ) && + is_string( $params['order'] ) && + in_array( strtoupper( $params['order'] ), [ 'ASC', 'DESC' ], true ) + ) { + $processed['order'] = strtoupper( sanitize_key( $params['order'] ) ); + } + + if ( + ! empty( $params['orderby'] ) && + in_array( $params['orderby'], self::SORTABLE, true ) + ) { + $processed['orderby'] = sanitize_key( $params['orderby'] ); + } + + /* + * Sent date. + */ + if ( ! empty( $params['date'] ) ) { + if ( is_string( $params['date'] ) ) { + $params['date'] = array_fill( 0, 2, $params['date'] ); + } elseif ( is_array( $params['date'] ) && count( $params['date'] ) === 1 ) { + $params['date'] = array_fill( 0, 2, $params['date'][0] ); + } + + // We pass array and treat it as a range from:to. + if ( is_array( $params['date'] ) && count( $params['date'] ) === 2 ) { + $date_start = WP::get_day_period_date( 'start_of_day', strtotime( $params['date'][0] ), 'Y-m-d H:i:s', true ); + $date_end = WP::get_day_period_date( 'end_of_day', strtotime( $params['date'][1] ), 'Y-m-d H:i:s', true ); + + if ( ! empty( $date_start ) && ! empty( $date_end ) ) { + $processed['date'] = [ $date_start, $date_end ]; + } + } + } + + // Merge missing values with defaults. + return wp_parse_args( + $processed, + $this->get_default_params() + ); + } + + /** + * Get the list of default params for a usual query. + * + * @since 1.5.0 + * @since 1.7.0 Added search defaults. + * + * @return array + */ + protected function get_default_params() { + + return array( + 'offset' => 0, + 'per_page' => self::$per_page, + 'order' => 'DESC', + 'orderby' => 'date_sent', + 'search' => array( + 'place' => 'people', + 'term' => '', + ), + ); + } + + /** + * Get the SQL-ready string of WHERE part for a query. + * Later we might implement something more useful and robust. + * For now we are leaving it as simple as possible. + * + * @since 1.5.0 + * @since 1.7.0 Added search support. Refactored the method. + * + * @return string + */ + private function build_where() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + global $wpdb; + + $where = [ '1=1' ]; + + /* + * Shortcut single ID or multiple IDs. + */ + if ( + ! empty( $this->params['id'] ) || + ! empty( $this->params['message_id'] ) || + ! empty( $this->params['ids'] ) + ) { + if ( ! empty( $this->params['id'] ) ) { + $where[] = $wpdb->prepare( 'id = %d', $this->params['id'] ); + } elseif ( ! empty( $this->params['message_id'] ) ) { + $where[] = $wpdb->prepare( 'message_id = %s', $this->params['message_id'] ); + } elseif ( ! empty( $this->params['ids'] ) ) { + $where[] = 'id IN (' . implode( ',', $this->params['ids'] ) . ')'; + } + + // When some ID(s) defined - we should ignore all other possible filtering options. + return implode( ' AND ', $where ); + } + + /* + * Status. + */ + if ( isset( $this->params['status'] ) ) { + if ( is_array( $this->params['status'] ) ) { + $placeholders = implode( ', ', array_fill( 0, count( $this->params['status'] ), '%d' ) ); + + $where[] = $wpdb->prepare( "status IN ( {$placeholders} )", $this->params['status'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + } else { + $where[] = $wpdb->prepare( 'status = %d', $this->params['status'] ); + } + } + + /* + * Mailer. + */ + if ( ! empty( $this->params['mailer'] ) ) { + $where[] = $wpdb->prepare( 'mailer = %s', $this->params['mailer'] ); + } + + /* + * Search. + */ + if ( ! empty( $this->params['search']['term'] ) && ! empty( $this->params['search']['place'] ) ) { + switch ( $this->params['search']['place'] ) { + case 'people': + $where[] = $wpdb->prepare( 'people LIKE %s', '%' . $wpdb->esc_like( $this->params['search']['term'] ) . '%' ); + break; + + case 'headers': + $where[] = '(' . + $wpdb->prepare( + 'subject LIKE %s', + '%' . $wpdb->esc_like( wp_kses( $this->params['search']['term'], [] ) ) . '%' + ) + . ' OR ' . + $wpdb->prepare( + 'headers LIKE %s', + '%' . $wpdb->esc_like( $this->params['search']['term'] ) . '%' + ) + . ')'; + break; + + case 'content': + $where[] = '(' . + $wpdb->prepare( + 'content_plain LIKE %s', + '%' . $wpdb->esc_like( $this->params['search']['term'] ) . '%' + ) + . ' OR ' . + $wpdb->prepare( + 'content_html LIKE %s', + '%' . $wpdb->esc_like( $this->params['search']['term'] ) . '%' + ) + . ')'; + break; + + case 'error_text': + $where[] = $wpdb->prepare( 'error_text LIKE %s', '%' . $wpdb->esc_like( $this->params['search']['term'] ) . '%' ); + break; + + case 'source': + $where[] = $wpdb->prepare( 'initiator_name LIKE %s', '%' . $wpdb->esc_like( $this->params['search']['term'] ) . '%' ); + break; + } + } + + /* + * Sent date. + */ + if ( + ! empty( $this->params['date'] ) && + is_array( $this->params['date'] ) && + count( $this->params['date'] ) === 2 + ) { + $where[] = $wpdb->prepare( + '( date_sent >= %s AND date_sent <= %s )', + $this->params['date'][0], + $this->params['date'][1] + ); + } + + return implode( ' AND ', $where ); + } + + /** + * Get the SQL-ready string of ORDER part for a query. + * Order is always in the params, as per our defaults. + * + * @since 1.5.0 + * @since 3.7.0 Added secondary order by id to group connected logs. + * + * @return string + */ + private function build_order() { + + return 'ORDER BY ' . $this->params['orderby'] . ' ' . $this->params['order'] . ', id ' . $this->params['order']; + } + + /** + * Get the SQL-ready string of LIMIT part for a query. + * Limit is always in the params, as per our defaults. + * + * @since 1.5.0 + * + * @return string + */ + private function build_limit() { + + return 'LIMIT ' . $this->params['offset'] . ', ' . $this->params['per_page']; + } + + /** + * Count the number of DB records according to filters. + * Do not retrieve actual records. + * + * @since 1.5.0 + * + * @return int + */ + public function get_count() { + + $table = Logs::get_table_name(); + + $where = $this->build_where(); + + return (int) WP::wpdb()->get_var( + "SELECT COUNT(id) FROM $table + WHERE {$where}" + ); + } + + /** + * Get the list of DB records. + * You can either use array returned there OR iterate over the whole object, + * as it implements Iterator interface. + * + * @since 1.5.0 + * + * @return \WPMailSMTP\Pro\Emails\Logs\EmailsCollection + */ + public function get() { + + $table = Logs::get_table_name(); + + $where = $this->build_where(); + $limit = $this->build_limit(); + $order = $this->build_order(); + + $data = WP::wpdb()->get_results( + "SELECT * FROM $table + WHERE {$where} + {$order} + {$limit}" + ); + + if ( ! empty( $data ) ) { + // As we got raw data we need to convert each row to Email. + foreach ( $data as $row ) { + $this->list[] = new Email( $row ); + } + } + + return $this; + } + + /** + * Delete emails one by one to be able to count how many were actually deleted. + * + * @since 1.5.0 + * + * @return int Number of deleted emails. + */ + public function delete() { + + $ids = ''; + + if ( ! empty( $this->params['ids'] ) ) { + $ids = implode( ',', $this->params['ids'] ); + } elseif ( ! empty( $this->params['id'] ) ) { + $ids = $this->params['id']; + } + + if ( ! empty( $ids ) ) { + $table = Logs::get_table_name(); + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $result = (int) WP::wpdb()->query( "DELETE FROM $table WHERE id IN ( $ids )" ); + + // Delete attachments. + ( new Attachments() )->delete_attachments( $ids ); + + // Delete tracking events. + ( new TrackingEvents() )->delete_events( $ids ); + + return $result; + } + + return 0; + } + + /********************************************************************************************* + * ****************************** \Counter interface method. ********************************* + *********************************************************************************************/ + + /** + * Count number of Record in a Queue. + * + * @since 1.5.0 + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() { + + return count( $this->list ); + } + + /********************************************************************************************* + * ****************************** \Iterator interface methods. ******************************* + *********************************************************************************************/ + + /** + * Rewind the Iterator to the first element. + * + * @since 1.5.0 + */ + #[\ReturnTypeWillChange] + public function rewind() { + + $this->iterator_position = 0; + } + + /** + * Return the current element. + * + * @since 1.5.0 + * + * @return \WPMailSMTP\Pro\Emails\Logs\Email|null Return null when no items in collection. + */ + #[\ReturnTypeWillChange] + public function current() { + + return $this->valid() ? $this->list[ $this->iterator_position ] : null; + } + + /** + * Return the key of the current element. + * + * @since 1.5.0 + * + * @return int + */ + #[\ReturnTypeWillChange] + public function key() { + + return $this->iterator_position; + } + + /** + * Move forward to next element. + * + * @since 1.5.0 + */ + #[\ReturnTypeWillChange] + public function next() { + + ++ $this->iterator_position; + } + + /** + * Checks if current position is valid. + * + * @since 1.5.0 + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() { + + return isset( $this->list[ $this->iterator_position ] ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/AbstractData.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/AbstractData.php new file mode 100644 index 00000000..77a5191b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/AbstractData.php @@ -0,0 +1,200 @@ +request = $request; + } + + /** + * Get field value. + * + * @since 2.8.0 + * + * @param string $key Field key. + * @param Email $email Email object. + * + * @return string + */ + public function get_field_value( $key, $email ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded + + $val = ''; + + switch ( $key ) { + + case 'people_to': + $val = ! empty( $email->get_people( 'to' ) ) ? implode( ', ', $email->get_people( 'to' ) ) : ''; + break; + + case 'people_from': + $val = ! empty( $email->get_people( 'from' ) ) ? $email->get_people( 'from' ) : ''; + break; + + case 'people_from_name': + $val = $email->get_from_name(); + break; + + case 'subject': + $val = $email->get_subject(); + break; + + case 'content': + $val = $email->get_content(); + break; + + case 'date_sent': + $val = date_i18n( + WP::datetime_format(), + strtotime( get_date_from_gmt( $email->get_date_sent()->format( WP::datetime_mysql_format() ) ) ) + ); + break; + + case 'attachments_count': + $val = (int) $email->get_attachments(); + break; + + case 'attachments': + $attachments = ( new Attachments() )->get_attachments( (int) $email->get_id() ); + + $val = implode( + "\r\n", + array_map( + function ( $attachment ) { + return esc_url( $attachment->get_url() ); + }, + $attachments + ) + ); + break; + + case 'status': + $val = $email->get_status_name(); + break; + + case 'error_text': + $val = trim( $email->get_error_text() ); + break; + + case 'people_cc': + $val = ! empty( $email->get_people( 'cc' ) ) ? implode( ',', $email->get_people( 'cc' ) ) : ''; + break; + + case 'people_bcc': + $val = ! empty( $email->get_people( 'bcc' ) ) ? implode( ',', $email->get_people( 'bcc' ) ) : ''; + break; + + case 'headers': + $val = trim( implode( "\r\n", (array) json_decode( $email->get_headers() ) ) ); + break; + + case 'mailer': + if ( ! empty( $email->get_mailer() ) ) { + $provider = wp_mail_smtp()->get_providers()->get_options( $email->get_mailer() ); + if ( $provider !== null ) { + $val = $provider->get_title(); + } else { + $val = $email->get_mailer(); + } + } + break; + + case 'log_id': + $val = $email->get_id(); + break; + + case 'opened': + $val = $this->get_was_email_already_opened( $email ); + break; + + case 'clicked': + $val = $this->get_was_email_link_already_clicked( $email ); + break; + + case 'initiator': + $val = $email->get_initiator_name(); + break; + } + + /** + * Filters export field value. + * + * @since 2.8.0 + * + * @param mixed $val Field value. + * @param string $key Field key. + * @param Email $email Current email. + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_export_data_get_field_value', $val, $key, $email ); + } + + /** + * Whether email was opened. + * + * @since 2.9.0 + * + * @param Email $email Email object. + * + * @return string + */ + private function get_was_email_already_opened( $email ) { + + if ( ! $email->is_content_type_html_based() || ! ( new Events() )->is_valid_db() ) { + return __( 'N/A', 'wp-mail-smtp-pro' ); + } + + return ( new OpenEmailEvent( $email->get_id() ) )->was_event_already_triggered() ? + __( 'Yes', 'wp-mail-smtp-pro' ) : + __( 'No', 'wp-mail-smtp-pro' ); + } + + /** + * Whether one of email links was clicked. + * + * @since 2.9.0 + * + * @param Email $email Email object. + * + * @return string + */ + private function get_was_email_link_already_clicked( $email ) { + + if ( ! $email->is_content_type_html_based() || ! ( new Events() )->is_valid_db() ) { + return __( 'N/A', 'wp-mail-smtp-pro' ); + } + + return ( new ClickLinkEvent( $email->get_id() ) )->was_event_already_triggered() ? + __( 'Yes', 'wp-mail-smtp-pro' ) : + __( 'No', 'wp-mail-smtp-pro' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Admin.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Admin.php new file mode 100644 index 00000000..c87c1e09 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Admin.php @@ -0,0 +1,289 @@ +assets_url . '/css/vendor/flatpickr.min.css', + [], + '4.6.9' + ); + wp_enqueue_script( + 'wp-mail-smtp-flatpickr', + wp_mail_smtp()->assets_url . '/js/vendor/flatpickr.min.js', + [ 'jquery' ], + '4.6.9', + true + ); + + wp_enqueue_style( + 'wp-mail-smtp-tools-export-email-logs', + wp_mail_smtp()->plugin_url . '/assets/pro/css/smtp-pro-tools-logs-export.min.css', + [], + WPMS_PLUGIN_VER + ); + wp_enqueue_script( + 'wp-mail-smtp-tools-export-email-logs', + wp_mail_smtp()->plugin_url . "/assets/pro/js/smtp-pro-tools-logs-export{$min}.js", + [ 'jquery', 'wp-mail-smtp-flatpickr' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-tools-export-email-logs', + 'wp_mail_smtp_tools_export_email_logs', + $this->get_localized_data() + ); + } + + /** + * Get localized data. + * + * @since 2.8.0 + * + * @return array + */ + private function get_localized_data() { + + return [ + 'nonce' => wp_create_nonce( 'wp-mail-smtp-tools-export-email-logs-nonce' ), + 'lang_code' => sanitize_key( WP::get_language_code() ), + 'export_page' => esc_url( $this->get_link() ), + 'i18n' => Export::get_config( 'i18n' ), + ]; + } + + /** + * Output HTML of the Email Logs export form. + * + * @since 2.8.0 + */ + public function display() { + + $this->request = new Request( 'GET', false ); + ?> +
    + + + + +
    +
    +

    +
    +
    +
    +
    +
    + display_export_type_block(); ?> +
    + +
    +
    + display_common_info_selection_block(); ?> +
    + +
    +
    + display_additional_info_selection_block(); ?> +
    + +
    +
    + display_date_range_block(); ?> +
    + + + +
    + + +
    +
    +
    +
    + request->get_arg( 'common_fields' ); + + foreach ( $fields as $key => $name ) { + printf( + '', + esc_attr( $key ), + esc_attr( $this->get_checked_property( $key, $selected ) ), + esc_html( $name ) + ); + } + } + + /** + * Additional fields checkboxes block HTML. + * + * @since 2.8.0 + */ + private function display_additional_info_selection_block() { + + $fields = Export::get_additional_fields(); + $selected = $this->request->get_arg( 'additional_fields' ); + + foreach ( $fields as $key => $name ) { + printf( + '', + esc_attr( $key ), + esc_attr( $this->get_checked_property( $key, $selected, '' ) ), + esc_html( $name ) + ); + } + } + + /** + * Export type block HTML. + * + * @since 2.8.0 + */ + private function display_export_type_block() { + + $fields = Export::get_export_types(); + $selected = $this->request->get_arg( 'export_type' ); + + foreach ( $fields as $key => $name ) { + printf( + '', + esc_attr( $key ), + esc_attr( $this->get_checked_property( $key, [ $selected ], '' ) ), + esc_html( $name ) + ); + } + } + + /** + * Date range block HTML. + * + * @since 2.8.0 + */ + private function display_date_range_block() { + + $date = implode( ' - ', $this->request->get_arg( 'date' ) ); + ?> + + + request->get_arg( 'search' ); + ?> + + + + get_tmp_filename( $request_id ); + + if ( is_wp_error( $export_file ) ) { + return; + } + + if ( is_file( $export_file ) ) { + unlink( $export_file ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/EMLData.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/EMLData.php new file mode 100644 index 00000000..615bf67d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/EMLData.php @@ -0,0 +1,277 @@ +request->get_data( 'db_args' ) ); + + foreach ( $emails->get() as $email ) { + + try { + $phpmailer = $this->prepare_phpmailer( $email ); + $phpmailer->preSend(); + $content = $phpmailer->getSentMIMEMessage(); + } catch ( \Exception $e ) { + + $this->request->add_notice( + sprintf( /* translators: %1$d - email log ID; %2$s - error message. */ + esc_html__( 'Email #%1$s was skipped. Reason: %2$s.', 'wp-mail-smtp-pro' ), + $email->get_id(), + esc_html( $e->getMessage() ) + ), + 'warning' + ); + continue; + } + + /** + * Filters export table data row. + * + * @since 2.9.0 + * + * @param array $row Row. + * @param Email $email Current email. + * @param TableData $data Data. + */ + $content = apply_filters( 'wp_mail_smtp_pro_emails_logs_export_eml_data_get_content', $content, $email, $this ); + + yield [ $email, $content ]; + } + } + + /** + * Fill in all required PHPMailer properties from email log. + * + * @since 2.9.0 + * + * @param Email $email Email. + * + * @return MailCatcherInterface + */ + protected function prepare_phpmailer( Email $email ) { + + $phpmailer = wp_mail_smtp()->generate_mail_catcher( true ); + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $phpmailer->AllowEmpty = true; + $phpmailer->MessageDate = $email->get_header( 'Date' ); + $phpmailer->MessageID = $email->get_header( 'Message-ID' ); + $phpmailer->XMailer = $email->get_header( 'X-Mailer' ); + + $content_type = $email->get_content_type(); + + if ( 'text/html' === $content_type ) { + $phpmailer->isHTML( true ); + } + + $phpmailer->ContentType = $content_type; + $phpmailer->CharSet = $email->get_charset(); + $phpmailer->Subject = $this->get_field_value( 'subject', $email ); + $phpmailer->Body = $this->get_field_value( 'content', $email ); + // phpcs:enable + + $this->set_addresses( $email, $phpmailer ); + $this->set_other_addresses( $email, $phpmailer ); + $this->set_custom_headers( $email, $phpmailer ); + $this->set_attachments( $email, $phpmailer ); + + return $phpmailer; + } + + /** + * Set email from and to addresses. + * + * @since 2.9.0 + * + * @param Email $email Email. + * @param MailCatcherInterface $phpmailer PHPMailer instance. + */ + protected function set_addresses( $email, $phpmailer ) { + + $phpmailer->setFrom( + $this->get_field_value( 'people_from', $email ), + $this->get_field_value( 'people_from_name', $email ), + false + ); + + $phpmailer->addAddress( $this->get_field_value( 'people_to', $email ) ); + } + + /** + * Set email cc, bcc and reply to addresses. + * + * @since 2.9.0 + * + * @param Email $email Email. + * @param MailCatcherInterface $phpmailer PHPMailer instance. + */ + protected function set_other_addresses( $email, $phpmailer ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded + + $address_headers = [ + 'cc' => array_filter( explode( ',', $this->get_field_value( 'header_cc', $email ) ) ), + 'bcc' => array_filter( explode( ',', $this->get_field_value( 'header_bcc', $email ) ) ), + 'reply_to' => array_filter( explode( ',', $this->get_field_value( 'header_reply_to', $email ) ) ), + ]; + + foreach ( $address_headers as $address_header => $addresses ) { + if ( empty( $addresses ) ) { + continue; + } + + foreach ( (array) $addresses as $address ) { + + $recipient_name = ''; + + if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) { + if ( count( $matches ) === 3 ) { + $recipient_name = $matches[1]; + $address = $matches[2]; + } + } + + switch ( $address_header ) { + case 'cc': + $phpmailer->addCc( $address, $recipient_name ); + break; + case 'bcc': + $phpmailer->addBcc( $address, $recipient_name ); + break; + case 'reply_to': + $phpmailer->addReplyTo( $address, $recipient_name ); + break; + } + } + } + } + + /** + * Set email custom headers. + * + * @since 2.9.0 + * + * @param Email $email Email. + * @param MailCatcherInterface $phpmailer PHPMailer instance. + */ + protected function set_custom_headers( $email, $phpmailer ) { + + $headers = (array) json_decode( $email->get_headers() ); + + // Exclude from custom headers. + $exclude = [ + 'Date' => true, + 'To' => true, + 'From' => true, + 'Cc' => true, + 'Bcc' => true, + 'Reply-To' => true, + 'Subject' => true, + 'Message-ID' => true, + 'X-Mailer' => true, + 'MIME-Version' => true, + 'Content-Type' => true, + ]; + + foreach ( $headers as $header ) { + if ( strpos( $header, ':' ) === false ) { + continue; + } + + list( $name, $content ) = explode( ':', trim( $header ), 2 ); + + $name = trim( $name ); + $content = trim( $content ); + + if ( ! isset( $exclude[ $name ] ) ) { + $phpmailer->addCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) ); + } + } + } + + /** + * Set email attachments. + * + * @since 2.9.0 + * + * @param Email $email Email. + * @param MailCatcherInterface $phpmailer PHPMailer instance. + */ + protected function set_attachments( $email, $phpmailer ) { + + /** Array of Attachment objects. @var Attachment[] $attachments */ + $attachments = ( new Attachments() )->get_attachments( $email->get_id() ); + + if ( empty( $attachments ) ) { + return; + } + + foreach ( $attachments as $attachment ) { + try { + $phpmailer->addAttachment( $attachment->get_path(), $attachment->get_filename() ); + } catch ( \Exception $e ) { + continue; + } + } + } + + /** + * Get field value. + * + * @since 2.9.0 + * + * @param string $key Field key. + * @param Email $email Email object. + * + * @return string + */ + public function get_field_value( $key, $email ) { + + switch ( $key ) { + case 'header_cc': + $val = $email->get_header( 'Cc' ); + break; + + case 'header_bcc': + $val = $email->get_header( 'Bcc' ); + break; + + case 'header_reply_to': + $val = $email->get_header( 'Reply-To' ); + break; + + default: + $val = parent::get_field_value( $key, $email ); + } + + /** + * Filters export field value. + * + * @since 2.9.0 + * + * @param mixed $val Field value. + * @param string $key Field key. + * @param Email $email Current email. + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_export_eml_data_get_field_value', $val, $key, $email ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Export.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Export.php new file mode 100644 index 00000000..3257d36c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Export.php @@ -0,0 +1,227 @@ +get_admin()->is_admin_page( 'tools' ) ) { + add_filter( + 'wp_mail_smtp_admin_page_tools_tabs', + function ( $tabs ) { + + $tabs['export'] = Admin::class; + + return $tabs; + } + ); + } + + // Init export request handler. + if ( $this->is_export_request() ) { + new Handler(); + } + } + + /** + * Get export config. + * + * @since 2.8.0 + * + * @param string $group Fields group if we need particular group. + * @param string $key Field key if we need particular option. + * + * @return mixed + */ + public static function get_config( $group = false, $key = false ) { + + $defaults = [ + // General settings. + 'export' => [ + // Export request and a temp file TTL value. + 'request_data_ttl' => DAY_IN_SECONDS, + // Number of email logs in a chunk that are retrieved and saved into a temp file per one iteration. + 'email_logs_per_step' => 5000, + // Columns separator. + 'csv_export_separator' => ',', + ], + // Error strings. + 'errors' => [ + 'common' => esc_html__( 'An error occurred while preparing your export file. Please recheck export settings and try again.', 'wp-mail-smtp-pro' ), + 'security' => esc_html__( 'You don\'t have enough capabilities to complete this request.', 'wp-mail-smtp-pro' ), + 'unknown_request' => esc_html__( 'Unknown request.', 'wp-mail-smtp-pro' ), + 'unknown_email_id' => esc_html__( 'Incorrect email ID has been specified.', 'wp-mail-smtp-pro' ), + 'file_not_readable' => esc_html__( 'Export file cannot be retrieved from the file system.', 'wp-mail-smtp-pro' ), + 'file_empty' => esc_html__( 'Export file is empty.', 'wp-mail-smtp-pro' ), + ], + // Strings to localize. + 'i18n' => [ + 'error_prefix' => esc_html__( 'An error occurred while preparing your export file. Please recheck export settings and try again.', 'wp-mail-smtp-pro' ), + 'prc_1_filtering' => esc_html__( 'Generating a list of email logs according to your filters.', 'wp-mail-smtp-pro' ), + 'prc_1_please_wait' => esc_html__( 'This can take a while. Please wait.', 'wp-mail-smtp-pro' ), + 'prc_2_no_email_logs' => esc_html__( 'No email logs found after applying your filters.', 'wp-mail-smtp-pro' ), + 'prc_3_done' => esc_html__( 'The file was generated successfully.', 'wp-mail-smtp-pro' ), + 'prc_3_partially' => esc_html__( 'The file was generated partially. Please check below notices.', 'wp-mail-smtp-pro' ), + 'prc_3_download' => esc_html__( 'If the download does not start automatically', 'wp-mail-smtp-pro' ), + 'prc_3_click_here' => esc_html__( 'click here', 'wp-mail-smtp-pro' ), + ], + ]; + + /** + * Filters export configuration. + * + * @since 2.8.0 + * + * @param array $defaults Default configuration values. + */ + $config = (array) apply_filters( 'wp_mail_smtp_pro_emails_logs_export_export_get_config', $defaults ); + + // Make sure that all defaults are set. + $config = WP::parse_args_r( $config, $defaults ); + + if ( $group !== false && $key === false ) { + return isset( $config[ $group ] ) ? $config[ $group ] : null; + } elseif ( $group !== false && $key !== false ) { + return isset( $config[ $group ][ $key ] ) ? $config[ $group ][ $key ] : null; + } + + return $config; + } + + /** + * Common information fields for email logs export. + * + * @since 2.8.0 + * + * @param string $key Field key if we need particular field. + * + * @return mixed + */ + public static function get_common_fields( $key = false ) { + + /** + * Filters export common fields. + * + * @since 2.8.0 + * + * @param array $fields Common fields. + */ + $fields = apply_filters( + 'wp_mail_smtp_pro_emails_logs_export_export_get_common_fields', + [ + 'people_to' => esc_html__( 'To Address', 'wp-mail-smtp-pro' ), + 'people_from' => esc_html__( 'From Address', 'wp-mail-smtp-pro' ), + 'people_from_name' => esc_html__( 'From Name', 'wp-mail-smtp-pro' ), + 'subject' => esc_html__( 'Subject', 'wp-mail-smtp-pro' ), + 'content' => esc_html__( 'Body', 'wp-mail-smtp-pro' ), + 'date_sent' => esc_html__( 'Created Date', 'wp-mail-smtp-pro' ), + 'attachments_count' => esc_html__( 'Number of Attachments', 'wp-mail-smtp-pro' ), + 'attachments' => esc_html__( 'Attachments', 'wp-mail-smtp-pro' ), + ] + ); + + if ( $key !== false ) { + return isset( $fields[ $key ] ) ? $fields[ $key ] : null; + } + + return $fields; + } + + /** + * Additional information fields for email logs export. + * + * @since 2.8.0 + * + * @param string $key Field key if we need particular field. + * + * @return mixed + */ + public static function get_additional_fields( $key = false ) { + + /** + * Filters export additional fields. + * + * @since 2.8.0 + * + * @param array $fields Additional fields. + */ + $fields = apply_filters( + 'wp_mail_smtp_pro_emails_logs_export_export_get_additional_fields', + [ + 'status' => esc_html__( 'Status', 'wp-mail-smtp-pro' ), + 'people_cc' => esc_html__( 'Carbon Copy (CC)', 'wp-mail-smtp-pro' ), + 'people_bcc' => esc_html__( 'Blind Carbon Copy (BCC)', 'wp-mail-smtp-pro' ), + 'headers' => esc_html__( 'Headers', 'wp-mail-smtp-pro' ), + 'mailer' => esc_html__( 'Mailer', 'wp-mail-smtp-pro' ), + 'error_text' => esc_html__( 'Error Details', 'wp-mail-smtp-pro' ), + 'log_id' => esc_html__( 'Email log ID', 'wp-mail-smtp-pro' ), + 'opened' => esc_html__( 'Opened', 'wp-mail-smtp-pro' ), + 'clicked' => esc_html__( 'Clicked', 'wp-mail-smtp-pro' ), + 'initiator' => esc_html__( 'Source', 'wp-mail-smtp-pro' ), + ] + ); + + if ( $key !== false ) { + return isset( $fields[ $key ] ) ? $fields[ $key ] : null; + } + + return $fields; + } + + /** + * Export types. + * + * @since 2.8.0 + * + * @return mixed + */ + public static function get_export_types() { + + $types = []; + + $types['csv'] = esc_html__( 'Export in CSV (.csv)', 'wp-mail-smtp-pro' ); + + // This option should be available only if zip PHP extension is loaded. + if ( class_exists( 'ZipArchive' ) ) { + $types['xlsx'] = esc_html__( 'Export in Microsoft Excel (.xlsx)', 'wp-mail-smtp-pro' ); + $types['eml'] = esc_html__( 'Export in EML (.eml)', 'wp-mail-smtp-pro' ); + } + + /** + * Filters export types. + * + * @since 2.8.0 + * + * @param array $types Export types. + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_export_export_get_export_types', $types ); + } + + /** + * Check if export request. + * + * @since 2.8.0 + * + * @return bool + */ + public function is_export_request() { + + $req = WP::is_doing_ajax() ? $_POST : $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing + + return isset( $req['action'] ) && substr( $req['action'], 0, 25 ) === 'wp_mail_smtp_tools_export'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/File.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/File.php new file mode 100644 index 00000000..29052ed8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/File.php @@ -0,0 +1,403 @@ +plugin_path . '/vendor/autoload.php'; + + $type = $request->get_data( 'type' ); + + if ( $type === 'xlsx' ) { + // Write to the .xlsx file. + return $this->write_xlsx( $request ); + } elseif ( $type === 'eml' ) { + // Writing to the .eml or .zip file. + return $this->write_eml( $request ); + } else { + // Writing to the .csv file. + return $this->write_csv( $request ); + } + } + + /** + * Write data to .csv file. + * + * @since 2.8.0 + * + * @param Request $request Export request. + * + * @return true|WP_Error + */ + public function write_csv( $request ) { + + $export_file = $this->get_tmp_filename( $request->get_request_id() ); + + if ( is_wp_error( $export_file ) ) { + return $export_file; + } + + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen + $f = fopen( 'php://temp', 'wb+' ); + $separator = Export::get_config( 'export', 'csv_export_separator' ); + $enclosure = '"'; + + $data = new TableData( $request ); + + if ( $request->get_arg( 'step' ) === 1 ) { + fputcsv( $f, $data->get_columns(), $separator, $enclosure ); + } + + foreach ( $data->get_row() as $row ) { + fputcsv( $f, $row, $separator, $enclosure ); + } + + rewind( $f ); + + $file_contents = stream_get_contents( $f ); + + $result = $this->put_contents( $export_file, $file_contents ); + + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose + fclose( $f ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + return true; + } + + /** + * Write data to .xlsx file. + * + * @since 2.8.0 + * + * @param Request $request Export request. + * + * @return true|WP_Error + */ + public function write_xlsx( $request ) { + + $export_file = $this->get_tmp_filename( $request->get_request_id() ); + + if ( is_wp_error( $export_file ) ) { + return $export_file; + } + + $writer = new XLSXWriter(); + $sheet_name = 'WPMailSMTP'; + + $data = new TableData( $request ); + + if ( $request->get_arg( 'step' ) === 1 ) { + $widths = array_map( + function ( $header ) { + return strlen( $header ) + 2; + }, + $data->get_columns() + ); + + $writer->writeSheetHeader( $sheet_name, array_fill_keys( $data->get_columns(), 'string' ), [ 'widths' => $widths ] ); + } + + foreach ( $data->get_row() as $row ) { + $writer->writeSheetRow( $sheet_name, $row, [ 'wrap_text' => true ] ); + } + + $writer->writeToFile( $export_file ); + + return true; + } + + /** + * Write data to .eml or .zip file. + * + * @since 2.9.0 + * + * @param Request $request Export request. + * + * @return true|WP_Error + */ + public function write_eml( $request ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $export_file = $this->get_tmp_filename( $request->get_request_id() ); + + if ( is_wp_error( $export_file ) ) { + return $export_file; + } + + $data = new EMLData( $request ); + + $db_args = $request->get_data( 'db_args' ); + + if ( ! empty( $db_args['id'] ) ) { + foreach ( $data->get_content() as $value ) { + list( , $content ) = $value; + file_put_contents( $export_file, $content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents + } + } else { + $zip = new \ZipArchive(); + + /* + * Use `\ZipArchive::OVERWRITE` instead of `\ZipArchive::CREATE` for compatibility with PHP 8.1. + * In PHP 8.1 with `\ZipArchive::CREATE` mode using empty file as ZipArchive is deprecated. + * Since we create file in `File::get_tmp_filename` method, we need to use `\ZipArchive::OVERWRITE` mode. + */ + $zip_open = $zip->open( $export_file, \ZipArchive::OVERWRITE ); + + if ( $zip_open !== true ) { + return new WP_Error( + 'zip_creating', + sprintf( /* translators: %s - Error code. */ + esc_html__( 'An error occurred when creating a zip file. Error code: %s.', 'wp-mail-smtp-pro' ), + $zip_open + ) + ); + } + + // Include polyfill if mbstring PHP extension is not enabled. + if ( ! function_exists( 'mb_substr' ) ) { + Helpers::include_mbstring_polyfill(); + } + + foreach ( $data->get_content() as $value ) { + list( $email, $content ) = $value; + + $filename = $email->get_id() . '-' . sanitize_title( mb_substr( $email->get_subject(), 0, 30 ) ) . '.eml'; + $zip->addFromString( $filename, $content ); + } + + $zip->close(); + } + + return true; + } + + /** + * Output the file. + * + * @since 2.8.0 + * + * @param Request $request Export request. + * + * @throws \Exception In case of file error. + */ + public function output_file( $request ) { + + if ( empty( $request->get_request_id() ) ) { + throw new \Exception( Export::get_config( 'errors', 'unknown_request' ) ); + } + + $export_file = $this->get_tmp_filename( $request->get_request_id() ); + + if ( is_wp_error( $export_file ) ) { + throw new \Exception( $export_file->get_error_message() ); + } + + clearstatcache( true, $export_file ); + + if ( ! is_readable( $export_file ) || is_dir( $export_file ) ) { + throw new \Exception( Export::get_config( 'errors', 'file_not_readable' ) ); + } + + if ( @filesize( $export_file ) === 0 ) { //phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + throw new \Exception( Export::get_config( 'errors', 'file_empty' ) ); + } + + $db_args = $request->get_data( 'db_args' ); + + if ( ! empty( $db_args['id'] ) ) { + $file_name = 'wp-mail-smtp-email-log-' . $db_args['id']; + } else { + $file_name = 'wp-mail-smtp-email-logs'; + } + + $file_ext = $request->get_data( 'type' ); + + if ( $file_ext === 'eml' ) { + $file_ext = 'zip'; + + if ( ! empty( $db_args['id'] ) ) { + $file_ext = 'eml'; + } + } + + $file_name .= '-' . current_time( 'Y-m-d-H-i-s' ) . '.' . $file_ext; + + /** + * Filters export filename. + * + * @since 2.8.0 + * + * @param string $file_name Filename. + * @param Request $request Export request. + */ + $file_name = apply_filters( 'wp_mail_smtp_pro_emails_logs_export_file_output_file', $file_name, $request ); + + $this->http_headers( sanitize_file_name( $file_name ), $request->get_data( 'type' ) ); + + readfile( $export_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_readfile + + exit; + } + + /** + * Temporary files directory. + * + * @since 2.8.0 + * + * @return string|WP_Error + */ + public function get_tmpdir() { + + $upload_dir = Uploads::upload_dir(); + + if ( is_wp_error( $upload_dir ) ) { + return $upload_dir; + } + + $upload_path = $upload_dir['path']; + + $export_path = trailingslashit( $upload_path ) . 'export'; + if ( ! file_exists( $export_path ) ) { + wp_mkdir_p( $export_path ); + } + + // Check if the .htaccess exists in the upload directory, if not - create it. + Uploads::create_upload_dir_htaccess_file(); + + // Check if the index.html exists in the directories, if not - create it. + Uploads::create_index_html_file( $upload_path ); + Uploads::create_index_html_file( $export_path ); + + // Normalize slashes for Windows. + $export_path = wp_normalize_path( $export_path ); + + return $export_path; + } + + /** + * Full pathname to the Temporary file. + * + * @since 2.8.0 + * + * @param string $request_id Request id. + * + * @return string|WP_Error + */ + public function get_tmp_filename( $request_id ) { + + $export_dir = $this->get_tmpdir(); + + if ( is_wp_error( $export_dir ) ) { + return $export_dir; + } + + $export_file = $export_dir . '/' . sanitize_key( $request_id ) . '.tmp'; + + touch( $export_file ); + + return $export_file; + } + + /** + * Send HTTP headers for file download. + * + * @since 2.8.0 + * + * @param string $file_name File name. + * @param string $type File type. + */ + public function http_headers( $file_name, $type ) { + + nocache_headers(); + header( 'Content-Description: File Transfer' ); + header( 'Content-Type: text/' . $type ); + header( 'Content-Disposition: attachment; filename=' . $file_name ); + header( 'Content-Transfer-Encoding: binary' ); + } + + /** + * Put file contents using WP Filesystem. + * + * @since 3.3.0 + * + * @param string $export_file Export filename. + * @param string $file_contents File contents. + * + * @return true|WP_Error + */ + private function put_contents( $export_file, $file_contents ) { + + global $wp_filesystem; + + if ( ! class_exists( 'WP_Filesystem_Direct' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; + } + + if ( ! $wp_filesystem instanceof \WP_Filesystem_Direct ) { + if ( get_filesystem_method() !== 'direct' ) { + return new WP_Error( + 'wrong-wp-filesystem-access', + esc_html__( 'Your WP site is not using the direct file system method, which is needed for this export.', 'wp-mail-smtp-pro' ) + ); + } + + // Instantiate $wp_filesystem global with direct access method. + WP_Filesystem( request_filesystem_credentials( site_url() ) ); + } + + // Borrowed from `WP_Filesystem_Direct::put_contents()`. + $fp = @fopen( $export_file, 'ab' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_read_fopen + + if ( ! $fp ) { + return new WP_Error( + 'export-file-access-error', + esc_html__( 'Something went wrong while opening the export file for writing.', 'wp-mail-smtp-pro' ) + ); + } + + mbstring_binary_safe_encoding(); + + $data_length = strlen( $file_contents ); + $bytes_written = fwrite( $fp, $file_contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite + + reset_mbstring_encoding(); + fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose + + if ( $data_length !== $bytes_written ) { + return new WP_Error( + 'export-file-write-error', + esc_html__( 'Something went wrong while writing to the export file.', 'wp-mail-smtp-pro' ) + ); + } + + $wp_filesystem->chmod( $export_file ); + + return true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Handler.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Handler.php new file mode 100644 index 00000000..1e9da3c6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Handler.php @@ -0,0 +1,256 @@ +hooks(); + } + + /** + * Register hooks. + * + * @since 2.8.0 + */ + public function hooks() { + + add_action( 'admin_init', [ $this, 'init' ] ); + } + + /** + * Initialize. + * + * @since 2.8.0 + */ + public function init() { + + $is_valid = $this->check_requirements(); + + if ( is_wp_error( $is_valid ) ) { + if ( WP::is_doing_ajax() ) { + wp_send_json_error( [ 'error' => $is_valid->get_error_message() ] ); + } else { + WP::add_admin_notice( $is_valid->get_error_message() ); + return; + } + } + + $this->request = new Request( WP::is_doing_ajax() ? 'POST' : 'GET' ); + $action = $this->request->get_arg( 'action' ); + + switch ( $action ) { + case 'wp_mail_smtp_tools_export_single_email_log': + $this->single_email_log_export(); + break; + + case 'wp_mail_smtp_tools_export_email_logs': + add_action( 'wp_ajax_' . $action, [ $this, 'email_logs_export_ajax_handler' ] ); + break; + + case 'wp_mail_smtp_tools_export_download_result': + $this->download_export_file(); + break; + } + } + + /** + * Single email log export file download. + * + * @since 2.8.0 + * + * @throws \Exception Try-catch. + */ + public function single_email_log_export() { + + try { + + // Check for email_id. + if ( empty( $this->request->get_arg( 'email_id' ) ) ) { + throw new \Exception( Export::get_config( 'errors', 'unknown_email_id' ) ); + } + + // Security check. + if ( + ! current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) || + ! wp_verify_nonce( $this->request->get_arg( 'nonce' ), 'wp-mail-smtp-tools-export-single-email-log-nonce' ) + ) { + throw new \Exception( Export::get_config( 'errors', 'security' ) ); + } + + $file = new File(); + $is_exported = $file->write( $this->request ); + + if ( is_wp_error( $is_exported ) ) { + throw new \Exception( $is_exported->get_error_message() ); + } + + ( new ExportCleanupTask() )->schedule( + $this->request->get_request_id(), + Export::get_config( 'export', 'request_data_ttl' ) + ); + + $file->output_file( $this->request ); + + } catch ( \Exception $e ) { + $this->remove_export_file( $this->request->get_request_id() ); + + $error = Export::get_config( 'errors', 'common' ) . '
    ' . $e->getMessage(); + WP::add_admin_notice( $error ); + } + } + + /** + * Bulk email logs ajax export. + * + * @since 2.8.0 + * + * @throws \Exception Try-catch. + */ + public function email_logs_export_ajax_handler() { + + try { + + // Security checks. + if ( + ! current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) || + ! check_ajax_referer( 'wp-mail-smtp-tools-export-email-logs-nonce', 'nonce', false ) + ) { + throw new \Exception( Export::get_config( 'errors', 'security' ) ); + } + + // Unlimited execution time. + WP::set_time_limit(); + + $is_written = ( new File() )->write( $this->request ); + + if ( is_wp_error( $is_written ) ) { + throw new \Exception( $is_written->get_error_message() ); + } + + // Store request data. + $this->request->persist(); + + if ( $this->request->get_data( 'total_steps' ) === $this->request->get_arg( 'step' ) ) { + ( new ExportCleanupTask() )->schedule( + $this->request->get_request_id(), + Export::get_config( 'export', 'request_data_ttl' ) + ); + } + + wp_send_json_success( + [ + 'request_id' => $this->request->get_request_id(), + 'count' => $this->request->get_data( 'count' ), + 'total_steps' => $this->request->get_data( 'total_steps' ), + 'notices' => $this->request->get_notices(), + ] + ); + } catch ( \Exception $e ) { + $this->remove_export_file( $this->request->get_request_id() ); + + $error = Export::get_config( 'errors', 'common' ) . '
    ' . $e->getMessage(); + wp_send_json_error( [ 'error' => $error ] ); + } + } + + /** + * Download export file. + * + * @since 2.8.0 + * + * @throws \Exception Try-catch. + */ + public function download_export_file() { + + try { + + // Security check. + if ( + ! wp_verify_nonce( $this->request->get_arg( 'nonce' ), 'wp-mail-smtp-tools-export-email-logs-nonce' ) || + ! current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) + ) { + throw new \Exception( Export::get_config( 'errors', 'security' ) ); + } + + // Check for request_id. + if ( empty( $this->request->get_request_id() ) ) { + throw new \Exception( Export::get_config( 'errors', 'unknown_request' ) ); + } + + ( new File() )->output_file( $this->request ); + + } catch ( \Exception $e ) { + + // phpcs:disable + $error = Export::get_config( 'errors', 'common' ) . '
    ' . $e->getMessage(); + $error = str_replace( "'", ''', $error ); + + echo " + +
    " . $error . '
    '; + exit; + // phpcs:enable + } + } + + /** + * Check export requirements. + * + * @since 2.8.0 + * + * @return WP_Error|true + */ + protected function check_requirements() { + + if ( ! wp_mail_smtp()->get_pro()->get_logs()->is_valid_db() ) { + return new WP_Error( 'empty_email_log', esc_html__( 'No Email Logs found. It looks like the Email Logs database table is missing.', 'wp-mail-smtp-pro' ) ); + } + + return true; + } + +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Request.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Request.php new file mode 100644 index 00000000..f64b667f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/Request.php @@ -0,0 +1,337 @@ +method = $method; + $this->args = $this->get_args(); + + if ( $init_data ) { + $this->populate_request_data(); + } + } + + /** + * Initialize new request or get existing by request id. + * + * @since 2.8.0 + */ + protected function populate_request_data() { + + if ( empty( $this->args['request_id'] ) ) { + $this->data = $this->get_initial_request_data(); + } else { + $data = get_transient( 'wp_mail_smtp_tools_export_email_logs_request_' . $this->args['request_id'] ); + + if ( $this->get_arg( 'step' ) > 1 ) { + $data['db_args']['offset'] = $data['db_args']['per_page'] * ( $this->get_arg( 'step' ) - 1 ); + } + + $this->data = $data; + } + } + + /** + * Save request data to transient option with defined time to live. + * + * @since 2.8.0 + * + * @return bool + */ + public function persist() { + + if ( empty( $this->get_request_id() ) ) { + return false; + } + + return set_transient( + 'wp_mail_smtp_tools_export_email_logs_request_' . $this->get_request_id(), + $this->data, + Export::get_config( 'export', 'request_data_ttl' ) + ); + } + + /** + * Get request data. + * + * @since 2.8.0 + * + * @param bool $key Data key if we need particular option. + * + * @return mixed|null + */ + public function get_data( $key = false ) { + + if ( $key !== false ) { + return isset( $this->data[ $key ] ) ? $this->data[ $key ] : null; + } + + return $this->data; + } + + /** + * Get argument value by key. + * + * @since 2.8.0 + * + * @param bool $key Argument key if we need particular argument value. + * + * @return mixed|null + */ + public function get_arg( $key ) { + + return isset( $this->args[ $key ] ) ? $this->args[ $key ] : null; + } + + /** + * Get request id helper. + * + * @since 2.8.0 + * + * @return string + */ + public function get_request_id() { + + return $this->get_data( 'request_id' ); + } + + /** + * Get request GET or POST args. + * + * @since 2.8.0 + * + * @return array + */ + public function get_args() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $args = []; + + $method = 'GET' === $this->method ? 'GET' : 'POST'; + $req = 'GET' === $method ? $_GET : $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended + + // Action. + $args['action'] = ''; + if ( ! empty( $req['action'] ) ) { + $args['action'] = sanitize_text_field( wp_unslash( $req['action'] ) ); + } + + // Nonce. + $args['nonce'] = ''; + if ( ! empty( $req['nonce'] ) ) { + $args['nonce'] = sanitize_text_field( wp_unslash( $req['nonce'] ) ); + } + + // Step. + $args['step'] = 1; + if ( ! empty( $req['step'] ) ) { + $args['step'] = intval( $req['step'] ); + } + + // Email ID. + $args['email_id'] = 0; + if ( ! empty( $req['email_id'] ) ) { + $args['email_id'] = (int) $req['email_id']; + } + + // Common Fields. + $args['common_fields'] = []; + if ( ! empty( $req['common_fields'] ) ) { + $args['common_fields'] = array_map( 'sanitize_text_field', wp_unslash( $req['common_fields'] ) ); + } + + // Additional Fields. + $args['additional_fields'] = []; + if ( ! empty( $req['additional_fields'] ) ) { + $args['additional_fields'] = array_map( 'sanitize_text_field', wp_unslash( $req['additional_fields'] ) ); + } + + // Export Type. + $args['export_type'] = 'csv'; + if ( ! empty( $req['export_type'] ) ) { + $args['export_type'] = sanitize_text_field( wp_unslash( $req['export_type'] ) ); + } + + // Date range. + $args['date'] = []; + if ( ! empty( $req['date'] ) ) { + $dates = explode( ' - ', sanitize_text_field( wp_unslash( $req['date'] ) ) ); + + switch ( count( $dates ) ) { + case 1: + $args['date'] = sanitize_text_field( $dates[0] ); + break; + + case 2: + $args['date'] = array_map( 'sanitize_text_field', $dates ); + break; + } + } + + // Search. + $args['search'] = [ + 'place' => 'people', + 'term' => '', + ]; + if ( isset( $req['search'] ) ) { + if ( isset( $req['search']['place'] ) ) { + $args['search']['place'] = sanitize_key( $req['search']['place'] ); + } + if ( ! empty( $req['search']['term'] ) ) { + $args['search']['term'] = sanitize_text_field( wp_unslash( $req['search']['term'] ) ); + } + } + + // Request id. + $args['request_id'] = ''; + if ( ! empty( $req['request_id'] ) ) { + $args['request_id'] = sanitize_text_field( wp_unslash( $req['request_id'] ) ); + } + + return $args; + } + + /** + * Get initial request data at first step. + * + * @since 2.8.0 + * + * @return array Request data. + */ + protected function get_initial_request_data() { + + $args = $this->args; + + // Prepare arguments. + $db_args = [ + 'offset' => 0, + 'per_page' => Export::get_config( 'export', 'email_logs_per_step' ), + 'id' => $args['email_id'], + 'date' => $args['date'], + ]; + + if ( $args['search']['term'] !== '' ) { + $db_args['search']['place'] = $args['search']['place']; + $db_args['search']['term'] = $args['search']['term']; + } + + // Count total email logs. + $count = ( new EmailsCollection( $db_args ) )->get_count(); + + // Prepare `request data` for saving. + $request_data = [ + 'request_id' => md5( wp_json_encode( $db_args ) . microtime() ), + 'db_args' => $db_args, + 'common_fields' => empty( $args['email_id'] ) ? $args['common_fields'] : array_keys( Export::get_common_fields() ), + 'additional_fields' => empty( $args['email_id'] ) ? $args['additional_fields'] : array_keys( Export::get_additional_fields() ), + 'count' => $count, + 'total_steps' => (int) ceil( $count / Export::get_config( 'export', 'email_logs_per_step' ) ), + 'type' => $args['export_type'], + ]; + + // Disable batching for XLSX. + if ( $args['export_type'] === 'xlsx' ) { + $request_data['db_args']['per_page'] = $count; + $request_data['total_steps'] = 1; + } + + return $request_data; + } + + /** + * Get notices. + * + * @since 2.9.0 + * + * @param string $type Error type. + * + * @return array Notices. + */ + public function get_notices( $type = null ) { + + $notices = isset( $this->data['notices'] ) ? $this->data['notices'] : []; + + if ( $type !== null ) { + return array_filter( + $notices, + function ( $notice ) use ( $type ) { + return $notice['type'] === $type; + } + ); + } + + $order = [ 'error', 'warning', 'info' ]; + + usort( + $notices, + function ( $a, $b ) use ( $order ) { + $pos_a = array_search( $a['type'], $order, true ); + $pos_b = array_search( $b['type'], $order, true ); + + return $pos_a - $pos_b; + } + ); + + return $notices; + } + + /** + * Add notice. + * + * @since 2.9.0 + * + * @param string $msg Notice message. + * @param string $type Error type. Valid types: info, warning, error. + */ + public function add_notice( $msg, $type = 'info' ) { + + $this->data['notices'][] = [ + 'type' => $type, + 'message' => $msg, + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/TableData.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/TableData.php new file mode 100644 index 00000000..ea16c197 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Export/TableData.php @@ -0,0 +1,103 @@ +request->get_data(); + + $columns = []; + + if ( ! empty( $request_data['common_fields'] ) ) { + foreach ( $request_data['common_fields'] as $key ) { + $columns[ $key ] = Export::get_common_fields( $key ); + } + } + + if ( ! empty( $request_data['additional_fields'] ) ) { + foreach ( $request_data['additional_fields'] as $key ) { + $columns[ $key ] = Export::get_additional_fields( $key ); + } + } + + /** + * Filters export table data columns. + * + * @since 2.8.0 + * + * @param array $columns Columns. + * @param TableData $data Data. + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_export_table_data_get_columns', $columns, $this ); + } + + /** + * Get single email data row. + * + * @since 2.8.0 + * + * @return Generator + */ + public function get_row() { + + $emails = new EmailsCollection( $this->request->get_data( 'db_args' ) ); + + foreach ( $emails->get() as $email ) { + + $row = []; + + foreach ( $this->get_columns() as $col_id => $col_label ) { + $value = $this->get_field_value( $col_id, $email ); + $row[ $col_id ] = $this->escape_value( $value ); + } + + /** + * Filters export table data row. + * + * @since 2.8.0 + * + * @param array $row Row. + * @param Email $email Current email. + * @param TableData $data Data. + */ + yield apply_filters( 'wp_mail_smtp_pro_emails_logs_export_table_data_get_row', $row, $email, $this ); + } + } + + /** + * Escape string for table data. + * + * @since 3.6.0 + * + * @param mixed $value Value to escape. + * + * @return string + */ + private function escape_value( $value ) { + + // Prevent formulas in spreadsheet applications. + if ( in_array( substr( (string) $value, 0, 1 ), [ '=', '-', '+', '@', "\t", "\r" ], true ) ) { + $value = "'" . $value; + } + + return html_entity_decode( $value, ENT_QUOTES ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterAbstract.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterAbstract.php new file mode 100644 index 00000000..b28a5668 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterAbstract.php @@ -0,0 +1,226 @@ +requirements_satisfied() ) { + return; + } + + $this->hooks(); + } + + /** + * WordPress-related hooks. + * + * @since 3.8.0 + * + * @return void + */ + public function hooks() { + + add_filter( 'wp_mail_smtp_admin_page_tools_tabs', [ $this, 'setup_admin_tab' ] ); + add_filter( 'wp_mail_smtp_pro_emails_logs_importers_importer_tab_abstract_scripts_data', [ $this, 'add_logs_to_import_count' ] ); + add_action( 'wp_ajax_wp_mail_smtp_importer_ajax_' . static::get_slug(), [ $this, 'ajax' ] ); + } + + /** + * Include the importer tab in Tools page. + * + * @since 3.8.0 + * + * @param array $tabs Array containing the tabs in WP Mail SMTP admin tools page. + * + * @return array + */ + public function setup_admin_tab( $tabs ) { + + $tabs[ static::get_slug() ] = $this->get_tab_class(); + + return $tabs; + } + + /** + * Add the logs to import count to the localized JS data. + * + * @since 3.8.0 + * + * @param array $data Data to be localized to Importer JS script. + * + * @return array + */ + public function add_logs_to_import_count( $data ) { + + $data['logs_to_import_count'] = absint( $this->get_logs_to_import_count( true ) ); + + return $data; + } + + /** + * Perform AJAX import action. + * + * @since 3.8.0 + * + * @return void + */ + public function ajax() { + + if ( ! check_ajax_referer( 'wp-mail-smtp-tools-log-importer-' . static::get_slug() . '-nonce', 'nonce', false ) ) { + wp_send_json( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ), 403 ); + } + + if ( ! $this->is_ajax_request_valid() ) { + wp_send_json( esc_html__( 'Import process rejected.', 'wp-mail-smtp-pro' ), 403 ); + } + + $this->perform_import_process( $_POST ); + + /* + * In theory, we shouldn't have end up here. But in case, let's send an error + * response to the client. + */ + $error = new WP_Error( + 'wp_mail_smtp_tools_import_error', + esc_html__( 'Import operation failed.', 'wp-mail-smtp-pro' ), + [ + 'importer' => static::get_slug(), + ] + ); + + wp_send_json_error( + $error, + 400 + ); + } + + /** + * Whether the ajax request is valid or not. + * + * @since 3.8.0 + * + * @return bool + */ + public function is_ajax_request_valid() { + + return current_user_can( wp_mail_smtp()->get_pro()->get_importers()->get_manage_capability() ); + } + + /** + * Get the key of the saved importer options. + * + * @since 3.8.0 + * + * @return string + */ + protected function get_options_key() { + + return 'wp_mail_smtp_logs_' . static::get_slug(); + } + + /** + * Get the saved options for the importer from `wp_options`. + * + * @since 3.8.0 + * + * @return mixed + */ + public function get_saved_options() { + + if ( ! is_null( $this->saved_options ) ) { + return $this->saved_options; + } + + $this->saved_options = get_option( $this->get_options_key(), [] ); + + return $this->saved_options; + } + + /** + * Save the importer options in `wp_options` table. + * + * @since 3.8.0 + * + * @param array $options Array containing the data to be saved. + * + * @return bool + */ + public function save_options( $options ) { + + return update_option( $this->get_options_key(), $options, false ); + } + + /** + * Update saved options. + * + * @since 3.8.0 + * + * @param array $data Array of the data to be saved/updated. + * + * @return bool + */ + public function update_options( $data ) { + + $new_options = []; + $saved_options = $this->get_saved_options(); + + // Loop through each of the currently saved options. + foreach ( $saved_options as $k => $v ) { + if ( array_key_exists( $k, $data ) ) { + // Update if necessary. + $new_options[ $k ] = $data[ $k ]; + } else { + // Make sure existing options are not removed. + $new_options[ $k ] = $v; + } + } + + // We also want to save the new data. + foreach ( $data as $k => $v ) { + if ( ! array_key_exists( $k, $saved_options ) ) { + $new_options[ $k ] = $v; + } + } + + // Save the new options. + return $this->save_options( $new_options ); + } + + /** + * Returns the URL to the importer page. + * + * @since 3.8.0 + * + * @return string + */ + public function get_importer_page_link() { + + return wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools&tab=' . static::get_slug() ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterInterface.php new file mode 100644 index 00000000..3bcff387 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterInterface.php @@ -0,0 +1,105 @@ +setup_slug(); + } + + /** + * WordPress-related hooks. + * + * @since 3.8.0 + * + * @return void + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + } + + /** + * Enqueue JS and CSS files in the importer tab. + * + * @since 3.8.0 + * + * @return void + */ + public function enqueue_assets() { + + $min = WP::asset_min(); + + wp_enqueue_script( + 'wp-mail-smtp-tools-importer', + wp_mail_smtp()->get_pro()->assets_url . "/js/smtp-pro-tools-logs-importer{$min}.js", + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + + /** + * Filters the localized JS data. + * + * @since 3.8.0 + * + * @param array $localized_data Data to be localized. + */ + $localized_data = apply_filters( + 'wp_mail_smtp_pro_emails_logs_importers_importer_tab_abstract_scripts_data', + [ + 'nonce' => wp_create_nonce( 'wp-mail-smtp-tools-log-importer-' . $this->get_slug() . '-nonce' ), + ] + ); + + wp_localize_script( + 'wp-mail-smtp-tools-importer', + 'wp_mail_smtp_tools_importer', + $localized_data + ); + } + + /** + * Display the importer UI. + * + * @since 3.8.0 + * + * @return void + */ + public function display() { + ?> +
    +
    + + tab_heading(); ?> + + tab_result_notice(); ?> + +
    + tab_description(); ?> +
    + + + + + + import_button_html(); ?> +
    +
    + +

    + +

    + +

    + / +

    + +
    +

    + + + + +

    +
    +

    +
    + get_admin()->get_admin_page_url( Area::SLUG . '-tools&tab=' . $this->get_slug() ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterTabAbstractInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterTabAbstractInterface.php new file mode 100644 index 00000000..72f6b6b3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/ImporterTabAbstractInterface.php @@ -0,0 +1,24 @@ + WPMailLogging\Importer::class, + ]; + + /** + * Importers constructor. + * + * @since 3.8.0 + */ + public function __construct() {} + + /** + * Register hooks. + * + * @since 3.8.0 + * + * @return void + */ + public function init() { + + if ( is_admin() && current_user_can( $this->get_manage_capability() ) ) { + $this->register_importers(); + } + } + + /** + * Register supported importers. + * + * @since 3.8.0 + * + * @return void + */ + private function register_importers() { + + $importers = $this->get_importers(); + + foreach ( $importers as $key => $importer ) { + + if ( ! is_a( $importer, ImporterAbstract::class, true ) ) { + continue; + } + + $this->importers_object[ $key ] = new $importer(); + + $this->importers_object[ $key ]->init(); + } + } + + /** + * Get all importers. + * + * @since 3.8.0 + * + * @return array + */ + private function get_importers() { + + /** + * Filter the supported importers. + * + * @since 3.8.0 + * + * @param array $importers Array containing the supported importers. + */ + return apply_filters( 'wp_mail_smtp_pro_emails_logs_importers_get_importers', $this->importers ); + } + + /** + * Get import logs manage capability. + * + * @since 3.8.0 + * + * @return string + */ + public function get_manage_capability() { + + /** + * Filter import logs manage capability. + * + * @since 3.8.0 + * + * @param string $capability Import logs manage capability. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_importers_get_manage_capability', + wp_mail_smtp()->get_capability_manage_options() + ); + } + + /** + * Get the importer object. + * + * @since 3.8.0 + * + * @param string $importer Importer slug of the importer object to fetch. + * + * @return ImporterAbstract|null + */ + public function get_importer( $importer ) { + + if ( array_key_exists( $importer, $this->importers_object ) ) { + return $this->importers_object[ $importer ]; + } + + return null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Importer.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Importer.php new file mode 100644 index 00000000..2d620cc6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Importer.php @@ -0,0 +1,934 @@ +update_options( [ 'noticed_dismissed' => true ] ) + ); + } + + /** + * Display the admin notification. + * + * @since 3.8.0 + * + * @return void + */ + public function display_admin_notification() { + + // We only display the notification in WP Mail SMTP admin pages except for the actual tab. + // We also don't display the notification if there are no logs to import. + if ( ! wp_mail_smtp()->get_admin()->is_admin_page() || $this->is_importer_page() || empty( $this->get_logs_to_import_count() ) ) { + return; + } + + $saved_options = $this->get_saved_options(); + + // We won't display the notification if import was already finished or the notification was dismissed. + if ( + ! empty( $saved_options ) && + ( ! empty( $saved_options['last_complete_import_date'] ) || ! empty( $saved_options['noticed_dismissed'] ) ) + ) { + return; + } + ?> +
    +

    + Get started.', 'wp-mail-smtp-pro' ), + $this->get_logs_to_import_count(), + esc_url( $this->get_importer_page_link() ) + ), + [ + 'a' => [ + 'href' => [], + ], + ] + ); + ?> +

    +
    + get_admin()->is_admin_page( 'tools' ) + && ! empty( $_GET['tab'] ) && $_GET['tab'] === self::get_slug(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + /** + * Returns the importer slug. + * + * @since 3.8.0 + * + * @return string + */ + public static function get_slug() { + + return self::SLUG; + } + + /** + * Returns the number of logs that can be imported to WP Mail SMTP. + * + * @since 3.8.0 + * + * @param bool $get_fresh Whether to use the cached data or get the fresh data. Default `false`. + * + * @return int + */ + public function get_logs_to_import_count( $get_fresh = false ) { + + if ( $get_fresh ) { + return $this->get_and_transient_fresh_logs_to_import_count(); + } + + $this->logs_to_import_count = get_transient( self::LOGS_TO_IMPORT_COUNT_TRANSIENT_KEY ); + + if ( $this->logs_to_import_count !== false ) { + return absint( $this->logs_to_import_count ); + } + + return $this->get_and_transient_fresh_logs_to_import_count(); + } + + /** + * Get fresh logs to import count. + * + * @since 3.8.0 + * + * @return int + */ + private function get_and_transient_fresh_logs_to_import_count() { + + global $wpdb; + + // Check if table exists. + $result = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + 'SHOW TABLES LIKE %s', + $this->get_table_name() + ) + ); + + if ( empty( $result ) ) { + $this->logs_to_import_count = 0; + } else { + $this->logs_to_import_count = absint( $wpdb->get_var( 'SELECT COUNT(*) FROM ' . esc_sql( $this->get_table_name() ) ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + } + + set_transient( self::LOGS_TO_IMPORT_COUNT_TRANSIENT_KEY, $this->logs_to_import_count, HOUR_IN_SECONDS ); + + return $this->logs_to_import_count; + } + + /** + * Returns the Tab class of this importer. + * + * @since 3.8.0 + * + * @return string + */ + public function get_tab_class() { + + return Tab::class; + } + + /** + * Whether the requirements to initialized/support importer is satisfied. + * + * @since 3.8.0 + * + * @return bool + */ + public function requirements_satisfied() { + + if ( ! Options::init()->get( 'logs', 'enabled' ) ) { + return false; + } + + return ! empty( $this->get_logs_to_import_count() ); + } + + /** + * Perform the import. + * + * @since 3.8.0 + * + * @param array $data Contains data regarding the import action. + */ + public function perform_import_process( $data ) { + /* + * 1. First import all logs without attachments. + * 2. Then import all logs with attachments. + */ + $options = $this->get_saved_options(); + + $import_data = []; + $import_status = ! empty( $options['status'] ) ? $options['status'] : self::STATUS_IMPORT_NO_ATTACHMENTS; + + switch ( $import_status ) { + case self::STATUS_IMPORT_COMPLETED: + $this->save_options( + [ + 'last_complete_import_date' => time(), + ] + ); + break; + + case self::STATUS_IMPORT_ATTACHMENTS: + $import_data = $this->start_import_logs_with_attachments(); + break; + + default: + $import_data = $this->start_import_logs_without_attachments(); + break; + } + + $this->send_ajax_response( $import_data, $import_status ); + } + + /** + * Start importing logs without attachments. + * + * @since 3.8.0 + * + * @return array Returns an array containing information regarding the imported logs. + */ + private function start_import_logs_without_attachments() { + + $logs = $this->get_logs( $this->get_number_of_logs_without_attachments_to_import() ); + + if ( empty( $logs ) ) { + $this->update_import_status( self::STATUS_IMPORT_ATTACHMENTS ); + + return [ + 'successful_import_count' => 0, + ]; + } + + $import = $this->import_logs_without_attachments( $logs ); + + if ( ! empty( $import['error'] ) ) { + return [ + 'error' => $import['error'], + 'successful_import_count' => 0, + ]; + } + + if ( $import['number_of_logs_fetched'] < $this->get_number_of_logs_without_attachments_to_import() ) { + $this->update_import_status( self::STATUS_IMPORT_ATTACHMENTS ); + + return [ + 'successful_import_count' => $import['successful_import_count'], + ]; + } + + // Update the last imported mail ID. + $this->save_options( + [ + 'status' => self::STATUS_IMPORT_NO_ATTACHMENTS, + 'last_imported_mail_id' => $import['last_imported_mail_id'], + ] + ); + + return [ + 'successful_import_count' => $import['successful_import_count'], + ]; + } + + /** + * Get logs from WP Mail Logging table. + * + * @since 3.8.0 + * + * @param int $number_of_logs_to_fetch Number of logs to fetch. + * @param bool $with_attachments Optional. Whether to get logs with attachments or not. Default `false`. + * + * @return array + */ + private function get_logs( $number_of_logs_to_fetch, $with_attachments = false ) { + + global $wpdb; + + if ( $with_attachments ) { + $where_clause = 'WHERE `attachments` != ""'; + } else { + $where_clause = 'WHERE `attachments` = ""'; + } + + $options = $this->get_saved_options(); + + if ( $options && ! empty( $options['last_imported_mail_id'] ) ) { + $where_clause .= $wpdb->prepare( ' AND `mail_id` > %d', $options['last_imported_mail_id'] ); + } + + return $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + 'SELECT * from ' . esc_sql( $this->get_table_name() ) . ' ' . $where_clause . ' ORDER BY mail_id ASC LIMIT %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $number_of_logs_to_fetch + ) + ); + } + + /** + * Update the import status. + * + * @since 3.8.0 + * + * @param int $status New status. + * + * @return bool + */ + private function update_import_status( $status ) { + + return $this->save_options( + [ + 'status' => $status, + ] + ); + } + + /** + * Get the number of logs without attachments to import per request. + * + * @since 3.8.0 + * + * @return int + */ + private function get_number_of_logs_without_attachments_to_import() { + + /** + * Filters the number of logs without attachments to import per request. + * + * @since 3.8.0 + * + * @param int $count Number of logs without attachments to import per request. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_importers_wp_mail_logging_importer_count_without_attachments', + self::DEFAULT_NUMBER_OF_LOGS_WITHOUT_ATTACHMENTS_TO_IMPORT_PER_REQUEST + ); + } + + /** + * Import logs without attachments. + * + * @since 3.8.0 + * + * @param array $logs Logs without attachments to be imported. + * + * @return array + */ + private function import_logs_without_attachments( $logs ) { + + global $wpdb; + + $insert_schema = $this->get_insert_schema(); + $insert_schema_keys = array_keys( $insert_schema ); + $insert_schema_val_str = implode( ',', $insert_schema ); + + /* + * This line basically does the following: + * 1. Get the columns (`$insert_schema_keys`) where new values will be inserted. + * 2. Make sure the columns are escaped properly for SQL - `array_map( 'esc_sql', $insert_schema_keys )`. + * 3. Convert the escaped columns from array to string using `implode()`. + * 4. Create the initial "INSERT" statement. + */ + $insert_sql = 'INSERT INTO ' . esc_sql( Logs::get_table_name() ) . ' (' . implode( ',', array_map( 'esc_sql', $insert_schema_keys ) ) . ') VALUES '; + + $logs_counter = count( $logs ); + $logs_last_index = $logs_counter - 1; + + // Keep track of the last imported WP Mail Logging `mail_id`. + $last_imported_mail_id = 0; + + for ( $counter = 0; $counter <= $logs_last_index; $counter++ ) { + + $wp_mail_smtp_log_array = $this->convert_to_wp_mail_smtp_log( $logs[ $counter ] ); + + if ( empty( $wp_mail_smtp_log_array ) ) { + continue; + } + + // Make sure that `$wp_mail_smtp_log_array` has the same keys with the `$insert_schema`. + if ( $insert_schema_keys !== array_keys( $wp_mail_smtp_log_array ) ) { + continue; + } + + $insert_sql .= $wpdb->prepare( + '(' . $insert_schema_val_str . ')', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + array_values( $wp_mail_smtp_log_array ) + ); + + if ( $counter !== $logs_last_index ) { + $insert_sql .= ','; + } + + $last_imported_mail_id = $logs[ $counter ]->mail_id; + } + + $query = $wpdb->query( $insert_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + if ( $query === false ) { + return [ + 'error' => $wpdb->last_error, + ]; + } + + return [ + 'successful_import_count' => $query, + 'number_of_logs_fetched' => $logs_counter, + 'last_imported_mail_id' => $last_imported_mail_id, + ]; + } + + /** + * Returns the insert schema of the import. + * + * This is basically an array with the `wp_wpmailsmtp_emails_log` columns as keys with the + * prepare statement placeholder as the value. + * + * @since 3.8.0 + * + * @return string[] + */ + private function get_insert_schema() { + + return [ + 'message_id' => '%s', + 'subject' => '%s', + 'people' => '%s', + 'headers' => '%s', + 'error_text' => '%s', + 'content_plain' => '%s', + 'content_html' => '%s', + 'status' => '%s', + 'date_sent' => '%s', + 'mailer' => '%s', + 'attachments' => '%d', + 'initiator_name' => '%s', + 'initiator_file' => '%s', + 'parent_id' => '%d', + ]; + } + + /** + * Returns an array which is converted log from WP Mail Logging to WP Mail SMTP Log. + * + * @since 3.8.0 + * + * @param object $log Log from WP Mail Logging to import. + * + * @return array + */ + private function convert_to_wp_mail_smtp_log( $log ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + try { + $email = new Email(); + + $email->set_subject( $log->subject ); + + if ( ! empty( $log->headers ) ) { + $email->set_headers( $this->prepare_headers( $log->headers ) ); + } + + if ( ! empty( $log->error ) ) { + $email->set_error_text( $log->error ); + // If there's an error, we assume that the email was not sucessfully sent. + $email->set_status( Email::STATUS_UNSENT ); + } else { + $email->set_status( Email::STATUS_SENT ); + } + + if ( Options::init()->get( 'logs', 'log_email_content' ) && ! empty( $log->message ) ) { + $email->set_content_html( $log->message ); + } + + if ( ! empty( $log->timestamp ) ) { + $email->set_date_sent( $log->timestamp ); + } + + $email->set_mailer( $this->get_mailer() ); + $email->set_initiator_name( $this->get_initiator_name() ); + + if ( ! empty( $log->receiver ) ) { + $email->set_people( $this->prepare_people_data( $log->receiver, $email->get_header( 'From' ) ) ); + } + + return [ + 'message_id' => $email->get_message_id(), + 'subject' => $email->get_subject(), + 'people' => wp_json_encode( $email->get_people() ), + 'headers' => $email->get_headers(), + 'error_text' => $email->get_error_text(), + 'content_plain' => $email->get_content_plain(), + 'content_html' => $email->get_content_html(), + 'status' => $email->get_status(), + 'date_sent' => $email->get_date_sent()->format( WP::datetime_mysql_format() ), + 'mailer' => $email->get_mailer(), + 'attachments' => $email->get_attachments(), + 'initiator_name' => $email->get_initiator_name(), + 'initiator_file' => $email->get_initiator_file(), + 'parent_id' => $email->get_parent_id(), + ]; + + } catch ( Exception $e ) { + return []; + } + } + + /** + * Convert the `receiver` data from WP Mail Logging to an array compliant for + * import to WP Mail SMTP `people` column. + * + * @since 3.8.0 + * + * @param string $receiver Data from `receiver` column from WP Mail Logging DB. + * @param string $from From data from the headers. + * + * @return array + */ + private function prepare_people_data( $receiver, $from ) { + + $receiver_arr = explode( ',', $receiver ); + + $to = []; + + foreach ( $receiver_arr as $re ) { + $to[] = trim( str_replace( "\\n", '', $re ) ); + } + + $return = []; + + if ( ! empty( $to ) ) { + $return['to'] = $to; + } + + if ( empty( $from ) ) { + return $return; + } + + $matches = null; + + if ( preg_match( '/<(.*?)>/', $from, $matches ) ) { + $from_email = sanitize_email( $matches[1] ); + + if ( ! empty( $from_email ) ) { + $return['from'] = $from_email; + } + } + + return $return; + } + + /** + * Convert the `headers` data from WP Mail Logging to an array compliant for + * import to WP Mail SMTP `headers` column. + * + * @since 3.8.0 + * + * @param string $header_to_import Data from `headers` column from WP Mail Logging DB. + * + * @return false|string[] + */ + private function prepare_headers( $header_to_import ) { + + return explode( "\n", $header_to_import ); + } + + /** + * Get the mailer to be used in this import. + * + * @since 3.8.0 + * + * @return string + */ + private function get_mailer() { + + /** + * Filters the mailer to be used in WP Mail Logging import. + * + * @since 3.8.0 + * + * @param string $mailer Mailer to be used in this import. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_importers_wp_mail_logging_importer_mailer', + 'mail' + ); + } + + /** + * Get the initiator name to be used in this import. + * + * @since 3.8.0 + * + * @return string + */ + private function get_initiator_name() { + + /** + * Filters the initiator name to be used in WP Mail Logging import. + * + * @since 3.8.0 + * + * @param string $initiator_name Initiator name to be used in this import. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_importers_wp_mail_logging_importer_initiator_name', + 'WP Mail Logging' + ); + } + + /** + * Start importing logs with attachments. + * + * @since 3.8.0 + * + * @return array Array containing import operation information. + */ + private function start_import_logs_with_attachments() { + + $logs = $this->get_logs( $this->get_number_of_logs_with_attachments_to_import(), true ); + + if ( empty( $logs ) ) { + $this->update_import_status( self::STATUS_IMPORT_COMPLETED ); + + return [ + 'successful_import_count' => 0, + ]; + } + + return $this->import_logs_with_attachments( $logs ); + } + + /** + * Get the number of logs with attachments to import per request. + * + * @since 3.8.0 + * + * @return int + */ + private function get_number_of_logs_with_attachments_to_import() { + + /** + * Filters the number of logs with attachments to import per request. + * + * @since 3.8.0 + * + * @param int $count Number of logs with attachments to import per request. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_importers_wp_mail_logging_importer_count_with_attachments', + self::DEFAULT_NUMBER_OF_LOGS_WITH_ATTACHMENTS_TO_IMPORT_PER_REQUEST + ); + } + + /** + * Import logs with attachments. + * + * @since 3.8.0 + * + * @param array $logs Logs to import. + * + * @return array Array containing import information. + */ + private function import_logs_with_attachments( $logs ) { + + $successful_import_count = 0; + $failed_import_count = 0; + $failed_attachment_count = 0; + + global $wpdb; + + $insert_schema = $this->get_insert_schema(); + $insert_placeholders = array_values( $insert_schema ); + + foreach ( $logs as $log ) { + + $wp_mail_smtp_log_array = $this->convert_to_wp_mail_smtp_log( $log ); + + $insert = $wpdb->insert( + Logs::get_table_name(), + $wp_mail_smtp_log_array, + $insert_placeholders + ); + + // Whether the import succeeds or not, we update the `last_imported_mail_id` to progress the import operation. + $this->save_options( + [ + 'status' => self::STATUS_IMPORT_ATTACHMENTS, + 'last_imported_mail_id' => $log->mail_id, + ] + ); + + if ( $insert === false ) { + $failed_import_count++; + + continue; + } + + if ( ! empty( $log->attachments ) && wp_mail_smtp()->get_pro()->get_logs()->is_enabled_save_attachments() && ! $this->handle_attachments( $log->attachments, $wpdb->insert_id ) ) { + $failed_attachment_count++; + } + + // Update the successful import count. + $successful_import_count++; + } + + if ( count( $logs ) < $this->get_number_of_logs_with_attachments_to_import() ) { + $this->update_import_status( self::STATUS_IMPORT_COMPLETED ); + } + + return [ + 'failed_import_count' => $failed_import_count, + 'failed_attachment_count' => $failed_attachment_count, + 'successful_import_count' => $successful_import_count, + ]; + } + + /** + * Handle the attachments of the imported log. + * + * @since 3.8.0 + * + * @param string $attachments Path of the attachments separated by comma. + * @param int $wp_mail_smtp_log_id The WP Mail SMTP Log ID. + * + * @return bool Whether all the attachments are migrated or not. + */ + private function handle_attachments( $attachments, $wp_mail_smtp_log_id ) { + + $upload_dir = wp_upload_dir(); + $attachments_arr = explode( ',', $attachments ); + + if ( empty( $attachments_arr ) || empty( $upload_dir['basedir'] ) ) { + return false; + } + + $successful_attached_ctr = 0; + + foreach ( $attachments_arr as $attachment ) { + + $attachment_obj = new Attachment(); + $trim_attachment = trim( $attachment, '\n' ); + + if ( $attachment_obj->add( $upload_dir['basedir'] . $this->leadingslashit( $trim_attachment ), $wp_mail_smtp_log_id ) ) { + $successful_attached_ctr++; + } + } + + if ( empty( $successful_attached_ctr ) ) { + return false; + } + + global $wpdb; + + $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + Logs::get_table_name(), + [ + 'attachments' => $successful_attached_ctr, + ], + [ + 'id' => $wp_mail_smtp_log_id, + ], + '%d', + '%d' + ); + + if ( $successful_attached_ctr !== count( $attachments_arr ) ) { + return false; + } + + return true; + } + + /** + * Prepends a slash to a string if it's not present. + * + * @since 3.8.0 + * + * @param string $string String to prepend slash to. + * + * @return string + */ + private function leadingslashit( $string ) { + + return DIRECTORY_SEPARATOR . ltrim( $string, DIRECTORY_SEPARATOR ); + } + + /** + * Sends back JSON response to the import AJAX request. + * + * @since 3.8.0 + * + * @param array $import_data Array containing information regarding the import process. + * @param int $import_status Import status. + * + * @return void + */ + private function send_ajax_response( $import_data, $import_status ) { + + if ( ! empty( $import_data['error'] ) ) { + wp_send_json_error( + [ + 'continue' => false, + 'error_message' => esc_html( $import_data['error'] ), + ] + ); + } + + wp_send_json_success( + [ + 'failed_import_count' => ! empty( $import_data['failed_import_count'] ) ? absint( $import_data['failed_import_count'] ) : 0, + 'failed_attachment_count' => ! empty( $import_data['failed_attachment_count'] ) ? absint( $import_data['failed_attachment_count'] ) : 0, + 'successful_import_count' => ! empty( $import_data['successful_import_count'] ) ? absint( $import_data['successful_import_count'] ) : 0, + 'continue' => $import_status !== self::STATUS_IMPORT_COMPLETED, + ] + ); + } + + /** + * Database table name where WP Mail Logging logs are stored. + * + * @since 3.8.0 + * + * @return string + */ + public function get_table_name() { + + global $wpdb; + + return $wpdb->prefix . 'wpml_mails'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Tab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Tab.php new file mode 100644 index 00000000..016f3275 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Importers/WPMailLogging/Tab.php @@ -0,0 +1,212 @@ +slug = Importer::get_slug(); + } + + /** + * Get the importer object associated with this importer tab. + * + * @since 3.8.0 + * + * @return ImporterAbstract|null + */ + public function get_importer() { + + if ( $this->importer === false ) { + return null; + } + + if ( ! is_null( $this->importer ) ) { + return $this->importer; + } + + $importer = wp_mail_smtp()->get_pro()->get_importers()->get_importer( 'wpmaillogging' ); + + if ( ! $importer ) { + $this->importer = false; + + return null; + } + + $this->importer = $importer; + + return $this->importer; + } + + /** + * WP-related hooks. + * + * @since 3.8.0 + * + * @return void + */ + public function hooks() { + + parent::hooks(); + add_filter( 'wp_mail_smtp_pro_emails_logs_importers_importer_tab_abstract_scripts_data', [ $this, 'add_localized_strings' ] ); + } + + /** + * Add strings to JS. + * + * @since 3.8.0 + * + * @param array $data Data to be localized. + * + * @return array + */ + public function add_localized_strings( $data ) { + + $data['notice_success'] = esc_html__( 'Successful import. All email logs were successfully imported.', 'wp-mail-smtp-pro' ); + $data['notice_warning'] = esc_html__( 'Some items failed to import.', 'wp-mail-smtp-pro' ); + $data['notice_fail'] = esc_html__( 'Import failed!', 'wp-mail-smtp-pro' ); + + return $data; + } + + /** + * Label of the tab. + * + * @since 3.8.0 + * + * @return string + */ + public function get_label() { + + return esc_html__( 'WP Mail Logging Importer', 'wp-mail-smtp-pro' ); + } + + /** + * Tab description section. + * + * @since 3.8.0 + * + * @return void + */ + public function tab_description() { + + if ( empty( $this->get_importer() ) ) { + return; + } + + $options = $this->get_importer()->get_saved_options(); + + if ( ! empty( $options['last_complete_import_date'] ) ) { + $this->display_repeat_import_description( $options['last_complete_import_date'] ); + + return; + } + + $this->display_first_time_import_description(); + } + + /** + * Tab heading. + * + * @since 3.8.0 + * + * @return void + */ + protected function tab_heading() { + ?> +

    + setTimezone( WP::wp_timezone() ); + ?> +
    +

    + format( get_option( 'date_format' ) ) ) + ) + ?> +

    +
    + display_first_time_import_description(); + } + + /** + * Description for first time import operation. + * + * @since 3.8.0 + * + * @return void + */ + private function display_first_time_import_description() { + + $logs_count = 0; + + if ( ! empty( $this->get_importer() ) ) { + $logs_count = $this->get_importer()->get_logs_to_import_count( true ); + } + ?> +

    + +

    + pro->assets_url . '/css/smtp-pro-logs.min.css'; + } ); + + // Redefine default Lite JS file. + add_filter( 'wp_mail_smtp_admin_enqueue_assets_logs_js', function () { + + return wp_mail_smtp()->pro->assets_url . '/js/smtp-pro-logs' . WP::asset_min() . '.js'; + } ); + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', array( $this, 'enqueue_assets' ) ); + + // Redefine the Logs page display class. + add_filter( 'wp_mail_smtp_admin_display_get_logs_fqcn', function () { + + if ( wp_mail_smtp()->pro->get_logs()->is_archive() ) { + return \WPMailSMTP\Pro\Emails\Logs\Admin\ArchivePage::class; + } else { + return \WPMailSMTP\Pro\Emails\Logs\Admin\SinglePage::class; + } + } ); + + // Add a new Email Log tab under General. + add_filter( 'wp_mail_smtp_admin_get_pages', function ( $pages ) { + + $misc = $pages['misc']; + unset( $pages['misc'] ); + + $pages['logs'] = new Admin\SettingsTab(); + $pages['misc'] = $misc; + + return $pages; + }, 0 ); + + // Filter admin area options save process. + add_filter( 'wp_mail_smtp_options_set', [ $this, 'filter_options_set' ] ); + add_filter( 'wp_mail_smtp_options_get_const_value', [ $this, 'filter_options_get_const_value' ], 10, 4 ); + add_filter( 'wp_mail_smtp_options_is_const_defined', [ $this, 'filter_options_is_const_defined' ], 10, 3 ); + + // Track single email Preview and Deletion. + add_action( 'admin_init', [ $this, 'process_email_preview' ] ); + add_action( 'admin_init', [ $this, 'process_email_delete' ] ); + + // Display notices. + add_action( 'admin_init', [ $this, 'display_notices' ] ); + + if ( $this->is_enabled() ) { + // Actually log emails - SMTP. + add_action( 'wp_mail_smtp_mailcatcher_smtp_pre_send_before', [ $this, 'process_smtp_pre_send_before' ] ); + add_action( 'wp_mail_smtp_mailcatcher_smtp_send_before', [ $this, 'process_smtp_send_before' ] ); + add_action( 'wp_mail_smtp_mailcatcher_smtp_send_after', [ $this, 'process_smtp_send_after' ], 10, 7 ); + + // Actually log emails - Catch All. + add_action( 'wp_mail_smtp_mailcatcher_pre_send_before', [ $this, 'process_pre_send_before' ] ); + add_action( 'wp_mail_smtp_mailcatcher_send_after', [ $this, 'process_log_save' ], 10, 2 ); + + // Log email errors. + add_action( 'wp_mail_smtp_mailcatcher_send_failed', [ $this, 'process_smtp_fails' ], 10, 7 ); + + // Process AJAX request for deleting all logs. + add_action( 'wp_ajax_wp_mail_smtp_delete_all_log_entries', [ $this, 'process_ajax_delete_all_log_entries' ] ); + + // Set parent email log ID for backup connection email. + add_action( + 'wp_mail_smtp_pro_backup_connections_send_email_before', + function () { + $this->set_current_email_parent_id( $this->get_current_email_id() ); + } + ); + add_action( 'wp_mail_smtp_pro_backup_connections_send_email_after', [ $this, 'reset_current_email_parent_id' ] ); + + add_action( 'wp_mail_smtp_mail_catcher_send_blocked', [ $this, 'maybe_log_blocked_email' ] ); + + // Initialize email print preview. + ( new PrintPreview() )->hooks(); + + // Initialize emails resend. + ( new Resend() )->hooks(); + + // Initialize email recheck delivery status. + ( new RecheckDeliveryStatus() )->hooks(); + } + + // Initialize webhooks. + $this->get_webhooks(); + + // Initialize email tracking. + ( new Tracking( $this->is_enabled_tracking() ) )->hooks(); + + // Initialize screen options for the logs admin archive page. + add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-logs', [ $this, 'archive_screen_options' ] ); + add_filter( 'set-screen-option', [ $this, 'set_archive_screen_options' ], 10, 3 ); + add_filter( 'set_screen_option_wp_mail_smtp_log_entries_per_page', [ $this, 'set_archive_screen_options' ], 10, 3 ); + + // Set default hidden columns for logs table. + add_filter( 'default_hidden_columns', [ $this, 'archive_table_default_hidden_columns' ], 10, 2 ); + + // Register the sent status verification tasks. + add_action( 'wp_mail_smtp_providers_mailer_verify_sent_status', [ $this, 'run_sent_status_verification' ], 10, 2 ); + + // Detect log retention period constant change. + if ( Options::init()->is_const_defined( 'logs', 'log_retention_period' ) ) { + add_action( 'admin_init', [ $this, 'detect_log_retention_period_constant_change' ] ); + } + + // Init logs table. + if ( $this->is_archive() ) { + add_action( + 'admin_init', + function () { + wp_mail_smtp()->get_admin()->generate_display_logs_object()->get_table(); + } + ); + } + + // Init reports admin page. + add_filter( + 'wp_mail_smtp_admin_page_reports_tabs', + function ( $tabs ) { + $tabs['reports'] = ReportsAdmin::class; + return $tabs; + } + ); + + // Disable summary report email if email log is disabled. + if ( ! $this->is_enabled() ) { + add_filter( 'wp_mail_smtp_reports_emails_summary_is_disabled', '__return_true' ); + } + } + + /** + * Register the screen options for the email logs archive page. + * + * @since 1.9.0 + */ + public function archive_screen_options() { + + $screen = get_current_screen(); + + if ( + ! is_object( $screen ) || + strpos( $screen->id, 'wp-mail-smtp_page_wp-mail-smtp-logs' ) === false || + isset( $_REQUEST['mode'] ) //phpcs:ignore + ) { + return; + } + + add_screen_option( + 'per_page', + array( + 'label' => esc_html__( 'Number of log entries per page:', 'wp-mail-smtp-pro' ), + 'option' => 'wp_mail_smtp_log_entries_per_page', + 'default' => EmailsCollection::PER_PAGE, + ) + ); + } + + /** + * Set the screen options for the archive logs page. + * + * @since 1.9.0 + * + * @param bool $keep Whether to save or skip saving the screen option value. + * @param string $option The option name. + * @param int $value The number of items to use. + * + * @return bool|int + */ + public function set_archive_screen_options( $keep, $option, $value ) { + + if ( 'wp_mail_smtp_log_entries_per_page' === $option ) { + return (int) $value; + } + + return $keep; + } + + /** + * Set default hidden columns for logs table. + * This method is only working for new users, + * for existing users a migration is hiding these columns. + * + * @since 3.1.0 + * + * @param string[] $hidden Array of IDs of columns hidden by default. + * @param \WP_Screen $screen WP_Screen object of the current screen. + * + * @return array + */ + public function archive_table_default_hidden_columns( $hidden, $screen ) { + + if ( $screen->id === 'wp-mail-smtp_page_wp-mail-smtp-logs' ) { + $hidden[] = 'cc'; + $hidden[] = 'bcc'; + } + + return $hidden; + } + + /** + * Sanitize admin area options. + * + * @since 1.5.0 + * + * @param array $options Currently processed options passed to a filter hook. + * + * @return array + */ + public function filter_options_set( $options ) { + + if ( ! isset( $options['logs'] ) ) { + // All options are off by default. + $options['logs'] = [ + 'enabled' => false, + 'log_blocked_emails' => false, + 'log_email_content' => false, + 'log_retention_period' => 0, + 'save_attachments' => false, + 'open_email_tracking' => false, + 'click_link_tracking' => false, + ]; + + return $options; + } + + foreach ( $options['logs'] as $key => $value ) { + if ( $key === 'log_retention_period' ) { + $options['logs'][ $key ] = intval( $value ); + + // If this option has changed, cancel the recurring cleanup task. + if ( Options::init()->is_option_changed( $options['logs'][ $key ], 'logs', $key ) ) { + ( new EmailLogCleanupTask() )->cancel(); + } + } else { + $options['logs'][ $key ] = (bool) $value; + } + } + + return $options; + } + + /** + * Process the options values through the constants check. + * + * @since 2.8.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 ) { + + $options = Options::init(); + + if ( $group === 'logs' ) { + switch ( $key ) { + case 'enabled': + $return = $options->is_const_defined( $group, $key ) ? + $options->parse_boolean( WPMS_LOGS_ENABLED ) : + $value; + break; + case 'log_blocked_emails': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore + $return = $options->is_const_defined( $group, $key ) ? + $options->parse_boolean( WPMS_LOG_BLOCKED_EMAILS ) : + $value; + break; + case 'log_email_content': + $return = $options->is_const_defined( $group, $key ) ? + $options->parse_boolean( WPMS_LOGS_LOG_EMAIL_CONTENT ) : + $value; + break; + case 'log_retention_period': + $return = $options->is_const_defined( $group, $key ) ? + intval( WPMS_LOGS_LOG_RETENTION_PERIOD ) : + $value; + break; + case 'save_attachments': + $return = $options->is_const_defined( $group, $key ) ? + $options->parse_boolean( WPMS_LOGS_SAVE_ATTACHMENTS ) : + $value; + break; + case 'open_email_tracking': + $return = $options->is_const_defined( $group, $key ) ? + $options->parse_boolean( WPMS_LOGS_OPEN_EMAIL_TRACKING ) : + $value; + break; + case 'click_link_tracking': + $return = $options->is_const_defined( $group, $key ) ? + $options->parse_boolean( WPMS_LOGS_CLICK_LINK_TRACKING ) : + $value; + break; + } + } + + return $return; + } + + /** + * Check is constant defined. + * + * @since 2.8.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 ) { + + if ( $group === 'logs' ) { + switch ( $key ) { + case 'enabled': + $return = defined( 'WPMS_LOGS_ENABLED' ); + break; + case 'log_blocked_emails': + $return = defined( 'WPMS_LOG_BLOCKED_EMAILS' ); + break; + case 'log_email_content': + $return = defined( 'WPMS_LOGS_LOG_EMAIL_CONTENT' ); + break; + case 'log_retention_period': + $return = defined( 'WPMS_LOGS_LOG_RETENTION_PERIOD' ); + break; + case 'save_attachments': + $return = defined( 'WPMS_LOGS_SAVE_ATTACHMENTS' ); + break; + case 'open_email_tracking': + $return = defined( 'WPMS_LOGS_OPEN_EMAIL_TRACKING' ); + break; + case 'click_link_tracking': + $return = defined( 'WPMS_LOGS_CLICK_LINK_TRACKING' ); + break; + } + } + + return $return; + } + + /** + * Detect log retention period constant change. + * + * @since 2.8.0 + */ + public function detect_log_retention_period_constant_change() { + + if ( ! WP::in_wp_admin() ) { + return; + } + + if ( Options::init()->is_const_changed( 'logs', 'log_retention_period' ) ) { + ( new EmailLogCleanupTask() )->cancel(); + } + } + + /** + * Get admin area page URL for Logs, regardless of page mode, default is Archive page. + * + * @since 1.5.0 + * + * @return string + */ + public function get_admin_page_url() { + + return wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' ); + } + + /** + * Get plugin settings page URL for Logs. + * + * @since 2.7.0 + * + * @return string + */ + public function get_settings_url() { + + return add_query_arg( 'tab', 'logs', wp_mail_smtp()->get_admin()->get_admin_page_url() ); + } + + /** + * Get email logs page manage capability. + * + * @since 2.8.0 + * + * @return string + */ + public function get_manage_capability() { + + /** + * Filter email logs page manage capability. + * + * @since 2.8.0 + * + * @param string $capability Email logs page manage capability. + */ + return apply_filters( + 'wp_mail_smtp_pro_emails_logs_logs_get_manage_capability', + wp_mail_smtp()->get_capability_manage_options() + ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 1.5.0 + */ + public function enqueue_assets() { + + if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) ) { + return; + } + + $settings = [ + 'plugin_url' => wp_mail_smtp()->plugin_url, + 'text_email_delete_sure' => esc_html__( 'Are you sure that you want to delete this email log? This action cannot be undone.', 'wp-mail-smtp-pro' ), + 'ok' => esc_html__( 'OK', 'wp-mail-smtp-pro' ), + 'icon' => esc_html__( 'Icon', 'wp-mail-smtp-pro' ), + 'delete_all_email_logs_confirmation_text' => esc_html__( 'Are you sure you want to permanently delete all email logs?', 'wp-mail-smtp-pro' ), + 'recheck_all_email_logs_status_confirmation_text' => esc_html__( 'Are you sure you want to re-check the statuses of all pending email logs?', 'wp-mail-smtp-pro' ), + 'heads_up_title' => esc_html__( 'Heads up!', 'wp-mail-smtp-pro' ), + 'yes_text' => esc_html__( 'Yes', 'wp-mail-smtp-pro' ), + 'cancel_text' => esc_html__( 'Cancel', 'wp-mail-smtp-pro' ), + 'error_occurred' => esc_html__( 'An error occurred!', 'wp-mail-smtp-pro' ), + ]; + + if ( $this->is_archive() ) { + wp_enqueue_style( + 'wp-mail-smtp-admin-flatpickr', + wp_mail_smtp()->assets_url . '/css/vendor/flatpickr.min.css', + [], + '4.6.9' + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-flatpickr', + wp_mail_smtp()->assets_url . '/js/vendor/flatpickr.min.js', + [], + '4.6.9', + false + ); + + $settings['lang_code'] = sanitize_key( WP::get_language_code() ); + $settings['bulk_resend_email_confirmation_text'] = Resend::prepare_resend_confirmation_content( false, true ); + $settings['bulk_resend_email_processing_text'] = esc_html__( 'Queuing emails...', 'wp-mail-smtp-pro' ); + } else { + $email = new Email( intval( $_GET['email_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + + if ( $email->is_valid() ) { + $settings['email_id'] = $email->get_id(); + $settings['resend_email_confirmation_text'] = Resend::prepare_resend_confirmation_content( $email ); + $settings['resend_email_processing_text'] = esc_html__( 'Sending email...', 'wp-mail-smtp-pro' ); + $settings['resend_email_invalid_recipients_addresses'] = esc_html__( 'Invalid recipients email addresses.', 'wp-mail-smtp-pro' ); + } + } + + wp_localize_script( + 'wp-mail-smtp-admin-logs', + 'wp_mail_smtp_logs', + $settings + ); + } + + /** + * Whether the logging to DB functionality is enabled or not. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_enabled() { + + return (bool) Options::init()->get( 'logs', 'enabled' ); + } + + /** + * Whether the DB table exists. + * + * @since 1.7.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; + } + + /** + * Whether the email content logging is enabled or not. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_enabled_content() { + + return (bool) Options::init()->get( 'logs', 'log_email_content' ); + } + + /** + * Whether the attachment storing/saving is enabled or not. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_enabled_save_attachments() { + + return $this->is_enabled() && (bool) Options::init()->get( 'logs', 'save_attachments' ); + } + + /** + * Whether the open email tracking is enabled or not. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_enabled_open_email_tracking() { + + return $this->is_enabled() && (bool) Options::init()->get( 'logs', 'open_email_tracking' ); + } + + /** + * Whether the click link tracking is enabled or not. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_enabled_click_link_tracking() { + + return $this->is_enabled() && (bool) Options::init()->get( 'logs', 'click_link_tracking' ); + } + + /** + * Whether the email tracking is enabled or not. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_enabled_tracking() { + + return $this->is_enabled_open_email_tracking() || $this->is_enabled_click_link_tracking(); + } + + /** + * Get the email connection. + * + * @since 3.7.0 + * + * @param Email $email Email object. + * + * @return ConnectionInterface + */ + public function get_email_connection( $email ) { + + $connection_id = $email->get_connection_id(); + $connections_manager = wp_mail_smtp()->get_connections_manager(); + + if ( ! empty( $connection_id ) ) { + $connection = $connections_manager->get_connection( $email->get_connection_id(), false ); + } else { + $connection = $connections_manager->get_primary_connection(); + } + + return $connection; + } + + /** + * Whether we are on a Logs page (archive, list of all emails). + * + * @since 1.5.0 + * + * @return bool + */ + public function is_archive() { + + return wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) && ! isset( $_GET['mode'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + /** + * Whether we are previewing the single email HTML. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_preview() { + + // Nonce verification. + if ( + ! isset( $_GET['_wpnonce'] ) || + ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wp_mail_smtp_pro_logs_log_preview' ) + ) { + return false; + } + + return wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) && + isset( $_GET['mode'] ) && + $_GET['mode'] === 'preview' && + ! empty( $_GET['email_id'] ); + } + + /** + * Whether we are deleting email(s) now. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_deleting() { + + $is_nonce_good = false; + + // Nonce verification. + if ( + isset( $_REQUEST['_wpnonce'] ) && + ( + wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'wp_mail_smtp_pro_logs_log_delete' ) || + wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-emails' ) + ) + ) { + $is_nonce_good = true; + } + + if ( ! $is_nonce_good ) { + return false; + } + + return wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) && + ! empty( $_REQUEST['email_id'] ) && + ( + ( // Single email deletion. + isset( $_REQUEST['mode'] ) && $_REQUEST['mode'] === 'delete' + ) || + ( // Bulk email deletion. + isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'delete' || + isset( $_REQUEST['action2'] ) && $_REQUEST['action2'] === 'delete' + ) + ); + } + + /** + * Generate an email preview and display it for users. + * + * @since 1.5.0 + */ + public function process_email_preview() { + + if ( ! $this->is_preview() ) { + return; + } + + if ( ! current_user_can( $this->get_manage_capability() ) ) { + wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + $email = new Email( intval( $_GET['email_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated + + // The preview response renders stored email HTML in a same-origin + // admin document. A strict CSP blocks script execution from any + // source, including entity-smuggled payloads from upstream carriers + // and importer history, while leaving inline CSS and images intact. + // frame-ancestors 'self' keeps the response embeddable in our own + // Thickbox + PrintPreview iframe wrappers (both already point here) + // and blocks cross-origin framing. + header( "Content-Security-Policy: script-src 'none'; object-src 'none'; base-uri 'none'; frame-ancestors 'self';" ); + + // It's a raw HTML (with html/body tags), so print as is. + echo $email->is_html() ? + $this->prepare_email_preview_html( $email->get_content() ) : // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + nl2br( esc_html( $email->get_content() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + exit; + } + + /** + * Prepare email preview html. + * + * @since 2.8.0 + * + * @param string $html Email content html. + * + * @return string + */ + public function prepare_email_preview_html( $html ) { + + ob_start(); + ?> + + /', $print_css . '', $html, 1 ); + + return $html; + } + + /** + * Delete the email log entry. + * + * @since 1.5.0 + */ + public function process_email_delete() { + + if ( ! $this->is_deleting() ) { + return; + } + + if ( ! current_user_can( $this->get_manage_capability() ) ) { + wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ! isset( $_REQUEST['email_id'] ) ) { + wp_die( esc_html__( 'Required parameters are missing.', 'wp-mail-smtp-pro' ) ); + } + + $emails = null; + + // phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( is_array( $_REQUEST['email_id'] ) ) { + $emails = new EmailsCollection( [ 'ids' => array_map( 'intval', array_values( $_REQUEST['email_id'] ) ) ] ); + } elseif ( is_numeric( $_REQUEST['email_id'] ) ) { + $emails = new EmailsCollection( [ 'id' => intval( $_REQUEST['email_id'] ) ] ); + } + // phpcs:enable + + $deleted = 0; + + if ( $emails !== null ) { + $deleted = $emails->delete(); + } + + if ( $deleted === 1 ) { + $url = add_query_arg( 'message', 'deleted_one', $this->get_admin_page_url() ); + } elseif ( $deleted > 1 ) { + $url = add_query_arg( 'message', 'deleted_some', $this->get_admin_page_url() ); + } else { + $url = add_query_arg( 'message', 'deleted_none', $this->get_admin_page_url() ); + } + + wp_safe_redirect( $url ); + exit; + } + + /** + * Display notices on Logs page when needed. + * + * @since 1.5.0 + */ + public function display_notices() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $message = isset( $_GET['message'] ) ? sanitize_key( $_GET['message'] ) : ''; + + if ( + empty( $message ) || + ! current_user_can( $this->get_manage_capability() ) || + ! wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) + ) { + return; + } + + switch ( $message ) { + case 'deleted_one': + WP::add_admin_notice( + esc_html__( 'Email Log entry was successfully deleted.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + break; + + case 'deleted_some': + WP::add_admin_notice( + esc_html__( 'Email Log entries were successfully deleted.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + break; + + case 'deleted_none': + WP::add_admin_notice( + esc_html__( 'There was an error while processing your request, and no email log entries were deleted. Please try again.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_WARNING + ); + break; + } + } + + /** + * Save the email that is going to be sent right now. + * + * @since 2.1.2 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + public function process_smtp_pre_send_before( $mailcatcher ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + $this->set_current_email_id( + ( new SMTP() )->set_source( $mailcatcher )->save_before( $this->get_current_email_parent_id() ) + ); + } + + /** + * Update the email that is going to be sent right now. + * It was originally saved before in `process_smtp_pre_send_before`. + * + * @since 1.5.0 + * @since 2.1.2 Update the Email before it will be sent. + * The saving was already done in `process_smtp_pre_send_before`. + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + public function process_smtp_send_before( $mailcatcher ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + ( new SMTP() )->set_source( $mailcatcher )->update_before( $this->get_current_email_id() ); + } + + /** + * Save to DB emails sent through both SMTP and mail(). + * + * @since 1.5.0 + * + * @param bool $is_sent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + * + * @throws \Exception When email saving failed. + */ + public function process_smtp_send_after( $is_sent, $to, $cc, $bcc, $subject, $body, $from ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + ( new SMTP() )->save_after( $this->get_current_email_id(), $is_sent ); + } + + /** + * Save the actual email data before email send. + * + * @since 2.9.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + public function process_pre_send_before( $mailcatcher ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + $this->set_current_email_id( + ( new Common() )->set_source( $mailcatcher )->save_before( $this->get_current_email_parent_id() ) + ); + } + + /** + * Save all emails as sent regardless of the actual status. Will be improved in the future. + * Supports every mailer, except SMTP, which is handled separately. + * + * @since 1.5.0 + * + * @param \WPMailSMTP\Providers\MailerAbstract $mailer The Mailer object. + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + */ + public function process_log_save( MailerAbstract $mailer, MailCatcherInterface $mailcatcher ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + $email_id = ( new Common( $mailer ) )->set_source( $mailcatcher )->save( $this->get_current_email_id() ); + + $mailer->verify_sent_status( $email_id ); + } + + /** + * Process the failed email sending. + * + * @since 2.1.0 + * @since 4.0.0 Added API based mailers error processing. + * @since 4.9.0 Added $response_code, $connection, and $error_key params. + * + * @param WP_Error|string $error The WP Error or error message. + * @param MailCatcherInterface $mailcatcher The MailCatcher instance (unused here). + * @param string $mailer_slug Mailer slug as passed by the hook (unused here; we + * resolve from the connection to stay backward compatible). + * @param string $error_code Structured error code captured at send-failure time. + * @param int $response_code HTTP/SMTP response code (unused here). + * @param ConnectionInterface $connection Connection instance (unused here). + * @param string $error_key Composite canonical error key (mailer:category:code). + */ + public function process_smtp_fails( $error, $mailcatcher = null, $mailer_slug = '', $error_code = '', $response_code = 0, $connection = null, $error_key = '' ) { + + if ( ! $this->is_valid_db() ) { + return; + } + + $resolved_mailer_slug = wp_mail_smtp()->get_connections_manager()->get_mail_connection()->get_mailer_slug(); + + if ( in_array( $resolved_mailer_slug, [ 'smtp', 'pepipost', 'mail' ], true ) ) { + ( new SMTP() )->failed( $this->get_current_email_id(), $error, $error_code, $error_key ); + } else { + ( new Common() )->failed( $this->get_current_email_id(), $error, $error_code, $error_key ); + } + } + + /** + * Get the current email ID. + * + * @since 1.5.0 + * + * @return int + */ + public function get_current_email_id() { + + return (int) $this->current_email_id; + } + + /** + * Set the email ID that is currently processing. + * + * @since 1.5.0 + * + * @param int $email_id + */ + public function set_current_email_id( $email_id ) { + + $this->current_email_id = (int) $email_id; + } + + /** + * Get the current email parent email log ID. + * + * @since 3.7.0 + * + * @return int + */ + private function get_current_email_parent_id() { + + return $this->current_email_parent_id; + } + + /** + * Set the current email parent email log ID. + * + * @since 3.7.0 + * + * @param int $email_id The email ID. + */ + public function set_current_email_parent_id( $email_id ) { + + $this->current_email_parent_id = (int) $email_id; + } + + /** + * Reset parent email log ID. + * + * @since 3.7.0 + */ + public function reset_current_email_parent_id() { + + $this->current_email_parent_id = 0; + } + + /** + * Get the table name. + * + * @since 1.5.0 + * + * @return string Table name, prefixed. + */ + public static function get_table_name() { + + global $wpdb; + + return $wpdb->prefix . 'wpmailsmtp_emails_log'; + } + + /** + * Process AJAX request for deleting all email log entries. + * + * @since 2.5.0 + */ + public function process_ajax_delete_all_log_entries() { + + if ( + empty( $_POST['nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_delete_log_entries' ) + ) { + wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) ); + } + + if ( ! current_user_can( $this->get_manage_capability() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + global $wpdb; + + $table = self::get_table_name(); + + $sql = "TRUNCATE TABLE `$table`;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + if ( $result !== false ) { + + // Delete all attachments if any. + ( new Attachments() )->delete_all_attachments(); + + // Delete all tracking events if any. + ( new TrackingEvents() )->delete_all_events(); + + wp_send_json_success( esc_html__( 'All email log entries were deleted successfully.', 'wp-mail-smtp-pro' ) ); + } + + wp_send_json_error( + sprintf( /* translators: %s - WPDB error message. */ + esc_html__( 'There was an issue while trying to delete all email log entries. Error message: %s', 'wp-mail-smtp-pro' ), + $wpdb->last_error + ) + ); + } + + /** + * Initialize the background tasks for sent status verification, based on the mailer and the email log ID. + * + * @since 2.5.0 + * + * @param int $email_log_id The Email log ID. + * @param MailerAbstract $mailer The mailer in use. + */ + public function run_sent_status_verification( $email_log_id, MailerAbstract $mailer ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $mailer_name = $mailer->get_mailer_name(); + + if ( Webhooks::is_allowed() && $mailer->get_connection()->is_primary() ) { + $webhooks_provider = $this->get_webhooks()->get_provider( $mailer_name ); + + // Skip AS task verification if webhooks are set up. + if ( $webhooks_provider !== false && $webhooks_provider->get_setup_status() === Webhooks::SUCCESS_SETUP ) { + return; + } + } + + ( new DeliveryVerification() )->schedule_verification( $email_log_id ); + } + + /** + * Initialize the Webhooks functionality. + * + * @since 3.3.0 + * + * @return Webhooks + */ + public function get_webhooks() { + + static $webhooks; + + if ( ! isset( $webhooks ) ) { + $webhooks = new Webhooks(); + $webhooks->hooks(); + } + + return $webhooks; + } + + /** + * Log blocked emails when "Do Not Send" and "Log Blocked Emails" options are enabled. + * + * @since 4.5.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + * + * @return void + */ + public function maybe_log_blocked_email( MailCatcherInterface $mailcatcher ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Check if "Log Blocked Emails" option is enabled. + $options = Options::init(); + $log_blocked_emails = $options->get( 'logs', 'log_blocked_emails' ); + + // Only proceed if the option is enabled and the Logs class exists. + if ( ! $log_blocked_emails ) { + return; + } + + $connection = wp_mail_smtp()->get_connections_manager()->get_mail_connection(); + $mailer_slug = $connection->get_mailer_slug(); + + // Define a custom header, that will be used to identify the plugin and the mailer. + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $mailcatcher->XMailer = 'WPMailSMTP/Mailer/' . $mailer_slug . ' ' . WPMS_PLUGIN_VER; + + // Only proceed if email logging is enabled. + if ( ! $this->is_enabled() || ! $this->is_valid_db() ) { + return; + } + + // Create a log entry for this blocked email. + if ( $mailer_slug === 'mail' || $mailer_slug === 'smtp' || $mailer_slug === 'pepipost' ) { + $email_id = ( new SMTP() )->set_source( $mailcatcher )->save_before(); + } else { + $mailer = $connection->get_mailer(); + $email_id = ( new Common( $mailer ) )->set_source( $mailcatcher )->save_before(); + } + + // Add custom notes about this being blocked. + if ( $email_id > 0 ) { + $email = new Email( $email_id ); + + // Save attachments to the email log. + ( new Attachments() )->process_attachments( $email_id, $mailcatcher->getAttachments() ); + + $email->set_error_text( esc_html__( 'Email was blocked by the "Do Not Send" option.', 'wp-mail-smtp-pro' ) ); + $email->set_status( Email::STATUS_BLOCKED ); + $email->save(); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Migration.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Migration.php new file mode 100644 index 00000000..cf29c29a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Migration.php @@ -0,0 +1,471 @@ +get_pro()->get_logs()->is_enabled(); + } + + /** + * Initial migration - create the table structure. + * + * @since 1.5.0 + * @since 1.6.0 Changed `date_sent` column type from DATETIME to TIMESTAMP to support MySQL 5.1+ for new clients. + * @since 2.1.0 Removed the specific DB table engine MyISAM (will now use the default MySQL engine). + * @since 2.1.2 Changed the `subject` type to varchar(255), removed the FULLTEXT indexes, removed `people` index, + * set the Engine to InnoDB and made the `collate` parameter optional for the query. + * @since 2.2.0 Added error saving to the WP option, so it can be displayed on the Email Log page. + */ + protected function migrate_to_1() { + + global $wpdb; + + $table = Logs::get_table_name(); + $collate = ! empty( $wpdb->collate ) ? "COLLATE='{$wpdb->collate}'" : ''; + + /* + * Create the table. + */ + $sql = " + CREATE TABLE `$table` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `subject` VARCHAR(191) NOT NULL, + `people` TEXT NOT NULL, + `headers` TEXT NOT NULL, + `content_plain` LONGTEXT NOT NULL, + `content_html` LONGTEXT NOT NULL, + `status` TINYINT UNSIGNED NOT NULL DEFAULT '0', + `date_sent` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `mailer` VARCHAR(255) NOT NULL, + `attachments` TINYINT UNSIGNED NOT NULL DEFAULT '0', + PRIMARY KEY (id), + INDEX subject (subject), + INDEX status (status) + ) + 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 ); + } + } + + /** + * Change the `date_sent` column type from DATETIME to TIMESTAMP to support MySQL 5.1+. + * Applied to older users, who initially created the table with the DATETIME type. + * + * @since 1.6.0 + * @since 1.6.1 Included previous DB migration call for new users on 1.6.0. + */ + protected function migrate_to_2() { + + $this->maybe_required_older_migrations( 2 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` CHANGE COLUMN `date_sent` `date_sent` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status`;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 2 ); + } + } + + /** + * Change the DB table engine for existing users to the default InnoDB. + * + * @since 2.1.0 + */ + protected function migrate_to_3() { + + $this->maybe_required_older_migrations( 3 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ENGINE=InnoDB;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 3 ); + } + } + + /** + * Make some Email Logs DB table changes: + * - change the `subject` column type from TEXT to VARCHAR(255), + * - change the `subject` index from FULLTEXT to normal index, + * - drop the index on `people` column, + * - at the end try to switch to the InnoDB engine. + * + * The check at the beginning (if subject type is `varchar(255)`) is needed, so that this migration can be skipped + * for new plugin installs, who should have the correct DB table setup in `migration_to_1`. + * + * @since 2.1.2 + */ + protected function migrate_to_4() { + + $this->maybe_required_older_migrations( 4 ); + + // Don't process if ActionScheduler is not usable. + if ( ! Tasks::is_usable() ) { + return; + } + + global $wpdb; + + $table = Logs::get_table_name(); + + $is_subject_varchar = false; + + // Check if subject column type is already set to varchar(255). + $table_info = $wpdb->get_results( "DESCRIBE `$table`;", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + foreach ( $table_info as $row ) { + if ( + ( isset( $row['Field'] ) && $row['Field'] === 'subject' ) && + ( isset( $row['Type'] ) && $row['Type'] === 'varchar(255)' ) + ) { + $is_subject_varchar = true; + break; + } + } + + if ( ! $is_subject_varchar ) { + ( new EmailLogMigration4() )->async()->register(); + } + + // Save the current version to DB. + $this->update_db_ver( 4 ); + } + + /** + * Change the `subject` DB table column length from 255 to 191. + * + * @since 2.2.0 + */ + protected function migrate_to_5() { + + $this->maybe_required_older_migrations( 5 ); + + // Don't process if ActionScheduler is not usable. + if ( ! Tasks::is_usable() ) { + return; + } + + ( new EmailLogMigration5() )->async()->register(); + + // Save the current version to DB. + $this->update_db_ver( 5 ); + } + + /** + * Add the `error_text` column to the DB table. + * + * @since 2.5.0 + */ + protected function migrate_to_6() { + + $this->maybe_required_older_migrations( 6 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ADD `error_text` TEXT NULL AFTER `headers`;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 6 ); + } + } + + /** + * Add the "initiator_name" and "initiator_file" columns to the DB table. + * + * @since 3.0.0 + */ + protected function migrate_to_7() { + + $this->maybe_required_older_migrations( 7 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ADD `initiator_name` VARCHAR(255) NULL AFTER `attachments`, ADD `initiator_file` TEXT NULL;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 7 ); + } + } + + /** + * Hide CC and BCC columns in email logs table for existing users. + * + * @since 3.1.0 + */ + protected function migrate_to_8() { + + $this->maybe_required_older_migrations( 8 ); + + global $wpdb; + + $meta_key = 'managewp-mail-smtp_page_wp-mail-smtp-logscolumnshidden'; + + $rows = $wpdb->get_results( "SELECT user_id, meta_value FROM {$wpdb->usermeta} WHERE meta_key = '{$meta_key}'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + foreach ( $rows as $row ) { + $value = maybe_unserialize( $row->meta_value ); + + if ( empty( $value ) || ! is_array( $value ) ) { + $value = []; + } + + $value[] = 'cc'; + $value[] = 'bcc'; + + update_user_meta( $row->user_id, $meta_key, array_unique( $value ) ); + } + + // Save the current version to DB. + $this->update_db_ver( 8 ); + } + + /** + * Add the `message_id` column to the DB table. + * + * @since 3.3.0 + */ + protected function migrate_to_9() { + + $this->maybe_required_older_migrations( 9 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ADD `message_id` VARCHAR(255) NULL AFTER `id`;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 9 ); + } + } + + /** + * Add the `parent_id` column to the DB table. + * + * @since 3.7.0 + */ + protected function migrate_to_10() { + + $this->maybe_required_older_migrations( 10 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ADD `parent_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `initiator_file`;"; + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $result = $wpdb->query( $sql ); + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 10 ); + } + } + + /** + * Delete orphaned Email Tracking Events, Email Tracking Links, and attachments. + * + * @since 3.8.0 + * + * @return void + */ + protected function migrate_to_11() { + + $this->maybe_required_older_migrations( 11 ); + + if ( ! Tasks::is_usable() || Tasks::is_scheduled( EmailLogMigration11::ACTION ) ) { + return; + } + + ( new EmailLogMigration11() )->recurring( + time() + MINUTE_IN_SECONDS, + $this->get_delete_orphaned_data_interval() + )->register(); + + // Save the current version to DB. + $this->update_db_ver( 11 ); + } + + /** + * Add indices on message_id, date_sent and and date_sent + status columns. + * + * @since 4.2.0 + * + * @return void + */ + protected function migrate_to_12() { + + $this->maybe_required_older_migrations( 12 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + // Add an index on message_id, + // and an index on date_sent, + // and an index on date_sent + status. + $sql = "ALTER TABLE `$table` + ADD INDEX `message_id` (`message_id`) USING BTREE, + ADD INDEX `date_sent` (`date_sent`) USING BTREE, + ADD INDEX `date_sent_status` (`date_sent`, `status`) USING BTREE;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 12 ); + } + } + + /** + * Add the `error_code` column to the DB table for storing structured + * mailer error codes (e.g. "cURL error 60", "421"). Powers the + * EmailSendingErrors troubleshoot-guide lookup on the single-log page. + * + * @since 4.9.0 + */ + protected function migrate_to_13() { + + $this->maybe_required_older_migrations( 13 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ADD `error_code` VARCHAR(64) NULL AFTER `error_text`;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 13 ); + } + } + + /** + * Add the `error_key` column to the DB table for storing the composite + * error key ({response}:{code}:{message}) used by the troubleshoot-guide lookup. + * + * @since 4.9.0 + */ + protected function migrate_to_14() { + + $this->maybe_required_older_migrations( 14 ); + + global $wpdb; + + $table = Logs::get_table_name(); + + $sql = "ALTER TABLE `$table` ADD `error_key` VARCHAR(64) NULL AFTER `error_code`;"; + + $result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + + // Save the current version to DB. + if ( $result !== false ) { + $this->update_db_ver( 14 ); + } + } + + /** + * Get the recurring interval time for the delete orphaned data task. + * + * @since 3.8.0 + * + * @return int + */ + private function get_delete_orphaned_data_interval() { + + return absint( + /** + * Filters recurring interval when performing the delete orphaned data task. + * + * @since 3.8.0 + * + * @param int $recur_interval Default inteval when performing the delete orphaned data. + */ + apply_filters( + 'wp_mail_smtp_pro_emails_logs_migration_get_delete_orphaned_data_interval', + MINUTE_IN_SECONDS * 10 + ) + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/Common.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/Common.php new file mode 100644 index 00000000..483d2427 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/Common.php @@ -0,0 +1,302 @@ +mailer = $mailer; + } + + /** + * Preserve the cloned instance of the MailCatcher class. + * + * @since 1.5.0 + * + * @param MailCatcherInterface $mailcatcher The MailCatcher object. + * + * @return Common + */ + public function set_source( MailCatcherInterface $mailcatcher ) { + + $this->mailcatcher = clone $mailcatcher; + + return $this; + } + + /** + * Save the actual email data before email send. + * + * @since 2.9.0 + * + * @param int $parent_email_id Parent email log ID. + * + * @return int + */ + public function save_before( $parent_email_id = 0 ) { + + $mailer_slug = wp_mail_smtp()->get_connections_manager()->get_mail_connection()->get_mailer_slug(); + $headers = $this->unfold_headers( $this->mailcatcher->createHeader() ); + $attachments = count( $this->mailcatcher->getAttachments() ); + $people = $this->get_people(); + $email_id = 0; + + try { + $email = new Email(); + + $email + ->set_subject( $this->mailcatcher->Subject ) + ->set_people( $people ) + ->set_headers( array_filter( $headers ) ) + ->set_attachments( $attachments ) + ->set_mailer( $mailer_slug ) + ->set_status( Email::STATUS_UNSENT ) + ->set_initiator() + ->set_parent_id( $parent_email_id ); + + if ( wp_mail_smtp()->pro->get_logs()->is_enabled_content() ) { + $email + ->set_content_plain( $this->mailcatcher->ContentType === 'text/plain' ? $this->mailcatcher->Body : $this->mailcatcher->AltBody ) + ->set_content_html( $this->mailcatcher->ContentType !== 'text/plain' ? $this->mailcatcher->Body : '' ); + } + + $email_id = $email->save()->get_id(); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing for now. + } + + // Return the state. + return $email_id; + } + + /** + * Save or update the actual email data after email send. + * + * @since 1.5.0 + * @since 2.9.0 Added $email_log parameter. + * + * @param int $email_id Email log ID. + * + * @return int + */ + public function save( $email_id = 0 ) { + + $headers = $this->unfold_headers( $this->mailcatcher->createHeader() ); + $attachments = count( $this->mailcatcher->getAttachments() ); + $people = $this->get_people(); + + try { + $email = new Email( $email_id ); + + $email + ->set_subject( $this->mailcatcher->Subject ) + ->set_people( $people ) + ->set_headers( array_filter( $headers ) ) + ->set_attachments( $attachments ) + ->set_mailer( $this->mailer->get_mailer_name() ) + ->set_status( $this->get_email_status() ) + ->set_message_id( $this->get_message_id() ); + + $email_id = $email->save()->get_id(); + + // Save attachments to the email log. + ( new Attachments() )->process_attachments( $email_id, $this->mailcatcher->getAttachments() ); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing for now. + } + + return $email_id; + } + + /** + * Get email status after email send. + * + * @since 3.3.0 + * + * @return int Email status. + */ + public function get_email_status() { + + $status = Email::STATUS_UNSENT; + + if ( $this->mailer->is_email_sent() ) { + $status = $this->mailer->should_verify_sent_status() ? Email::STATUS_WAITING : Email::STATUS_SENT; + } + + return $status; + } + + /** + * Process the failed email sending. + * + * @since 4.0.0 + * @since 4.9.0 Added $error_code param, persisted on the log entry so the + * EmailSendingErrors Registry can resolve a troubleshoot-guide URL. + * @since 4.9.0 Added $error_key param (composite canonical key: mailer:category:code). + * + * @param int $email_id The Email ID. + * @param WP_Error|string $error The WP Error or error message. + * @param string $error_code Structured error code captured at send-failure time. + * @param string $error_key Composite canonical error key (mailer:category:code). + */ + public function failed( $email_id, $error, $error_code = '', $error_key = '' ) { + + if ( empty( $email_id ) ) { + return; + } + + if ( empty( $error ) ) { + $error = esc_html__( 'Unknown error.', 'wp-mail-smtp-pro' ); + } elseif ( is_wp_error( $error ) ) { + $error = $error->get_error_message(); + } + + try { + $email = new Email( $email_id ); + + if ( empty( $email->get_id() ) ) { + return; + } + + $email + ->set_error_text( $error ) + ->set_error_code( $error_code ) + ->set_error_key( $error_key ) + ->set_status( Email::STATUS_UNSENT ) + ->save(); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing for now. + } + } + + /** + * Get the people data from the MailCatcher object. + * + * @since 2.9.0 + * + * @return array + */ + private function get_people() { + + $people = []; + + foreach ( $this->mailcatcher->getToAddresses() as $to ) { + $people['to'][] = $to[0]; + } + foreach ( $this->mailcatcher->getCcAddresses() as $cc ) { + $people['cc'][] = $cc[0]; + } + foreach ( $this->mailcatcher->getBccAddresses() as $bcc ) { + $people['bcc'][] = $bcc[0]; + } + + $people['from'] = $this->mailcatcher->From; + + return $people; + } + + /** + * Get message ID based on mailer. + * + * @since 3.3.0 + * + * @return string + */ + private function get_message_id() { + + $message_id = ''; + + $custom_id_mailers = [ + 'sendlayer', + 'smtpcom', + 'postmark', + 'sparkpost', + 'sendgrid', + 'smtp2go', + 'mailjet', + 'elasticemail', + 'mailersend', + 'mandrill', + 'resend', + ]; + + if ( in_array( $this->mailer->get_mailer_name(), $custom_id_mailers, true ) ) { + foreach ( $this->mailcatcher->getCustomHeaders() as $header ) { + if ( $header[0] === 'X-Msg-ID' ) { + $message_id = $header[1]; + break; + } + } + } + + if ( empty( $message_id ) ) { + $message_id = trim( $this->mailcatcher->getLastMessageID(), '<>' ); + } + + return $message_id; + } + + /** + * Unfold long email headers. + * + * @see Section 2.2.3 of https://www.rfc-editor.org/rfc/rfc2822.txt + * + * @since 4.1.0 + * + * @param string $headers Generated email headers. + * + * @return false|string[] + */ + private function unfold_headers( $headers ) { + + $line_ending = $this->mailcatcher->get_line_ending(); + $wsp_characters = '[ \t]'; + $headers = preg_replace( "/{$line_ending}{$wsp_characters}/", '', $headers ); + $headers = explode( $line_ending, $headers ); + + return $headers; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/SMTP.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/SMTP.php new file mode 100644 index 00000000..d7ac8db7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Providers/SMTP.php @@ -0,0 +1,235 @@ +mailcatcher = clone $mailcatcher; + + return $this; + } + + /** + * Save the actual email data before we got response from SMTP server about its status. + * + * @since 1.5.0 + * + * @param int $parent_email_id Parent email log ID. + * + * @return int + */ + public function save_before( $parent_email_id = 0 ) { + + $mailer_slug = wp_mail_smtp()->get_connections_manager()->get_mail_connection()->get_mailer_slug(); + $headers = $this->unfold_headers( $this->mailcatcher->createHeader() ); + $attachments = count( $this->mailcatcher->getAttachments() ); + $people = $this->get_people(); + $email_id = 0; + + try { + $email = new Email(); + + $email + ->set_subject( $this->mailcatcher->Subject ) + ->set_people( $people ) + ->set_headers( array_filter( $headers ) ) + ->set_attachments( $attachments ) + ->set_mailer( $mailer_slug ) + ->set_status( Email::STATUS_UNSENT ) + ->set_initiator() + ->set_parent_id( $parent_email_id ); + + if ( wp_mail_smtp()->pro->get_logs()->is_enabled_content() ) { + $email + ->set_content_plain( $this->mailcatcher->ContentType === 'text/plain' ? $this->mailcatcher->Body : $this->mailcatcher->AltBody ) + ->set_content_html( $this->mailcatcher->ContentType !== 'text/plain' ? $this->mailcatcher->Body : '' ); + } + + $email_id = $email->save()->get_id(); + } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing for now. + } + + // Return the state. + return $email_id; + } + + /** + * Update the actual email data with current MailCatcher data before we got response from + * SMTP server about its status. + * + * @since 2.1.2 + * + * @param int $email_id The ID of the email to update. + */ + public function update_before( $email_id ) { + + if ( empty( $email_id ) ) { + return; + } + + $headers = $this->unfold_headers( $this->mailcatcher->createHeader() ); + $attachments = count( $this->mailcatcher->getAttachments() ); + $people = $this->get_people(); + + try { + $email = new Email( $email_id ); + $email + ->set_subject( $this->mailcatcher->Subject ) + ->set_people( $people ) + ->set_headers( array_filter( $headers ) ) + ->set_attachments( $attachments ); + + $email->save(); + + // Save attachments to the email log. + ( new Attachments() )->process_attachments( $email_id, $this->mailcatcher->getAttachments() ); + } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing for now. + } + } + + /** + * Update the status of the currently sent email. + * + * @since 1.5.0 + * + * @param int $email_id Email ID to process. + * @param bool $is_sent Whether email is sent or not. + */ + public function save_after( $email_id, $is_sent ) { + + if ( empty( $email_id ) ) { + return; + } + + try { + $email = new Email( $email_id ); + $email + ->set_status( (bool) $is_sent ? Email::STATUS_SENT : Email::STATUS_UNSENT ) + ->save(); + } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing for now. + } + } + + /** + * Process the failed email sending. + * + * @since 2.1.0 + * @since 4.9.0 Added $error_code param, persisted on the log entry so the + * EmailSendingErrors Registry can resolve a troubleshoot-guide URL. + * @since 4.9.0 Added $error_key param (composite canonical key: mailer:category:code). + * + * @param int $email_id The Email ID. + * @param WP_Error|string $error The WP Error or error message. + * @param string $error_code Structured error code captured at send-failure time. + * @param string $error_key Composite canonical error key (mailer:category:code). + */ + public function failed( $email_id, $error, $error_code = '', $error_key = '' ) { + + if ( empty( $email_id ) ) { + return; + } + + if ( empty( $error ) ) { + $error = esc_html__( 'Unknown error.', 'wp-mail-smtp-pro' ); + } elseif ( is_wp_error( $error ) ) { + $error = $error->get_error_message(); + } + + try { + $email = new Email( $email_id ); + + if ( empty( $email->get_id() ) ) { + return; + } + + $email + ->set_error_text( $error ) + ->set_error_code( $error_code ) + ->set_error_key( $error_key ) + ->set_status( Email::STATUS_UNSENT ) + ->save(); + } catch ( \Exception $e ) { //phpcs:ignore + // Do nothing for now. + } + } + + /** + * Get the people data from the MailCatcher object. + * + * @since 2.1.2 + * + * @return array + */ + private function get_people() { + + $people = []; + + foreach ( $this->mailcatcher->getToAddresses() as $to ) { + $people['to'][] = $to[0]; + } + foreach ( $this->mailcatcher->getCcAddresses() as $cc ) { + $people['cc'][] = $cc[0]; + } + foreach ( $this->mailcatcher->getBccAddresses() as $bcc ) { + $people['bcc'][] = $bcc[0]; + } + + $people['from'] = $this->mailcatcher->From; + + return $people; + } + + /** + * Unfold long email headers. + * + * @see Section 2.2.3 of https://www.rfc-editor.org/rfc/rfc2822.txt + * + * @since 4.1.0 + * + * @param string $headers Generated email headers. + * + * @return false|string[] + */ + private function unfold_headers( $headers ) { + + $line_ending = $this->mailcatcher->get_line_ending(); + $wsp_characters = '[ \t]'; + $headers = preg_replace( "/{$line_ending}{$wsp_characters}/", '', $headers ); + $headers = explode( $line_ending, $headers ); + + return $headers; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/RecheckDeliveryStatus.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/RecheckDeliveryStatus.php new file mode 100644 index 00000000..de087de8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/RecheckDeliveryStatus.php @@ -0,0 +1,184 @@ +get_admin()->is_admin_page( 'logs' ) || + ! current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) || + empty( $_GET['email_id'] ) + ) { + return; + } + + // Check if we are doing bulk recheck. + if ( + ( + isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'recheck_email_status' || + isset( $_REQUEST['action2'] ) && $_REQUEST['action2'] === 'recheck_email_status' + ) && + isset( $_REQUEST['_wpnonce'] ) && + wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-emails' ) && + is_array( $_GET['email_id'] ) + ) { + $email_ids = array_map( 'absint', $_GET['email_id'] ); + + $this->bulk_recheck_delivery_status( $email_ids ); + + return; + } + + if ( + ! empty( $_GET['recheck_email_status'] ) && + wp_verify_nonce( sanitize_key( $_GET['recheck_email_status'] ), self::SINGLE_LOG_NONCE_ACTION ) + ) { + $email_id = absint( $_GET['email_id'] ); + + $this->single_recheck_delivery_status( $email_id ); + } + } + + /** + * Recheck the delivery status of email logs in bulk. + * + * @since 3.9.0 + * + * @param int[] $email_ids Email log IDs to recheck. + * + * @return void + */ + private function bulk_recheck_delivery_status( $email_ids ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + $bulk_verifier_task = new BulkVerifySentStatusTask(); + + foreach ( array_chunk( $email_ids, BulkVerifySentStatusTask::EMAILS_PER_BATCH ) as $chunk ) { + $bulk_verifier_task->schedule( $chunk ); + } + + WP::add_admin_notice( esc_html__( 'Re-checking email status was added to queue.', 'wp-mail-smtp-pro' ) ); + } + + /** + * Recheck the delivery status of a single email log. + * + * @since 3.9.0 + * + * @param int $email_id Email log ID. + * + * @return void + */ + private function single_recheck_delivery_status( $email_id ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + $verifier = ( new DeliveryVerification() )->get_verifier( $email_id ); + + if ( is_wp_error( $verifier ) ) { + WP::add_admin_notice( $verifier->get_error_message(), WP::ADMIN_NOTICE_ERROR ); + + return; + } + + $verifier->verify(); + + if ( $verifier->is_verified() ) { + WP::add_admin_notice( esc_html__( 'Email delivery status was updated.', 'wp-mail-smtp-pro' ), WP::ADMIN_NOTICE_SUCCESS ); + } else { + WP::add_admin_notice( esc_html__( 'Unable to verify the email status at this time. Please try again later.', 'wp-mail-smtp-pro' ) ); + } + } + + /** + * Handles the AJAX request to re-check all emails delivery verification status. + * + * @since 3.9.0 + * + * @return void + */ + public function ajax_recheck_all_email_logs_status() { + + if ( + empty( $_POST['nonce'] ) || + ! check_ajax_referer( self::ARCHIVE_NONCE_ACTION, 'nonce' ) || + ! current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) + ) { + wp_send_json_error( esc_html__( 'Invalid request!', 'wp-mail-smtp-pro' ) ); + } + + $db = WP::wpdb(); + + /* + * Get all the email IDs only. + */ + $results = $db->get_results( + $db->prepare( + 'SELECT `id` FROM `%1$s` WHERE `status` = %2$d', + Logs::get_table_name(), + Email::STATUS_WAITING + ), + ARRAY_A + ); + + if ( empty( $results ) ) { + return; + } + + $bulk_verifier_task = new BulkVerifySentStatusTask(); + + foreach ( array_chunk( $results, BulkVerifySentStatusTask::EMAILS_PER_BATCH ) as $chunk ) { + $email_ids = wp_list_pluck( $chunk, 'id' ); + + $bulk_verifier_task->schedule( $email_ids ); + } + + wp_send_json_success( esc_html__( 'Re-checking email status was added to queue.', 'wp-mail-smtp-pro' ) ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Admin.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Admin.php new file mode 100644 index 00000000..4d2c4c62 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Admin.php @@ -0,0 +1,482 @@ +report = new Report( $this->parse_report_params() ); + } + + /** + * Parse report params from request. + * + * @since 3.0.0 + * + * @return array + */ + private function parse_report_params() { + + $params = []; + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_REQUEST['order'] ) ) { + $params['order'] = sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ); + } + + if ( ! empty( $_REQUEST['orderby'] ) ) { + $params['orderby'] = sanitize_key( $_REQUEST['orderby'] ); + } + + if ( ! empty( $_REQUEST['s'] ) ) { + $params['search'] = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ); + } + + $timespan = ! empty( $_REQUEST['timespan'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['timespan'] ) ) : 7; + + if ( $timespan === 'custom' && isset( $_REQUEST['date'] ) ) { + $date = array_filter( explode( ' - ', sanitize_text_field( wp_unslash( $_REQUEST['date'] ) ) ) ); + $params['date'] = ! empty( $date ) ? $date : [ current_time( 'Y-m-d' ) ]; + } elseif ( is_numeric( $timespan ) ) { + $params['date'] = [ + ( new \DateTime( 'now', WP::wp_timezone() ) )->modify( "- $timespan days" )->format( 'Y-m-d' ), + ( new \DateTime( 'now', WP::wp_timezone() ) )->modify( '- 1 day' )->format( 'Y-m-d' ), + ]; + } + // phpcs:enable + + return $params; + } + + /** + * Register hooks. + * + * @since 3.0.0 + */ + public function hooks() { + + add_action( 'admin_init', [ $this, 'init_table' ] ); + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + + // Initialize screen options. + add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-' . $this->get_parent_slug(), [ $this, 'screen_options' ] ); + add_filter( 'set-screen-option', [ $this, 'set_screen_options' ], 10, 3 ); + add_filter( 'set_screen_option_wp_mail_smtp_report_items_per_page', [ $this, 'set_screen_options' ], 10, 3 ); + } + + /** + * Register ajax hooks. + * + * @since 3.0.0 + */ + public function ajax() { + + add_action( 'wp_ajax_wp_mail_smtp_email_reports_get_single_stats', [ $this, 'get_single_stats_ajax' ] ); + } + + /** + * Initialize stats table. + * + * @since 3.0.0 + */ + public function init_table() { + + $this->table = new Table( $this->report ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.0.0 + */ + public function enqueue_assets() { + + $this->enqueue_main_styles(); + + if ( + ! wp_mail_smtp()->get_pro()->get_logs()->is_enabled() || + ! wp_mail_smtp()->get_pro()->get_logs()->is_valid_db() + ) { + return; + } + + $min = WP::asset_min(); + + wp_enqueue_script( + 'wp-mail-smtp-chart', + wp_mail_smtp()->assets_url . '/js/vendor/chart.min.js', + [ 'moment' ], + '4.4.9', + true + ); + + wp_enqueue_script( + 'wp-mail-smtp-chart-adapter', + wp_mail_smtp()->assets_url . '/js/vendor/chartjs-adapter-moment.min.js', + [ 'moment', 'wp-mail-smtp-chart' ], + '1.0.1', + true + ); + + wp_enqueue_style( + 'wp-mail-smtp-admin-flatpickr', + wp_mail_smtp()->assets_url . '/css/vendor/flatpickr.min.css', + [], + '4.6.9' + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-flatpickr', + wp_mail_smtp()->assets_url . '/js/vendor/flatpickr.min.js', + [], + '4.6.9', + false + ); + + wp_enqueue_script( + 'wp-mail-smtp-email-reports', + wp_mail_smtp()->pro->assets_url . "/js/smtp-pro-email-reports{$min}.js", + [ 'jquery', 'wp-mail-smtp-chart', 'wp-mail-smtp-chart-adapter' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-email-reports', + 'wp_mail_smtp_email_reports', + [ + 'nonce' => wp_create_nonce( 'wp-mail-smtp-email-reports-nonce' ), + 'stats_by_date_chart_data' => $this->report->get_stats_by_date_chart_data(), + 'stats_totals' => $this->report->get_stats_totals(), + 'date_range' => $this->report->get_date_range(), + 'is_search' => ! empty( $this->report->get_params( 'search' ) ), + 'texts' => [ + 'all_emails' => esc_html__( 'All Emails', 'wp-mail-smtp-pro' ), + 'search_results' => esc_html__( 'Search Results', 'wp-mail-smtp-pro' ), + 'confirmed_emails' => esc_html__( 'Confirmed sent emails', 'wp-mail-smtp-pro' ), + 'sent_emails' => esc_html__( 'Sent emails', 'wp-mail-smtp-pro' ), + 'unconfirmed_emails' => esc_html__( 'Unconfirmed sent emails', 'wp-mail-smtp-pro' ), + 'failed_emails' => esc_html__( 'Failed emails', 'wp-mail-smtp-pro' ), + 'opened_emails' => esc_html__( 'Opened emails', 'wp-mail-smtp-pro' ), + 'clicked_links' => esc_html__( 'Clicked links', 'wp-mail-smtp-pro' ), + ], + 'no_send_confirmations' => Helpers::mailer_without_send_confirmation(), + 'open_email_tracking' => wp_mail_smtp()->get_pro()->get_logs()->is_enabled_open_email_tracking(), + 'click_link_tracking' => wp_mail_smtp()->get_pro()->get_logs()->is_enabled_click_link_tracking(), + ] + ); + } + + /** + * Enqueue main CSS. + * + * @since 3.0.0 + */ + public function enqueue_main_styles() { + + wp_enqueue_style( + 'wp-mail-smtp-email-reports', + wp_mail_smtp()->pro->assets_url . '/css/smtp-pro-email-reports.min.css', + [], + WPMS_PLUGIN_VER + ); + } + + /** + * Register the screen options. + * + * @since 3.0.0 + */ + public function screen_options() { + + add_screen_option( + 'per_page', + [ + 'label' => esc_html__( 'Number of items per page:', 'wp-mail-smtp-pro' ), + 'option' => 'wp_mail_smtp_report_items_per_page', + 'default' => 20, + ] + ); + } + + /** + * Set the screen options. + * + * @since 3.0.0 + * + * @param bool $keep Whether to save or skip saving the screen option value. + * @param string $option The option name. + * @param int $value The number of items to use. + * + * @return bool|int + */ + public function set_screen_options( $keep, $option, $value ) { + + if ( $option === 'wp_mail_smtp_report_items_per_page' ) { + return (int) $value; + } + + return $keep; + } + + /** + * Output HTML of the reports page. + * + * @since 3.0.0 + */ + public function display() { + + if ( ! wp_mail_smtp()->get_pro()->get_logs()->is_enabled() ) { + $this->display_reports_disabled(); + } elseif ( ! wp_mail_smtp()->get_pro()->get_logs()->is_valid_db() ) { + $this->display_reports_not_installed(); + } else { + $this->display_reports(); + } + } + + /** + * Output HTML of the email reports. + * + * @since 3.0.0 + */ + private function display_reports() { + + ?> +
    + + +
    +
    +

    + report->get_params( 'search' ) ) ) : ?> + + + + +

    + +
    +
    + + +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    + +
    + get_pro()->get_logs()->is_enabled_open_email_tracking() ) : ?> +
    + +
    + + get_pro()->get_logs()->is_enabled_click_link_tracking() ) : ?> +
    + +
    + +
    +
    + +
    +
    + +
    +
    + + table->prepare_items(); ?> + + report->get_params( 'search' ) ) ) : ?> +
    + %1$d item where the subject contains %2$s', 'Found %1$d items where the subject contains %2$s', count( $this->table->items ), 'wp-mail-smtp-pro' ), + count( $this->table->items ), + $this->report->get_params( 'search' ) + ), + [ + 'strong' => [], + 'i' => [], + ] + ); + ?> + + + +
    + + + table->display(); ?> +
    + +
    +

    +

    + +

    + + + +
    + 1, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-reports' ) + ), + Area::SLUG . '-create-missing-db-tables' + ); + + $contact_support_url = wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/account/support/', + [ + 'medium' => 'email-reports', + 'content' => 'Email Reports not installed correctly', + ] + ); + ?> + +
    +

    +

    + create the missing DB tables by clicking on this link. If this issue persists, please contact our support and provide the error message below:', 'wp-mail-smtp-pro' ), + esc_url( $create_missing_tables_url ), + esc_url( $contact_support_url ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + echo '

    '; + echo '' . esc_html( $error_message ) . ''; + } else { + echo wp_kses( + sprintf( /* translators: %1$s - create missing tables link; %2$s - contact support link. */ + __( '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.', 'wp-mail-smtp-pro' ), + esc_url( $create_missing_tables_url ), + esc_url( $contact_support_url ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + } + ?> +

    +
    + get_admin()->get_logs_access_capability() ) ) { + wp_send_json( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ), 403 ); + } + + $report = new Report( $this->parse_report_params() ); + + wp_send_json( + [ + 'totals' => $report->get_stats_totals(), + 'by_date_chart_data' => $report->get_stats_by_date_chart_data(), + ] + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Emails/Summary.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Emails/Summary.php new file mode 100644 index 00000000..1c338555 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Emails/Summary.php @@ -0,0 +1,454 @@ +report = $report; + } + + /** + * Get summary report email general content HTML. + * + * @since 3.0.0 + * + * @return string + */ + protected function get_main_html() { + + $view_all_link = $this->get_view_all_link(); + + $comparison_report = $this->get_comparison_report(); + + $stats_totals = $this->report->get_stats_totals(); + $prev_stats_totals = $comparison_report->get_stats_totals(); + + // Get first four stat items. + $stats_by_subject = array_slice( $this->report->get_stats_by_subject(), 0, 4 ); + + ob_start(); + ?> +
    + +
    +

    + +

    + + + + + + + + + + + + + + +
    + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Total Emails', 'wp-mail-smtp-pro' ), + 'icon-email.png', + $this->report->get_total_count( $stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->report->get_total_count( $prev_stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#809EB0', + ! Helpers::mailer_without_send_confirmation() ? 'border-top-right-radius: 0;border-bottom-right-radius: 0;' : '' + ); + ?> + + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Sent', 'wp-mail-smtp-pro' ), + 'icon-check.png', + $this->report->get_sent_count( $stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->report->get_sent_count( $prev_stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#6AA08B' + ); + ?> + + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Confirmed', 'wp-mail-smtp-pro' ), + 'icon-check.png', + $this->report->get_confirmed_count( $stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->report->get_confirmed_count( $prev_stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#6AA08B', + 'border-left:none;border-radius: 0;' + ); + ?> + + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Unconfirmed', 'wp-mail-smtp-pro' ), + 'icon-check-gray.png', + $this->report->get_unconfirmed_count( $stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->report->get_unconfirmed_count( $prev_stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#787C82', + 'border-left:none;border-right:none;border-radius: 0;' + ); + ?> + + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Failed', 'wp-mail-smtp-pro' ), + 'icon-error.png', + $this->report->get_unsent_count( $stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $this->report->get_unsent_count( $prev_stats_totals ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#D63638', + ! Helpers::mailer_without_send_confirmation() ? 'border-top-left-radius: 0;border-bottom-left-radius: 0;' : '' + ); + ?> +
    + + + + + + + +
    +

    + +

    +
    + + + +
    + +
    + +
    + +
    + +
    + + get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Total Emails', 'wp-mail-smtp-pro' ), + 'icon-email.png', + $this->report->get_total_count( $item ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'total' + ); + + if ( Helpers::mailer_without_send_confirmation() ) { + echo $this->get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Sent Emails', 'wp-mail-smtp-pro' ), + 'icon-check.png', + $this->report->get_sent_count( $item ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'sent' + ); + } else { + echo $this->get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Confirmed Emails', 'wp-mail-smtp-pro' ), + 'icon-check.png', + $this->report->get_confirmed_count( $item ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'confirmed' + ); + + echo $this->get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Unconfirmed Emails', 'wp-mail-smtp-pro' ), + 'icon-check-gray.png', + $this->report->get_unconfirmed_count( $item ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'unconfirmed' + ); + } + + echo $this->get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Failed Emails', 'wp-mail-smtp-pro' ), + 'icon-error.png', + $this->report->get_unsent_count( $item ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + 'unsent' + ); + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_open_email_tracking() ) { + echo $this->get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Opened Emails', 'wp-mail-smtp-pro' ), + 'icon-open.png', + sprintf( + '%1$d (%2$d%%)', + esc_html( $this->report->get_open_count( $item ) ), + esc_html( $this->report->get_open_percent_count( $item ) ) + ), + 'opened' + ); + } + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_click_link_tracking() ) { + echo $this->get_stats_column_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Clicked Links', 'wp-mail-smtp-pro' ), + 'icon-click.png', + sprintf( + '%1$d (%2$d%%)', + esc_html( $this->report->get_click_count( $item ) ), + esc_html( $this->report->get_click_percent_count( $item ) ) + ), + 'clicked' + ); + } + ?> + +
    + + +
    + + get_stats_column_width( $column ); + $images_dir_url = wp_mail_smtp()->assets_url . '/images/reports/email/'; + $icon_width = ( $icon === 'icon-email.png' ) ? 16 : 15; + + ob_start(); + ?> + +
    + <?php esc_attr( $title ); ?>  + + [ 'style' => [] ] ] ); ?> + +
    + + get_stats_column_min_width( $column ); + + if ( Helpers::mailer_without_send_confirmation() ) { + $result += $this->get_stats_column_min_width( 'confirmed' ) / $this->get_stats_columns_count(); + } + + if ( ! wp_mail_smtp()->get_pro()->get_logs()->is_enabled_open_email_tracking() ) { + $result += $this->get_stats_column_min_width( 'opened' ) / $this->get_stats_columns_count(); + } + + if ( ! wp_mail_smtp()->get_pro()->get_logs()->is_enabled_click_link_tracking() ) { + $result += $this->get_stats_column_min_width( 'clicked' ) / $this->get_stats_columns_count(); + } + + $cache[ $column ] = $result; + + return $result; + } + + /** + * Get stats total block width in px. + * + * @since 3.0.0 + * + * @return int + */ + protected function get_stats_total_item_width() { + + return Helpers::mailer_without_send_confirmation() ? 140 : 120; + } + + /** + * Get view all emails report link. + * + * @since 3.0.0 + * + * @return string + */ + protected function get_view_all_link() { + + $link = add_query_arg( + [ + 'orderby' => 'total', + 'order' => 'desc', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-reports' ) + ); + + if ( ! empty( $this->report->get_params( 'date' ) ) ) { + $link = add_query_arg( + [ + 'timespan' => 'custom', + 'date' => implode( + ' - ', + [ + $this->report->get_from_date()->format( 'Y-m-d' ), + $this->report->get_to_date()->format( 'Y-m-d' ), + ] + ), + ], + $link + ); + } + + return $link; + } + + /** + * Get stats columns count. + * + * @since 3.0.0 + * + * @return int + */ + private function get_stats_columns_count() { + + $columns = 3; + + if ( ! Helpers::mailer_without_send_confirmation() ) { + $columns ++; + } + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_open_email_tracking() ) { + $columns ++; + } + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_click_link_tracking() ) { + $columns ++; + } + + return $columns; + } + + /** + * Get previous period report for comparison. + * + * @since 3.0.0 + * + * @return Report + */ + private function get_comparison_report() { + + $params = $this->report->get_raw_params(); + + if ( ! empty( $this->report->get_params( 'date' ) ) ) { + $from_date = clone $this->report->get_from_date(); + $to_date = clone $this->report->get_from_date(); + + $from_date->modify( '- ' . ( $this->report->get_date_range() + 1 ) . ' days' ); + $to_date->modify( '- 1 day' ); + + $params['date'] = [ + $from_date->format( 'Y-m-d' ), + $to_date->format( 'Y-m-d' ), + ]; + } + + $report = new Report( $params ); + + return $report; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Report.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Report.php new file mode 100644 index 00000000..cb0d35e2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Report.php @@ -0,0 +1,780 @@ +raw_params = $params; + $this->params = $this->process_params( $params ); + } + + /** + * Process report params. + * + * @since 3.0.0 + * + * @param array $params Report params. + * + * @return array + */ + private function process_params( $params ) { + + $params = (array) $params; + $processed = []; + + // Date. + if ( ! empty( $params['date'] ) ) { + $processed['date'] = $this->parse_date_param( $params['date'] ); + } + + // Search. + if ( ! empty( $params['search'] ) ) { + $processed['search'] = sanitize_text_field( $params['search'] ); + } + + // Mailer. + if ( ! empty( $params['mailer'] ) && is_string( $params['mailer'] ) ) { + $processed['mailer'] = sanitize_text_field( $params['mailer'] ); + } + + // Order. + if ( + ! empty( $params['order'] ) && + is_string( $params['order'] ) && + in_array( strtoupper( $params['order'] ), [ 'ASC', 'DESC' ], true ) + ) { + $processed['order'] = strtoupper( $params['order'] ); + } + + $allowed_order_by = [ 'subject', 'total', 'sent', 'delivered', 'unsent', 'open_count', 'click_count' ]; + + if ( ! empty( $params['orderby'] ) && in_array( $params['orderby'], $allowed_order_by, true ) ) { + $processed['orderby'] = $params['orderby']; + } + + // Merge missing values with defaults. + return wp_parse_args( + $processed, + $this->get_default_params() + ); + } + + /** + * Parse date param. + * + * @since 3.0.0 + * + * @param array $date Dates array in format 'Y-m-d'. + * + * @return array|bool + */ + private function parse_date_param( $date ) { + + $date = array_filter( array_values( (array) $date ) ); + + if ( empty( $date ) ) { + return false; + } + + if ( count( $date ) === 1 ) { + $date = array_fill( 0, 2, $date[0] ); + } + + if ( count( $date ) !== 2 ) { + return false; + } + + try { + $date_start = \DateTime::createFromFormat( 'Y-m-d', $date[0] ); + $date_end = \DateTime::createFromFormat( 'Y-m-d', $date[1] ); + } catch ( \Exception $e ) { + return false; + } + + return [ + 'from' => $date_start->setTime( 0, 0 ), + 'to' => $date_end->setTime( 23, 59, 59 ), + ]; + } + + /** + * Get the list of default params for a usual query. + * + * @since 3.0.0 + * + * @return array + */ + private function get_default_params() { + + return [ + 'order' => 'DESC', + 'orderby' => 'total', + ]; + } + + /** + * Returns all report params or single param by key. + * + * @since 3.0.0 + * + * @param string $key Param key. + * + * @return mixed + */ + public function get_params( $key = null ) { + + if ( ! is_null( $key ) ) { + return isset( $this->params[ $key ] ) ? $this->params[ $key ] : false; + } + + return $this->params; + } + + /** + * Returns all report raw params. + * + * @since 3.0.0 + * + * @return mixed + */ + public function get_raw_params() { + + return $this->raw_params; + } + + /** + * Returns from date param. + * + * @since 3.0.0 + * + * @return \DateTime|false + */ + public function get_from_date() { + + return ! empty( $this->params['date'] ) ? $this->params['date']['from'] : false; + } + + /** + * Returns to date param. + * + * @since 3.0.0 + * + * @return \DateTime|false + */ + public function get_to_date() { + + return ! empty( $this->params['date']['to'] ) ? $this->params['date']['to'] : false; + } + + /** + * Returns date interval between from and to date in days. + * + * @since 3.0.0 + * + * @return integer + */ + public function get_date_range() { + + if ( ! $this->get_to_date() || ! $this->get_from_date() ) { + return 0; + } + + return $this->get_from_date()->diff( $this->get_to_date() )->days; + } + + /** + * Get the base stats. + * + * @since 4.2.0 + * + * @return array + */ + private function get_stats() { + + if ( ! is_null( $this->stats ) ) { + return $this->stats; + } + + global $wpdb; + + $logs_table = Logs::get_table_name(); + $select = $this->build_select(); + $join = $this->build_join(); + $where = $this->build_where(); + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $this->stats = $wpdb->get_results( + "SELECT {$select} FROM {$logs_table} as logs {$join} WHERE {$where} GROUP BY day, subject", + \ARRAY_A + ); + + // Return empty array on database errors. + $this->stats = $this->stats === null ? [] : $this->stats; + + // phpcs:enable + + return $this->stats; + } + + /** + * Get the list of stats fields. + * + * @since 4.2.0 + * + * @return array|null + */ + private function get_stat_fields() { + + $fields = [ + 'total', + 'unsent', + 'sent', + 'delivered', + ]; + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_tracking() ) { + $fields[] = 'open_count'; + $fields[] = 'click_count'; + } + + return $fields; + } + + /** + * Get the totals count. + * + * @since 3.0.0 + * + * @return array + */ + public function get_stats_totals() { + + if ( ! is_null( $this->stats_totals ) ) { + return $this->stats_totals; + } + + $stats = []; + + foreach ( $this->get_stat_fields() as $key ) { + $stats[ $key ] = array_sum( array_column( $this->get_stats(), $key ) ); + } + + $this->stats_totals = $stats; + + return $this->stats_totals; + } + + /** + * Get the totals count grouped by date. + * + * @since 3.0.0 + * + * @return array + */ + public function get_stats_by_date() { + + if ( ! is_null( $this->stats_by_date ) ) { + return $this->stats_by_date; + } + + $date_groups = []; + + foreach ( $this->get_stats() as $stat ) { + $date_groups[ $stat['day'] ][] = array_intersect_key( + $stat, + array_flip( $this->get_stat_fields() ) + ); + } + + $stats = []; + + foreach ( $date_groups as $date => $group ) { + $stat = [ + 'day' => $date, + ]; + + foreach ( $this->get_stat_fields() as $key ) { + $stat[ $key ] = array_sum( array_column( $group, $key ) ); + } + + $stats[] = $stat; + } + + $this->stats_by_date = $stats; + + return $this->stats_by_date; + } + + /** + * Get the totals count grouped by subject. + * + * @since 3.0.0 + * + * @return array + */ + public function get_stats_by_subject() { + + if ( ! is_null( $this->stats_by_subject ) ) { + return $this->stats_by_subject; + } + + $subject_groups = []; + + foreach ( $this->get_stats() as $stat ) { + $subject_groups[ $stat['subject'] ][] = array_intersect_key( + $stat, + array_flip( $this->get_stat_fields() ) + ); + } + + $stats = []; + + foreach ( $subject_groups as $subject => $group ) { + $stat = [ + 'subject' => $subject, + ]; + + foreach ( $this->get_stat_fields() as $key ) { + $stat[ $key ] = array_sum( array_column( $group, $key ) ); + } + + $stats[] = $stat; + } + + array_multisort( + array_column( $stats, 'total' ), + SORT_DESC, + $stats + ); + + $this->stats_by_subject = $stats; + + return $this->stats_by_subject; + } + + /** + * Get the totals count grouped by date and prepared for chart. + * + * @since 3.0.0 + * + * @return array + */ + public function get_stats_by_date_chart_data() { + + $results = $this->get_stats_by_date(); + + if ( ! is_array( $results ) ) { + return []; + } + + if ( empty( $this->get_from_date() ) || empty( $this->get_to_date() ) ) { + return $results; + } + + $results = array_combine( array_column( $results, 'day' ), $results ); + + $period = new \DatePeriod( $this->get_from_date(), new \DateInterval( 'P1D' ), $this->get_to_date() ); + + // Fill DB results with empty entries where there's no data. + foreach ( $period as $value ) { + $date = $value->format( 'Y-m-d' ); + + $results[ $date ] = array_merge( + [ + 'day' => $date, + 'total' => 0, + 'unsent' => 0, + 'sent' => 0, + 'delivered' => 0, + 'open_count' => 0, + 'click_count' => 0, + ], + array_key_exists( $date, $results ) ? $results[ $date ] : [] + ); + } + + ksort( $results ); + + return $results; + } + + /** + * Get total emails count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_total_count( $item ) { + + return intval( $item['sent'] ) + intval( $item['delivered'] ) + intval( $item['unsent'] ); + } + + /** + * Get sent emails count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_sent_count( $item ) { + + return intval( $item['sent'] ) + intval( $item['delivered'] ); + } + + /** + * Get confirmed emails count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_confirmed_count( $item ) { + + return intval( $item['delivered'] ); + } + + /** + * Get unconfirmed emails count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_unconfirmed_count( $item ) { + + return intval( $item['sent'] ); + } + + /** + * Get failed emails count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_unsent_count( $item ) { + + return intval( $item['unsent'] ); + } + + /** + * Get opened emails count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_open_count( $item ) { + + return intval( $item['open_count'] ); + } + + /** + * Get click links count. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_click_count( $item ) { + + return intval( $item['click_count'] ); + } + + /** + * Get sent emails percent. + * + * @since 3.8.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_sent_percent_count( $item ) { + + return $this->get_percentage( $this->get_sent_count( $item ), $item['total'] ); + } + + /** + * Get confirmed emails percent. + * + * @since 3.8.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_confirmed_percent_count( $item ) { + + return $this->get_percentage( $this->get_confirmed_count( $item ), $item['total'] ); + } + + /** + * Get unconfirmed emails percent. + * + * @since 3.8.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_unconfirmed_percent_count( $item ) { + + return $this->get_percentage( $this->get_unconfirmed_count( $item ), $item['total'] ); + } + + /** + * Get failed emails percent. + * + * @since 3.8.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_unsent_percent_count( $item ) { + + return $this->get_percentage( $this->get_unsent_count( $item ), $item['total'] ); + } + + /** + * Get opened emails percent. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_open_percent_count( $item ) { + + return $this->get_percentage( $this->get_open_count( $item ), $item['total'] ); + } + + /** + * Get click links percent. + * + * @since 3.0.0 + * + * @param array $item Stats item. + * + * @return int + */ + public function get_click_percent_count( $item ) { + + return $this->get_percentage( $this->get_click_count( $item ), $item['total'] ); + } + + /** + * Get the percent of a count relative to the total. + * + * @since 3.8.0 + * + * @param int $count Count to get the percent of. + * @param int $total Total count. + * + * @return int + */ + private function get_percentage( $count, $total ) { + + return ! empty( $total ) ? intval( $count / $total * 100 ) : 0; + } + + /** + * Get the SQL-ready string of SELECT part for a query. + * + * @since 3.0.0 + * + * @return string + */ + private function build_select() { + + global $wpdb; + + $select = $wpdb->prepare( + 'CAST(logs.date_sent AS DATE) as day, + logs.subject as subject, + COUNT(DISTINCT logs.id) as total, + COUNT(DISTINCT CASE WHEN logs.status = %d THEN logs.id ELSE NULL END) as unsent, + COUNT(DISTINCT CASE WHEN logs.status = %d THEN logs.id ELSE NULL END) as sent, + COUNT(DISTINCT CASE WHEN logs.status = %d THEN logs.id ELSE NULL END) as delivered', + Email::STATUS_UNSENT, + Email::STATUS_SENT, + Email::STATUS_DELIVERED + ); + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_tracking() ) { + $select .= $wpdb->prepare( + ', COUNT(DISTINCT CASE WHEN events.event_type = %s THEN events.email_log_id ELSE NULL END) as open_count, + COUNT(DISTINCT CASE WHEN events.event_type = %s THEN events.email_log_id ELSE NULL END) as click_count', + OpenEmailEvent::get_type(), + ClickLinkEvent::get_type() + ); + } + + return trim( $select ); + } + + /** + * Get the SQL-ready string of JOIN part for a query. + * + * @since 3.0.0 + * + * @return string + */ + private function build_join() { + + $join = ''; + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_tracking() ) { + $events_table = Tracking::get_events_table_name(); + + $join .= " LEFT JOIN {$events_table} as events ON events.email_log_id = logs.id"; + } + + return trim( $join ); + } + + /** + * Get the SQL-ready string of WHERE part for a query. + * + * @since 3.0.0 + * + * @return string + */ + private function build_where() { + + global $wpdb; + + $where = [ '1=1' ]; + + // Search by subject. + if ( ! empty( $this->params['search'] ) ) { + $where[] = $wpdb->prepare( + 'subject LIKE %s', + '%' . $wpdb->esc_like( wp_kses( $this->params['search'], [] ) ) . '%' + ); + } + + // Sent date. + if ( ! empty( $this->params['date'] ) ) { + $where[] = $wpdb->prepare( + '( date_sent >= %s AND date_sent <= %s )', + $this->get_from_date()->format( 'Y-m-d H:i:s' ), + $this->get_to_date()->format( 'Y-m-d H:i:s' ) + ); + } + + // Mailer. + if ( ! empty( $this->params['mailer'] ) ) { + $where[] = $wpdb->prepare( 'mailer = %s', $this->params['mailer'] ); + } + + // Exclude waiting emails from reports. + $where[] = $wpdb->prepare( 'status != %d', Email::STATUS_WAITING ); + + return implode( ' AND ', $where ); + } + + /** + * Get the SQL-ready string of ORDER part for a query. + * + * @since 3.0.0 + * + * @return string + */ + private function build_order() { + + return 'ORDER BY ' . $this->params['orderby'] . ' ' . $this->params['order']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Reports.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Reports.php new file mode 100644 index 00000000..2b826c7b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Reports.php @@ -0,0 +1,48 @@ + [ + ( new \DateTime( 'now', WP::wp_timezone() ) )->modify( '- 7 days' )->format( 'Y-m-d' ), + ( new \DateTime( 'now', WP::wp_timezone() ) )->modify( '- 1 day' )->format( 'Y-m-d' ), + ], + 'order' => 'desc', + 'orderby' => 'total', + ] + ); + } + + /** + * Get emails stats weekly summary report email. + * + * @since 3.0.0 + * + * @return SummaryReportEmail + */ + public function get_summary_report_email() { + + return new SummaryReportEmail( $this->get_summary_report() ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Table.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Table.php new file mode 100644 index 00000000..af75111e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Reports/Table.php @@ -0,0 +1,406 @@ +report = $report; + + // Set parent defaults. + parent::__construct( + [ + 'singular' => 'report-item', + 'plural' => 'report-items', + 'screen' => 'wp-mail-smtp_page_wp-mail-smtp-reports', + ] + ); + } + + /** + * Define the table columns. + * + * @since 3.0.0 + * + * @return array Associative array of slug=>name columns data. + */ + public function get_columns() { + + $columns = []; + + $columns['subject'] = esc_html__( 'Subject', 'wp-mail-smtp-pro' ); + $columns['total_count'] = esc_html__( 'Total', 'wp-mail-smtp-pro' ); + + if ( Helpers::mailer_without_send_confirmation() ) { + $columns['sent_count'] = esc_html__( 'Sent', 'wp-mail-smtp-pro' ); + } else { + $columns['confirmed_count'] = esc_html__( 'Confirmed', 'wp-mail-smtp-pro' ); + $columns['unconfirmed_count'] = esc_html__( 'Unconfirmed', 'wp-mail-smtp-pro' ); + } + + $columns['unsent_count'] = esc_html__( 'Failed', 'wp-mail-smtp-pro' ); + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_open_email_tracking() ) { + $columns['open_count'] = esc_html__( 'Open Count', 'wp-mail-smtp-pro' ); + } + + if ( wp_mail_smtp()->get_pro()->get_logs()->is_enabled_click_link_tracking() ) { + $columns['click_count'] = esc_html__( 'Click Count', 'wp-mail-smtp-pro' ); + } + + $columns['graph'] = esc_html__( 'Graph', 'wp-mail-smtp-pro' ); + + return $columns; + } + + /** + * Define columns that are sortable. + * + * @since 3.0.0 + * + * @return array List of columns that should be sortable. + */ + public function get_sortable_columns() { + + return [ + 'subject' => [ 'subject', false ], + 'total_count' => [ 'total', false ], + 'sent_count' => [ 'sent', false ], + 'confirmed_count' => [ 'delivered', false ], + 'unconfirmed_count' => [ 'sent', false ], + 'unsent_count' => [ 'unsent', false ], + 'open_count' => [ 'open_count', false ], + 'click_count' => [ 'click_count', false ], + ]; + } + + /** + * Display Email subject. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_subject( $item ) { + + return sprintf( + '%2$s', + Email::esc_subject_attr( $item['subject'] ), + esc_html( $item['subject'] ) + ); + } + + /** + * Display emails total count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_total_count( $item ) { + + return $this->report->get_total_count( $item ); + } + + /** + * Display emails sent count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_sent_count( $item ) { + + return sprintf( + '%1$d (%2$d%%)', + $this->report->get_sent_count( $item ), + $this->report->get_sent_percent_count( $item ) + ); + } + + /** + * Display emails confirmed count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_confirmed_count( $item ) { + + return sprintf( + '%1$d (%2$d%%)', + $this->report->get_confirmed_count( $item ), + $this->report->get_confirmed_percent_count( $item ) + ); + } + + /** + * Display emails unconfirmed count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_unconfirmed_count( $item ) { + + return sprintf( + '%1$d (%2$d%%)', + $this->report->get_unconfirmed_count( $item ), + $this->report->get_unconfirmed_percent_count( $item ) + ); + } + + /** + * Display emails failed count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_unsent_count( $item ) { + + return sprintf( + '%1$d (%2$d%%)', + $this->report->get_unsent_count( $item ), + $this->report->get_unsent_percent_count( $item ) + ); + } + + /** + * Display emails open count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_open_count( $item ) { + + return sprintf( + '%1$d (%2$d%%)', + $this->report->get_open_count( $item ), + $this->report->get_open_percent_count( $item ) + ); + } + + /** + * Display emails click link count. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_click_count( $item ) { + + return sprintf( + '%1$d (%2$d%%)', + $this->report->get_click_count( $item ), + $this->report->get_click_percent_count( $item ) + ); + } + + /** + * Display graph icon. + * Used for enable/disable single stat. + * + * @since 3.0.0 + * + * @param array $item Stats row. + * + * @return string + */ + public function column_graph( $item ) { + + return sprintf( + '
    ', + Email::esc_subject_attr( $item['subject'] ) + ); + } + + /** + * Get the data. + * + * @since 3.0.0 + */ + public function prepare_items() { + + $items = $this->report->get_stats_by_subject(); + + // Pagination options. + $per_page = $this->get_items_per_page( 'wp_mail_smtp_report_items_per_page' ); + $page = isset( $_GET['paged'] ) && $_GET['paged'] > 0 ? intval( $_GET['paged'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $offset = $per_page * ( $page - 1 ); + + $this->items = array_slice( $items, $offset, $per_page ); + + $this->set_pagination_args( + [ + 'total_items' => count( $items ), + 'per_page' => $per_page, + ] + ); + } + + /** + * Whether the table has items to display or not. + * + * @since 3.0.0 + * + * @return bool + */ + public function has_items() { + + return count( $this->items ) > 0; + } + + /** + * Message to be displayed when there are no items. + * + * @since 3.0.0 + */ + public function no_items() { + + esc_html_e( 'No email reports are available.', 'wp-mail-smtp-pro' ); + } + + /** + * Get the name of the primary column. + * Important for the mobile view. + * + * @since 3.0.0 + * + * @return string The name of the primary column. + */ + protected function get_primary_column_name() { + + return 'subject'; + } + + /** + * Generates the table navigation above the table. + * + * @since 3.0.0 + * + * @param string $which Navigation location. + */ + protected function display_tablenav( $which ) { + + ?> +
    + filters(); + + // Display search box. + $this->search_box( + esc_html__( 'Search Emails', 'wp-mail-smtp-pro' ), + Area::SLUG . '-reports-search-input' + ); + } else { + $this->pagination( $which ); + } + ?> +
    + esc_html__( 'Last 7 days', 'wp-mail-smtp-pro' ), + '14' => esc_html__( 'Last 14 days', 'wp-mail-smtp-pro' ), + '30' => esc_html__( 'Last 30 days', 'wp-mail-smtp-pro' ), + 'custom' => esc_html__( 'Custom Date Range', 'wp-mail-smtp-pro' ), + ]; + + ?> +
    + + + + + +
    + get_pro()->get_logs()->get_manage_capability() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + $email_id = isset( $_POST['email_id'] ) ? intval( $_POST['email_id'] ) : false; + + if ( $email_id === false ) { + wp_send_json_error( esc_html__( 'Email ID must be specified.', 'wp-mail-smtp-pro' ) ); + } + + $recipients = isset( $_POST['recipients'] ) && ! empty( trim( $_POST['recipients'] ) ) ? $this->parse_emails( $_POST['recipients'] ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + + if ( $recipients === false ) { + wp_send_json_error( esc_html__( 'Email recipients must be specified.', 'wp-mail-smtp-pro' ) ); + } elseif ( is_wp_error( $recipients ) ) { + wp_send_json_error( esc_html__( 'Invalid recipients email addresses.', 'wp-mail-smtp-pro' ) ); + } + + $connection_id = ! empty( $_POST['connection_id'] ) ? sanitize_key( $_POST['connection_id'] ) : 'primary'; + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id ); + + $email = new Email( $email_id ); + + if ( $email->get_id() === 0 ) { + wp_send_json_error( esc_html__( 'Invalid email ID.', 'wp-mail-smtp-pro' ) ); + } + + $is_sent = $this->send_email( $email, $recipients, $connection ); + + if ( ! is_wp_error( $is_sent ) ) { + wp_send_json_success( esc_html__( 'Email was successfully sent!', 'wp-mail-smtp-pro' ) ); + } else { + wp_send_json_error( + sprintf( + wp_kses( /* translators: %s - Error message. */ + __( 'Email sending error.
    %s', 'wp-mail-smtp-pro' ), + [ + 'br' => [], + ] + ), + str_replace( "\n", '
    ', $is_sent->get_error_message() ) + ) + ); + } + } + + /** + * Emails bulk resend ajax handler. + * + * @since 2.9.0 + */ + public function bulk_resend_emails_ajax_handler() { + + if ( check_ajax_referer( 'wp-mail-smtp-admin', 'nonce', false ) === false ) { + wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) ); + } + + if ( ! current_user_can( wp_mail_smtp()->get_pro()->get_logs()->get_manage_capability() ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + $email_ids = isset( $_POST['email_ids'] ) ? array_map( 'intval', $_POST['email_ids'] ) : false; + + if ( $email_ids === false ) { + wp_send_json_error( esc_html__( 'Email ID\'s must be specified.', 'wp-mail-smtp-pro' ) ); + } + + $connection_id = ! empty( $_POST['connection_id'] ) ? sanitize_key( $_POST['connection_id'] ) : 'primary'; + + $this->schedule_emails_send( $email_ids, $connection_id ); + + wp_send_json_success( esc_html__( 'Emails were added to the send queue. If these selected emails have their email content, they will be resent shortly.', 'wp-mail-smtp-pro' ) ); + } + + /** + * Parse, sanitize and validate emails string. + * + * @since 2.9.0 + * + * @param string $emails Email addresses separated by comma. + * + * @return array|\WP_Error Emails array or validation error. + */ + protected function parse_emails( $emails ) { + + $errors = new \WP_Error(); + + $emails = explode( ',', $emails ); + $emails = array_map( 'trim', $emails ); + $emails = array_map( 'sanitize_email', $emails ); + + foreach ( $emails as $email ) { + if ( ! is_email( $email ) ) { + $errors->add( 'invalid_email', $email ); + } + } + + return ! empty( $errors->errors ) ? $errors : $emails; + } + + /** + * The content of the "Resend email confirmation" modal window. + * + * @since 2.9.0 + * + * @param Email|false $email Email instance. + * @param bool $bulk Whether to get bulk resend content version. + * + * @return string + */ + public static function prepare_resend_confirmation_content( $email, $bulk = false ) { + + $connections = ( new AdditionalConnections() )->get_configured_connections(); + $primary_connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + + ob_start(); + ?> +
    +

    + + + + + +

    + + +

    + + +

    + +

    > + + +

    +
    + cleanup_tracking( Tracking::get_events_table_name() ); + } + + /** + * Cleanup orphaned tracking links. + * + * @since 3.8.0 + * + * @return bool Whether or not all orphaned tracking links are deleted. + */ + public function cleanup_tracking_links() { + + return $this->cleanup_tracking( Tracking::get_links_table_name() ); + } + + /** + * Cleanup orphaned tracking events. + * + * @since 3.8.0 + * + * @param string $tracking_table Tracking table to cleanup orphaned data. + * + * @return bool Returns `true` if all orphaned tracking data is removed or + * if tracking DB is not valid. + * Otherwise, returns `false`. + */ + private function cleanup_tracking( $tracking_table ) { + + if ( ! ( new Events() )->is_valid_db() ) { + return true; + } + + $wpdb = WP::wpdb(); + $logs_table = Logs::get_table_name(); + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + $wpdb->query( + $wpdb->prepare( + 'DELETE tracking + FROM `%1$s` tracking + LEFT JOIN `%2$s` email_logs + ON tracking.email_log_id = email_logs.id + WHERE email_logs.id IS NULL', + $tracking_table, + $logs_table + ) + ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + + // Check if there are still tracking data to be removed. + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + $count_query = $wpdb->prepare( + 'SELECT COUNT(*) + FROM `%1$s` tracking + LEFT JOIN `%2$s` email_logs + ON tracking.email_log_id = email_logs.id + WHERE email_logs.id IS NULL', + $tracking_table, + $logs_table + ); + // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared + $count_results = absint( $wpdb->get_var( $count_query ) ); + + if ( empty( $count_results ) ) { + return true; + } + + return false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/AbstractEvent.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/AbstractEvent.php new file mode 100644 index 00000000..a792cd7c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/AbstractEvent.php @@ -0,0 +1,274 @@ +email_log_id = $email_log_id; + } + + /** + * Get the event ID. + * + * @since 2.9.0 + * + * @return int Event ID. + */ + public function get_id() { + + return $this->id; + } + + /** + * Get the Email Log ID. + * + * @since 2.9.0 + * + * @return int Email Log ID. + */ + public function get_email_log_id() { + + return $this->email_log_id; + } + + /** + * Get the event date/time when event was created. + * + * @since 2.9.0 + * + * @return DateTime Event created date/time (return current date/time if property is empty). + */ + public function get_date_created() { + + if ( $this->date_created instanceof DateTime ) { + return $this->date_created; + } + + $timezone = new DateTimeZone( 'UTC' ); + $date = false; + + if ( ! empty( $this->date_created ) ) { + $date = DateTime::createFromFormat( WP::datetime_mysql_format(), $this->date_created, $timezone ); + } + + if ( $date === false ) { + $date = new DateTime( 'now', $timezone ); + } + + $this->date_created = $date; + + return $this->date_created; + } + + /** + * Get the event related object ID (e.g link ID in click link event). + * + * @since 2.9.0 + * + * @return int Object ID. + */ + public function get_object_id() { + + return $this->object_id; + } + + /** + * Set the event ID. + * + * @since 2.9.0 + * + * @param int $id Event ID. + */ + public function set_id( $id ) { + + $this->id = $id; + + return $this; + } + + /** + * Set the Email Log ID. + * + * @since 2.9.0 + * + * @param int $email_log_id Email Log ID. + */ + public function set_email_log_id( $email_log_id ) { + + $this->email_log_id = $email_log_id; + + return $this; + } + + /** + * Set the event date/time when event was created. + * + * @since 2.9.0 + * + * @param string $date_created Event created date/time in mysql format: Y-m-d H:i:s. + */ + public function set_date_created( $date_created ) { + + // Validate the date. Time is ignored. + $mm = substr( $date_created, 5, 2 ); + $jj = substr( $date_created, 8, 2 ); + $aa = substr( $date_created, 0, 4 ); + + $valid_date = wp_checkdate( $mm, $jj, $aa, $date_created ); + $timezone = new DateTimeZone( 'UTC' ); + + if ( $valid_date ) { + $date_created = DateTime::createFromFormat( WP::datetime_mysql_format(), $date_created, $timezone ); + } else { + $date_created = new DateTime( 'now', $timezone ); + } + + $this->date_created = $date_created; + + return $this; + } + + /** + * Set the event related object ID (e.g link ID in click link event). + * + * @since 2.9.0 + * + * @param int $object_id Object ID. + */ + public function set_object_id( $object_id ) { + + $this->object_id = $object_id; + + return $this; + } + + /** + * Whether the tracking event is enabled or not. + * + * @since 2.9.0 + * + * @return bool Active by default. + */ + public function is_active() { + + return true; + } + + /** + * Persist event data to DB. + * + * @since 2.9.0 + * + * @return int|false Event ID or false if saving failed. + */ + public function persist() { + + global $wpdb; + + $data = [ + 'email_log_id' => intval( $this->get_email_log_id() ), + 'event_type' => sanitize_key( static::get_type() ), + 'date_created' => $this->get_date_created()->format( WP::datetime_mysql_format() ), + 'object_id' => $this->get_object_id() ? intval( $this->get_object_id() ) : null, + ]; + + $result = $wpdb->insert( Tracking::get_events_table_name(), $data, [ '%d', '%s', '%s', '%d' ] ); + + if ( $result !== false ) { + $this->set_id( $wpdb->insert_id ); + } + + /** + * Fires after tracking event save to DB. + * + * @since 3.5.0 + * + * @param AbstractEvent $event Current event object. + * @param string $event_type Event type. + */ + do_action( 'wp_mail_smtp_pro_emails_logs_tracking_events_event_persist', $this, static::get_type() ); + + return $this->get_id() > 0 ? $this->get_id() : false; + } + + /** + * Whether the event was already triggered or not. + * + * @since 2.9.0 + * + * @return bool + */ + public function was_event_already_triggered() { + + global $wpdb; + + $table = Tracking::get_events_table_name(); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $result = $wpdb->query( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + "SELECT * FROM {$table} WHERE event_type = %s AND email_log_id = %d LIMIT 1", + sanitize_key( static::get_type() ), + intval( $this->get_email_log_id() ) + ) + ); + + return $result === 1; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventFactory.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventFactory.php new file mode 100644 index 00000000..d74e8fa2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventFactory.php @@ -0,0 +1,108 @@ +get_classname_from_event_type( $type ); + + return $class_name ? new $class_name( $email_log_id ) : false; + } + + /** + * Get event by event ID. + * + * @since 2.9.0 + * + * @param int $event_id Email ID. + * + * @return AbstractEvent|false + */ + public function get_event( $event_id ) { + + if ( ! is_numeric( $event_id ) ) { + return false; + } + + global $wpdb; + + $table = Tracking::get_events_table_name(); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", intval( $event_id ) ) ); + + if ( ! $row ) { + return false; + } + + $class_name = $this->get_classname_from_event_type( $row->event_type ); + + if ( ! $class_name ) { + return false; + } + + $event = new $class_name( $row->email_log_id ); + + $event->set_date_created( $row->date_created ); + + if ( ! empty( $row->object_id ) ) { + $event->set_object_id( $row->object_id ); + } + + return $event; + } + + /** + * Get event FQCN by event type. + * + * @since 2.9.0 + * + * @param string $type Event type. + * + * @return string|false + */ + public function get_classname_from_event_type( $type ) { + + foreach ( self::get_events_class_names() as $class_name ) { + if ( $class_name::get_type() === $type ) { + return $class_name; + } + } + + return false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventInterface.php new file mode 100644 index 00000000..6dd13206 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/EventInterface.php @@ -0,0 +1,38 @@ +get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $events_table ) ); + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $links_exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $links_table ) ); + + $is_valid = $events_exists && $links_exists; + + return $is_valid; + } + + /** + * Delete events connected to the provided email log id list. + * + * @since 2.9.0 + * + * @param string $email_log_ids A string of Email Log IDs to be deleted. + */ + public function delete_events( $email_log_ids ) { + + global $wpdb; + + if ( ! $this->is_valid_db() ) { + return; + } + + $log_ids_array = explode( ',', (string) $email_log_ids ); + $log_ids_array = array_map( 'intval', $log_ids_array ); + $placeholders = implode( ', ', array_fill( 0, count( $log_ids_array ), '%d' ) ); + + // Delete the DB rows of events connected to the provided email log ids list. + $events_db_table = Tracking::get_events_table_name(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$events_db_table} WHERE email_log_id IN ( {$placeholders} )", + $log_ids_array + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + + // Delete the DB rows of links connected to the provided email log ids list. + $links_db_table = Tracking::get_links_table_name(); + + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $wpdb->query( + $wpdb->prepare( + "DELETE FROM {$links_db_table} WHERE email_log_id IN ( {$placeholders} )", + $log_ids_array + ) + ); + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + } + + /** + * Delete all events from the DB. + * + * @since 2.9.0 + */ + public function delete_all_events() { + + global $wpdb; + + // Truncate events DB tables. + if ( $this->is_valid_db() ) { + $events_db_table = Tracking::get_events_table_name(); + $links_db_table = Tracking::get_links_table_name(); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( "TRUNCATE TABLE `$events_db_table`;" ); + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->query( "TRUNCATE TABLE `$links_db_table`;" ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/AbstractInjectableEvent.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/AbstractInjectableEvent.php new file mode 100644 index 00000000..c79b3599 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/AbstractInjectableEvent.php @@ -0,0 +1,153 @@ +get_tracking_url_data( $extra_data ); + $hash = $this->generate_signature( $data ); + + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + $encoded_data = base64_encode( + http_build_query( + [ + 'data' => $data, + 'hash' => $hash, + ] + ) + ); + + return get_rest_url( null, 'wp-mail-smtp/v1/e/' . $encoded_data ); + } + + /** + * Get tracking url event data. + * + * @since 2.9.0 + * + * @param array $extra_data Additional event data. + * + * @return array Event data. + */ + protected function get_tracking_url_data( $extra_data = [] ) { + + return array_merge( + [ + 'email_log_id' => $this->get_email_log_id(), + 'event_type' => $this->get_type(), + ], + $extra_data + ); + } + + /** + * Verify HMAC hash from request. + * + * @since 2.9.0 + * + * @param array $event_data Event data from request. + * @param string $hash Hash from request. + * + * @return bool + */ + public function verify_signature( $event_data, $hash ) { + + return hash_equals( $this->generate_signature( $event_data ), $hash ); + } + + /** + * Generate HMAC hash based on event data. + * + * @since 2.9.0 + * + * @param array $event_data Event data. + * + * @return string + */ + protected function generate_signature( $event_data ) { + + static $secret_key = null; + + if ( is_null( $secret_key ) ) { + $secret_key = Crypto::get_secret_key( true ); + } + + return hash_hmac( 'sha256', implode( '-', $event_data ), $secret_key, false ); + } + + /** + * Set tracking REST Request. + * + * @since 2.9.0 + * + * @param WP_REST_Request $request Tracking REST Request. + */ + public function set_request( $request ) { + + $this->request = $request; + } + + /** + * Get tracking REST Request. + * + * @since 2.9.0 + * + * @return WP_REST_Request + */ + public function get_request() { + + return $this->request; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/ClickLinkEvent.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/ClickLinkEvent.php new file mode 100644 index 00000000..38748c2d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/ClickLinkEvent.php @@ -0,0 +1,293 @@ +get_pro()->get_logs()->is_enabled_click_link_tracking(); + } + + /** + * Inject tracking link to each link in email content. + * + * @since 2.9.0 + * + * @param string $email_content Email content. + * + * @return string Email content with injected tracking code. + */ + public function inject( $email_content ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + // Skip if DOMDocument is not available. + if ( ! class_exists( 'DOMDocument' ) ) { + return $email_content; + } + + $html = make_clickable( $email_content ); + $links = $this->get_all_links( $html ); + + if ( empty( $links ) ) { + return $email_content; + } + + $created_links = []; + + foreach ( $links as $link ) { + $url = $link['url']; + + // Skip empty, anchor or mailto links. + if ( + strlen( trim( $url ) ) === 0 || + substr( trim( $url ), 0, 1 ) === '#' || + substr( trim( $url ), 0, 6 ) === 'mailto' + ) { + continue; + } + + /** + * Filters whether current url is trackable or not. + * + * @since 2.9.0 + * + * @param bool $is_trackable Whether url is trackable or not. + * @param string $url Current url. + */ + $is_trackable_url = apply_filters( + 'wp_mail_smtp_pro_emails_logs_tracking_events_injectable_click_link_event_inject_link', + true, + $url + ); + + if ( ! $is_trackable_url || isset( $created_links[ $url ] ) ) { + continue; + } + + $link_id = $this->add_link( $url ); + + // Skip if link was not created. + if ( $link_id === false ) { + continue; + } + + $created_links[ $url ] = $link_id; + + $tracking_url = $this->get_tracking_url( + [ + 'object_id' => $link_id, + 'url' => rawurlencode( $url ), + ] + ); + + $html = str_replace( $link['attr'], 'href="' . $tracking_url . '"', $html ); + } + + return $html; + } + + /** + * Persist event data to DB. + * + * @since 2.9.0 + * + * @return int|false Event ID or false if saving failed. + */ + public function persist() { + + // In case if images loading disabled in email, create open email event when first link in email clicked. + $open_event = new OpenEmailEvent( $this->get_email_log_id() ); + + if ( ! $open_event->was_event_already_triggered() ) { + $open_event->persist(); + } + + return parent::persist(); + } + + /** + * Redirect user to actual url. + * + * @since 2.9.0 + * + * @param array $event_data Event data from request. + * + * @return WP_REST_Response REST response. + */ + public function get_response( $event_data ) { + + $response = new WP_REST_Response(); + + $response->header( 'Cache-Control', 'must-revalidate, no-cache, no-store, max-age=0, no-transform' ); + $response->header( 'Pragma', 'no-cache' ); + $response->set_status( 301 ); + $response->header( 'Location', $this->normalize_url( urldecode( $event_data['url'] ) ) ); + + return $response; + } + + /** + * Save tracked link to DB. + * + * @since 2.9.0 + * + * @param string $url Actual url form email. + * + * @return false|int Link ID. + */ + public function add_link( $url ) { + + global $wpdb; + + $data = [ + 'email_log_id' => intval( $this->get_email_log_id() ), + 'url' => esc_url_raw( $url ), + ]; + + $result = $wpdb->insert( Tracking::get_links_table_name(), $data, [ '%d', '%s' ] ); + + return $result !== false ? $wpdb->insert_id : false; + } + + /** + * Delete all email related tracked links. + * + * @since 2.9.0 + * + * @return bool + */ + public function delete_links() { + + global $wpdb; + + $email_log_id = intval( $this->get_email_log_id() ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->delete( Tracking::get_links_table_name(), [ 'email_log_id' => $email_log_id ], [ '%d' ] ); + } + + /** + * Get all links from email content. + * + * @since 4.0.0 + * + * @param string $email_content Email content. + * + * @return array + */ + private function get_all_links( $email_content ) { + + // Insert each into new line. + $email_content = str_replace( '', "\n", $email_content ); + + // Find all link tags. + preg_match_all( '/.*<\/a>/isU', $email_content, $links, PREG_SET_ORDER ); + + if ( empty( $links ) ) { + return []; + } + + $links = array_column( $links, 1 ); + $result = []; + + foreach ( $links as $link_attrs ) { + // Find href attribute with double quotes. + preg_match_all( '/href=\"([^\r\n]*)\"/iU', $link_attrs, $href, PREG_SET_ORDER ); + + // If href attribute not found, try to find it with single quotes. + if ( empty( $href ) ) { + preg_match_all( '/href=\'([^\r\n]*)\'/iU', $link_attrs, $href, PREG_SET_ORDER ); + } + + if ( empty( $href ) ) { + continue; + } + + $result[] = [ + 'url' => $href[0][1], + 'attr' => $href[0][0], + ]; + } + + return $result; + } + + /** + * Normalize url. + * + * Mainly used to decode html entities like & to &. + * To make it as safe as possible the `DOMDocument` is used. + * + * @since 4.0.2 + * @since 4.1.0 Disabled libxml warning and error reporting. + * + * @param string $url Url to normalize. + * + * @return string + */ + private function normalize_url( $url ) { + + // Skip if DOMDocument is not available. + if ( ! class_exists( 'DOMDocument' ) ) { + return $url; + } + + if ( ! function_exists( 'mb_encode_numericentity' ) ) { + Helpers::include_mbstring_polyfill(); + } + + $link_html = ''; + $encoded_link_html = mb_encode_numericentity( $link_html, [ 0x80, 0x10FFFF, 0, ~0 ], 'UTF-8' ); + + if ( ! empty( $encoded_link_html ) ) { + $link_html = $encoded_link_html; + } + + $dom = new DOMDocument(); + $libxml_internal_errors = libxml_use_internal_errors( true ); + $normalized_url = $url; + + $dom->loadHTML( $link_html ); + + $link = $dom->getElementById( 'link' ); + + if ( ! empty( $link ) ) { + $href = $link->getAttribute( 'href' ); + $normalized_url = ! empty( $href ) ? $href : $normalized_url; + } + + libxml_clear_errors(); + libxml_use_internal_errors( $libxml_internal_errors ); + + return $normalized_url; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/OpenEmailEvent.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/OpenEmailEvent.php new file mode 100644 index 00000000..02fc7c48 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Tracking/Events/Injectable/OpenEmailEvent.php @@ -0,0 +1,73 @@ +get_pro()->get_logs()->is_enabled_open_email_tracking(); + } + + /** + * Inject tracking pixel to content. + * + * @since 2.9.0 + * + * @param string $email_content Email content. + * + * @return string Email content with injected tracking code. + */ + public function inject( $email_content ) { + + $pixel_img = sprintf( '', $this->get_tracking_url() ); + + if ( strpos( $email_content, '' ) !== 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(); + ?> + + +
    + Take advantage of powerful features +
    +

    + ', array_column( $features, 'title' ) ), [ 'br' => [] ] ); ?> +

    +

    + These powerful features are available in your WP Mail SMTP Pro settings. +

    + + + 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(); + ?> + + + + + + +
    + + + + + +
    + SendLayer Icon + +

    + 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! +

    +
    +
    + + + 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(); + ?> +
    + + +
    + is_const_defined( 'license', 'key' ) || $is_valid ) ? 'disabled' : ''; ?> + /> + + + + + + + + + + + + + +
    + + WP Mail SMTP Account Dashboard. Don\'t have a license? Sign up today!', 'wp-mail-smtp-pro' ), + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/', [ 'content' => 'License Key Account Dashboard Link' ] ) ), + esc_url( wp_mail_smtp()->get_upgrade_link( [ 'content' => 'License Sign Up Today' ] ) ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + } elseif ( $is_valid ) { + $type_message = sprintf( /* translators: $s - license type. */ + esc_html__( 'Your license key level is %s.', 'wp-mail-smtp-pro' ), + '' . esc_html( $type ) . '' + ); + + $desc_message = wp_kses( + __( 'If your license has been upgraded or is incorrect, then please force a refresh.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'id' => [], + ], + ] + ); + } elseif ( $is_expired ) { + $type_message = wp_kses( + __( 'Your license has expired. An active license is needed to access some of the Pro features, plugin updates (including security improvements), and our world class support!', 'wp-mail-smtp-pro' ), + [ + 'b' => [], + ] + ); + + $desc_message = wp_kses( + __( 'If your license has been upgraded or is incorrect, then please force a refresh.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'id' => [], + ], + ] + ); + } elseif ( $is_disabled ) { + $type_message = wp_kses( + __( 'Your license key has been disabled. Please use a different key to continue receiving automatic updates.', 'wp-mail-smtp-pro' ), + [ + 'b' => [], + ] + ); + } elseif ( $is_invalid ) { + $type_message = wp_kses( + __( 'Your license key is invalid. The key no longer exists or the user associated with the key has been deleted. Please use a different key to continue receiving automatic updates.', 'wp-mail-smtp-pro' ), + [ + 'b' => [], + ] + ); + } elseif ( $is_limit_reached ) { + $type_message = sprintf( + wp_kses( /* translators: %1$s - WPMailSMTP.com account area URL; %2$s - WPMailSMTP.com pricing page URL. */ + __( 'Sorry, but this 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 key field', + 'content' => 'license site limit reached', + ] + ) + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/pricing/', + [ + 'medium' => 'license key field', + 'content' => 'license site limit reached', + ] + ) + ) + ); + + $desc_message = wp_kses( + __( 'If your license has been upgraded or is incorrect, then please force a refresh.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'id' => [], + ], + ] + ); + } + + if ( ! empty( $type_message ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + printf( '

    %s

    ', $type_message ); + } + + if ( ! empty( $desc_message ) ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + printf( '

    %s

    ', $desc_message ); + } + + ?> +
    + '', + 'type' => 'lite', + 'is_expired' => false, + 'is_disabled' => false, + 'is_invalid' => false, + 'is_limit_reached' => false, + ]; + } + + return $options; + } + + /** + * Verify a license key entered by the user. + * + * @since 1.5.0 + * + * @param string $key + * @param bool $ajax + * + * @return bool + */ + public function verify_key( $key = '', $ajax = false ) { + + if ( empty( $key ) ) { + return false; + } + + $options = Options::init(); + $all_opt = $options->get_all(); + + // Perform a request to verify the key. + $verify = $this->perform_remote_request( 'verify-key', array( 'tgm-updater-key' => $key ) ); + + // If it returns false, send back a generic error message and return. + if ( ! $verify ) { + $msg = esc_html__( 'There was an error connecting to the remote key API. Please try again later.', 'wp-mail-smtp-pro' ); + if ( $ajax ) { + wp_send_json_error( $msg ); + } else { + $this->errors[] = $msg; + + return false; + } + } + + // If an error is returned, set the error and return. + if ( ! empty( $verify->error ) ) { + if ( $ajax ) { + wp_send_json_error( $verify->error ); + } else { + $this->errors[] = $verify->error; + + return false; + } + } + + $success = isset( $verify->success ) ? $verify->success : esc_html__( 'Congratulations! This site is now receiving automatic updates.', 'wp-mail-smtp-pro' ); + + $this->success[] = $success; + + $license_type = isset( $verify->type ) ? $verify->type : $all_opt['license']['type']; + + // Otherwise, our request has been done successfully. Update the option and set the success message. + $data = [ + 'license' => [ + 'key' => $key, + 'type' => $license_type, + 'is_expired' => false, + 'is_disabled' => false, + 'is_invalid' => false, + 'is_limit_reached' => false, + ], + ]; + + $options->set( $data, false, false ); + + wp_clean_plugins_cache( true ); + + if ( $ajax ) { + wp_send_json_success( + [ + 'type' => $license_type, + 'message' => $success, + 'settings_html' => $this->display_settings_license_key_field_content( $options, false ), + ] + ); + } + + return true; + } + + /** + * Maybe validate a license key entered by the user. + * + * @since 1.5.0 + */ + public function maybe_validate_key() { + + $options = Options::init(); + $all_opt = $options->get_all(); + + if ( empty( $all_opt['license']['key'] ) ) { + return; + } + + if ( empty( $all_opt['license']['updates'] ) ) { + $data = [ + 'license' => [ + 'updates' => strtotime( '+24 hours' ), + ], + ]; + + $options->set( $data, false, false ); + + // Perform a request to validate the key. + $this->validate_key( $all_opt['license']['key'] ); + } else { + $current_timestamp = time(); + if ( $current_timestamp < $all_opt['license']['updates'] ) { + return; + } else { + $data = [ + 'license' => [ + 'updates' => strtotime( '+24 hours' ), + ], + ]; + + $options->set( $data, false, false ); + $this->validate_key( $all_opt['license']['key'] ); + } + } + } + + /** + * Validate a license key entered by the user. + * + * @since 1.5.0 + * + * @param string $key License key. + * @param bool $forced Force to set contextual messages (false by default). + * @param bool $ajax AJAX. + * @param bool $return_status Option to return the license status. + * + * @return string|bool + */ + public function validate_key( $key = '', $forced = false, $ajax = false, $return_status = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $validate = $this->perform_remote_request( 'validate-key', [ 'tgm-updater-key' => $key ] ); + $options = Options::init(); + $all_opt = $options->get_all(); + + // If there was a basic API error in validation - do nothing. + if ( ! $validate ) { + // If forced, set contextual success message. + if ( $forced ) { + $msg = esc_html__( 'There was an error connecting to the remote server. Please try again later.', 'wp-mail-smtp-pro' ); + + if ( $ajax ) { + wp_send_json_error( $msg ); + } else { + $this->errors[] = $msg; + } + } + + return false; + } + + $data = [ + 'license' => [ + 'is_expired' => false, + 'is_disabled' => false, + 'is_invalid' => false, + 'is_limit_reached' => false, + ], + ]; + + // If a key or author error is returned, the license no longer exists or the user has been deleted, so reset license. + if ( isset( $validate->key ) || isset( $validate->author ) ) { + $data['license']['is_invalid'] = true; + + $options->set( $data, false, false ); + + if ( $ajax ) { + wp_send_json_error( esc_html__( 'Your license key for WP Mail SMTP Pro is invalid. The key no longer exists or the user associated with the key has been deleted. Please use a different key to continue receiving automatic updates.', 'wp-mail-smtp-pro' ) ); + } + + return $return_status ? 'invalid' : false; + } + + // If the license has expired, set the transient and expired flag and return. + if ( isset( $validate->expired ) ) { + $data['license']['is_expired'] = true; + + $options->set( $data, false, false ); + + if ( $ajax ) { + wp_send_json_error( esc_html__( 'Your license key for WP Mail SMTP Pro has expired. Please renew your license key on WPMailSMTP.com to continue receiving automatic updates.', 'wp-mail-smtp-pro' ) ); + } + + return $return_status ? 'expired' : false; + } + + // If the license is disabled, set the transient and disabled flag and return. + if ( isset( $validate->disabled ) ) { + $data['license']['is_disabled'] = true; + + $options->set( $data, false, false ); + + if ( $ajax ) { + wp_send_json_error( esc_html__( 'Your license key for WP Mail SMTP Pro has been disabled. Please use a different key to continue receiving automatic updates.', 'wp-mail-smtp-pro' ) ); + } + + return $return_status ? 'disabled' : false; + } + + // If the license site activations limit reached, set limit reached flag and return. + if ( isset( $validate->limit_reached ) ) { + $data['license']['is_limit_reached'] = true; + + $options->set( $data, false, false ); + + if ( $ajax ) { + wp_send_json_error( + sprintf( + wp_kses( /* translators: %1$s - WPMailSMTP.com account area URL; %2$s - WPMailSMTP.com pricing page URL. */ + __( 'Sorry, but this 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' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/account/licenses/', + [ + 'medium' => 'license-alert-modal', + 'content' => 'license site limit reached', + ] + ) + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/pricing/', + [ + 'medium' => 'license-alert-modal', + 'content' => 'license site limit reached', + ] + ) + ) + ) + ); + } + + return $return_status ? 'limit_reached' : false; + } + + $license_type = isset( $validate->type ) ? $validate->type : $all_opt['license']['type']; + + // Otherwise, our check has returned successfully. Set the transient and update our license type and flags. + $data['license']['type'] = $license_type; + + $options->set( $data, false, false ); + + // If forced, set contextual success message. + if ( $forced ) { + $msg = esc_html__( 'Your key has been refreshed successfully.', 'wp-mail-smtp-pro' ); + $this->success[] = $msg; + + if ( $ajax ) { + wp_send_json_success( + [ + 'type' => $license_type, + 'message' => $msg, + 'settings_html' => $this->display_settings_license_key_field_content( $options, false ), + ] + ); + } + } + + return $return_status ? 'valid' : true; + } + + /** + * Deactivate a license key entered by the user. + * + * @since 1.5.0 + * + * @param bool $ajax + */ + public function deactivate_key( $ajax = false ) { + + $options = Options::init(); + $all_opt = $options->get_all(); + + if ( empty( $all_opt['license']['key'] ) ) { + return; + } + + // Perform a request to deactivate the key. + $deactivate = $this->perform_remote_request( 'deactivate-key', array( 'tgm-updater-key' => $all_opt['license']['key'] ) ); + + // If it returns false, send back a generic error message and return. + if ( ! $deactivate ) { + $msg = esc_html__( 'There was an error connecting to the remote server. Please try again later.', 'wp-mail-smtp-pro' ); + if ( $ajax ) { + wp_send_json_error( $msg ); + } else { + $this->errors[] = $msg; + + return; + } + } + + // If an error is returned, set the error and return. + if ( ! empty( $deactivate->error ) ) { + if ( $ajax ) { + wp_send_json_error( $deactivate->error ); + } else { + $this->errors[] = $deactivate->error; + + return; + } + } + + // Otherwise, our request has been done successfully. Reset the option and set the success message. + $success = isset( $deactivate->success ) ? $deactivate->success : esc_html__( 'You have deactivated the key from this site successfully.', 'wp-mail-smtp-pro' ); + $this->success[] = $success; + + $raw_settings = $options->get_all_raw(); + $raw_settings['license'] = [ + 'key' => '', + 'type' => 'lite', + ]; + + $options->set( $raw_settings ); + + if ( $ajax ) { + wp_send_json_success( + [ + 'message' => $success, + 'settings_html' => $this->display_settings_license_key_field_content( $options, false ), + ] + ); + } + } + + /** + * Output any notices generated by the class. + * + * @since 1.5.0 + * @since 3.8.0 Add `manage_options` capability check. + * + * @param bool $below_h2 + */ + public function notices( $below_h2 = false ) { + + // Only users with sufficient capability can see the notices. + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + return; + } + + // Grab the option and output any nag dealing with license keys. + $options = Options::init(); + $all_opt = $options->get_all(); + $below_h2 = $below_h2 ? 'below-h2' : ''; + + // If there is no license key, output nag about ensuring key is set for automatic updates. + if ( empty( $all_opt['license']['key'] ) ) : + ?> +
    +

    + 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', + ] + ) + ) + ); + ?> +

    +
    + errors ) ) : + ?> +
    +

    ', array_map( 'esc_html', $this->errors ) ); ?>

    +
    + success ) ) : + ?> +
    +

    ', array_map( 'esc_html', $this->success ) ); ?>

    +
    + $action, + 'tgm-updater-key' => $body['tgm-updater-key'], + 'tgm-updater-wp-version' => get_bloginfo( 'version' ), + 'tgm-updater-php-version' => phpversion(), + 'tgm-updater-referer' => WP::get_site_url(), + ] + ); + + if ( $this->is_validate_key_request( (string) $action ) === true ) { + $query_params['wpforms_refresh_key'] = 1; + } + + $args = [ + 'headers' => $headers, + 'user-agent' => Helpers::get_default_user_agent(), + 'timeout' => 30, + ]; + + if ( defined( 'WPMS_UPDATER_API' ) ) { + $this->remote_url = WPMS_UPDATER_API; + } + + $remote_url = $this->remote_url . '/' . $action; + + // Perform the query and retrieve the response. + $response = wp_remote_get( add_query_arg( $query_params, $remote_url ), $args ); + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = wp_remote_retrieve_body( $response ); + + // Bail out early if there are any errors. + if ( 200 != $response_code || is_wp_error( $response_body ) ) { + return false; + } + + // Return the json decoded content. + return json_decode( $response_body ); + } + + /** + * The status of the license. + * + * @since 1.9.0 + * + * @return array The results array with 'valid' (bool) and 'message' (string) attributes. + */ + public function get_status() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $license_key = wp_mail_smtp()->get_license_key(); + + $result = [ + 'valid' => false, + ]; + + if ( empty( $license_key ) ) { + $result['message'] = sprintf( + wp_kses( /* translators: %s - plugin settings page URL. */ + __( 'Please enter and activate your license key for WP Mail SMTP Pro to enable automatic updates.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ) + ); + + return $result; + } + + $license_status = $this->validate_key( $license_key, false, false, true ); + + if ( $license_status === false ) { + $result['message'] = esc_html__( 'There was an error connecting to the remote server. Please try again later.', 'wp-mail-smtp-pro' ); + + return $result; + } + + if ( $license_status === 'expired' ) { + $result['message'] = sprintf( + wp_kses( /* translators: %s - WPMailSMTP.com login page URL. */ + __( 'Your license key for WP Mail SMTP Pro has expired. Please click here to renew your license key and continue receiving automatic updates.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( $this->get_renewal_link( 'renew your license key' ) ) + ); + + return $result; + } + + if ( $license_status === 'disabled' ) { + $result['message'] = sprintf( + wp_kses( /* translators: %s - plugin settings page URL. */ + __( 'Your license key for WP Mail SMTP Pro has been disabled. Please enter and activate a different key for WP Mail SMTP Pro to continue receiving automatic updates.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ) + ); + + return $result; + } + + if ( $license_status === 'invalid' ) { + $result['message'] = sprintf( + wp_kses( /* translators: %s - plugin settings page URL. */ + __( 'Your license key for WP Mail SMTP Pro is invalid. Please enter and activate a different key for WP Mail SMTP Pro to continue receiving automatic updates.', 'wp-mail-smtp-pro' ), + [ + 'a' => [ + 'href' => [], + ], + ] + ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ) + ); + + return $result; + } + + if ( $license_status === 'limit_reached' ) { + $result['message'] = sprintf( + wp_kses( /* translators: %1$s - WPMailSMTP.com account area URL; %2$s - WPMailSMTP.com pricing page URL. */ + __( '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' ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/account/licenses/', + [ + 'content' => 'license site limit reached', + ] + ) + ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/pricing/', + [ + 'content' => 'license site limit reached', + ] + ) + ) + ); + + return $result; + } + + return [ + 'valid' => true, + 'message' => esc_html__( 'Your WP Mail SMTP Pro license is active and valid.', 'wp-mail-smtp-pro' ), + ]; + } + + /** + * Check whether the license is valid. + * + * @since 3.5.0 + * + * @param bool $remote Perform remote request or use DB license data. + * + * @return bool + */ + public function is_valid( $remote = false ) { + + if ( $remote ) { + return $this->get_status()['valid']; + } + + $saved_license = Options::init()->get_group( 'license' ); + + return ! empty( $saved_license['key'] ) && + empty( $saved_license['is_expired'] ) && + empty( $saved_license['is_disabled'] ) && + empty( $saved_license['is_invalid'] ) && + empty( $saved_license['is_limit_reached'] ); + } + + /** + * Check whether the license is expired. + * + * @since 3.11.0 + * + * @return bool + */ + public function is_expired() { + + $saved_license = Options::init()->get_group( 'license' ); + + return ! empty( $saved_license['is_expired'] ); + } + + /** + * Renewal link used within the various admin pages. + * + * @since 3.8.0 + * + * @param array|string $utm Array of UTM params, or if string provided - utm_content URL parameter. + * + * @return string + */ + public function get_renewal_link( $utm ) { + + $license_key = wp_mail_smtp()->get_license_key(); + + if ( ! empty( $license_key ) && strlen( $license_key ) === 32 ) { + return wp_mail_smtp()->get_utm_url( + add_query_arg( + 'edd_license_key', + $license_key, + 'https://wpmailsmtp.com/checkout/' + ), + $utm + ); + } + + return wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/licenses/', $utm ); + } + + /** + * Fetch the remote latest version. + * + * @since 3.8.0 + * + * @param bool $force_remote Whether or not to force remote fetch. Optional. Default `false`. + * + * @return string + */ + public function fetch_latest_plugin_version( $force_remote = false ) { + + if ( $force_remote ) { + return $this->remote_fetch_and_cache_latest_plugin_version(); + } + + $cache = get_transient( self::CACHE_REMOTE_LATEST_VERSION_KEY ); + + if ( $cache === false ) { + return $this->remote_fetch_and_cache_latest_plugin_version(); + } + + return $cache['version']; + } + + /** + * Fetch the latest version from our remote source. + * + * @since 3.8.0 + * + * @return string Returns empty string '' if unable to fetch the latest version. + * Otherwise, returns the latest version. + */ + private function remote_fetch_and_cache_latest_plugin_version() { + + // Perform the query and retrieve the response. + $response = wp_remote_get( + $this->latest_version_remote_url, + [ + 'user-agent' => Helpers::get_default_user_agent(), + ] + ); + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = wp_remote_retrieve_body( $response ); + + // Bail out early if there are any errors. + if ( $response_code !== 200 || is_wp_error( $response_body ) ) { + $this->cache_remote_latest_version( '' ); + + return ''; + } + + // Decode the response. + $json_response = json_decode( $response_body ); + + if ( empty( $json_response ) || empty( $json_response[0]->version ) ) { + $this->cache_remote_latest_version( '' ); + + return ''; + } + + $this->cache_remote_latest_version( $json_response[0]->version ); + + return $json_response[0]->version; + } + + /** + * Cache the remote latest version. + * + * @since 3.8.0 + * + * @param string $latest_version Latest version to cache. + * + * @return void + */ + private function cache_remote_latest_version( $latest_version ) { + + set_transient( + self::CACHE_REMOTE_LATEST_VERSION_KEY, + [ + 'version' => $latest_version, + 'last_checked' => time(), + ], + DAY_IN_SECONDS * $this->get_remote_latest_version_interval() + ); + } + + /** + * Get the interval time, in days, to remote fetch the latest version. + * + * @since 3.8.0 + * + * @return int + */ + private function get_remote_latest_version_interval() { + + return absint( + /** + * Filters the interval time, in days, to remote fetch the latest version. + * + * @since 3.8.0 + * + * @param int $interval Interval time in days. + */ + apply_filters( 'wp_mail_smtp_pro_license_get_remote_latest_version_interval', self::REMOTE_FETCH_LATEST_VERSION_INTERVAL_IN_DAYS ) + ); + } + + /** + * Check if this is an ajax request to validate the key. + * + * @since 4.1.0 + * + * @param string $action Action. + * + * @return bool + */ + private function is_validate_key_request( string $action ): bool { + + $allowed_tasks = [ 'license_verify', 'license_refresh' ]; + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( + ! isset( $_REQUEST['action'] ) || + $_REQUEST['action'] !== 'wp_mail_smtp_pro_license_ajax' || + ! isset( $_REQUEST['task'] ) || + ! in_array( $_REQUEST['task'], $allowed_tasks, true ) + ) { + return false; + } + + $is_verify_key_request = ( + $_REQUEST['task'] === 'license_verify' && + $action === 'verify-key' + ); + + $is_refresh_key_request = ( + $_REQUEST['task'] === 'license_refresh' && + $action === 'validate-key' + ); + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + return $is_verify_key_request || $is_refresh_key_request; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/License/Updater.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/License/Updater.php new file mode 100644 index 00000000..1c0bc560 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/License/Updater.php @@ -0,0 +1,474 @@ +$arg = $config[ $arg ]; + } + } + + if ( defined( 'WPMS_UPDATER_API' ) ) { + $this->remote_url = WPMS_UPDATER_API; + } + + // If the user cannot update plugins, + // or it's not a WP CLI request, + // stop processing here. + if ( ! current_user_can( 'update_plugins' ) && ! Helpers::is_wp_cli() ) { + return; + } + + // Load the updater hooks and filters. + add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'update_plugins_filter' ) ); + add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 ); + add_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 ); + } + + /** + * Infuse plugin update details when WordPress runs its update checker. + * + * @since 1.5.0 + * + * @param object $value The WordPress update object. + * + * @return object $value Amended WordPress update object on success, default if object is empty. + */ + public function update_plugins_filter( $value ) { + + // If no update object exists, return early. + if ( empty( $value ) ) { + return $value; + } + + // Run update check by pinging the external API. If it fails, return the default update object. + if ( ! $this->update ) { + $this->update = $this->perform_remote_request( 'get-plugin-update', array( 'tgm-updater-plugin' => $this->plugin_slug ) ); + + // No update is available. + if ( ! $this->update || ! empty( $this->update->error ) ) { + $this->update = false; + + $value->no_update[ $this->plugin_path ] = $this->get_no_update(); + + return $value; + } + } + + // Infuse the update object with our data if the version from the remote API is newer. + if ( isset( $this->update->new_version ) && version_compare( $this->version, $this->update->new_version, '<' ) ) { + + $package = isset( $this->update->package ) ? $this->update->package : ''; + + // Refuse to offer an update whose package URL is not from our update host. + if ( ! $this->is_valid_package_url( $package ) ) { + $host = wp_parse_url( $package, PHP_URL_HOST ); + + DebugEvents::add_throttled( + sprintf( + 'Plugin update rejected: package URL host "%s" failed validation.', + is_string( $host ) ? $host : 'unknown' + ), + 'wp_mail_smtp_pro_invalid_update_package_url' + ); + + $value->no_update[ $this->plugin_path ] = $this->get_no_update(); + + return $value; + } + + // The $plugin_update object contains new_version, package, slug and last_update keys. + $this->update->old_version = $this->version; + $this->update->plugin = $this->plugin_path; + $value->response[ $this->plugin_path ] = $this->update; + } else { + $value->no_update[ $this->plugin_path ] = $this->get_no_update(); + } + + // Return the update object. + return $value; + } + + /** + * Disable SSL verification to prevent download package failures. + * + * @since 1.5.0 + * + * @param array $args Array of request args. + * @param string $url The URL to be pinged. + * + * @return array $args Amended array of request args. + */ + public function http_request_args( $args, $url ) { + + return $args; + } + + /** + * Filter the plugins_api function to get our own custom plugin information from our private repo. + * + * @since 1.5.0 + * + * @param object $api The original plugins_api object. + * @param string $action The action sent by plugins_api. + * @param array $args Additional args to send to plugins_api. + * + * @return object $api New stdClass with plugin information on success, default response on failure. + */ + public function plugins_api( $api, $action = '', $args = null ) { + + $plugin = ( 'plugin_information' === $action ) && isset( $args->slug ) && ( $this->plugin_slug === $args->slug ); + + // If our plugin matches the request, set our own plugin data, else return the default response. + if ( $plugin ) { + return $this->set_plugins_api( $api ); + } else { + return $api; + } + } + + /** + * Pings a remote API to retrieve plugin information for WordPress to display. + * + * @since 1.5.0 + * + * @param object $default_api The default API object. + * + * @return object $api Return custom plugin information to plugins_api. + */ + public function set_plugins_api( $default_api ) { + + // Perform the remote request to retrieve our plugin information. If it fails, return the default object. + if ( ! $this->info ) { + $this->info = $this->perform_remote_request( 'get-plugin-info', array( 'tgm-updater-plugin' => $this->plugin_slug ) ); + if ( ! $this->info || ! empty( $this->info->error ) ) { + $this->info = false; + + return $default_api; + } + } + + // Create a new stdClass object and populate it with our plugin information. + $api = new \stdClass(); + $api->name = isset( $this->info->name ) ? $this->info->name : ''; + $api->slug = isset( $this->info->slug ) ? $this->info->slug : ''; + $api->version = isset( $this->info->version ) ? $this->info->version : ''; + $api->author = isset( $this->info->author ) ? $this->info->author : ''; + $api->author_profile = isset( $this->info->author_profile ) ? $this->info->author_profile : ''; + $api->requires = isset( $this->info->requires ) ? $this->info->requires : ''; + $api->tested = isset( $this->info->tested ) ? $this->info->tested : ''; + $api->last_updated = isset( $this->info->last_updated ) ? $this->info->last_updated : ''; + $api->homepage = isset( $this->info->homepage ) ? $this->info->homepage : ''; + $api->sections['changelog'] = isset( $this->info->changelog ) ? $this->info->changelog : ''; + $api->download_link = $this->get_validated_download_link(); + $api->active_installs = isset( $this->info->active_installs ) ? $this->info->active_installs : ''; + $api->banners = isset( $this->info->banners ) ? (array) $this->info->banners : ''; + + // Return the new API object with our custom data. + return $api; + } + + /** + * Return the plugin-info download link only if it passes URL validation. + * + * @since 4.9.0 + * + * @return string Empty string when the link is missing or invalid. + */ + private function get_validated_download_link() { + + $download_link = isset( $this->info->download_link ) ? $this->info->download_link : ''; + + if ( + $download_link === '' || + ! $this->is_valid_package_url( $download_link ) + ) { + return ''; + } + + return $download_link; + } + + /** + * Query the remote URL via wp_remote_get and return a json decoded response. + * + * @since 1.5.0 + * @since 2.7.0 Switch from POST to GET request. + * + * @param string $action The name of the request action var. + * @param array $body The GET query attributes. + * @param array $headers The headers to send to the remote URL. + * @param string $return_format The format for returning content from the remote URL. + * + * @return string|bool Json decoded response on success, false on failure. + */ + public function perform_remote_request( $action, $body = [], $headers = [], $return_format = 'json' ) { + + // Request query parameters. + $query_params = wp_parse_args( + $body, + [ + 'tgm-updater-action' => $action, + 'tgm-updater-key' => $this->key, + 'tgm-updater-wp-version' => get_bloginfo( 'version' ), + 'tgm-updater-php-version' => phpversion(), + 'tgm-updater-referer' => WP::get_site_url(), + ] + ); + + $args = [ + 'headers' => $headers, + 'user-agent' => Helpers::get_default_user_agent(), + 'timeout' => 30, + ]; + + $remote_url = $this->remote_url . '/' . $action; + + // Perform the query and retrieve the response. + $response = wp_remote_get( add_query_arg( $query_params, $remote_url ), $args ); + $response_code = wp_remote_retrieve_response_code( $response ); + $response_body = wp_remote_retrieve_body( $response ); + + // Bail out early if there are any errors. + if ( 200 !== $response_code || is_wp_error( $response_body ) ) { + return false; + } + + $response_body = json_decode( $response_body ); + + // A few items need to be converted from an object to an array as that + // is what WordPress expects. + if ( ! empty( $response_body->package ) ) { + if ( ! empty( $response_body->icons ) ) { + $response_body->icons = (array) $response_body->icons; + } + if ( ! empty( $response_body->banners ) ) { + $response_body->banners = (array) $response_body->banners; + } + } + + // Return the json decoded content. + return $response_body; + } + + /** + * Validate that a download package URL points at our production update host. + * + * Enforced only under the production updater URL; custom environments are + * exempt. Exact normalized host equality. + * + * @since 4.9.0 + * + * @param string $url The package/download URL to validate. + * + * @return bool + */ + public function is_valid_package_url( $url ) { + + /** + * Recovery hatch: bypass package URL validation via wp-config.php or mu-plugin. + * + * @since 4.9.0 + * + * @param bool $skip Whether to skip validation. Default false. + * @param string $url The package URL being validated. + */ + if ( apply_filters( 'wp_mail_smtp_pro_license_updater_skip_package_url_validation', false, $url ) ) { + return true; + } + + // Enforced only under the production updater URL; custom environments are exempt. + if ( $this->remote_url !== self::UPDATER_API_URL ) { + return true; + } + + if ( ! is_string( $url ) || $url === '' ) { + return false; + } + + $package = wp_parse_url( $url ); + + if ( empty( $package['scheme'] ) || empty( $package['host'] ) ) { + return false; + } + + return strtolower( $package['scheme'] ) === 'https' + && strtolower( $package['host'] ) === 'wpmailsmtpapi.com'; + } + + /** + * Prepare the "mock" item to the `no_update` property. + * Is required for the enable/disable auto-updates links to correctly appear in UI. + * + * @since 3.0.0 + * @since 3.8.0 Refactor to get the `return` object from a public static method. + * + * @return object + */ + protected function get_no_update() { + + return self::get_no_update_object( + [ + 'plugin_path' => $this->plugin_path, + 'plugin_slug' => $this->plugin_slug, + 'version' => $this->version, + ] + ); + } + + /** + * Returns the "mock" item to the `no_update` property. + * + * @since 3.8.0 + * + * @param array $plugin_data Plugin data. + * + * @return object + */ + public static function get_no_update_object( $plugin_data ) { + + $plugin = wp_parse_args( + $plugin_data, + [ + 'plugin_path' => '', + 'plugin_slug' => '', + 'version' => '', + ] + ); + + return (object) [ + 'id' => $plugin['plugin_path'], + 'slug' => $plugin['plugin_slug'], + 'plugin' => $plugin['plugin_path'], + 'new_version' => $plugin['version'], + 'url' => '', + 'package' => '', + 'icons' => [], + 'banners' => [], + 'banners_rtl' => [], + 'tested' => '', + 'requires_php' => '', + 'compatibility' => new stdClass(), + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcher.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcher.php new file mode 100644 index 00000000..a3305246 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcher.php @@ -0,0 +1,20 @@ +get_connections_manager(); + $connection = $connections_manager->get_mail_connection(); + + if ( ! $connection->is_primary() ) { + $this->addCustomHeader( 'X-WP-Mail-SMTP-Connection', $connection->get_id() ); + } + + if ( self::$is_backup_connection ) { + $this->addCustomHeader( 'X-WP-Mail-SMTP-Connection-Type', 'backup' ); + } + + $exception = false; + + try { + $is_sent = parent::send(); + } catch ( Exception $e ) { + $is_sent = false; + $exception = $e; + } + + $is_backup_sent = false; + + if ( + ! $is_sent && + ! self::$is_backup_connection && + ! $this->is_test_email() && + ! $this->is_setup_wizard_test_email() && + ! $this->is_emailing_blocked() + ) { + $backup_connections = wp_mail_smtp()->get_pro()->get_backup_connections(); + + $alert_type = Alerts::FAILED_EMAIL; + $error_message = $this->latest_error; + + // Capture before the backup re-entry overwrites Logs::current_email_id. + $email_logs = wp_mail_smtp()->get_pro()->get_logs(); + $primary_log_id = $email_logs->get_current_email_id(); + + $primary_debug = [ + 'email_log_id' => $primary_log_id, + ]; + $backup_debug = []; + + if ( $backup_connections->is_ready() ) { + self::$is_backup_connection = true; + $is_backup_sent = $backup_connections->send_email(); + self::$is_backup_connection = false; + + if ( $is_backup_sent ) { + $alert_type = Alerts::FAILED_PRIMARY_EMAIL; + } else { + $alert_type = Alerts::FAILED_BACKUP_EMAIL; + $backup_error_message = $this->latest_error; + + if ( $error_message !== $backup_error_message ) { + $error_message = esc_html( + __( 'Primary connection', 'wp-mail-smtp-pro' ) . WP::EOL . $error_message + ); + $error_message .= WP::EOL . WP::EOL . esc_html( + __( 'Backup connection', 'wp-mail-smtp-pro' ) . WP::EOL . $backup_error_message + ); + } + } + + // Primary record: 'sent' when the backup was rescued. + if ( $is_backup_sent ) { + $primary_debug['status'] = 'sent'; + } else { + $backup_debug['email_log_id'] = $email_logs->get_current_email_id(); + } + } + + $backup_connection = $connections_manager->get_mail_backup_connection(); + + EmailSendingDebug::merge( $connection->get_id(), $primary_debug ); + + if ( ! empty( $backup_debug ) && $backup_connection ) { + EmailSendingDebug::merge( $backup_connection->get_id(), $backup_debug ); + } + + // Process alerts. + ( new Alerts() )->handle_failed_email( $error_message, $this, $connection->get_mailer_slug(), $alert_type, $primary_log_id ); + } + + $connections_manager->reset_mail_connection(); + + if ( $exception && ! $is_backup_sent ) { + throw $e; + } + + return $is_sent || $is_backup_sent; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcherV6.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcherV6.php new file mode 100644 index 00000000..c66b06ab --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/MailCatcherV6.php @@ -0,0 +1,20 @@ +get( 'mail', 'mailer' ); + + if ( $mailer === 'outlook' ) { + $all = $options->get_all(); + + // To save in DB. + $all['mail']['from_email_force'] = true; + + $options->set( $all, false, true ); + } + + // Save the current version to DB. + $this->update_db_ver( 1 ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Multisite.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Multisite.php new file mode 100644 index 00000000..9b3ee065 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Multisite.php @@ -0,0 +1,1045 @@ +get_admin(), 'add_wpms_network_wide_setting_product_education_page' ] ); + + // Add the plugin admin pages for WPMS and remove unneeded menu items. + add_action( 'network_admin_menu', [ wp_mail_smtp()->get_admin(), 'add_admin_options_page' ] ); + + // Add the multisite plugin setting. + add_filter( 'wp_mail_smtp_admin_settings_tab_display', [ $this, 'add_multisite_network_wide_setting' ] ); + + // Filter plugin settings save process. + add_filter( 'wp_mail_smtp_options_set', [ $this, 'multisite_network_wide_filter_options_set' ] ); + + // Process the settings tab post submission data. + add_filter( 'wp_mail_smtp_settings_tab_process_post', [ $this, 'multisite_network_wide_process_settings_tab_post' ] ); + + // Filter the core plugin options population. + add_filter( 'wp_mail_smtp_populate_options', [ $this, 'filter_populate_options' ] ); + + // Filter the crypto key option. + add_filter( 'wp_mail_smtp_helpers_crypto_get_secret_key', [ $this, 'filter_crypto_secret_key' ] ); + + // Filter the WP::admin_url. + add_filter( 'wp_mail_smtp_admin_url', [ $this, 'filter_wp_admin_url' ], 10, 3 ); + + // Change Gmail auth redirect URL for network admin (WPMS). + add_filter( 'wp_mail_smtp_gmail_get_plugin_auth_url', [ $this, 'change_gmail_auth_redirect_url' ] ); + add_filter( 'wp_mail_smtp_pro_providers_gmail_auth_get_plugin_auth_url', [ $this, 'change_gmail_auth_redirect_url' ] ); + + // Change Outlook auth redirect URL for network admin (WPMS). + add_filter( 'wp_mail_smtp_outlook_get_plugin_auth_url', [ $this, 'change_outlook_auth_redirect_url' ] ); + add_filter( 'wp_mail_smtp_pro_providers_outlook_one_click_auth_get_plugin_auth_url', [ $this, 'change_outlook_oneclick_auth_redirect_url' ] ); + + // Remove other settings tabs if on network admin and the global settings options is disabled. + add_filter( 'wp_mail_smtp_admin_get_pages', [ $this, 'maybe_remove_other_setting_tabs' ] ); + + // Remove WP update nag on plugin pages. + add_action( 'admin_init', [ $this, 'remove_wp_update_nag' ] ); + + // Maybe change the admin bar menu item link for network-wide setting. + add_filter( 'wp_mail_smtp_admin_adminbarmenu_main_menu_href', [ $this, 'maybe_change_main_menu_admin_bar_menu_href' ] ); + + // Maybe disable notifications for sub-sites. + add_filter( 'wp_mail_smtp_admin_notifications_has_access', [ $this, 'maybe_disable_notifications' ] ); + + // Aggregated network-admin notice for subsites with email sending failures. + add_action( 'network_admin_notices', [ $this, 'render_aggregated_subsite_errors' ] ); + + // AJAX handler for the "Clear these errors" link in the aggregated notice. + add_action( 'wp_ajax_wp_mail_smtp_pro_multisite_clear_error_notices', [ $this, 'clear_aggregated_error_notices_callback' ] ); + + if ( is_network_admin() ) { + + // Enqueue assets. + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + + // Add scripts data. + add_filter( 'wp_mail_smtp_admin_area_enqueue_assets_scripts_data', [ $this, 'scripts_data' ], 10, 2 ); + } + + // Check if on network admin and subsite related request. + if ( $this->is_network_admin_subsite_related_request() ) { + + // Display network admin email logs site selector. + add_action( + 'wp_mail_smtp_pro_emails_logs_admin_archive_page_display_header', + [ $this, 'display_network_admin_site_selector' ] + ); + + // Display network admin email logs export site selector. + add_action( + 'wp_mail_smtp_admin_page_tools_export_display_header', + [ $this, 'display_network_admin_site_selector' ] + ); + + // Display network admin email reports site selector. + add_action( + 'wp_mail_smtp_admin_page_reports_reports_display_header', + [ $this, 'display_network_admin_site_selector' ] + ); + + // Handle network admin site selector. This option must be saved early. + $this->handle_network_admin_site_selector(); + + // Change network admin edit email log link from network to related site. + add_filter( + 'wp_mail_smtp_pro_emails_logs_admin_table_get_item_link', + [ $this, 'network_admin_emails_logs_item_link' ], + 10, + 3 + ); + + // Switch blog on email logs page. + add_action( 'wp-mail-smtp_page_wp-mail-smtp-logs', [ $this, 'switch_blog_to_selection' ], 0 ); + add_action( 'wp-mail-smtp_page_wp-mail-smtp-logs', 'restore_current_blog', PHP_INT_MAX ); + + // Switch blog on email logs export page. + add_action( 'wp-mail-smtp_page_wp-mail-smtp-tools', [ $this, 'switch_blog_to_selection' ], 0 ); + add_action( 'wp-mail-smtp_page_wp-mail-smtp-tools', 'restore_current_blog', PHP_INT_MAX ); + + // Switch blog on email reports page. + add_action( 'wp-mail-smtp_page_wp-mail-smtp-reports', [ $this, 'switch_blog_to_selection' ], 0 ); + add_action( 'wp-mail-smtp_page_wp-mail-smtp-reports', 'restore_current_blog', PHP_INT_MAX ); + + // Switch blog on admin actions. + add_action( 'admin_init', [ $this, 'switch_blog_to_selection' ], 0 ); + add_action( 'admin_init', 'restore_current_blog', PHP_INT_MAX ); + + // Switch blog on enqueue assets (mainly for js data objects preparation). + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'switch_blog_to_selection' ], 0 ); + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', 'restore_current_blog', PHP_INT_MAX ); + + // Filters options. + add_filter( 'wp_mail_smtp_populate_options', [ $this, 'network_admin_filter_options' ] ); + } + + // Handle network admin subsite related AJAX request. + if ( + WP::is_doing_self_ajax() && + ! empty( $_REQUEST['network_admin_subsite_related_request'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended + current_user_can( 'manage_network' ) + ) { + add_action( 'admin_init', [ $this, 'switch_blog_to_selection' ], 0 ); + } + + // Get network sites ajax handler. + add_action( 'wp_ajax_wp_mail_smtp_pro_get_sites_ajax', [ $this, 'get_sites_ajax' ] ); + + // Maybe remove plugin admin pages for network child sites. + $this->maybe_remove_plugin_admin_pages_from_child_sites(); + + // Fire DB migrations once per day on "init" action instead of "admin_init" for correct DB tables creation on subsites. + if ( + ! WP::in_wp_admin() && + ! WP::is_doing_ajax() && + ! is_network_admin() && + ! wp_doing_cron() && + ! get_transient( 'wp_mail_smtp_ms_init_migrations_daily' ) + ) { + $migrations = wp_mail_smtp()->get_migrations(); + + remove_action( 'admin_init', [ $migrations, 'init_migrations_on_request' ] ); + add_action( 'init', [ $migrations, 'init_migrations_on_request' ] ); + set_transient( 'wp_mail_smtp_ms_init_migrations_daily', true, DAY_IN_SECONDS ); + } + + add_filter( 'wp_mail_smtp_tasks_get_tasks', [ $this, 'unset_global_tasks_on_subsites' ] ); + } + + /** + * Enqueue required JS and CSS. + * + * @since 2.9.0 + */ + public function enqueue_assets() { + + if ( $this->is_network_admin_subsite_related_request() ) { + wp_enqueue_style( + 'wp-mail-smtp-admin-select2', + wp_mail_smtp()->assets_url . '/pro/css/vendor/select2.min.css', + [], + '4.0.13' + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-select2', + wp_mail_smtp()->assets_url . '/pro/js/vendor/select2.full.min.js', + [], + '4.0.13', + false + ); + wp_enqueue_script( + 'wp-mail-smtp-admin-select2-enhanced', + wp_mail_smtp()->assets_url . '/pro/js/smtp-pro-select2.min.js', + [ 'wp-mail-smtp-admin-select2' ], + WPMS_PLUGIN_VER, + false + ); + } + + wp_enqueue_script( + 'wp-mail-smtp-network-admin', + wp_mail_smtp()->assets_url . '/pro/js/smtp-pro-network-admin' . WP::asset_min() . '.js', + [ 'wp-mail-smtp-admin' ], + WPMS_PLUGIN_VER, + false + ); + } + + /** + * Plugin scripts data. + * + * @since 2.9.0 + * + * @param array $data Data. + * @param string $hook Current hook. + * + * @return array + */ + public function scripts_data( $data, $hook ) { + + if ( $this->is_network_admin_subsite_related_request() ) { + $data['select2'] = [ + 'i18n' => [ + 'error_loading' => esc_html__( 'The results could not be loaded.', 'wp-mail-smtp-pro' ), + 'loading_more' => esc_html__( 'Loading more results...', 'wp-mail-smtp-pro' ), + 'no_results' => esc_html__( 'No results found.', 'wp-mail-smtp-pro' ), + 'searching' => esc_html__( 'Searching...', 'wp-mail-smtp-pro' ), + ], + ]; + } + + $data['network_subsite_mode'] = $this->is_network_admin_subsite_related_request(); + + return $data; + } + + /** + * Add the multisite network_wide plugin setting + * HTML output. + * + * @since 2.2.0 + * + * @param string $settings_output Default HTML output for General settings tab. + * + * @return string + */ + public function add_multisite_network_wide_setting( $settings_output ) { + + // If not on multisite and not on main site, output normal plugin settings. + if ( ! is_multisite() || ! is_network_admin() ) { + return $settings_output; + } + + // Fetch fresh plugin options with updated general->network_wide setting. + $global_settings_enabled = (bool) Options::init()->get( 'general', 'network_wide' ); + + ob_start(); + ?> + +
    +
    +

    +
    +
    + + +
    +
    + +
    +
    + + id="wp-mail-smtp-setting-network-wide"> + + + +

    + +
    + +

    +
    +
    + + [ + 'network_wide' => 'true', + ], + 'mail' => [ + 'from_email_force' => false, + 'from_name_force' => false, + 'return_path' => true, + ], + 'smtp' => [ + 'autotls' => false, + 'auth' => false, + ], + ]; + + // Maybe revert the default checkbox values of plugin settings when network_wide is switched to enabled state. + if ( $data === $network_wide_enabled_first_time_settings ) { + $options = Options::init(); + $old_opt = $options->get_all(); + + // Set the old plugin values for the checkboxes that were set to false because they didn't exist in the POST request. + $data['mail']['from_email_force'] = isset( $old_opt['mail']['from_email_force'] ) ? + $old_opt['mail']['from_email_force'] : $data['mail']['from_email_force']; + $data['mail']['from_name_force'] = isset( $old_opt['mail']['from_name_force'] ) ? + $old_opt['mail']['from_name_force'] : $data['mail']['from_name_force']; + $data['mail']['return_path'] = isset( $old_opt['mail']['return_path'] ) ? + $old_opt['mail']['return_path'] : $data['mail']['return_path']; + $data['smtp']['autotls'] = isset( $old_opt['smtp']['autotls'] ) ? + $old_opt['smtp']['autotls'] : $data['smtp']['autotls']; + $data['smtp']['auth'] = isset( $old_opt['smtp']['auth'] ) ? + $old_opt['smtp']['auth'] : $data['smtp']['auth']; + } + + return $data; + } + + /** + * Remove plugin settings page from subsites if this is a multisite + * and the network_wide option is enabled in the network admin. + * + * @since 2.2.0 + */ + public function maybe_remove_plugin_admin_pages_from_child_sites() { + + if ( WP::use_global_plugin_settings() ) { + add_action( + 'admin_menu', + function () { + remove_submenu_page( Area::SLUG, Area::SLUG ); + + if ( ! wp_mail_smtp()->pro->get_logs()->is_enabled() ) { + remove_submenu_page( Area::SLUG, Area::SLUG . '-logs' ); + remove_submenu_page( Area::SLUG, Area::SLUG . '-reports' ); + } + + /* + * We can't remove top level page with function remove_menu_page(), because we need to keep it for visible sub-pages. + * So, we just restrict access to top level page. + */ + add_action( + 'load-' . wp_mail_smtp()->get_admin()->hook, + function () { + /* Translators: %s - Network admin email address. */ + wp_die( sprintf( esc_html__( 'Sorry, you are not allowed to access the WP Mail SMTP settings for individual network subsite. The network-wide setting is enabled, so the plugin settings are only accessible in the network admin dashboard. Please contact your network admin: %s.', 'wp-mail-smtp-pro' ), esc_html( get_site_option( 'admin_email' ) ) ), 403 ); + } + ); + }, + 100 + ); + } + } + + /** + * Filter the core plugin options population. + * Use the main site options if the network_wide is enabled. + * + * @since 2.2.0 + * + * @param array $options Default plugin options. + * + * @return array + */ + public function filter_populate_options( $options ) { + + if ( ! WP::use_global_plugin_settings() ) { + return $options; + } + + return get_blog_option( get_main_site_id(), Options::META_KEY, [] ); + } + + /** + * Filter plugin's wrapper for `admin_url`. + * Call `network_admin_url` if on network admin pages. + * + * @since 2.2.0 + * + * @param string $url The Admin URL link with optional path appended. + * @param string $path Optional path relative to the admin URL. + * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl(). + * 'http' or 'https' can be passed to force those schemes. + * + * @return string + */ + public function filter_wp_admin_url( $url, $path, $scheme ) { + + if ( ! is_multisite() || ! is_network_admin() ) { + return $url; + } + + return network_admin_url( $path, $scheme ); + } + + /** + * Change the Gmail auth redirect URL if this is a WP multisite and + * the global network setting is enabled. + * + * @since 2.2.0 + * + * @param string $url The default Gmail auth redirect URL. + * + * @return string + */ + public function change_gmail_auth_redirect_url( $url ) { + + if ( ! WP::use_global_plugin_settings() ) { + return $url; + } + + return add_query_arg( + [ + 'page' => Area::SLUG, + 'tab' => 'auth', + ], + WP::admin_url() + ); + } + + /** + * Change the Outlook auth redirect URL if this is a WP multisite and + * the global network setting is enabled. + * + * @since 2.5.2 + * + * @param string $url The default Outlook auth redirect URL. + * + * @return string + */ + public function change_outlook_auth_redirect_url( $url ) { + + if ( ! WP::use_global_plugin_settings() ) { + return $url; + } + + return network_admin_url(); + } + + /** + * Change the Outlook one-click auth redirect URL if this is a WP multisite and + * the global network setting is enabled. + * + * @since 4.7.0 + * + * @param string $url The default Outlook auth redirect URL. + * + * @return string + */ + public function change_outlook_oneclick_auth_redirect_url( $url ) { + + if ( ! WP::use_global_plugin_settings() ) { + return $url; + } + + return add_query_arg( + [ + 'page' => Area::SLUG, + 'tab' => 'auth', + ], + WP::admin_url() + ); + } + + /** + * Remove setting tabs (except "General" tab), if on network admin and + * the global plugin settings option is disabled. + * + * @since 2.2.0 + * + * @param array $tabs The default setting tabs. + * + * @return array + */ + public function maybe_remove_other_setting_tabs( $tabs ) { + + if ( + is_network_admin() && + ! WP::use_global_plugin_settings() && + ! empty( $tabs['settings'] ) + ) { + return [ 'settings' => $tabs['settings'] ]; + } + + return $tabs; + } + + /** + * Remove unneeded admin menu items for the WP Multisite. + * + * @deprecated 2.9.0 + * + * @since 2.2.0 + */ + public function remove_admin_menu_items() { + + _deprecated_function( __METHOD__, '2.9.0' ); + + remove_submenu_page( Area::SLUG, Area::SLUG . '-logs' ); + } + + /** + * Remove the WP update nag on plugin pages. + * + * @since 2.2.0 + */ + public function remove_wp_update_nag() { + + if ( wp_mail_smtp()->get_admin()->is_admin_page() ) { + remove_action( 'network_admin_notices', 'update_nag', 3 ); + } + } + + /** + * Change the main menu item href in the admin bar menu if the network-wide setting is enabled. + * + * @since 2.3.0 + * + * @param string $href The default href of the main menu item in the admin bar menu. + * + * @return string + */ + public function maybe_change_main_menu_admin_bar_menu_href( $href ) { + + if ( WP::use_global_plugin_settings() ) { + return add_query_arg( 'page', Area::SLUG, network_admin_url( 'admin.php' ) ); + } + + return $href; + } + + /** + * Maybe disable notifications for multisite sub-sites. + * + * @since 2.3.0 + * + * @param bool $access The default notifications access state. + * + * @return bool + */ + public function maybe_disable_notifications( $access ) { + + if ( + WP::use_global_plugin_settings() && + ! is_network_admin() + ) { + $access = false; + } + + return $access; + } + + /** + * Use the global plugin settings crypto secret key if network-wide setting is enabled. + * + * @since 2.5.3 + * + * @param string $key Default crypto secret key. + * + * @return string + */ + public function filter_crypto_secret_key( $key ) { + if ( WP::use_global_plugin_settings() ) { + return get_blog_option( get_main_site_id(), 'wp_mail_smtp_mail_key' ); + } + + return $key; + } + + /** + * Whether network admin subsite related request or not. + * + * @since 2.9.0 + * + * @return bool + */ + public function is_network_admin_subsite_related_request() { + + $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + if ( + is_network_admin() && + ( + wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) || + ( wp_mail_smtp()->get_admin()->is_admin_page( 'tools' ) && $current_tab === 'export' ) || + ( wp_mail_smtp()->get_admin()->is_admin_page( 'reports' ) && ( $current_tab === '' || $current_tab === 'reports' ) ) + ) + ) { + return true; + } + + return false; + } + + /** + * Display network admin site selector. + * + * @since 2.9.0 + */ + public function display_network_admin_site_selector() { + + $form_action = wp_mail_smtp()->get_admin()->get_admin_page_url(); + $current_action = current_action(); + + if ( $current_action === 'wp_mail_smtp_pro_emails_logs_admin_archive_page_display_header' ) { + $form_action = wp_mail_smtp()->get_pro()->get_logs()->get_admin_page_url(); + } elseif ( $current_action === 'wp_mail_smtp_admin_page_tools_export_display_header' ) { + $form_action = wp_mail_smtp()->get_admin()->get_parent_pages()['tools']->get_link( 'export' ); + } elseif ( $current_action === 'wp_mail_smtp_admin_page_reports_reports_display_header' ) { + $form_action = wp_mail_smtp()->get_admin()->get_parent_pages()['reports']->get_link( 'reports' ); + } + + global $blog_id; + ?> +
    + + +
    + switch_blog_to_selection(); + $options = get_option( Options::META_KEY, [] ); + restore_current_blog(); + + return $options; + } + + /** + * Filters network admin ajax url. + * + * @deprecated 3.1.0 + * + * @since 2.9.0 + * + * @param array $script_data Data. + * @param string $hook Current hook. + * + * @return array + */ + public function network_admin_filter_ajax_url( $script_data, $hook ) { + + _deprecated_function( __METHOD__, '3.1.0' ); + + $this->switch_blog_to_selection(); + $script_data['ajax_url'] = admin_url( 'admin-ajax.php' ); + restore_current_blog(); + + return $script_data; + } + + /** + * Change network admin edit email log link from network to related site. + * + * @since 2.9.0 + * + * @param string $url Item link. + * @param Email $item Email instance. + * @param string $link Link type. + * + * @return string + */ + public function network_admin_emails_logs_item_link( $url, $item, $link ) { + + if ( $link === 'edit' ) { + $url = add_query_arg( + [ + 'page' => Area::SLUG . '-logs', + 'email_id' => $item->get_id(), + 'mode' => 'view', + ], + admin_url( 'admin.php' ) + ); + } + + return $url; + } + + /** + * Get network sites ajax handler. + * + * @since 2.9.0 + */ + public function get_sites_ajax() { + + // Run a security check. + check_ajax_referer( 'wp-mail-smtp-admin', 'nonce' ); + + if ( ! current_user_can( 'manage_network' ) ) { + wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) ); + } + + $page = isset( $_GET['page'] ) ? intval( $_GET['page'] ) : 1; + $term = isset( $_GET['term'] ) ? sanitize_text_field( wp_unslash( $_GET['term'] ) ) : ''; + + $sites_per_page = 10; + + $query_args = [ + 'fields' => 'ids', + 'number' => $sites_per_page, + 'search' => $term, + 'offset' => $sites_per_page * ( $page - 1 ), + 'no_found_rows' => false, + ]; + + $query = new \WP_Site_Query(); + + $results = array_map( + function ( $site_id ) { + + return [ + 'id' => intval( $site_id ), + 'text' => esc_html( get_blog_details( $site_id )->blogname ), + ]; + }, + $query->query( $query_args ) + ); + + wp_send_json( + [ + 'results' => $results, + 'pagination' => [ + 'more' => $page < $query->max_num_pages, + ], + ] + ); + } + + /** + * Unset global tasks that are not needed for subsites + * if the network-wide option is enabled. + * + * @since 4.4.0 + * + * @param array $tasks Tasks. + * + * @return array + */ + public function unset_global_tasks_on_subsites( $tasks ) { + + if ( WP::use_global_plugin_settings() && ! is_main_site() ) { + $tasks_to_remove = [ + NotificationsUpdateTask::class, + LicenseCheckTask::class, + SendUsageTask::class, + ]; + + $tasks = array_filter( + $tasks, + function ( $task ) use ( $tasks_to_remove ) { + return ! in_array( $task, $tasks_to_remove, true ); + } + ); + } + + return $tasks; + } + + /** + * Render the aggregated network-admin notice listing every subsite whose + * primary connection has a failed email sending record. + * + * Scope is primary-connection-only — additional connection errors are not + * surfaced in the network admin view. + * + * @since 4.9.0 + */ + public function render_aggregated_subsite_errors() { + + if ( ! WP::use_global_plugin_settings() || ! is_network_admin() ) { + return; + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + return; + } + + $errors = $this->collect_subsite_primary_errors(); + + if ( empty( $errors ) ) { + return; + } + + $rows = []; + + foreach ( $errors as $error ) { + $rows[] = sprintf( + '%1$s - %2$s
    %3$s: %4$s', + esc_html( $error['name'] ), + esc_url( $error['url'] ), + esc_html( $error['mailer_title'] ), + esc_html( $error['error_message'] ) + ); + } + + ?> +
    +

    + Heads up! The last email your site attempted to send was unsuccessful.', 'wp-mail-smtp-pro' ), + [ 'strong' => [] ] + ); + ?> +

    +
    +
    +
    +

    + + + +

    +
    + blog_id, EmailSendingDebug::OPTION_KEY, [] ); + $record = is_array( $option ) && isset( $option['primary'] ) ? $option['primary'] : null; + + if ( ! is_array( $record ) ) { + continue; + } + + if ( ! isset( $record['status'], $record['context'] ) ) { + continue; + } + + if ( $record['status'] !== 'failed' || $record['context'] !== 'regular' ) { + continue; + } + + $mailer_slug = isset( $record['mailer'] ) ? (string) $record['mailer'] : ''; + + switch_to_blog( $site->blog_id ); + + $site_details = get_blog_details( $site->blog_id ); + $site_name = ( $site_details && ! empty( $site_details->blogname ) ) ? $site_details->blogname : $site->domain; + $site_url = get_admin_url( $site->blog_id ); + + $mailer_title = ''; + + if ( $mailer_slug !== '' ) { + $provider = wp_mail_smtp()->get_providers()->get_options( $mailer_slug ); + + if ( $provider !== null ) { + $mailer_title = $provider->get_title(); + } + } + + if ( $mailer_title === '' ) { + $mailer_title = $mailer_slug; + } + + restore_current_blog(); + + $rows[] = [ + 'blog_id' => (int) $site->blog_id, + 'name' => $site_name, + 'url' => $site_url, + 'mailer_title' => $mailer_title, + 'error_message' => isset( $record['error_message'] ) ? (string) $record['error_message'] : '', + ]; + } + + return $rows; + } + + /** + * AJAX handler: clear the primary-connection failure record on every subsite. + * + * Only the `primary` key is unset on each subsite's EmailSendingDebug + * option — additional-connection records are preserved so their per-site + * banners remain intact. + * + * @since 4.9.0 + */ + public function clear_aggregated_error_notices_callback() { + + check_ajax_referer( 'wp-mail-smtp-pro-admin' ); + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_global_options() ) ) { + wp_send_json_error(); + } + + if ( ! WP::use_global_plugin_settings() || ! is_multisite() ) { + wp_send_json_error(); + } + + foreach ( get_sites() as $site ) { + $option = get_blog_option( $site->blog_id, EmailSendingDebug::OPTION_KEY, [] ); + + if ( ! is_array( $option ) || ! isset( $option['primary'] ) ) { + continue; + } + + unset( $option['primary'] ); + + update_blog_option( $site->blog_id, EmailSendingDebug::OPTION_KEY, $option ); + } + + wp_send_json_success(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Pro.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Pro.php new file mode 100644 index 00000000..a2cb117a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Pro.php @@ -0,0 +1,1344 @@ +assets_url = wp_mail_smtp()->assets_url . '/pro'; + + $this->init(); + } + + /** + * Initialize the main Pro logic. + * + * @since 1.5.0 + * @since 3.11.0 Init SiteHealth module only in admin context. + */ + public function init() { + + // Load translations just in case. + load_plugin_textdomain( 'wp-mail-smtp-pro', false, plugin_basename( wp_mail_smtp()->plugin_path ) . '/assets/pro/languages' ); + + add_filter( 'http_request_args', [ $this, 'request_lite_translations' ], 10, 2 ); + + // Add the action links to a plugin on Plugins page. + add_filter( 'plugin_action_links_' . plugin_basename( WPMS_PLUGIN_FILE ), [ $this, 'add_plugin_action_link' ], 15, 1 ); + + // Register Action Scheduler tasks. + add_filter( 'wp_mail_smtp_tasks_get_tasks', [ $this, 'get_tasks' ] ); + + // Register DB migrations. + add_filter( 'wp_mail_smtp_migrations_get_migrations', [ $this, 'get_migrations' ] ); + + // Add Pro specific DB tables to the list of custom DB tables. + add_filter( 'wp_mail_smtp_core_get_custom_db_tables', [ $this, 'add_pro_specific_custom_db_tables' ] ); + + // Display custom auth notices based on the error/success codes. + add_action( 'admin_init', [ $this, 'display_custom_auth_notices' ] ); + + // Disable the admin education notice-bar. + add_filter( 'wp_mail_smtp_admin_education_notice_bar', '__return_false' ); + + add_filter( 'plugin_action_links_wp-mail-smtp/wp_mail_smtp.php', [ $this, 'replace_action_links' ] ); + + // Alias WPMailArgs class. + class_alias( 'WPMailSMTP\WPMailArgs', 'WPMailSMTP\Pro\WPMailArgs' ); + + $this->get_multisite()->init(); + $this->get_control(); + $this->get_logs(); + $this->get_providers(); + $this->get_license(); + $this->get_additional_connections(); + $this->get_backup_connections(); + $this->get_importers(); + $this->get_translations(); + $this->get_rate_limiting(); + $this->get_product_api(); + + if ( is_admin() ) { + $this->get_site_health()->init(); + } + + if ( current_user_can( $this->get_logs()->get_manage_capability() ) ) { + $this->get_logs_export()->init(); + } + + // Initialize alerts. + ( new Alerts() )->hooks(); + + // Initialize smart routing. + ( new SmartRouting() )->hooks(); + + // Initialize Plugins List. + ( new PluginsList() )->hooks(); + + // Initialize test email. + ( new TestEmail() )->hooks(); + + // Initialize admin area. + ( new Area() )->hooks(); + + // Initialize upgrades. + ( new Upgrade() )->hooks(); + + // Initialize WP-CLI options args registry. + ( new WPCLIOptionsRegistry() )->hooks(); + + // Usage tracking hooks. + add_filter( 'wp_mail_smtp_usage_tracking_get_data', [ $this, 'usage_tracking_get_data' ] ); + add_filter( 'wp_mail_smtp_admin_pages_misc_tab_show_usage_tracking_setting', '__return_false' ); + add_filter( 'wp_mail_smtp_usage_tracking_is_enabled', '__return_true' ); + + // Setup wizard hooks. + add_filter( 'wp_mail_smtp_admin_setup_wizard_prepare_mailer_options', [ $this, 'setup_wizard_prepare_mailer_options' ] ); + add_action( 'wp_mail_smtp_admin_setup_wizard_get_oauth_url', [ $this, 'prepare_oauth_url_redirect' ], 10, 2 ); + add_action( 'wp_mail_smtp_admin_setup_wizard_license_exists', [ $this, 'does_license_key_exist' ] ); + add_action( 'wp_ajax_wp_mail_smtp_vue_get_amazon_ses_identities', [ $this, 'get_amazon_ses_identities' ] ); + add_action( 'wp_ajax_wp_mail_smtp_vue_amazon_ses_identity_registration', [ $this, 'amazon_ses_identity_registration' ] ); + add_action( 'wp_ajax_wp_mail_smtp_vue_verify_license_key', [ $this, 'verify_license_key' ] ); + + // Maybe cancel Pro recurring AS tasks for PHP 8 compatibility in v2.6. + add_filter( 'wp_mail_smtp_migration_cancel_recurring_tasks', [ $this, 'maybe_cancel_recurring_as_tasks_for_v26' ] ); + + // Use the Pro Dashboard Widget. + add_filter( + 'wp_mail_smtp_core_get_dashboard_widget', + function () { + return DashboardWidget::class; + } + ); + + // Use the Pro Reports. + add_filter( + 'wp_mail_smtp_core_get_reports', + function () { + return Reports::class; + } + ); + + // Use the Pro DBRepair. + add_filter( + 'wp_mail_smtp_core_get_db_repair', + function () { + return DBRepair::class; + } + ); + + // Use the Pro ConnectionsManager. + add_filter( + 'wp_mail_smtp_core_get_connections_manager', + function () { + return ConnectionsManager::class; + } + ); + + // Use the Pro MailCatcher. + add_filter( + 'wp_mail_smtp_core_generate_mail_catcher', + function () { + return version_compare( get_bloginfo( 'version' ), '5.5-alpha', '<' ) ? MailCatcher::class : MailCatcherV6::class; + } + ); + + // Fix `Options::array_merge_recursive` numeric keys array duplicates. + add_filter( + 'wp_mail_smtp_options_set', + function ( $options ) { + foreach ( array_keys( ( new AlertsLoader() )->get_providers() ) as $alert ) { + if ( isset( $options["alert_$alert"]['connections'] ) ) { + $options["alert_$alert"]['connections'] = array_unique( + $options["alert_$alert"]['connections'], + SORT_REGULAR + ); + } + } + + if ( isset( $options['outlook']['scopes'] ) ) { + $options['outlook']['scopes'] = array_unique( $options['outlook']['scopes'], SORT_REGULAR ); + } + + return $options; + } + ); + + wp_mail_smtp()->get_abilities_registrar()->add( + [ + ListEmailLogsAbility::class, + GetEmailLogAbility::class, + GetEmailStatsAbility::class, + ] + ); + } + + /** + * Load the Control functionality. + * + * @since 1.5.0 + * + * @return Emails\Control\Control + */ + public function get_control() { + + static $control; + + if ( ! isset( $control ) ) { + $control = apply_filters( 'wp_mail_smtp_pro_get_control', new Emails\Control\Control() ); + + if ( method_exists( $control, 'init' ) ) { + $control->init(); + } + } + + return $control; + } + + /** + * Load the Logs functionality. + * + * @since 1.5.0 + * + * @return Emails\Logs\Logs + */ + public function get_logs() { + + static $logs; + + if ( ! isset( $logs ) ) { + $logs = apply_filters( 'wp_mail_smtp_pro_get_logs', new Emails\Logs\Logs() ); + + if ( method_exists( $logs, 'init' ) ) { + $logs->init(); + } + } + + return $logs; + } + + /** + * Load the Logs export functionality. + * + * @since 2.9.0 + * + * @return Emails\Logs\Export\Export + */ + public function get_logs_export() { + + static $logs_export; + + if ( ! isset( $logs_export ) ) { + $logs_export = apply_filters( 'wp_mail_smtp_pro_get_logs_export', new Emails\Logs\Export\Export() ); + } + + return $logs_export; + } + + /** + * Load the new Providers functionality. + * + * @since 1.5.0 + * + * @return \WPMailSMTP\Pro\Providers\Providers + */ + public function get_providers() { + + static $providers; + + if ( ! isset( $providers ) ) { + $providers = apply_filters( 'wp_mail_smtp_pro_get_providers', new Providers\Providers() ); + + if ( method_exists( $providers, 'init' ) ) { + $providers->init(); + } + } + + return $providers; + } + + /** + * Load the new License functionality. + * + * @since 1.5.0 + * + * @return \WPMailSMTP\Pro\License\License + */ + public function get_license() { + + static $license; + + if ( ! isset( $license ) ) { + $license = apply_filters( 'wp_mail_smtp_pro_get_license', new License\License() ); + + if ( method_exists( $license, 'init' ) ) { + $license->init(); + } + } + + return $license; + } + + /** + * Load the Site Health functionality. + * + * @since 1.9.0 + * + * @return \WPMailSMTP\Pro\SiteHealth + */ + public function get_site_health() { + + static $site_health; + + if ( ! isset( $site_health ) ) { + $site_health = apply_filters( 'wp_mail_smtp_pro_get_site_health', new SiteHealth() ); + } + + return $site_health; + } + + /** + * Get the Multisite object. + * + * @since 2.2.0 + * + * @return Multisite + */ + public function get_multisite() { + + static $multisite; + + if ( ! isset( $multisite ) ) { + $multisite = apply_filters( 'wp_mail_smtp_pro_get_multisite', new Multisite() ); + } + + return $multisite; + } + + /** + * Get the DashboardWidget object. + * + * @deprecated 2.9.0 + * + * @since 2.7.0 + * + * @return DashboardWidget + */ + public function get_dashboard_widget() { + + _deprecated_function( __METHOD__, '2.9.0' ); + + static $dashboard_widget; + + if ( ! isset( $dashboard_widget ) ) { + $dashboard_widget = apply_filters( 'wp_mail_smtp_pro_get_dashboard_widget', new DashboardWidget() ); + } + + return $dashboard_widget; + } + + /** + * Load the Additional Connections functionality. + * + * @since 3.7.0 + * + * @return AdditionalConnections + */ + public function get_additional_connections() { + + static $additional_connections; + + if ( ! isset( $additional_connections ) ) { + + /** + * Filter the Additional Connections object. + * + * @since 3.7.0 + * + * @param AdditionalConnections $additional_connections The Additional Connections object. + */ + $additional_connections = apply_filters( 'wp_mail_smtp_pro_get_get_additional_connections', new AdditionalConnections() ); + + if ( method_exists( $additional_connections, 'hooks' ) ) { + $additional_connections->hooks(); + } + } + + return $additional_connections; + } + + /** + * Load the Backup Connections functionality. + * + * @since 3.7.0 + * + * @return BackupConnections + */ + public function get_backup_connections() { + + static $backup_connections; + + if ( ! isset( $backup_connections ) ) { + + /** + * Filter the Backup Connections object. + * + * @since 3.7.0 + * + * @param BackupConnections $backup_connections The Backup Connections object. + */ + $backup_connections = apply_filters( 'wp_mail_smtp_pro_get_get_backup_connections', new BackupConnections() ); + + if ( method_exists( $backup_connections, 'hooks' ) ) { + $backup_connections->hooks(); + } + } + + return $backup_connections; + } + + /** + * Load the Importers functionality. + * + * @since 3.8.0 + * + * @return Importers + */ + public function get_importers() { + + static $importers; + + if ( ! isset( $importers ) ) { + /** + * Filter the Importers object. + * + * @since 3.8.0 + * + * @param Importers $importers The Importers object. + */ + $importers = apply_filters( 'wp_mail_smtp_pro_get_importers', new Importers() ); + + if ( method_exists( $importers, 'init' ) ) { + $importers->init(); + } + } + + return $importers; + } + + /** + * Load the Pro Translations functionality. + * + * @since 3.9.0 + * + * @return Translations + */ + public function get_translations() { + + static $translations; + + if ( ! isset( $translations ) ) { + /** + * Filter the Translations object. + * + * @since 3.9.0 + * + * @param Translations $translations The Translations object. + */ + $translations = apply_filters( 'wp_mail_smtp_pro_get_translations', new Translations() ); + + if ( method_exists( $translations, 'hooks' ) ) { + $translations->hooks(); + } + } + + return $translations; + } + + /** + * Adds WP Mail SMTP (Lite) to the update checklist of installed plugins, to check for new translations. + * + * @since 1.6.0 + * + * @param array $args HTTP Request arguments to modify. + * @param string $url The HTTP request URI that is executed. + * + * @return array The modified Request arguments to use in the update request. + */ + public function request_lite_translations( $args, $url ) { + + // Only do something on upgrade requests. + if ( strpos( $url, 'api.wordpress.org/plugins/update-check' ) === false ) { + return $args; + } + + // Bail if plugins list is missing. + if ( empty( $args['body']['plugins'] ) ) { + return $args; + } + + /* + * If WP Mail SMTP is already in the list, don't add it again. + * + * Checking this by name because the install path is not guaranteed. + * The capitalized json data defines the array keys, therefore we need to check and define these as such. + */ + $plugins = json_decode( $args['body']['plugins'], true ); + + // Bail if plugin list can't be decoded or plugins list is missing. + if ( $plugins === null || ! isset( $plugins['plugins'] ) ) { + return $args; + } + + foreach ( $plugins['plugins'] as $slug => $data ) { + if ( isset( $data['Name'] ) && $data['Name'] === 'WP Mail SMTP' ) { + return $args; + } + } + + // Pro plugin (current plugin) key in $plugins['plugins']. + $pro_plugin_key = plugin_basename( wp_mail_smtp()->plugin_path ) . '/wp_mail_smtp.php'; + + // The pro plugin key has to exist for the code below to work. + if ( ! isset( $plugins['plugins'][ $pro_plugin_key ] ) ) { + return $args; + } + + /* + * Add an entry to the list that matches the WordPress.org slug for WP Mail SMTP Lite. + * + * This entry is based on the currently present data from this plugin, to make sure the version and textdomain + * settings are as expected. Take care of the capitalized array key as before. + */ + $plugins['plugins']['wp-mail-smtp/wp_mail_smtp.php'] = $plugins['plugins'][ $pro_plugin_key ]; + // Override the name of the plugin. + $plugins['plugins']['wp-mail-smtp/wp_mail_smtp.php']['Name'] = 'WP Mail SMTP'; + // Override the version of the plugin to prevent increasing the update count. + $plugins['plugins']['wp-mail-smtp/wp_mail_smtp.php']['Version'] = '9999.0'; + + // Overwrite the plugins argument in the body to be sent in the upgrade request. + $args['body']['plugins'] = wp_json_encode( $plugins ); + + return $args; + } + + /** + * Get the list of all custom DB tables that should be present in the DB. + * + * @deprecated 3.0.0 + * + * @since 1.9.0 + * + * @return array List of table names. + */ + public function get_custom_db_tables() { + + _deprecated_function( __METHOD__, '3.0.0', '\WPMailSMTP\Core::get_custom_db_tables' ); + + return [ + Logs::get_table_name(), + Attachments::get_email_attachments_table_name(), + Attachments::get_attachment_files_table_name(), + Tracking::get_events_table_name(), + Tracking::get_links_table_name(), + ]; + } + + /** + * Add Pro specific custom DB tables to the list of all plugin's custom DB tables. + * + * @since 2.1.2 + * + * @param array $tables A list of existing custom tables. + * + * @return array + */ + public function add_pro_specific_custom_db_tables( $tables ) { + + $pro_tables = []; + + if ( $this->get_logs()->is_enabled() ) { + $pro_tables[] = Logs::get_table_name(); + } + + if ( $this->get_logs()->is_enabled_save_attachments() ) { + $pro_tables[] = Attachments::get_email_attachments_table_name(); + $pro_tables[] = Attachments::get_attachment_files_table_name(); + } + + if ( $this->get_logs()->is_enabled_tracking() ) { + $pro_tables[] = Tracking::get_events_table_name(); + $pro_tables[] = Tracking::get_links_table_name(); + } + + return array_merge( $tables, $pro_tables ); + } + + /** + * Add plugin action links on Plugins page. + * + * @since 2.0.0 + * + * @param array $links Existing plugin action links. + * + * @return array + */ + public function add_plugin_action_link( $links ) { + + /* + * Add "Settings" links in almost all cases, except if in Multisite setup + * and network-wide option is enabled. + */ + if ( ! WP::use_global_plugin_settings() ) { + $custom['wp-mail-smtp-settings'] = sprintf( + '%s', + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ), + esc_attr__( 'Go to WP Mail SMTP Settings page', 'wp-mail-smtp-pro' ), + esc_html__( 'Settings', 'wp-mail-smtp-pro' ) + ); + } + + $custom['wp-mail-smtp-support'] = sprintf( + '%3$s', + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/account/support/', [ 'medium' => 'all-plugins', 'content' => 'Support' ] ) ), + esc_attr__( 'Go to WPMailSMTP.com support page', 'wp-mail-smtp-pro' ), + esc_html__( 'Support', 'wp-mail-smtp-pro' ) + ); + + $custom['wp-mail-smtp-docs'] = sprintf( + '%3$s', + // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/', [ 'medium' => 'all-plugins', 'content' => 'Documentation' ] ) ), + esc_attr__( 'Go to WPMailSMTP.com documentation page', 'wp-mail-smtp-pro' ), + esc_html__( 'Docs', 'wp-mail-smtp-pro' ) + ); + + return array_merge( $custom, (array) $links ); + } + + /** + * Register the pro version Action Scheduler tasks. + * + * @since 2.1.0 + * @since 2.1.2 Add EmailLogMigration4 task. + * @since 2.2.0 Add EmailLogMigration5 task. + * @since 3.8.0 Add EmailLogMigration11 task. + * + * @param array $tasks Action Scheduler tasks to be registered. + * + * @return array + */ + public function get_tasks( $tasks ) { + + // phpcs:disable WPForms.PHP.BackSlash.UseShortSyntax + return array_merge( + $tasks, + [ + \WPMailSMTP\Pro\Tasks\EmailLogCleanupTask::class, + \WPMailSMTP\Pro\Tasks\Migrations\EmailLogMigration4::class, + \WPMailSMTP\Pro\Tasks\Migrations\EmailLogMigration5::class, + \WPMailSMTP\Pro\Tasks\Migrations\EmailLogMigration11::class, + \WPMailSMTP\Pro\Tasks\Logs\Sendlayer\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\MailerSend\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\Mailgun\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\Sendinblue\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\SMTPcom\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\Postmark\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\SparkPost\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\SMTP2GO\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\Mailjet\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\ElasticEmail\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\Mandrill\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\Resend\VerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\Logs\ExportCleanupTask::class, + \WPMailSMTP\Pro\Tasks\Logs\ResendTask::class, + \WPMailSMTP\Pro\Tasks\Logs\BulkVerifySentStatusTask::class, + \WPMailSMTP\Pro\Tasks\NotifierTask::class, + \WPMailSMTP\Pro\Tasks\LicenseCheckTask::class, + ] + ); + // phpcs:enable WPForms.PHP.BackSlash.UseShortSyntax + } + + /** + * Register DB migrations. + * + * @since 3.0.0 + * + * @param array $migrations Migrations classes. + * + * @return array + */ + public function get_migrations( $migrations ) { + + // phpcs:disable WPForms.PHP.BackSlash.UseShortSyntax + return array_merge( + $migrations, + [ + Migration::class, + \WPMailSMTP\Pro\Emails\Logs\Migration::class, + \WPMailSMTP\Pro\Emails\Logs\Tracking\Migration::class, + \WPMailSMTP\Pro\Emails\Logs\Attachments\Migration::class, + ] + ); + // phpcs:enable WPForms.PHP.BackSlash.UseShortSyntax + } + + /** + * Display custom auth notices for pro mailers based on the error/success codes. + * + * @since 2.3.0 + */ + public function display_custom_auth_notices() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + // phpcs:disable WordPress.Security.NonceVerification.Recommended + $error = isset( $_GET['error'] ) ? sanitize_key( $_GET['error'] ) : ''; + $success = isset( $_GET['success'] ) ? sanitize_key( $_GET['success'] ) : ''; + // phpcs:enable WordPress.Security.NonceVerification.Recommended + + if ( empty( $error ) && empty( $success ) ) { + return; + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + return; + } + + switch ( $error ) { + case 'oauth_invalid_connection': + WP::add_admin_notice_with_debug( + esc_html__( 'There was an error while processing the authentication request. The connection was not found. Please try again.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_ERROR + ); + break; + + case 'microsoft_no_code': + case 'zoho_no_code': + WP::add_admin_notice_with_debug( + esc_html__( 'There was an error while processing the authentication request. The authorization code is missing. Please try again.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_ERROR + ); + break; + + case 'microsoft_invalid_nonce': + case 'zoho_invalid_nonce': + WP::add_admin_notice_with_debug( + esc_html__( 'There was an error while processing the authentication request. The nonce is invalid. Please try again.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_ERROR + ); + break; + + case 'microsoft_unsuccessful_oauth': + WP::add_admin_notice_with_debug( + esc_html__( 'There was an error while processing the authentication request. Please recheck your Client ID and Client Secret and try again.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_ERROR + ); + break; + + case 'zoho_no_clients': + WP::add_admin_notice_with_debug( + esc_html__( 'There was an error while processing the authentication request. Please make sure that you have Client ID and Client Secret both valid and saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_ERROR + ); + break; + + case 'zoho_access_denied': + WP::add_admin_notice_with_debug( + /* translators: %s - error code, returned by Zoho API. */ + sprintf( esc_html__( 'There was an error while processing the authentication request: %s. Please try again.', 'wp-mail-smtp-pro' ), '' . 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 ) { + + ?> +
    + pagination( $which ); ?> + +
    +
    + Name columns data. + */ + public function get_columns_for_js() { + + $columns = $this->get_columns(); + $prepared_columns = []; + + foreach ( $columns as $key => $label ) { + $prepared_columns[] = [ + 'label' => $label, + 'key' => $key, + ]; + } + + return $prepared_columns; + } + + /** + * Get all identity object's data for use in JS. + * + * @since 2.6.0 + * + * @return array Array of arrays of Identity object's data. + */ + public function get_items_for_js() { + + $identities = $this->get_items(); + $prepared_identities = []; + + foreach ( $identities as $identity ) { + $prepared_identities[] = $identity->get_all(); + } + + return $prepared_identities; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Identity.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Identity.php new file mode 100644 index 00000000..4fcb4d61 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Identity.php @@ -0,0 +1,258 @@ +value = $value; + $this->type = $type; + $this->status = $status; + $this->txt_token = $txt_token; + $this->dkim_tokens = $dkim_tokens; + } + + /** + * Get the identity value (email address or domain name). + * + * @since 2.4.0 + * + * @return string + */ + public function get_value() { + + return $this->value; + } + + /** + * Get the identity type (email or domain). + * + * @since 2.4.0 + * + * @return string + */ + public function get_type() { + + return $this->type; + } + + /** + * Get the identity status. + * + * @since 2.4.0 + * + * @return string + */ + public function get_status() { + + return $this->status; + } + + /** + * Get the domain identity DNS txt token. + * + * @since 2.4.0 + * + * @return string|null + */ + public function get_txt_token() { + + if ( $this->type !== self::DOMAIN_TYPE ) { + return null; + } + + return $this->txt_token; + } + + /** + * Get the domain identity DNS DKIM tokens. + * + * @since 3.3.0 + * + * @return array|null + */ + public function get_dkim_tokens() { + + if ( $this->type !== self::DOMAIN_TYPE ) { + return null; + } + + return $this->dkim_tokens; + } + + /** + * Check if this identity is a domain. + * + * @since 2.4.0 + * + * @return bool + */ + public function is_domain() { + + return $this->get_type() === self::DOMAIN_TYPE; + } + + /** + * Check if this identity is a email. + * + * @since 2.4.0 + * + * @return bool + */ + public function is_email() { + + return $this->get_type() === self::EMAIL_TYPE; + } + + /** + * Get the identity action links. + * + * HTML output. + * + * @since 2.4.0 + * + * @return string + */ + public function get_action_links() { + + $delete_link_title = $this->is_domain() ? + esc_html__( 'Delete this domain', 'wp-mail-smtp-pro' ) : + esc_html__( 'Delete this email', 'wp-mail-smtp-pro' ); + + ob_start(); + ?> + + is_domain() && $this->get_status() === 'Pending' ) : ?> + + + | + is_email() && $this->get_status() === 'Pending' ) : ?> + + + | + + + + + + + $this->get_value(), + 'type' => $this->get_type(), + 'status' => $this->get_status(), + 'txt_token' => $this->get_txt_token(), + 'dkim_tokens' => $this->get_dkim_tokens(), + 'actions' => [ + 'view_dns' => $this->is_domain() && $this->get_status() === 'Pending', + 'resend' => $this->is_email() && $this->get_status() === 'Pending', + 'delete' => true, + ], + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Mailer.php new file mode 100644 index 00000000..07e228ae --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Providers/AmazonSES/Mailer.php @@ -0,0 +1,192 @@ +is_valid_phpmailer( $phpmailer ) ) { + return; + } + + $this->phpmailer = $phpmailer; + } + + /** + * Use AWS SDK to send emails. + * + * @since 1.5.0 + * @since 2.4.0 Switch to AWS SDK. + * @since 3.5.0 Switch to AWS SDK `SesV2Client`. + */ + public function send() { + + // Prepare the auth and client objects. + $auth = new Auth( $this->connection ); + + $data = [ + 'Content' => [ + 'Raw' => [ + 'Data' => $this->phpmailer->getSentMIMEMessage(), + ], + ], + ]; + + /** + * Filters Amazon SES email sending arguments. + * + * @since 4.5.0 + * + * @param array $data The email sending arguments. + * @param Mailer $mailer The Amazon SES mailer instance. + */ + $data = apply_filters( 'wp_mail_smtp_pro_providers_amazon_ses_mailer_send_args', $data, $this ); + + try { + $response = $auth->get_client( 'v2' )->sendEmail( $data ); + + DebugEvents::add_debug( + esc_html__( 'An email request was sent to the Amazon SES API.', 'wp-mail-smtp-pro' ) + ); + + $this->process_response( $response ); + } catch ( \Exception $e ) { + $this->error_message = $e->getMessage(); + + // Extract AWS error code if available (e.g. MessageRejected, AccessDeniedException). + if ( method_exists( $e, 'getAwsErrorCode' ) ) { + $this->error_code = $e->getAwsErrorCode(); + } + } + } + + /** + * Check the correct output of the response. + * + * @since 1.5.0 + * + * @param WPMailSMTP\Vendor\Aws\Result $response Response object from AWS SDK request. + */ + protected function process_response( $response ) { + + $this->response = $response; + + $error = ''; + + if ( empty( $this->response ) ) { + $error = esc_html__( 'Amazon SES request failed (empty response).', 'wp-mail-smtp-pro' ); + } + + if ( is_object( $this->response ) && empty( $this->response->get( 'MessageId' ) ) ) { + $error = esc_html__( 'Something went wrong. Please try again.', 'wp-mail-smtp-pro' ); + } + + // Save the error text. + if ( ! empty( $error ) ) { + $this->error_message = $error; + } + } + + /** + * Whether the email was successfully sent. + * + * @since 1.5.0 + * + * @return bool + */ + public function is_email_sent() { + + $is_sent = false; + + if ( + ! empty( $this->response ) && + method_exists( $this->response, 'get' ) && + ! empty( $this->response->get( 'MessageId' ) ) + ) { + $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 ); + } + + /** + * Get mailer debug information, that is helpful during support. + * + * @since 1.5.0 + * + * @return string + */ + public function get_debug_info() { + + $debug_items = []; + + $auth = new Auth( $this->connection ); + + $debug_items[] = 'Access Key ID/Secret: ' . ( $auth->is_clients_saved() ? 'Yes' : 'No' ); + + return implode( '
    ', $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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_id' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-client_id" spellcheck="false" + /> +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_secret' ) ) : ?> + + display_const_set_message( 'WPMS_AMAZONSES_CLIENT_SECRET' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'client_secret' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][client_secret]", + 'id' => "wp-mail-smtp-setting-{$slug}-client_secret", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Secret Access Key', 'wp-mail-smtp-pro' ), + ] + ); + ?> + +
    +
    + + +
    +
    + +
    +
    + get_slug() === $this->connection->get_mailer_slug() ) { + $is_region_guessed = false; + $current_region = Auth::prepare_region( $this->connection_options->get( $this->get_slug(), 'region' ) ); + + if ( empty( $current_region ) ) { + $current_region = $this->get_closest_region(); + + $is_region_guessed = ! empty( $current_region ); + } + ?> + +

    + +
    + +
    + + +

    + +

    + +
    +
    + + + +
    +
    + +
    +
    +
    + connection_options->get_group( $this->get_slug() ); + + if ( + empty( $ses_settings['client_id'] ) || + empty( $ses_settings['client_secret'] ) || + empty( $ses_settings['region'] ) + ) { + echo '

    ' . esc_html( $this->get_connection_not_ready_error_text() ) . '

    '; + } else { + echo wp_mail_smtp()->prepare_loader(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + ?> +
    + +
    +
    + + + get_slug() === $this->connection->get_mailer_slug() ) { + echo '

    ' . esc_html( $this->get_connection_not_ready_error_text() ) . '

    '; + + return ob_get_clean(); + } + + $auth = new Auth( $this->connection ); + + if ( ! $auth->is_connection_ready() ) { + echo '

    ' . esc_html( $this->get_connection_not_ready_error_text() ) . '

    '; + + return ob_get_clean(); + } + + // Prepare the SES identities. + $table = new IdentitiesTable( $this->connection ); + + $table->prepare_items(); + + $error = $table->get_last_error(); + + if ( ! $table->has_items() && $error instanceof WP_Error ) { + // Display an error message to a user. + echo '

    ' . esc_html( $error->get_error_message() ) . '

    '; + + return ob_get_clean(); + } + ?> + + has_items() ) : ?> + +
    + display(); ?> +
    + +

    + +

    + + + +

    + + +

    + + + +

    + +

    + +
    +
    + +

    + +

    + +

    + + + + +

    +

    + + +

    + +
    +
    + $coords ) { + $distance_to[ $region ] = Geo::get_distance_between( + $site['latitude'], + $site['longitude'], + $coords['lat'], + $coords['lon'] + ); + } + + return array_search( min( $distance_to ), $distance_to, true ); + } + + /** + * Get domain DKIM DNS records. + * + * @since 3.3.0 + * + * @param string $domain Domain. + * @param array $dkim_tokens DKIM tokens. + * @param ConnectionInterface $connection The Connection object. + * + * @return array + */ + public static function prepare_dkim_dns_records( $domain, $dkim_tokens, $connection = null ) { + + $result = []; + + $region = $connection->get_options()->get( self::SLUG, 'region' ); + + if ( ! empty( $region ) ) { + $region = Auth::prepare_region( $region ); + } else { + $region = Auth::AWS_US_EAST_1; + } + + foreach ( $dkim_tokens as $token ) { + $region_part = ''; + + // Include region to record value for particular regions. + // Domains list in "DKIM Domains" section: https://docs.aws.amazon.com/general/latest/gr/ses.html. + if ( in_array( $region, [ 'af-south-1', 'ap-northeast-3', 'eu-south-1' ], true ) ) { + $region_part = '.' . $region; + } + + $result[] = [ + 'name' => $token . '._domainkey.' . $domain, + 'value' => $token . '.dkim' . $region_part . '.amazonses.com', + ]; + } + + return $result; + } + + /** + * Prepare the HTML output for domain DKIM DNS records notice. + * + * @since 3.3.0 + * + * @param string $domain Domain. + * @param array $dkim_tokens DKIM tokens. + * @param ConnectionInterface $connection The Connection object. + * + * @return string + */ + public static function prepare_domain_dkim_records_notice( $domain, $dkim_tokens, $connection = null ) { + + ob_start(); + ?> +
    + +

    + +

    + +
    +
    +
    + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + +

    + 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' + ); + ?> +

    +
    + ' . esc_html__( 'Please add this TXT record to your domain\'s DNS settings:', 'wp-mail-smtp-pro' ) . '

    '; + $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' ); + ?> + +
    +
    + +
    +
    + 'wp-mail-smtp[' . esc_attr( $this->get_slug() ) . '][one_click_setup_enabled]', + 'id' => 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-one_click_setup_enabled', + 'checked' => $one_click_setup_enabled, + ] + ); + ?> +

    + +

    +
    +
    + +
    > + display_options(); ?> +
    + +
    > + get_pro()->get_license()->is_valid() ) : ?> + +
    +
    + display_license_notice(); ?> +
    +
    + + + +
    +
    + +
    +
    + display_auth_setting_action(); ?> +
    +
    +
    + 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() ) : ?> + + is_reauth_required() && $license->is_valid() ) : ?> +
    + + + + + + +
    + get_user_info(); + + if ( ! empty( $user['email'] ) ) { + printf( + /* translators: %1$s - email address, as received from Google API. */ + esc_html__( 'Connected as %1$s', 'wp-mail-smtp-pro' ), + '' . esc_html( $user['email'] ) . '' + ); + } + ?> +
    +
    +

    + +

    + +
    + + + +
    + get_user_info(); + + if ( ! empty( $user['email'] ) ) { + printf( + /* translators: %1$s - email address, as received from Google API. */ + esc_html__( 'Connected as %1$s', 'wp-mail-smtp-pro' ), + '' . 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' ); + ?> + +
    +
    + +
    +
    + 'wp-mail-smtp[' . esc_attr( $this->get_slug() ) . '][one_click_setup_enabled]', + 'id' => 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-one_click_setup_enabled', + 'checked' => $one_click_setup_enabled, + ] + ); + ?> +

    + +

    +
    +
    + +
    > + manual_setup_options->display_options(); ?> +
    + +
    > + get_pro()->get_license()->is_valid() ) : ?> + +
    +
    + display_license_notice(); ?> +
    +
    + + + +
    +
    + +
    +
    + display_auth_setting_action(); ?> +
    +
    +
    + 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() ) : ?> + + is_reauth_required() && $license->is_valid() ) : ?> +
    + + + + + + +
    + get_user_info(); + + if ( ! empty( $user['email'] ) && ! empty( $user['name'] ) ) { + printf( + /* translators: %s - Display name and email, as received from Microsoft API. */ + esc_html__( 'Connected as %s', 'wp-mail-smtp-pro' ), + '' . esc_html( $user['name'] . ' <' . $user['email'] . '>' ) . '' + ); + } + ?> +
    +
    +

    + +

    + +
    + + + +
    + get_user_info(); + + if ( ! empty( $user['email'] ) && ! empty( $user['name'] ) ) { + printf( + /* translators: %s - Display name and email, as received from Microsoft API. */ + esc_html__( 'Connected as %s', 'wp-mail-smtp-pro' ), + '' . 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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_id' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-client_id" spellcheck="false" + /> +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_secret' ) ) : ?> + + display_const_set_message( 'WPMS_OUTLOOK_CLIENT_SECRET' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'client_secret' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][client_secret]", + 'id' => "wp-mail-smtp-setting-{$slug}-client_secret", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Application Password', 'wp-mail-smtp-pro' ), + ] + ); + ?> + +
    +
    + + +
    +
    + +
    +
    + + +

    + +
    + Redirect URIs" web field for your application on Microsoft Azure site for your project there.', 'wp-mail-smtp-pro' ); ?> +

    +
    +
    + + +
    +
    + +
    +
    + display_auth_setting_action(); ?> +
    +
    + + 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 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//messages + * + * The domain and accountId will be added in constructor so it will look something like: + * https://mail.zoho.com/api/accounts/3231jq...9ej2119/ + * + * The actual endpoint eg. `messages` will be added when needed. + * + * @since 2.3.0 + * @since 4.2.0 Will be defined in the constructor based on the domain. + * + * @var string + */ + protected $root_url = ''; + + /** + * Mailer constructor. + * + * @since 2.3.0 + * + * @param MailCatcher $phpmailer The MailCatcher object. + * @param ConnectionInterface $connection The Connection object. + */ + public function __construct( $phpmailer, $connection = null ) { + + // Init the client that checks tokens and re-saves them if needed. + new Auth( $connection ); + + // We want to prefill everything from \WPMailSMTP\MailCatcher class, which extends \PHPMailer. + parent::__construct( $phpmailer, $connection ); + + $token = $this->connection_options->get( $this->mailer, 'access_token' ); + $domain = $this->connection_options->get( $this->mailer, 'domain' ); + $user_details = $this->connection_options->get( $this->mailer, 'user_details' ); + $account_id = ! empty( $user_details['account_id'] ) ? $user_details['account_id'] : ''; + + // Define the root URL. + $this->root_url = isset( ZohoAuth::$mail_endpoints[ $domain ] ) ? ZohoAuth::$mail_endpoints[ $domain ] : ZohoAuth::$mail_endpoints['com']; + $this->root_url .= $domain . '/api/accounts/' . $account_id . '/'; + + if ( ! empty( $token['access_token'] ) ) { + $this->set_header( 'Authorization', 'Zoho-oauthtoken ' . $token['access_token'] ); + } + $this->set_header( 'Content-Type', 'application/json' ); + + $this->process_phpmailer( $phpmailer ); + } + + /** + * PhpMailer generates certain headers, including our custom. + * We want to preserve them when sending an email. + * + * Zoho does not support custom email headers. + * So we do nothing. + * + * @since 2.3.0 + * + * @param array $headers The headers to set. + */ + public function set_headers( $headers ) {} + + /** + * Define the from email and name. + * + * It doesn't support random email, should be the same as used for OAuth authentication/connection. + * + * @since 2.3.0 + * + * @param string $email Not used. + * @param string $name The from name. + */ + public function set_from( $email, $name = '' ) { + + $sender = $this->connection_options->get( $this->mailer, 'user_details' ); + $from = sprintf( + '"%1$s" <%2$s>', + wp_slash( $name ), + isset( $sender['email'] ) ? $sender['email'] : '' + ); + + $this->set_body_param( + [ + 'fromAddress' => $from, + ] + ); + } + + /** + * Define the CC/BCC/TO email and name lists. + * + * @since 2.3.0 + * + * @param array $recipients The multi dimensional array of all to/cc/bcc email addresses and names. + */ + public function set_recipients( $recipients ) { + + 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; + } + + $type_id = $type . 'Address'; + + $data[ $type_id ] = []; + + // Iterate over all emails for each type. + // There might be multiple to/cc/bcc emails. + foreach ( $emails as $email ) { + $addr = isset( $email[0] ) ? $email[0] : false; + $name = isset( $email[1] ) ? $email[1] : false; + + if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) { + continue; + } + + if ( ! empty( $name ) ) { + $addr = sprintf( '%1$s <%2$s>', $name, $addr ); + } + + $data[ $type_id ][] = $addr; + } + } + + if ( ! empty( $data ) ) { + foreach ( $data as $type_id => $type_data ) { + $this->set_body_param( + [ + $type_id => implode( ',', $type_data ), + ] + ); + } + } + } + + /** + * Set the email content. + * Zoho supports only HTML email content, and they create a multipart email with + * automatic HTML stripping for the plain text version. + * + * @since 2.3.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; + } + + $content = ! empty( $content['html'] ) ? $content['html'] : $content['text']; + } else { + $content = nl2br( $content ); + } + + $this->set_body_param( + [ + 'content' => $content, + ] + ); + } + + /** + * Set Reply-To part of the message. + * + * Zoho requires the users to verify the reply-to email address. + * Only one reply-to email address is allowed, so we take the first one. + * + * @since 2.3.0 + * @since 2.9.0 Zoho does not allow dynamic reply-to addresses, so we removed the support for them. GH #645. + * + * @param array $reply_to The multidimensional array of email addresses and names that + * should be used for the reply field. + */ + public function set_reply_to( $reply_to ) {} + + /** + * Set the return path. + * + * Zoho doesn't support return_path params. + * So we do nothing. + * + * @since 2.3.0 + * + * @param string $from_email The email address that should be used for the return path. + */ + public function set_return_path( $from_email ) {} + + /** + * Add attachments to the body. + * Zoho requires the attachments to be uploaded via an endpoint prior to sending the email. + * The collected upload data has to be then added to the email sending request. + * + * @since 2.3.0 + * + * @see https://www.zoho.com/mail/help/api/post-send-email-attachment.html + * @see https://www.zoho.com/mail/help/api/post-upload-attachments.html + * + * @param array $attachments Array of attachments. + */ + 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 Zoho API. + * + * @since 3.1.0 + * + * @param array $attachments Array of attachments. + * + * @return array + */ + protected function prepare_attachments( $attachments ) { + + $data = []; + + // Prepare request headers. + $headers = $this->get_headers(); + $headers['Content-Type'] = 'application/octet-stream'; + + foreach ( $attachments as $attachment ) { + $file = $this->get_attachment_file_content( $attachment ); + + if ( $file === false ) { + continue; + } + + // Upload the attachment via Zoho API. + $url = add_query_arg( + 'fileName', + $this->get_attachment_file_name( $attachment ), + $this->root_url . 'messages/attachments' + ); + + $params = [ + 'headers' => $headers, + 'body' => $file, + ]; + + $response = wp_safe_remote_post( $url, $params ); + + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { + continue; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( ! empty( $body['data'] ) ) { + $data[] = $body['data']; + } + } + + return $data; + } + + /** + * Redefine the way email body is returned. + * Zoho API needs JSON object. + * + * @since 2.3.0 + * + * @return string + */ + public function get_body() { + + return wp_json_encode( parent::get_body() ); + } + + /** + * Send the email using Zoho Mail API. + * + * @since 2.3.0 + */ + public function send() { + + $params = PluginOptions::array_merge_recursive( + $this->get_default_params(), + [ + 'headers' => $this->get_headers(), + 'body' => $this->get_body(), + ] + ); + + /* + * Right now Zoho 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']; + } + + $response = wp_safe_remote_post( $this->root_url . 'messages', $params ); + + DebugEvents::add_debug( + esc_html__( 'An email request was sent to the Zoho Mail API.', 'wp-mail-smtp-pro' ) + ); + + $this->process_response( $response ); + } + + /** + * Process Zoho API response with a helpful error. + * + * @since 2.3.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->errorCode ) || ! empty( $body->data->moreInfo ) ) { + $message = ! empty( $body->status->description ) ? $body->status->description : ''; + $code = ! empty( $body->status->code ) ? $body->status->code : ''; + $description = ! empty( $body->data->errorCode ) ? $body->data->errorCode : $body->data->moreInfo; + + $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 Zoho Mail 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->data->errorCode ) ) { + return $body->data->errorCode; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + } + } + + return parent::get_response_error_code(); + } + + /** + * Get mailer debug information, that is helpful during support. + * + * @since 2.3.0 + * + * @return string + */ + public function get_debug_info() { + + $mg_text = []; + + $auth = new Auth( $this->connection ); + + $mg_text[] = 'Domain: ' . (string) $this->connection_options->get( $this->mailer, 'domain' ); + $mg_text[] = 'Client ID/Secret: ' . ( $auth->is_clients_saved() ? 'Yes' : 'No' ); + $mg_text[] = 'Tokens: ' . ( ! $auth->is_auth_required() ? 'Yes' : 'No' ); + + return implode( '
    ', $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'; + ?> + + +
    +
    + +
    +
    + +

    +
    +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_id' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-client_id" spellcheck="false" + /> +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_secret' ) ) : ?> + + display_const_set_message( 'WPMS_ZOHO_CLIENT_SECRET' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'client_secret' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][client_secret]", + 'id' => "wp-mail-smtp-setting-{$slug}-client_secret", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Client Secret', 'wp-mail-smtp-pro' ), + ] + ); + ?> + +
    +
    + + +
    +
    + +
    +
    + + +

    + +
    + Authorized Redirect URIs" web field for your application on Zoho Developer Console.', 'wp-mail-smtp-pro' ); ?> +

    +
    +
    + + +
    +
    + +
    +
    + display_auth_setting_action(); ?> +
    +
    + + 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['zoho_remove_nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_GET['zoho_remove_nonce'] ), 'zoho_remove' ) + ) { + return; + } + + if ( $this->connection->get_mailer_slug() !== $this->get_slug() ) { + return; + } + + $old_opt = $this->connection_options->get_all_raw(); + + foreach ( $old_opt[ $this->get_slug() ] as $key => $value ) { + // Unset everything except Domain, Client ID and Client Secret. + if ( ! in_array( $key, [ 'domain', 'client_id', 'client_secret' ], true ) ) { + unset( $old_opt[ $this->get_slug() ][ $key ] ); + } + } + + $this->connection_options->set( $old_opt ); + } + + /** + * Get zoho domains in label/value pairs. + * + * @since 2.6.0 + * + * @return array + */ + public function get_zoho_domains() { + + $data = []; + + foreach ( $this->zoho_domains as $value => $label ) { + $data[] = [ + 'label' => $label, + 'value' => $value, + ]; + } + + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/SiteHealth.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SiteHealth.php new file mode 100644 index 00000000..76c4c7cf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SiteHealth.php @@ -0,0 +1,212 @@ + esc_html__( 'Is WP Mail SMTP Pro license active and valid?', 'wp-mail-smtp-pro' ), + 'test' => 'pro_license_test', + ); + + $tests['async']['wp_mail_smtp_wpmailsmtpdotcom_communication'] = array( + 'label' => esc_html__( 'Is wpmailsmtp.com reachable?', 'wp-mail-smtp' ), + 'test' => 'wpmailsmtpdotcom_communication_test', + ); + + return $tests; + } + + /** + * Register debug information. + * This will be displayed in the "Info" tab of the WP Site Health page. + * + * @since 1.9.0 + * + * @param array $debug_info Array of existing debug information. + * + * @return array + */ + public function register_pro_debug_information( $debug_info ) { + + if ( ! isset( $debug_info[ SiteHealthLite::DEBUG_INFO_SLUG ] ) ) { + return $debug_info; + } + + $pro_fields = []; + + // Install date. + $activated = get_option( 'wp_mail_smtp_activated', [] ); + if ( ! empty( $activated['pro'] ) ) { + $date = $activated['pro'] + ( get_option( 'gmt_offset' ) * 3600 ); + + $pro_fields['pro_install_date'] = [ + 'label' => esc_html__( 'Pro install date', 'wp-mail-smtp-pro' ), + 'value' => date_i18n( esc_html__( 'M j, Y @ g:ia' ), $date ), + ]; + } + + if ( wp_mail_smtp()->pro->get_logs()->is_enabled() && wp_mail_smtp()->pro->get_logs()->is_valid_db() ) { + $pro_fields['email_log_entries'] = array( + 'label' => esc_html__( 'Email log entries', 'wp-mail-smtp-pro' ), + 'value' => ( new EmailsCollection() )->get_count(), + ); + } + + $debug_info[ SiteHealthLite::DEBUG_INFO_SLUG ]['fields'] = array_merge( + $debug_info[ SiteHealthLite::DEBUG_INFO_SLUG ]['fields'], + $pro_fields + ); + + return $debug_info; + } + + /** + * Perform the test (async) for checking and verifying the pro license. + * + * @since 1.9.0 + */ + public function pro_license_test() { + + check_ajax_referer( 'health-check-site-status' ); + + if ( ! current_user_can( 'view_site_health_checks' ) ) { + wp_send_json_error(); + } + + $result = array( + 'label' => esc_html__( 'WP Mail SMTP Pro license is active and valid', 'wp-mail-smtp-pro' ), + 'status' => 'good', + 'badge' => array( + 'label' => wp_mail_smtp()->get_site_health()->get_label(), + 'color' => SiteHealthLite::BADGE_COLOR, + ), + 'description' => '', + 'actions' => sprintf( + '

    %s

    ', + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ), + esc_html__( 'View license setting', 'wp-mail-smtp-pro' ) + ), + 'test' => 'wp_mail_smtp_pro_license_check', + ); + + $license_status = wp_mail_smtp()->pro->get_license()->get_status(); + + $result['description'] = $license_status['message']; + + if ( $license_status['valid'] === false ) { + $result['label'] = esc_html__( 'WP Mail SMTP Pro license is invalid', 'wp-mail-smtp-pro' ); + $result['badge']['color'] = 'orange'; + $result['status'] = 'recommended'; + } + + wp_send_json_success( $result ); + } + + /** + * Perform the test (async) for checking if this WP site can communicate with wpmailsmtp.com. + * + * @since 1.9.0 + */ + public function wpmailsmtpdotcom_communication_test() { + + check_ajax_referer( 'health-check-site-status' ); + + if ( ! current_user_can( 'view_site_health_checks' ) ) { + wp_send_json_error(); + } + + $result = array( + 'label' => esc_html__( 'Your site can communicate with wpmailsmtp.com', 'wp-mail-smtp-pro' ), + 'status' => 'good', + 'badge' => array( + 'label' => wp_mail_smtp()->get_site_health()->get_label(), + 'color' => SiteHealthLite::BADGE_COLOR, + ), + 'description' => sprintf( + '

    %s

    ', + esc_html__( 'Communicating with the wpmailsmtp.com servers is used to check for new plugin versions, and to verify license keys.', 'wp-mail-smtp-pro' ) + ), + 'actions' => '', + 'test' => 'wp_mail_smtp_wpmailsmtpdotcom_communication', + ); + + // Send dummy timestamp data to prevent issues with some cURL configurations which don't allow empty requests. + $response = wp_remote_post( + self::CONNECTION_PING_URL, + [ + 'user-agent' => Helpers::get_default_user_agent(), + 'body' => [ + 'timestamp' => time(), + ], + ] + ); + + if ( wp_remote_retrieve_response_code( $response ) !== 200 ) { + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + $result['label'] = esc_html__( 'Could not reach wpmailsmtp.com', 'wp-mail-smtp-pro' ); + + $result['description'] .= sprintf( + '

    %s

    ', + sprintf( + '%s %s', + esc_html__( 'Error', 'wp-mail-smtp-pro' ), + sprintf( /* translators: %s - The error returned by the lookup. */ + esc_html__( 'Your site is unable to reach wpmailsmtp.com, and returned the error: %s', 'wp-mail-smtp-pro' ), + is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_response_message( $response ) + ) + ) + ); + } + + wp_send_json_success( $result ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/Admin/SettingsTab.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/Admin/SettingsTab.php new file mode 100644 index 00000000..baf5d0a5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/Admin/SettingsTab.php @@ -0,0 +1,459 @@ +get_admin()->get_current_tab() === $this->slug ) { + $this->init(); + $this->hooks(); + } + } + + /** + * Initialize object. + * + * @since 3.7.0 + */ + private function init() { + + $conditional_logic_properties = [ + 'subject' => [ + 'label' => esc_html__( 'Subject', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'message' => [ + 'label' => esc_html__( 'Message', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'from_email' => [ + 'label' => esc_html__( 'From Email', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'from_name' => [ + 'label' => esc_html__( 'From Name', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'to_email' => [ + 'label' => esc_html__( 'To', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'cc' => [ + 'label' => esc_html__( 'CC', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'bcc' => [ + 'label' => esc_html__( 'BCC', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'reply_to' => [ + 'label' => esc_html__( 'Reply-To', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'header_name' => [ + 'label' => esc_html__( 'Header Name', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'header_value' => [ + 'label' => esc_html__( 'Header Value', 'wp-mail-smtp-pro' ), + 'type' => 'text', + ], + 'initiator' => [ + 'label' => esc_html__( 'Initiator', 'wp-mail-smtp-pro' ), + 'type' => 'select', + 'choices' => $this->get_initiators_choices(), + ], + ]; + + $this->additional_connections = wp_mail_smtp()->get_pro()->get_additional_connections(); + $this->conditional_logic_settings = new ConditionalLogicSettings( $conditional_logic_properties ); + } + + /** + * Register hooks. + * + * @since 3.7.0 + */ + public function hooks() { + + add_action( 'wp_mail_smtp_admin_area_enqueue_assets', [ $this, 'enqueue_assets' ] ); + + add_action( 'admin_footer', [ $this, 'footer_scripts' ] ); + + $this->conditional_logic_settings->hooks(); + } + + /** + * Enqueue required JS and CSS. + * + * @since 3.7.0 + */ + public function enqueue_assets() { + + $min = WP::asset_min(); + + wp_enqueue_script( 'wp-util' ); + + wp_enqueue_style( + 'wp-mail-smtp-smart-routing', + wp_mail_smtp()->plugin_url . '/assets/css/smtp-smart-routing.min.css', + [], + WPMS_PLUGIN_VER + ); + wp_enqueue_script( + 'wp-mail-smtp-smart-routing', + wp_mail_smtp()->plugin_url . "/assets/pro/js/smtp-pro-smart-routing{$min}.js", + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + } + + /** + * Output footer scripts. + * + * @since 3.7.0 + */ + public function footer_scripts() { + + ?> + + get( 'smart_routing', 'enabled' ); + $routes = Options::init()->get( 'smart_routing', 'routes' ); + + if ( empty( $routes ) ) { + // Default value. + $routes = [ 'route-0' => [] ]; + } + ?> +
    + wp_nonce_field(); ?> + +
    +
    +

    + + additional_connections->has_connections() ) : ?> + + + + +

    +

    + Learn More.', 'wp-mail-smtp-pro' ), + esc_url( + wp_mail_smtp()->get_utm_url( + 'https://wpmailsmtp.com/docs/setting-up-smart-email-routing/', + [ + 'content' => 'Smart Routing description', + ] + ) + ) + ), + [ + 'a' => [ + 'href' => [], + 'target' => [], + 'rel' => [], + ], + ] + ); + ?> +

    +
    +
    + + additional_connections->has_connections() ) : ?> +
    +

    + additional connection before you can use Smart Routing.', 'wp-mail-smtp-pro' ), + add_query_arg( + [ + 'tab' => 'connections', + ], + wp_mail_smtp()->get_admin()->get_admin_page_url() + ) + ), + [ + 'a' => [ + 'href' => [], + ], + ] + ); + ?> +

    +
    + + +
    + esc_html__( 'Enable Smart Routing', 'wp-mail-smtp-pro' ), + 'name' => 'wp-mail-smtp[smart_routing][enabled]', + 'id' => 'wp-mail-smtp-smart-routing-enabled', + 'class' => 'wp-mail-smtp-smart-routing-toggle', + 'checked' => $is_enabled, + ] + ); + ?> +
    + +
    +
    + $route ) : ?> + display_route( $route_index, $route ); ?> + +
    + +
    + <?php esc_attr_e( 'Light bulb icon', 'wp-mail-smtp-pro' ); ?> +

    + Primary Connection will be used for all emails that do not match the conditions above.', 'wp-mail-smtp-pro' ), + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ) + ), + [ + 'b' => [], + 'u' => [], + 'a' => [ + 'href' => [], + ], + ] + ); + ?> +

    +
    +
    + + display_save_btn(); ?> +
    + +
    +
    + + display_connection_select( $route_index, $connection_id ); ?> + + +
    +
    + + +
    + + +
    +
    +
    + + additional_connections->connection_exists( $connection_id ) ) : ?> +
    +

    + +

    +
    + + + conditional_logic_settings->block( + [ + 'field_name' => 'wp-mail-smtp[smart_routing][routes][' . $route_index . ']', + 'conditionals' => ! empty( $route['conditionals'] ) ? $route['conditionals'] : '', + ] + ); + ?> +
    +
    + additional_connections->get_configured_connections(); + } + + $invalid_connection = ! empty( $connection_id ) && ! $this->additional_connections->connection_exists( $connection_id ); + ?> + + get_all_raw(); + + // Unchecked checkboxes doesn't exist in $_POST, so we need to ensure we actually have them in data to save. + if ( empty( $data['smart_routing']['enabled'] ) ) { + $data['smart_routing']['enabled'] = false; + } + + // All options should be overwritten to prevent duplications. + $all_opt['smart_routing'] = isset( $data['smart_routing'] ) ? $data['smart_routing'] : []; + + // All the sanitization is done there. + $options->set( $all_opt ); + + WP::add_admin_notice( + esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp-pro' ), + WP::ADMIN_NOTICE_SUCCESS + ); + } + + /** + * Get initiators choices for conditional. + * + * @since 3.7.0 + * + * @return array + */ + private function get_initiators_choices() { + + if ( ! function_exists( 'get_plugins' ) ) { + include ABSPATH . '/wp-admin/includes/plugin.php'; + } + + // WP Core. + $initiators['wp-core'] = esc_html__( 'WP Core', 'wp-mail-smtp-pro' ); + + // Theme. + $theme = wp_get_theme(); + + $initiators[ $theme->get_stylesheet() ] = sprintf( + /* translators: %s - theme name. */ + esc_html__( 'Theme (%s)', 'wp-mail-smtp-pro' ), + $theme->name + ); + + // Plugins. + $plugins = wp_list_pluck( + array_intersect_key( get_plugins(), array_flip( (array) get_option( 'active_plugins' ) ) ), + 'Name' + ); + + // Must use plugins. + $mu_plugins = wp_list_pluck( + get_mu_plugins(), + 'Name' + ); + + $initiators = array_merge( $initiators, $plugins, $mu_plugins ); + + return $initiators; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/ConditionalLogic.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/ConditionalLogic.php new file mode 100644 index 00000000..baf90c52 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/ConditionalLogic.php @@ -0,0 +1,183 @@ +wp_mail_args = $wp_mail_args; + } + + /** + * Process conditional rules. + * + * @since 3.7.0 + * + * @param array $conditionals List of conditionals. + * + * @return bool + */ + public function process( $conditionals ) { + + $values = $this->get_values( $conditionals ); + + return $this->process_conditionals( $conditionals, $values ); + } + + /** + * Get conditionals required values. + * + * @since 3.7.0 + * + * @param array $conditionals List of conditionals. + * + * @return array + */ + private function get_values( $conditionals ) { + + $values = []; + + foreach ( $conditionals as $group ) { + foreach ( $group as $rule ) { + if ( isset( $rule['property'] ) && ! isset( $values[ $rule['property'] ] ) ) { + $values[ $rule['property'] ] = $this->get_value( $rule['property'] ); + } + } + } + + return $values; + } + + /** + * Get property value. + * + * @since 3.7.0 + * + * @param string $property Property name. + * + * @return mixed + */ + private function get_value( $property ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + if ( isset( $this->conditional_values[ $property ] ) ) { + return $this->conditional_values[ $property ]; + } + + $value = ''; + + switch ( $property ) { + case 'subject': + $value = $this->wp_mail_args->get_subject(); + break; + + case 'message': + $value = $this->wp_mail_args->get_message(); + break; + + case 'from_email': + $processor = wp_mail_smtp()->get_processor(); + + remove_filter( 'wp_mail_from', [ $processor, 'filter_mail_from_email' ], PHP_INT_MAX ); + + /** This filter is documented in wp-includes/pluggable.php. */ + $value = apply_filters( 'wp_mail_from', $this->wp_mail_args->get_from_email() ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName + + add_filter( 'wp_mail_from', [ $processor, 'filter_mail_from_email' ], PHP_INT_MAX ); + break; + + case 'from_name': + $processor = wp_mail_smtp()->get_processor(); + + remove_filter( 'wp_mail_from_name', [ $processor, 'filter_mail_from_name' ], PHP_INT_MAX ); + + /** This filter is documented in wp-includes/pluggable.php. */ + $value = apply_filters( 'wp_mail_from_name', $this->wp_mail_args->get_from_name() ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName + + add_filter( 'wp_mail_from_name', [ $processor, 'filter_mail_from_name' ], PHP_INT_MAX ); + break; + + case 'to_email': + $value = $this->wp_mail_args->get_to_email(); + break; + + case 'cc': + $value = $this->wp_mail_args->get_header( 'cc' ); + break; + + case 'bcc': + $value = $this->wp_mail_args->get_header( 'bcc' ); + break; + + case 'reply_to': + $value = $this->wp_mail_args->get_header( 'reply-to' ); + break; + + case 'header_name': + $value = array_keys( $this->wp_mail_args->get_headers() ); + break; + + case 'header_value': + $value = array_values( $this->wp_mail_args->get_headers() ); + break; + + case 'initiator': + $initiator = wp_mail_smtp()->get_wp_mail_initiator(); + + // Change the initiator to "wp-core" if the email was sent from the reloaded method in the email controls. + if ( + ! empty( $initiator->get_file() ) && + strpos( str_replace( '\\', '/', $initiator->get_file() ), 'src/Pro/Emails/Control/Reload.php' ) !== false + ) { + $value = 'wp-core'; + } else { + $value = $initiator->get_slug(); + } + break; + } + + $this->conditional_values[ $property ] = $value; + + return $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/SmartRouting.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/SmartRouting.php new file mode 100644 index 00000000..1af5ed23 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/SmartRouting/SmartRouting.php @@ -0,0 +1,167 @@ +get_processor()->get_filtered_wp_mail_args(); + + if ( ! empty( $args ) && $this->is_enabled() ) { + $this->process_smart_routing( $args ); + } + } + + /** + * Initialize settings tab. + * + * @since 3.7.0 + * + * @param array $tabs Tabs array. + */ + public function init_settings_tab( $tabs ) { + + static $settings_tab = null; + + if ( is_null( $settings_tab ) ) { + $settings_tab = new SettingsTab(); + } + + $tabs['routing'] = $settings_tab; + + return $tabs; + } + + /** + * Process smart routing. + * + * @since 3.7.0 + * + * @param array $args Array of the `wp_mail` function arguments. + */ + public function process_smart_routing( $args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $connections_manager = wp_mail_smtp()->get_connections_manager(); + + // Bail if email connection was already set. + if ( ! empty( $connections_manager->get_mail_connection( false ) ) ) { + return; + } + + $wp_mail_args = new WPMailArgs( $args ); + + // Bail if it's a test email. + if ( + $wp_mail_args->get_header( 'X-Mailer-Type' ) === 'WPMailSMTP/Admin/Test' || + $wp_mail_args->get_header( 'X-Mailer-Type' ) === 'WPMailSMTP/Admin/SetupWizard/Test' + ) { + return; + } + + $conditional_logic = new ConditionalLogic( $wp_mail_args ); + + $routes = Options::init()->get( 'smart_routing', 'routes' ); + + if ( ! empty( $routes ) ) { + foreach ( $routes as $route ) { + $connection_id = isset( $route['connection_id'] ) ? $route['connection_id'] : false; + $conditionals = isset( $route['conditionals'] ) ? $route['conditionals'] : false; + + $connection = $connections_manager->get_connection( $connection_id, false ); + + if ( empty( $connection ) || empty( $conditionals ) ) { + continue; + } + + if ( $conditional_logic->process( $conditionals ) ) { + $connections_manager->set_mail_connection( $connection ); + break; + } + } + } + } + + /** + * Sanitize options. + * + * @since 3.7.0 + * + * @param array $options Currently processed options passed to a filter hook. + * + * @return array + */ + public function filter_options_set( $options ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded + + if ( ! isset( $options['smart_routing'] ) ) { + $options['smart_routing'] = [ + 'enabled' => false, + 'routes' => [], + ]; + + return $options; + } + + foreach ( $options['smart_routing'] as $key => $value ) { + if ( $key === 'enabled' ) { + $options['smart_routing'][ $key ] = (bool) $value; + } elseif ( $key === 'routes' && is_array( $value ) ) { + foreach ( $value as $route_key => $route ) { + if ( isset( $route['connection_id'] ) ) { + $options['smart_routing'][ $key ][ $route_key ]['connection_id'] = sanitize_key( $route['connection_id'] ); + } + + if ( isset( $route['conditionals'] ) && is_array( $route['conditionals'] ) ) { + $options['smart_routing'][ $key ][ $route_key ]['conditionals'] = WP::sanitize_text( $route['conditionals'] ); + } + } + } + } + + return $options; + } + + /** + * Whether smart routing is enabled. + * + * @since 3.7.0 + * + * @return bool + */ + private function is_enabled() { + + return (bool) Options::init()->get( 'smart_routing', 'enabled' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/EmailLogCleanupTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/EmailLogCleanupTask.php new file mode 100644 index 00000000..459b3219 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/EmailLogCleanupTask.php @@ -0,0 +1,149 @@ +get( 'logs', 'log_retention_period' ); + + // Exit if the retention period is not defined (set to "forever") or this task is already scheduled. + if ( empty( $retention_period ) || Tasks::is_scheduled( self::ACTION ) !== false ) { + return; + } + + // Schedule the task. + $this->recurring( + strtotime( 'tomorrow' ), + (int) apply_filters( 'wp_mail_smtp_tasks_email_log_cleanup_interval', DAY_IN_SECONDS ) + ) + ->params( $retention_period ) + ->register(); + } + + /** + * Perform the cleanup action: remove outdated email logs. + * + * @since 2.1.0 + * @since 3.8.0 Cleanup orphaned tracking data and attachments. + * + * @param int $meta_id The Meta ID with the stored task parameters. + * + * @throws Exception Exception will be logged in the Action Scheduler logs table. + */ + public function process( $meta_id ) { + + $task_meta = new Meta(); + $meta = $task_meta->get( (int) $meta_id ); + + // We should actually receive the passed parameter. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) !== 1 ) { + return; + } + + /** + * Date in seconds (examples: 86400, 100500). + * Email logs older than this period will be deleted. + * + * @var int $retention_period + */ + $retention_period = (int) $meta->data[0]; + + if ( empty( $retention_period ) ) { + return; + } + + // Bail if DB tables was not created. + if ( ! wp_mail_smtp()->get_pro()->get_logs()->is_valid_db() ) { + return; + } + + // This cleanup could take longer depending on the number of orphaned data. + set_time_limit( 300 ); + + $wpdb = WP::wpdb(); + $date = ( new DateTime( "- $retention_period seconds" ) )->format( WP::datetime_mysql_format() ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query( + $wpdb->prepare( + 'DELETE FROM `%1$s` WHERE date_sent < "%2$s"', // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder + Logs::get_table_name(), + $date + ) + ); + + $this->cleanup_tracking(); + $this->cleanup_attachments(); + } + + /** + * Cleanup orphaned tracking data. + * + * @since 3.8.0 + * + * @return void + */ + private function cleanup_tracking() { + + $tracking_cleanup = new TrackingCleanup(); + + $tracking_cleanup->cleanup_tracking_events(); + $tracking_cleanup->cleanup_tracking_links(); + } + + /** + * Cleanup orphaned attachments. + * + * @since 3.8.0 + * + * @return void + */ + private function cleanup_attachments() { + + ( new AttachmentsCleanup() )->cleanup_attachments(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/LicenseCheckTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/LicenseCheckTask.php new file mode 100644 index 00000000..8923f7ea --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/LicenseCheckTask.php @@ -0,0 +1,98 @@ +recurring( + strtotime( '+1 day' ), + $this->get_license_check_interval() + )->register(); + } + + /** + * Get the license check interval. + * + * @since 3.9.0 + * + * @return int + */ + private function get_license_check_interval() { + + /** + * Filters the interval for the license check task. + * + * @since 3.9.0 + * + * @param int $interval The interval in seconds. Default to a day (in seconds). + */ + return (int) apply_filters( 'wp_mail_smtp_pro_tasks_license_check_task_get_license_check_interval', DAY_IN_SECONDS ); + } + + /** + * Check license status. + * + * @since 3.9.0 + * + * @throws Exception Exception will be logged in the Action Scheduler logs table. + */ + public function process() { + + // Delete license check task duplicates. + try { + $this->remove_pending( 1000 ); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing. + } + + $license_key = wp_mail_smtp()->get_license_key(); + + if ( empty( $license_key ) ) { + return; + } + + wp_mail_smtp()->get_pro()->get_license()->validate_key( $license_key ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/BulkVerifySentStatusTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/BulkVerifySentStatusTask.php new file mode 100644 index 00000000..7570275b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/BulkVerifySentStatusTask.php @@ -0,0 +1,104 @@ +get( (int) $meta_id ); + + // We should actually receive the passed parameter. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) !== 1 ) { + return; + } + + $email_ids = $meta->data[0]; + + if ( empty( $email_ids ) ) { + return; + } + + foreach ( $email_ids as $email_id ) { + $verifier = ( new DeliveryVerification() )->get_verifier( $email_id ); + + if ( is_wp_error( $verifier ) ) { + continue; + } + + $verifier->verify(); + } + } + + /** + * Schedule emails verify sent status. + * + * @since 3.9.0 + * + * @param int[] $email_ids Email ids. + */ + public function schedule( $email_ids ) { + + // Exit if AS function does not exist. + if ( ! function_exists( 'as_has_scheduled_action' ) ) { + return; + } + + // Schedule the task. + $this->async() + ->params( $email_ids ) + ->register(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/ElasticEmail/VerifySentStatusTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/ElasticEmail/VerifySentStatusTask.php new file mode 100644 index 00000000..845f33e5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/ElasticEmail/VerifySentStatusTask.php @@ -0,0 +1,20 @@ +once( time() + $request_data_ttl ) + ->params( $request_id ) + ->register(); + } + + /** + * Perform the cleanup action: remove export file. + * + * @since 2.8.0 + * + * @param int $meta_id The Meta ID with the stored task parameters. + */ + public function process( $meta_id ) { + + $task_meta = new Meta(); + $meta = $task_meta->get( (int) $meta_id ); + + // We should actually receive the passed parameter. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) !== 1 ) { + return; + } + + $request_id = $meta->data[0]; + + if ( empty( $request_id ) ) { + return; + } + + $this->remove_export_file( $request_id ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/MailerSend/VerifySentStatusTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/MailerSend/VerifySentStatusTask.php new file mode 100644 index 00000000..19e09cf6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/MailerSend/VerifySentStatusTask.php @@ -0,0 +1,20 @@ +async() + ->params( $email_ids, $connection_id ) + ->register(); + } + + /** + * Perform the emails resend. + * + * @since 2.9.0 + * + * @param int $meta_id The Meta ID with the stored task parameters. + */ + public function process( $meta_id ) { + + $task_meta = new Meta(); + $meta = $task_meta->get( (int) $meta_id ); + + // We should actually receive the passed parameter. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) < 1 ) { + return; + } + + $email_ids = $meta->data[0]; + + if ( empty( $email_ids ) ) { + return; + } + + $connection_id = ! empty( $meta->data[1] ) ? $meta->data[1] : 'primary'; + $connection = wp_mail_smtp()->get_connections_manager()->get_connection( $connection_id ); + + $collection = new EmailsCollection( + [ + 'ids' => $email_ids, + 'per_page' => count( $email_ids ), + ] + ); + + foreach ( $collection->get() as $email ) { + $this->send_email( $email, null, $connection ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/SMTP2GO/VerifySentStatusTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/SMTP2GO/VerifySentStatusTask.php new file mode 100644 index 00000000..85bccf66 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Logs/SMTP2GO/VerifySentStatusTask.php @@ -0,0 +1,20 @@ +get_meta_data( $meta_id ); + + if ( empty( $meta ) ) { + return; + } + + list( $email_log_id, $try ) = $meta; + + $verifier = ( new DeliveryVerification() )->get_verifier( $email_log_id ); + + if ( is_wp_error( $verifier ) ) { + return; + } + + $delivery_status = $verifier->verify(); + + if ( is_wp_error( $delivery_status ) || ! $delivery_status->is_verified() ) { + $this->maybe_retry( $email_log_id, $try, new Email( $email_log_id ) ); + } elseif ( $delivery_status->is_failed() ) { + $email = $verifier->get_email(); + $fail_reason = $delivery_status->get_fail_reason(); + $error_code = ! empty( $delivery_status->get_error_code() ) ? $delivery_status->get_error_code() : 'unknown'; + + $this->handle_failed_delivery_alert( $fail_reason, $email ); + + /** + * Fires when an email delivery failure is detected via delivery verification. + * + * @since 4.8.0 + * + * @param string $mailer_slug Current mailer name. + * @param string $error_code Error code. + * @param string $error_message Error message. + */ + do_action( 'wp_mail_smtp_email_delivery_failed', $email->get_mailer(), $error_code, $fail_reason ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName + } + } + + /** + * Retry the sent status verification. + * + * @since 2.5.0 + * + * @param int $email_log_id The Email log ID. + * @param int $try The number of the try to perform. + * @param Email $email The Email object. + * + * @throws \Exception When there was an issue with task registration. + */ + protected function maybe_retry( $email_log_id, $try, $email ) { + + if ( $try > static::ALLOWED_TRIES ) { + $email->set_status( Email::STATUS_SENT ); + $email->set_error_text( + sprintf( /* translators: %d - The number of tries. */ + esc_html__( + 'The email sent status verification timed-out after %d tries. No information on email status (delivered/failed) was provided by the mailer API.', + 'wp-mail-smtp-pro' + ), + static::ALLOWED_TRIES + ) + ); + $email->save(); + + return; + } + + // Run a new background task. + ( new static() ) + ->params( $email_log_id, $try ) + ->once( time() + ( static::SCHEDULE_TASK_IN * $try ) ) + ->register(); + } + + /** + * Get the correct meta data information in a simple numeric array. + * + * @since 2.5.0 + * + * @param int $meta_id The Meta ID with the stored task parameters. + * + * @return array First item - email log ID, second item - number of tries. Empty array in case of error. + */ + protected function get_meta_data( $meta_id ) { + + $task_meta = new Meta(); + $meta = $task_meta->get( (int) $meta_id ); + + // We should actually receive the passed parameters. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) !== 2 ) { + return []; + } + + $email_log_id = (int) $meta->data[0]; + + if ( empty( $email_log_id ) ) { + return []; + } + + return [ + $email_log_id, + ! empty( $meta->data[1] ) ? (int) $meta->data[1] + 1 : 1, + ]; + } + + /** + * Trigger alerts on failed email delivery. + * + * @since 3.10.0 + * + * @param string $fail_reason Fail reason. + * @param Email $email Failed email. + * + * @return void + */ + private function handle_failed_delivery_alert( $fail_reason, $email ) { + + $error_text = ( + empty( $fail_reason ) ? + esc_html__( 'The email failed to be delivered. No specific reason was provided by the API.', 'wp-mail-smtp-pro' ) : + $fail_reason + ); + + ( new Alerts() )->handle_hard_bounced_email( $error_text, $email ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration11.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration11.php new file mode 100644 index 00000000..32154f8d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration11.php @@ -0,0 +1,268 @@ +hooks(); + } + + /** + * Hook our migration action. + * + * @since 3.8.0 + * + * @return void + */ + private function hooks() { + + add_action( self::ACTION, [ $this, 'process' ] ); + } + + /** + * Start the cleanup process. + * + * The cleanup process goes like this: + * 1. Tracking Events. + * 2. Tracking Links. + * 3. Attachments. + * + * @since 3.8.0 + * + * @return void + */ + public function process() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + // This migration could take longer depending on the number of orphaned data. + set_time_limit( 300 ); + + switch ( $this->get_current_status() ) { + case self::CLEANUP_TRACKING_LINKS_STATUS: + $this->cleanup_tracking_links(); + break; + + case self::CLEANUP_ATTACHMENT_STATUS: + $this->cleanup_attachments(); + break; + + case self::CLEANUP_COMPLETE_STATUS: + add_action( 'shutdown', [ $this, 'after_migration_cycle' ] ); + break; + + default: + $this->cleanup_tracking_events(); + break; + } + } + + /** + * Get the migration's current status. + * + * @since 3.8.0 + * + * @return string + */ + private function get_current_status() { + + if ( ! is_null( $this->current_status ) ) { + return $this->current_status; + } + + $this->current_status = get_option( self::STATUS_OPTION_NAME, self::CLEANUP_TRACKING_EVENTS_STATUS ); + + return $this->current_status; + } + + /** + * Clean up orphaned data in "Tracking Events" table. + * + * @since 3.8.0 + * + * @return void + */ + private function cleanup_tracking_events() { + + if ( $this->get_tracking_cleanup()->cleanup_tracking_events() ) { + $this->update_status( self::CLEANUP_TRACKING_LINKS_STATUS ); + } + } + + /** + * Clean up orphaned data in "Tracking Links" table. + * + * @since 3.8.0 + * + * @return void + */ + private function cleanup_tracking_links() { + + if ( $this->get_tracking_cleanup()->cleanup_tracking_links() ) { + $this->update_status( self::CLEANUP_ATTACHMENT_STATUS ); + } + } + + /** + * Update the migration's status. + * + * @since 3.8.0 + * + * @param string $new_status The new status. + * + * @return void + */ + private function update_status( $new_status ) { + + $this->current_status = $new_status; + + update_option( self::STATUS_OPTION_NAME, $new_status ); + } + + /** + * Get Tracking Cleanup object. + * + * @since 3.8.0 + * + * @return TrackingCleanup + */ + private function get_tracking_cleanup() { + + if ( is_null( $this->tracking_cleanup ) ) { + $this->tracking_cleanup = new TrackingCleanup(); + } + + return $this->tracking_cleanup; + } + + /** + * Cleanup orphaned attachments data in Email Attachments and + * Attachment Files DB tables. As well as the physical file. + * + * @since 3.8.0 + * + * @return void + */ + private function cleanup_attachments() { + + if ( ( new AttachmentsCleanup() )->cleanup_attachments() ) { + $this->update_status( self::CLEANUP_COMPLETE_STATUS ); + } + } + + /** + * Remove this recurring AS task. + * + * @since 3.8.0 + * + * @return void + */ + public function after_migration_cycle() { + + // Possibly just check if the task is no longer scheduled? + if ( $this->get_current_status() !== self::CLEANUP_COMPLETE_STATUS ) { + return; + } + + $this->cancel(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration4.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration4.php new file mode 100644 index 00000000..fd85b679 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration4.php @@ -0,0 +1,86 @@ +query( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration5.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration5.php new file mode 100644 index 00000000..b24bc60c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/Migrations/EmailLogMigration5.php @@ -0,0 +1,63 @@ +query( "ALTER TABLE `$table` CHANGE COLUMN `subject` `subject` VARCHAR(191) NOT NULL AFTER `id`;" ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/NotifierTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/NotifierTask.php new file mode 100644 index 00000000..36844eed --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Tasks/NotifierTask.php @@ -0,0 +1,86 @@ +get( (int) $meta_id ); + + // We should actually receive the passed parameters. + if ( empty( $meta->data ) || count( $meta->data ) !== 2 ) { + return; + } + + $notifier = new Notifier(); + $loader = new Loader(); + $options = Options::init(); + + // Register all enabled handlers. + foreach ( $loader->get_handlers_all() as $slug => $handler ) { + if ( $options->get( 'alert_' . $slug, 'enabled' ) ) { + $notifier->push_handler( $handler ); + } + } + + $alert_type = $meta->data[0]; + $alert_data = $meta->data[1]; + + $alert = new Alert( $alert_type, $alert_data ); + + // Send notifications. + $notifier->notify( $alert ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Translations.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Translations.php new file mode 100644 index 00000000..6c2defbc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Translations.php @@ -0,0 +1,471 @@ +allow_load() ) { + return; + } + + if ( $pagenow === 'update-core.php' ) { + + // Clear cache for translations. + add_action( 'set_site_transient_update_plugins', [ $this, 'clear_translations_cache' ] ); + + // Add translations to the list of available for download. + add_filter( 'site_transient_update_plugins', [ $this, 'register_t15s_translations' ] ); + } + + // Download translations on plugin activation. + add_action( 'activate_plugin', [ $this, 'activate_plugin' ] ); + + // Remove translation cache for a plugin on deactivation. + // Translation removal is handled on plugin removal by WordPress. + add_action( 'deactivate_plugin', [ $this, 'clear_plugin_translation_cache' ] ); + + // Download translations when language for the site has been changed. + add_action( 'update_option_WPLANG', [ $this, 'download_plugins_translations' ] ); + + // Download translations on plugin activation on Plugins page. + if ( + $pagenow === 'plugins.php' && + get_transient( 'wp_mail_smtp_just_activated' ) && + ( ! empty( $_GET['activate'] ) || ! empty( $_GET['activate-multi'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ) { + $this->download_plugins_translations(); + } + } + + /** + * Whether the provided slug is WP Mail SMTP Pro plugin. + * + * @since 3.9.0 + * + * @param string $slug Plugin slug. + * + * @return bool + */ + private function is_wpmailsmtp_pro_plugin( $slug ) { + + return strpos( $slug, 'wp-mail-smtp-pro' ) === 0; + } + + /** + * Get WP Mail SMTP Pro plugin slug. + * + * @since 3.9.0 + * + * @return string + */ + private function get_plugin_slug() { + + return $this->plugin_slug; + } + + /** + * Get available translations for the plugin. + * + * @since 3.9.0 + * + * @param array $translations List of translations. + * @param string $slug Plugin slug. + * + * @return array + */ + private function get_available_plugin_translations( $translations, $slug ) { + + $available_languages = $this->get_available_languages(); + + if ( empty( $available_languages ) ) { + return []; + } + + foreach ( $translations as $key => $language ) { + if ( ! is_object( $language ) ) { + $language = (object) $language; + } + if ( + ( ! property_exists( $language, 'slug' ) || ! property_exists( $language, 'language' ) ) || + $slug !== $language->slug || + ! in_array( $language->language, $available_languages, true ) + ) { + unset( $translations[ $key ] ); + } + } + + return $translations; + } + + /** + * Download translations for the plugin. + * + * @since 3.9.0 + * + * @param string $slug Slug of plugin. + * @param array $translations List of available translations. + */ + private function download_plugin_translations( $slug, $translations ) { + + $this->load_download_requirements(); + + $available_translations = $this->get_available_plugin_translations( $translations, $slug ); + + foreach ( $available_translations as $language ) { + if ( ! is_object( $language ) ) { + $language = (object) $language; + } + + $this->skin->language_update = $language; + + $this->upgrader->run( + [ + 'package' => $language->package, + 'destination' => WP_LANG_DIR . '/plugins', + 'abort_if_destination_exists' => false, + 'is_multi' => true, + 'hook_extra' => [ + 'language_update_type' => $language->type, + 'language_update' => $language, + ], + ] + ); + } + } + + /** + * Load required libraries. + * + * @since 3.9.0 + */ + private function load_download_requirements() { + + $this->skin = new Automatic_Upgrader_Skin(); + $this->upgrader = new Language_Pack_Upgrader( $this->skin ); + } + + /** + * Download translations. + * + * @since 3.9.0 + */ + public function download_plugins_translations() { + + $slug = $this->get_plugin_slug(); + $translations = $this->get_translations( $slug ); + + if ( ! empty( $translations['translations'] ) ) { + $this->download_plugin_translations( $slug, $translations['translations'] ); + } + } + + /** + * Get all available translations for the plugin. + * + * @since 3.9.0 + * + * @param string $slug Plugin slug. + * + * @return array Translation data. + */ + private function get_available_translations( $slug ) { + + $translations = get_site_transient( $this->get_cache_key( $slug ) ); + + if ( $translations !== false ) { + return $translations; + } + + $translations = json_decode( + wp_remote_retrieve_body( + wp_remote_get( + sprintf( self::API_URL, $slug ), + [ + 'timeout' => 2, + ] + ) + ), + true + ); + + if ( ! is_array( $translations ) || empty( $translations['translations'] ) ) { + $translations = [ 'translations' => [] ]; + } + + // Convert translations from API to a WordPress standard. + foreach ( $translations['translations'] as $key => $translation ) { + $translations['translations'][ $key ]['type'] = 'plugin'; + $translations['translations'][ $key ]['slug'] = $slug; + } + + set_site_transient( $this->get_cache_key( $slug ), $translations ); + + return $translations; + } + + /** + * Get a list of needed translations for the plugin. + * + * @since 3.9.0 + * + * @param string $slug Plugin slug. + * + * @return array + */ + private function get_translations( $slug ) { + + $translations = $this->get_available_translations( $slug ); + $available_languages = $this->get_available_languages(); + $installed_translations = $this->get_installed_translations(); + + foreach ( $translations['translations'] as $key => $translation ) { + if ( empty( $translation['language'] ) || ! in_array( $translation['language'], $available_languages, true ) ) { + unset( $translations['translations'][ $key ] ); + } + + // Skip languages which were updated locally. + if ( isset( $installed_translations[ $slug ][ $translation['language'] ]['PO-Revision-Date'], $translation['updated'] ) ) { + $local = strtotime( $installed_translations[ $slug ][ $translation['language'] ]['PO-Revision-Date'] ); + $remote = strtotime( $translation['updated'] ); + + if ( $local >= $remote ) { + unset( $translations['translations'][ $key ] ); + } + } + } + + return $translations; + } + + /** + * Register all translations from our Translations endpoint. + * + * @since 3.9.0 + * + * @param object $value Value of the `update_plugins` transient option. + * + * @return stdClass + */ + public function register_t15s_translations( $value ) { + + if ( ! $value ) { + $value = new stdClass(); + } + + if ( ! isset( $value->translations ) ) { + $value->translations = []; + } + + $slug = $this->get_plugin_slug(); + $translations = $this->get_translations( $slug ); + + if ( empty( $translations['translations'] ) ) { + return $value; + } + + foreach ( $translations['translations'] as $translation ) { + $value->translations[] = $translation; + } + + return $value; + } + + /** + * Get a dynamic cache key which has the plugin slug in its name. + * + * @since 3.9.0 + * + * @param string $slug Slug. + * + * @return string + */ + private function get_cache_key( $slug ) { + + return "wp_mail_smtp_t15s_{$slug}"; + } + + /** + * Clear existing translation cache. + * + * @since 3.9.0 + */ + public function clear_translations_cache() { + + delete_site_transient( $this->get_cache_key( $this->get_plugin_slug() ) ); + } + + /** + * Clear existing translation cache for a specific plugin. + * + * @since 3.9.0 + * + * @param string $plugin Plugin slug. + */ + public function clear_plugin_translation_cache( $plugin ) { + + $slug = dirname( $plugin ); + + if ( ! $this->is_wpmailsmtp_pro_plugin( $slug ) ) { + return; + } + + delete_site_transient( $this->get_cache_key( $slug ) ); + } + + /** + * Get available languages. + * + * @since 3.9.0 + * + * @return array + */ + private function get_available_languages() { + + if ( $this->available_languages ) { + return $this->available_languages; + } + + $this->available_languages = get_available_languages(); + + return $this->available_languages; + } + + /** + * Get installed translations. + * + * @since 3.9.0 + * + * @return array + */ + private function get_installed_translations() { + + if ( $this->installed_translations ) { + return $this->installed_translations; + } + + $this->installed_translations = wp_get_installed_translations( 'plugins' ); + + return $this->installed_translations; + } + + /** + * Download translations for the plugin after its activation. + * + * @since 3.9.0 + * + * @param string $plugin Plugin main file. + */ + public function activate_plugin( $plugin ) { + + $slug = dirname( $plugin ); + + if ( ! $this->is_wpmailsmtp_pro_plugin( $slug ) ) { + return; + } + + $translations = $this->get_translations( $slug ); + + if ( empty( $translations['translations'] ) ) { + return; + } + + $this->download_plugin_translations( $slug, $translations['translations'] ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/Upgrade.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Upgrade.php new file mode 100644 index 00000000..5c23397c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/Upgrade.php @@ -0,0 +1,62 @@ +cancel(); + ( new LicenseCheckTask() )->cancel(); + ( new SendUsageTask() )->cancel(); + } +} + diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/WPCLI/Options/Registry.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/WPCLI/Options/Registry.php new file mode 100644 index 00000000..0bfb881f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/WPCLI/Options/Registry.php @@ -0,0 +1,645 @@ +logs_args(), + $this->optimized_sending_args(), + $this->rate_limit_args(), + $this->controls_args(), + $this->alerts_args() + ); + } + + /** + * Email Logs settings. + * + * @since 4.9.0 + * + * @return array + */ + private function logs_args() { + + return [ + [ + 'flag' => 'logs.enabled', + 'type' => 'bool', + 'description' => __( 'Enable email logging.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'logs.log_email_content', + 'type' => 'bool', + 'description' => __( 'Store full email content (subject + body) in each log entry. Required to resend emails.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'logs.save_attachments', + 'type' => 'bool', + 'description' => __( 'Save sent attachments to the uploads folder alongside each log entry.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'logs.open_email_tracking', + 'type' => 'bool', + 'description' => __( 'Track when recipients open logged emails.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'logs.click_link_tracking', + 'type' => 'bool', + 'description' => __( 'Track when recipients click links in logged emails.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'logs.retention_seconds', + 'type' => 'int', + 'storage_path' => 'logs.log_retention_period', + 'description' => __( 'Email log retention in seconds (e.g. 86400 = 1 day, 604800 = 1 week; empty/0 = forever).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Optimized Email Sending toggle. + * + * Stored under the shared `general` group; the toggle is Pro-gated at runtime. + * + * @since 4.9.0 + * + * @return array + */ + private function optimized_sending_args() { + + return [ + [ + 'flag' => 'general.optimize_sending', + 'type' => 'bool', + 'storage_path' => 'general.optimize_email_sending_enabled', + 'description' => __( 'Send emails via a background queue instead of inline with the page request.', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Rate Limiting settings. + * + * @since 4.9.0 + * + * @return array + */ + private function rate_limit_args() { + + return [ + [ + 'flag' => 'rate_limit.enabled', + 'type' => 'bool', + 'description' => __( 'Enable per-interval rate limiting on outgoing email.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'rate_limit.minute', + 'type' => 'int', + 'description' => __( 'Maximum emails per minute (empty/0 = unlimited).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'rate_limit.hour', + 'type' => 'int', + 'description' => __( 'Maximum emails per hour (empty/0 = unlimited).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'rate_limit.day', + 'type' => 'int', + 'description' => __( 'Maximum emails per day (empty/0 = unlimited).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'rate_limit.week', + 'type' => 'int', + 'description' => __( 'Maximum emails per week (empty/0 = unlimited).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'rate_limit.month', + 'type' => 'int', + 'description' => __( 'Maximum emails per month (empty/0 = unlimited).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Email Controls — toggles that suppress specific WordPress core notifications. + * + * Operator-facing flag `control.block_*` maps to the `control.dis_*` storage key. + * + * @since 4.9.0 + * + * @return array + */ + private function controls_args() { + + return [ + // Comments. + [ + 'flag' => 'control.block_comment_moderation_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_comments_awaiting_moderation', + 'description' => __( 'Suppress the WP "comment awaiting moderation" email.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_comment_published_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_comments_published', + 'description' => __( 'Suppress the WP "comment published" email to the post author.', 'wp-mail-smtp-pro' ), + ], + + // Change of admin email. + [ + 'flag' => 'control.block_admin_email_change_attempt_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_admin_email_attempt', + 'description' => __( 'Suppress the "site admin email change attempted" email.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_admin_email_changed_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_admin_email_changed', + 'description' => __( 'Suppress the "site admin email changed" notice to the old address.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_network_admin_email_change_attempt_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_admin_email_network_attempt', + 'description' => __( 'Suppress the "network admin email change attempted" email (multisite).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_network_admin_email_changed_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_admin_email_network_changed', + 'description' => __( 'Suppress the "network admin email changed" notice (multisite).', 'wp-mail-smtp-pro' ), + ], + + // Change of user email or password. + [ + 'flag' => 'control.block_password_reset_request_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_user_details_password_reset_request', + 'description' => __( 'Suppress the "password reset requested" email.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_password_reset_admin_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_user_details_password_reset', + 'description' => __( 'Suppress the "password was reset" admin notification.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_password_changed_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_user_details_password_changed', + 'description' => __( 'Suppress the "password changed" email to the user.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_user_email_change_attempt_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_user_details_email_change_attempt', + 'description' => __( 'Suppress the "user requested email change" confirmation email.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_user_email_changed_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_user_details_email_changed', + 'description' => __( 'Suppress the "user email changed" notification.', 'wp-mail-smtp-pro' ), + ], + + // Personal data requests. + [ + 'flag' => 'control.block_personal_data_confirmed_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_personal_data_user_confirmed', + 'description' => __( 'Suppress the "user confirmed data export/erasure request" admin email.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_personal_data_erased_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_personal_data_erased_data', + 'description' => __( 'Suppress the "admin erased your personal data" email to the requester.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_personal_data_export_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_personal_data_sent_export_link', + 'description' => __( 'Suppress the "your data export is ready" email to the requester (blocks export delivery).', 'wp-mail-smtp-pro' ), + ], + + // Automatic updates. + [ + 'flag' => 'control.block_plugin_auto_update_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_auto_updates_plugin_status', + 'description' => __( 'Suppress background auto-update result emails for plugins.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_theme_auto_update_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_auto_updates_theme_status', + 'description' => __( 'Suppress background auto-update result emails for themes.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_core_auto_update_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_auto_updates_status', + 'description' => __( 'Suppress background auto-update result emails for WP core.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_auto_update_debug_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_auto_updates_full_log', + 'description' => __( 'Suppress the full auto-update log email (development builds only).', 'wp-mail-smtp-pro' ), + ], + + // New user. + [ + 'flag' => 'control.block_new_user_admin_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_user_created_to_admin', + 'description' => __( 'Suppress the "new user registered" email to the site admin.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_new_user_welcome_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_user_created_to_user', + 'description' => __( 'Suppress the "your account was created" email to the new user.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_network_user_site_invite_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_user_invited_to_site_network', + 'description' => __( 'Suppress the "you were invited to a site" email (multisite).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_network_new_user_admin_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_user_created_network', + 'description' => __( 'Suppress the "new user account created" email to the network admin (multisite).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_network_user_activated_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_user_added_activated_network', + 'description' => __( 'Suppress the "your account was added/activated" email to the user (multisite).', 'wp-mail-smtp-pro' ), + ], + + // New site (multisite). + [ + 'flag' => 'control.block_new_site_registered_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_site_user_registered_site_network', + 'description' => __( 'Suppress the "user registered for a new site" email to the site admin (multisite).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_new_site_network_admin_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_site_user_added_activated_site_in_network_to_admin', + 'description' => __( 'Suppress the "site activated/added" email to the network admin (multisite).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'control.block_new_site_site_admin_email', + 'type' => 'bool', + 'storage_path' => 'control.dis_new_site_user_added_activated_site_in_network_to_site', + 'description' => __( 'Suppress the "site activated/added" email to the site admin (multisite).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Alert provider settings (primary connection only). + * + * Friendly channel flags map via `storage_path` to the WPMS layout + * `alert_.connections[0].`. + * + * @since 4.9.0 + * + * @return array + */ + private function alerts_args() { + + return array_merge( + $this->alert_events_args(), + $this->alert_email_args(), + $this->alert_slack_args(), + $this->alert_discord_args(), + $this->alert_teams_args(), + $this->alert_webhook_args(), + $this->alert_twilio_args(), + $this->alert_whatsapp_args(), + $this->alert_push_args() + ); + } + + /** + * Alert trigger events (which conditions raise an alert). + * + * @since 4.9.0 + * + * @return array + */ + private function alert_events_args() { + + return [ + [ + 'flag' => 'alert.events.email_hard_bounced', + 'type' => 'bool', + 'storage_path' => 'alert_events.email_hard_bounced', + 'description' => __( 'Raise alerts when a logged email hard-bounces (requires email logging and a mailer that reports bounces).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Email alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_email_args() { + + $req = [ 'alert.email.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.email.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_email.enabled', + 'description' => __( 'Enable email alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.email.send_to', + 'type' => 'email', + 'storage_path' => 'alert_email.connections.0.send_to', + 'required_if' => $req, + 'description' => __( 'Recipient address for failure alerts (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Slack alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_slack_args() { + + $req = [ 'alert.slack.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.slack.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_slack_webhook.enabled', + 'description' => __( 'Enable Slack alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.slack.webhook_url', + 'type' => 'string', + 'storage_path' => 'alert_slack_webhook.connections.0.webhook_url', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Slack incoming-webhook URL (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Discord alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_discord_args() { + + $req = [ 'alert.discord.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.discord.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_discord_webhook.enabled', + 'description' => __( 'Enable Discord alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.discord.webhook_url', + 'type' => 'string', + 'storage_path' => 'alert_discord_webhook.connections.0.webhook_url', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Discord incoming-webhook URL (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Microsoft Teams alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_teams_args() { + + $req = [ 'alert.teams.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.teams.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_teams_webhook.enabled', + 'description' => __( 'Enable Microsoft Teams alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.teams.webhook_url', + 'type' => 'string', + 'storage_path' => 'alert_teams_webhook.connections.0.webhook_url', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Microsoft Teams incoming-webhook URL (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Custom webhook alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_webhook_args() { + + $req = [ 'alert.webhook.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.webhook.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_custom_webhook.enabled', + 'description' => __( 'Enable custom webhook alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.webhook.webhook_url', + 'type' => 'string', + 'storage_path' => 'alert_custom_webhook.connections.0.webhook_url', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Custom webhook URL (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Twilio SMS alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_twilio_args() { + + $req = [ 'alert.twilio.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.twilio.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_twilio_sms.enabled', + 'description' => __( 'Enable Twilio SMS alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.twilio.account_sid', + 'type' => 'string', + 'storage_path' => 'alert_twilio_sms.connections.0.account_sid', + 'required_if' => $req, + 'description' => __( 'Twilio Account SID (primary connection).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.twilio.auth_token', + 'type' => 'string', + 'storage_path' => 'alert_twilio_sms.connections.0.auth_token', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Twilio Auth Token (primary connection).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.twilio.from_phone_number', + 'type' => 'string', + 'storage_path' => 'alert_twilio_sms.connections.0.from_phone_number', + 'required_if' => $req, + 'description' => __( 'Twilio sending phone number in E.164 format (primary connection).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.twilio.to_phone_number', + 'type' => 'string', + 'storage_path' => 'alert_twilio_sms.connections.0.to_phone_number', + 'required_if' => $req, + 'description' => __( 'Recipient phone number in E.164 format (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * WhatsApp alert channel. + * + * @since 4.9.0 + * + * @return array + */ + private function alert_whatsapp_args() { + + $req = [ 'alert.whatsapp.enabled' => true ]; + + return [ + [ + 'flag' => 'alert.whatsapp.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_whatsapp.enabled', + 'description' => __( 'Enable WhatsApp alerts.', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.whatsapp.access_token', + 'type' => 'string', + 'storage_path' => 'alert_whatsapp.connections.0.access_token', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'WhatsApp Cloud API permanent access token (primary connection).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.whatsapp.business_account_id', + 'type' => 'string', + 'storage_path' => 'alert_whatsapp.connections.0.whatsapp_business_id', + 'required_if' => $req, + 'description' => __( 'WhatsApp Business Account ID (primary connection).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.whatsapp.phone_number_id', + 'type' => 'string', + 'storage_path' => 'alert_whatsapp.connections.0.phone_number_id', + 'required_if' => $req, + 'description' => __( 'WhatsApp business phone-number ID (primary connection).', 'wp-mail-smtp-pro' ), + ], + [ + 'flag' => 'alert.whatsapp.to_phone_number', + 'type' => 'string', + 'storage_path' => 'alert_whatsapp.connections.0.to_phone_number', + 'required_if' => $req, + 'description' => __( 'Recipient phone number, digits only, no spaces or symbols (primary connection).', 'wp-mail-smtp-pro' ), + ], + ]; + } + + /** + * Push-notification alert channel. + * + * Only the enable toggle is exposed; subscriptions are per-device (browser-only). + * + * @since 4.9.0 + * + * @return array + */ + private function alert_push_args() { + + return [ + [ + 'flag' => 'alert.push.enabled', + 'type' => 'bool', + 'storage_path' => 'alert_push_notifications.enabled', + 'description' => __( 'Enable browser push-notification alerts (subscriptions are added per-device from the admin UI).', 'wp-mail-smtp-pro' ), + ], + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Pro/activation.php b/web/app/plugins/wp-mail-smtp-pro/src/Pro/activation.php new file mode 100644 index 00000000..a802fe0b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Pro/activation.php @@ -0,0 +1,91 @@ + 'plugin', + 'slug' => 'wp-mail-smtp', + 'language' => $locale, + 'version' => WPMS_PLUGIN_VER, + 'package' => 'https://downloads.wordpress.org/translation/plugin/wp-mail-smtp/' . WPMS_PLUGIN_VER . '/' . $locale . '.zip', + 'autoupdate' => true, + ]; + } + + $upgrader = new Language_Pack_Upgrader( new Automatic_Upgrader_Skin() ); + + $upgrader->bulk_upgrade( $to_update ); + }, + 20 +); + +/** + * Arm bundled-license auto-activation. + * + * Only writes a pending flag locally, no HTTP. The remote verify runs + * later on an admin page load. + */ +add_action( + 'activate_' . plugin_basename( WPMS_PLUGIN_FILE ), + function () { + + ( new BundledLicense() )->set_pending_on_activation(); + }, + 20 +); + +/** + * Set default settings for Pro plugin. + * + * We need to apply this filter before the Lite activation hook + * and that's why it should be located here. + * + * @since 3.11.0 + */ +add_filter( + 'wp_mail_smtp_options_get_defaults', + function ( $defaults ) { + return array_merge( + $defaults, + [ + 'gmail' => [ + 'one_click_setup_enabled' => true, + ], + 'outlook' => [ + 'one_click_setup_enabled' => true, + ], + ] + ); + } +); diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Processor.php b/web/app/plugins/wp-mail-smtp-pro/src/Processor.php new file mode 100644 index 00000000..30129f90 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Processor.php @@ -0,0 +1,528 @@ +connections_manager = wp_mail_smtp()->get_connections_manager(); + } else { + $this->connections_manager = $connections_manager; + } + } + + /** + * Assign all hooks to proper places. + * + * @since 1.0.0 + */ + public function hooks() { + + add_action( 'phpmailer_init', [ $this, 'phpmailer_init' ] ); + + // High priority number tries to ensure our plugin code executes last and respects previous hooks, if not forced. + add_filter( 'wp_mail_from', [ $this, 'filter_mail_from_email' ], PHP_INT_MAX ); + add_filter( 'wp_mail_from_name', [ $this, 'filter_mail_from_name' ], PHP_INT_MAX ); + + add_action( 'wp_mail', [ $this, 'capture_early_wp_mail_filter_call' ], - PHP_INT_MAX ); + add_action( 'wp_mail', [ $this, 'capture_late_wp_mail_filter_call' ], PHP_INT_MAX ); + } + + /** + * Redefine certain PHPMailer options with our custom ones. + * + * @since 1.0.0 + * + * @param \PHPMailer $phpmailer It's passed by reference, so no need to return anything. + */ + public function phpmailer_init( $phpmailer ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $connection = $this->connections_manager->get_mail_connection(); + $connection_options = $connection->get_options(); + $mailer = $connection->get_mailer_slug(); + + // Check that mailer is not blank, and if mailer=smtp, host is not blank. + if ( + ! $mailer || + ( 'smtp' === $mailer && ! $connection_options->get( 'smtp', 'host' ) ) + ) { + return; + } + + // If the mailer is pepipost, make sure we have a username and password. + if ( + 'pepipost' === $mailer && + ( ! $connection_options->get( 'pepipost', 'user' ) && ! $connection_options->get( 'pepipost', 'pass' ) ) + ) { + return; + } + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + + // Set the mailer type as per config above, this overrides the already called isMail method. + // It's basically always 'smtp'. + $phpmailer->Mailer = $mailer; + + // Set the Sender (return-path) if required. + if ( $connection_options->get( 'mail', 'return_path' ) ) { + $phpmailer->Sender = $phpmailer->From; + } + + // Set the SMTPSecure value, if set to none, leave this blank. Possible values: 'ssl', 'tls', ''. + if ( 'none' === $connection_options->get( $mailer, 'encryption' ) ) { + $phpmailer->SMTPSecure = ''; + } else { + $phpmailer->SMTPSecure = $connection_options->get( $mailer, 'encryption' ); + } + + // Check if user has disabled SMTPAutoTLS. + if ( $connection_options->get( $mailer, 'encryption' ) !== 'tls' && ! $connection_options->get( $mailer, 'autotls' ) ) { + $phpmailer->SMTPAutoTLS = false; + } + + // Check if original WP from email can be set as the reply_to attribute. + if ( $this->allow_setting_original_from_email_to_reply_to( $phpmailer->getReplyToAddresses(), $mailer ) ) { + $phpmailer->addReplyTo( $this->wp_mail_from ); + } + + // If we're sending via SMTP, set the host. + if ( 'smtp' === $mailer ) { + // Set the other options. + $phpmailer->Host = $connection_options->get( $mailer, 'host' ); + $phpmailer->Port = $connection_options->get( $mailer, 'port' ); + + // If we're using smtp auth, set the username & password. + if ( $connection_options->get( $mailer, 'auth' ) ) { + $phpmailer->SMTPAuth = true; + $phpmailer->Username = $connection_options->get( $mailer, 'user' ); + $phpmailer->Password = $connection_options->get( $mailer, 'pass' ); + } + } elseif ( 'pepipost' === $mailer ) { + // Set the Pepipost settings for BC. + $phpmailer->Mailer = 'smtp'; + $phpmailer->Host = 'smtp.pepipost.com'; + $phpmailer->Port = $connection_options->get( $mailer, 'port' ); + $phpmailer->SMTPSecure = $connection_options->get( $mailer, 'encryption' ) === 'none' ? '' : $connection_options->get( $mailer, 'encryption' ); + $phpmailer->SMTPAuth = true; + $phpmailer->Username = $connection_options->get( $mailer, 'user' ); + $phpmailer->Password = $connection_options->get( $mailer, 'pass' ); + } + + $phpmailer->Timeout = 30; + // phpcs:enable + + // Maybe set default reply-to header. + $this->set_default_reply_to( $phpmailer ); + + // You can add your own options here. + // See the phpmailer documentation for more info: https://github.com/PHPMailer/PHPMailer/tree/5.2-stable. + /* @noinspection PhpUnusedLocalVariableInspection It's passed by reference. */ + $phpmailer = apply_filters( 'wp_mail_smtp_custom_options', $phpmailer ); + } + + /** + * Check if it's allowed to set the original WP from email to the reply_to field. + * + * @since 2.1.0 + * + * @param array $reply_to Array of currently set reply to emails. + * @param string $mailer The slug of current mailer. + * + * @return bool + */ + protected function allow_setting_original_from_email_to_reply_to( $reply_to, $mailer ) { + + $connection = $this->connections_manager->get_mail_connection(); + $connection_options = $connection->get_options(); + $forced = $connection_options->get( 'mail', 'from_email_force' ); + $from_email = $connection_options->get( 'mail', 'from_email' ); + + if ( ! empty( $reply_to ) || empty( $this->wp_mail_from ) ) { + return false; + } + + if ( in_array( $mailer, [ 'zoho' ], true ) ) { + $sender = $connection_options->get( $mailer, 'user_details' ); + $from_email = ! empty( $sender['email'] ) ? $sender['email'] : ''; + $forced = true; + } + + if ( + $from_email === $this->wp_mail_from || + ! $forced + ) { + return false; + } + + return true; + } + + /** + * Deprecated stub kept for backwards compatibility. + * + * Previously registered as PHPMailer's $action_function and dispatched per recipient + * via PHPMailer's doCallback(). The fan-out caused duplicate `wp_mail_smtp_mailcatcher_smtp_send_after` + * invocations and racing EmailSendingDebug writes on partial-recipient failures. + * + * Logic moved to MailCatcherTrait: doCallback() collects failed recipients, and + * smtp_send() fires the after-send action exactly once per email in its success + * and failure paths. + * + * No replacement. Kept for third-party code that may still reference the + * callable directly. Behavior is now a no-op. + * + * @since 1.3.0 + * @since 1.5.0 Added a do_action() to be able to hook into. + * @deprecated {VERSION} + * + * @param bool $is_sent If the email was sent. + * @param array $to To email address. + * @param array $cc CC email addresses. + * @param array $bcc BCC email addresses. + * @param string $subject The email subject. + * @param string $body The email body. + * @param string $from The from email address. + */ + public static function send_callback( $is_sent, $to, $cc, $bcc, $subject, $body, $from ) { } + + /** + * Validate the email address. + * + * @since 3.6.0 + * + * @param string $email The email address. + * + * @return boolean True if email address is valid, false on failure. + */ + public static function is_email_callback( $email ) { + + return (bool) is_email( $email ); + } + + /** + * Modify the email address that is used for sending emails. + * + * @since 1.0.0 + * @since 1.3.0 Forcing email rewrite if option is selected. + * @since 1.7.0 Default email may be empty, so pay attention to that as well. + * + * @param string $wp_email The email address passed by the filter. + * + * @return string + */ + public function filter_mail_from_email( $wp_email ) { + + // Save the original from address. + $this->filtered_from_email = filter_var( $wp_email, FILTER_VALIDATE_EMAIL ); + + $connection = $this->connections_manager->get_mail_connection(); + $connection_options = $connection->get_options(); + $forced = $connection_options->get( 'mail', 'from_email_force' ); + $from_email = $connection_options->get( 'mail', 'from_email' ); + $def_email = WP::get_default_email(); + + // Save the "original" set WP email from address for later use. + if ( $wp_email !== $def_email ) { + $this->wp_mail_from = filter_var( $wp_email, FILTER_VALIDATE_EMAIL ); + } + + // Return FROM EMAIL if forced in settings. + if ( $forced && ! empty( $from_email ) ) { + return $from_email; + } + + // If the FROM EMAIL is not the default, return it unchanged. + if ( ! empty( $def_email ) && $wp_email !== $def_email ) { + return $wp_email; + } + + return ! empty( $from_email ) ? $from_email : $wp_email; + } + + /** + * Modify the sender name that is used for sending emails. + * + * @since 1.0.0 + * @since 1.3.0 Forcing name rewrite if option is selected. + * + * @param string $name The from name passed through the filter. + * + * @return string + */ + public function filter_mail_from_name( $name ) { + + // Save the original from name. + $this->filtered_from_name = $name; + + $connection = $this->connections_manager->get_mail_connection(); + $connection_options = $connection->get_options(); + $force = $connection_options->get( 'mail', 'from_name_force' ); + + // If the FROM NAME is not the default and not forced, return it unchanged. + if ( ! $force && $name !== $this->get_default_name() ) { + return $name; + } + + $name = $connection_options->get( 'mail', 'from_name' ); + + return $name; + } + + /** + * Get the default email address based on domain name. + * + * @since 1.0.0 + * @since 1.7.0 May return an empty string. + * + * @return string Empty string when we aren't able to get the site domain (CLI, misconfigured server etc). + */ + public function get_default_email() { + + $server_name = Geo::get_site_domain(); + + if ( empty( $server_name ) ) { + return ''; + } + + // Get rid of www. + $sitename = strtolower( $server_name ); + if ( substr( $sitename, 0, 4 ) === 'www.' ) { + $sitename = substr( $sitename, 4 ); + } + + return 'wordpress@' . $sitename; + } + + /** + * Get the default email FROM NAME generated by WordPress. + * + * @since 1.3.0 + * + * @return string + */ + public function get_default_name() { + return 'WordPress'; + } + + /** + * Get or create the phpmailer. + * + * @since 1.9.0 + * + * @return MailCatcherInterface + */ + public function get_phpmailer() { + + global $phpmailer; + + // Make sure the PHPMailer class has been instantiated. + if ( ! is_object( $phpmailer ) || ! is_a( $phpmailer, 'PHPMailer' ) ) { + $phpmailer = wp_mail_smtp()->generate_mail_catcher( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } + + return $phpmailer; + } + + /** + * Set the default reply_to header, if: + * - no other reply_to headers are already set and, + * - the default reply_to address filter `wp_mail_smtp_processor_default_reply_to_addresses` is configured. + * + * @since 2.1.1 + * + * @param MailCatcherInterface $phpmailer The PHPMailer object. + */ + private function set_default_reply_to( $phpmailer ) { + + if ( ! empty( $phpmailer->getReplyToAddresses() ) ) { + return; + } + + $default_reply_to_emails = apply_filters( 'wp_mail_smtp_processor_set_default_reply_to', '' ); + + if ( empty( $default_reply_to_emails ) ) { + return; + } + + foreach ( explode( ',', $default_reply_to_emails ) as $email ) { + $email = trim( $email ); + + if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { + $phpmailer->addReplyTo( $email ); + } + } + } + + /** + * Capture `wp_mail` filter call on earliest priority. + * + * Currently used to capture the original `wp_mail` arguments before they are filtered. + * + * @since 4.0.0 + * + * @param array $args The original `wp_mail` arguments. + * + * @return array + */ + public function capture_early_wp_mail_filter_call( $args ) { + + $this->original_wp_mail_args = $args; + + return $args; + } + + /** + * Capture `wp_mail` filter call on latest priority. + * + * Currently used to capture the `wp_mail` arguments after they are filtered + * and capture `wp_mail` function call. + * + * @since 4.0.0 + * + * @param array $args The filtered `wp_mail` arguments. + * + * @return array + */ + public function capture_late_wp_mail_filter_call( $args ) { + + $this->filtered_wp_mail_args = $args; + + $this->capture_wp_mail_call(); + + return $args; + } + + /** + * Capture `wp_mail` function call. + * + * @since 4.0.0 + */ + private function capture_wp_mail_call() { + + /** + * Fires on `wp_mail` function call. + * + * @since 4.0.0 + */ + do_action( 'wp_mail_smtp_processor_capture_wp_mail_call' ); + } + + /** + * Get the original `wp_mail` arguments. + * + * @since 4.0.0 + * + * @return array + */ + public function get_original_wp_mail_args() { + + return $this->original_wp_mail_args; + } + + /** + * Get the filtered `wp_mail` arguments. + * + * @since 4.0.0 + * + * @return array + */ + public function get_filtered_wp_mail_args() { + + return $this->filtered_wp_mail_args; + } + + /** + * Get the filtered `wp_mail_from` value. + * + * @since 4.0.0 + * + * @return string + */ + public function get_filtered_from_email() { + + return $this->filtered_from_email; + } + + /** + * Get the filtered `wp_mail_from_name` value. + * + * @since 4.0.0 + * + * @return string + */ + public function get_filtered_from_name() { + + return $this->filtered_from_name; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/AmazonSES/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/AmazonSES/Options.php new file mode 100644 index 00000000..57d5c95e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/AmazonSES/Options.php @@ -0,0 +1,44 @@ + wp_mail_smtp()->assets_url . '/images/providers/aws.svg', + 'slug' => 'amazonses', + 'title' => esc_html__( 'Amazon SES', 'wp-mail-smtp' ), + 'disabled' => true, + ) + ); + } + + /** + * @inheritdoc + */ + public function display_options() { + + ?> +
    +

    + +

    +
    + connection = $connection; + } else { + $this->connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + } + + $this->connection_options = $this->connection->get_options(); + $this->mailer_slug = $this->connection->get_mailer_slug(); + } + + /** + * Use the composer autoloader to include the auth library and all dependencies. + * + * @since 1.0.0 + */ + protected function include_vendor_lib() { + + require_once wp_mail_smtp()->plugin_path . '/vendor/autoload.php'; + } + + /** + * Get the url, that users will be redirected back to finish the OAuth process. + * + * @since 1.0.0 + * + * @return string + */ + public static function get_plugin_auth_url() { + + return add_query_arg( 'tab', 'auth', wp_mail_smtp()->get_admin()->get_admin_page_url() ); + } + + /** + * Update auth code in our DB. + * + * @since 1.0.0 + * + * @param string $code + */ + protected function update_auth_code( $code ) { + + $all = $this->connection_options->get_all(); + + // To save in DB. + $all[ $this->mailer_slug ]['auth_code'] = $code; + + // To save in currently retrieved options array. + $this->options['auth_code'] = $code; + + // NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575! + $this->connection_options->set( $all, false, true ); + } + + /** + * Update Setup Wizard flag in our DB. + * + * @since 2.6.0 + * + * @param boolean $state A state (true/false) to set the is_setup_wizard_auth mailer setting. + */ + public function update_is_setup_wizard_auth( $state ) { + + $all = $this->connection_options->get_all(); + + // To save in DB. + $all[ $this->mailer_slug ]['is_setup_wizard_auth'] = (bool) $state; + + // To save in currently retrieved options array. + $this->options['is_setup_wizard_auth'] = (bool) $state; + + // NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575! + $this->connection_options->set( $all, false, true ); + } + + /** + * Update access token in our DB. + * + * @since 1.0.0 + * + * @param mixed $token + */ + protected function update_access_token( $token ) { + + $all = $this->connection_options->get_all(); + + // To save in DB. + $all[ $this->mailer_slug ]['access_token'] = $token; + + // To save in currently retrieved options array. + $this->options['access_token'] = $token; + + // NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575! + $this->connection_options->set( $all, false, true ); + } + + /** + * Update refresh token in our DB. + * + * @since 1.0.0 + * + * @param mixed $token + */ + protected function update_refresh_token( $token ) { + + $all = $this->connection_options->get_all(); + + // To save in DB. + $all[ $this->mailer_slug ]['refresh_token'] = $token; + + // To save in currently retrieved options array. + $this->options['refresh_token'] = $token; + + // NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575! + $this->connection_options->set( $all, false, true ); + } + + /** + * Update access token scopes in our DB. + * + * @since 3.4.0 + * + * @param array $scopes Scopes array. + */ + protected function update_scopes( $scopes ) { + + $all = $this->connection_options->get_all(); + + // To save in DB. + $all[ $this->mailer_slug ]['scopes'] = $scopes; + + // To save in currently retrieved options array. + $this->options['scopes'] = $scopes; + + // NOTE: These options need to be saved by overwriting all options, because WP automatic updates can cause an issue: GH #575! + $this->connection_options->set( $all, false, true ); + } + + /** + * Get state value that should be used for `state` parameter in OAuth authorization request. + * + * @since 3.7.0 + * + * @return string + */ + protected function get_state() { + + $state = [ + wp_create_nonce( $this->state_key ), + $this->connection->get_id(), + ]; + + return implode( '-', $state ); + } + + /** + * @inheritdoc + */ + public function is_clients_saved() { + + return ! empty( $this->options['client_id'] ) && ! empty( $this->options['client_secret'] ); + } + + /** + * @inheritdoc + */ + public function is_auth_required() { + + return empty( $this->options['access_token'] ) || empty( $this->options['refresh_token'] ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/AuthInterface.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/AuthInterface.php new file mode 100644 index 00000000..9802c857 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/AuthInterface.php @@ -0,0 +1,30 @@ +set_header( 'Accept', 'application/json' ); + $this->set_header( 'Content-Type', 'application/json' ); + $this->set_header( 'X-ElasticEmail-ApiKey', $this->connection_options->get( $this->mailer, 'api_key' ) ); + } + + /** + * Redefine the way custom headers are processed for this mailer - they should be in body. + * + * @since 4.3.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.3.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; + } + + $this->set_body_param( + [ + 'Content' => [ + 'Headers' => [ + $name => $this->sanitize_header_value( $name, $value ), + ], + ], + ] + ); + } + + /** + * Set the From information for an email. + * + * @since 4.3.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( + [ + 'Content' => [ + 'From' => $this->address_format( [ $email, $name ] ), + ], + ] + ); + } + + /** + * Set email recipients: to, cc, bcc. + * + * @since 4.3.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. + $recipient_mappings = [ + 'to' => 'To', + 'cc' => 'CC', + 'bcc' => 'BCC', + ]; + + $allowed_types = array_keys( $recipient_mappings ); + $data = []; + + foreach ( $recipients as $type => $emails ) { + if ( + ! in_array( $type, $allowed_types, true ) || + empty( $emails ) || + ! is_array( $emails ) + ) { + continue; + } + + $field = $recipient_mappings[ $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[ $field ][] = $this->address_format( $email ); + } + } + + if ( ! empty( $data ) ) { + $this->set_body_param( + [ + 'Recipients' => $data, + ] + ); + } + } + + /** + * Set the Reply To information for an email. + * + * @since 4.3.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( + [ + 'Content' => [ + 'ReplyTo' => $data[0], + ], + ] + ); + } + } + + /** + * Set email subject. + * + * @since 4.3.0 + * + * @param string $subject Email subject. + */ + public function set_subject( $subject ) { + + $this->set_body_param( + [ + 'Content' => [ + 'Subject' => $subject, + ], + ] + ); + } + + /** + * Set email content. + * + * @since 4.3.0 + * + * @param string|array $content Email content. + */ + public function set_content( $content ) { + + if ( empty( $content ) ) { + return; + } + + $data = []; + + if ( is_array( $content ) ) { + if ( ! empty( $content['text'] ) ) { + $data[] = [ + 'ContentType' => 'PlainText', + 'Content' => $content['text'], + ]; + } + + if ( ! empty( $content['html'] ) ) { + $data[] = [ + 'ContentType' => 'HTML', + 'Content' => $content['html'], + ]; + } + } else { + if ( $this->phpmailer->ContentType === 'text/plain' ) { + $data[] = [ + 'ContentType' => 'PlainText', + 'Content' => $content, + ]; + } else { + $data[] = [ + 'ContentType' => 'HTML', + 'Content' => $content, + ]; + } + } + + $this->set_body_param( + [ + 'Content' => [ + 'Body' => $data, + ], + ] + ); + } + + /** + * Set attachments for an email. + * + * @since 4.3.0 + * + * @param array $attachments Attachments array. + */ + public function set_attachments( $attachments ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( empty( $attachments ) ) { + return; + } + + $data = $this->prepare_attachments( $attachments ); + + if ( ! empty( $data ) ) { + $this->set_body_param( + [ + 'Content' => [ + 'Attachments' => $data, + ], + ] + ); + } + } + + /** + * Prepare attachments data for SendLayer API. + * + * @since 4.3.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[] = [ + 'Name' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'BinaryContent' => base64_encode( $file ), + 'ContentType' => $attachment[4], + ]; + } + + return $data; + } + + /** + * Doesn't support this. + * So we do nothing. + * + * @since 4.3.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. + * ElasticEmail requires a JSON, so we encode the body. + * + * @since 4.3.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.3.0 + * + * @param mixed $response Response data. + */ + protected function process_response( $response ) { + + parent::process_response( $response ); + + if ( + ! is_wp_error( $response ) && + ! empty( $this->response['body'] ) && + ! empty( $this->response['body']->TransactionID ) + ) { + $this->phpmailer->addCustomHeader( 'X-Msg-ID', $this->response['body']->TransactionID ); + $this->verify_sent_status = true; + } + } + + /** + * Whether the email is sent or not. + * We check response code and a non-empty `TransactionID` field in the response body. + * + * @since 4.3.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'] ) && + ! empty( $this->response['body']->TransactionID ) + ) { + $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 an Elastic Email specific response with a helpful error. + * + * @since 4.3.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 ); + + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + if ( ! empty( $body->Error ) ) { + $error_text[] = Helpers::format_error_message( $body->Error ); + } 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 ) ) ); + } + + /** + * Whether the mailer has all its settings correctly set up and saved. + * + * @since 4.3.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 4.3.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/ElasticEmail/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/ElasticEmail/Options.php new file mode 100644 index 00000000..65667632 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/ElasticEmail/Options.php @@ -0,0 +1,132 @@ +get_connections_manager()->get_primary_connection(); + } + + $description = sprintf( + wp_kses( /* translators: %1$s - URL to ElasticEmail.com site. */ + __( 'Elastic Email is a cloud-based email marketing platform offering tools for email campaigns, automation, transactional emails, and analytics, designed for businesses of all sizes.

    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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_ELASTICEMAIL_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + + +

    + ' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + mailer_slug !== Options::SLUG ) { + return; + } + + $this->options = $this->connection_options->get_group( $this->mailer_slug ); + + if ( wp_mail_smtp()->is_pro() && ! empty( $this->options['one_click_setup_enabled'] ) ) { + return; + } + + if ( $this->is_clients_saved() ) { + $this->include_vendor_lib(); + + $this->client = $this->get_client(); + } + } + + /** + * Get the url, that users will be redirected back to finish the OAuth process. + * + * @since 1.5.2 Returned to the old, pre-1.5, structure of the link to preserve BC. + * + * @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(); + } + + $auth_url = apply_filters( + 'wp_mail_smtp_gmail_get_plugin_auth_url', + add_query_arg( + [ + 'page' => Area::SLUG, + 'tab' => 'auth', + ], + admin_url( 'options-general.php' ) + ) + ); + + return add_query_arg( 'state', self::get_state_param( $connection ), $auth_url ); + } + + /** + * Init and get the Google Client object. + * + * @since 1.0.0 + * @since 1.5.0 Add ability to apply custom options to the client via a filter. + * + * @param bool $force If the client should be forcefully reinitialized. + * + * @return Google_Client + */ + 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(); + + $client = new Google_Client( + [ + 'client_id' => $this->options['client_id'], + 'client_secret' => $this->options['client_secret'], + 'redirect_uris' => [ + self::get_oauth_redirect_url(), + ], + ] + ); + $client->setApplicationName( 'WP Mail SMTP v' . WPMS_PLUGIN_VER ); + $client->setAccessType( 'offline' ); + $client->setPrompt( 'consent' ); + $client->setIncludeGrantedScopes( false ); + // We request only the sending capability, as it's what we only need to do. + $client->setScopes( [ Gmail::MAIL_GOOGLE_COM ] ); + $client->setRedirectUri( self::get_oauth_redirect_url() ); + + // Set our custom logger to replace Monolog dependency. + $client->setLogger( new Logger() ); + + if ( self::use_self_oauth_redirect_url() ) { + $client->setState( self::get_state_param( $this->connection ) ); + } else { + $client->setState( self::get_plugin_auth_url( $this->connection ) ); + } + + // Apply custom options to the client. + $client = apply_filters( 'wp_mail_smtp_providers_gmail_auth_get_client_custom_options', $client ); + + $this->client = $client; + + if ( ! empty( $this->options['access_token'] ) ) { + $this->client->setAccessToken( $this->options['access_token'] ); + + // Refresh expired token if needed. + if ( $this->client->isAccessTokenExpired() ) { + $this->refresh_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 Google. + * + * @return WP_Error|true + */ + private function obtain_access_token( $code ) { + + if ( empty( $code ) ) { + return true; + } + + try { + $creds = $this->client->fetchAccessTokenWithAuthCode( $code ); + } catch ( Exception $e ) { + $creds = [ 'error' => $e->getMessage() ]; + } + + if ( ! empty( $creds['error'] ) ) { + $error_message = $creds['error']; + + if ( $error_message === 'invalid_client' ) { + $error_message .= PHP_EOL . esc_html__( 'Please make sure your Google Client ID and Secret in the plugin settings are valid. Save the settings and try the Authorization again.', 'wp-mail-smtp' ); + } + + return new WP_Error( 'gmail_auth_failed', $error_message ); + } + + $this->update_access_token( $this->client->getAccessToken() ); + $this->update_refresh_token( $this->client->getRefreshToken() ); + $this->update_user_details( $this->client ); + + // Update the "from email" to the connected user's email. + if ( ! empty( $this->options['user_details']['email'] ) ) { + $this->connection_options->set( + [ + 'mail' => [ + 'from_email' => $this->options['user_details']['email'], + ], + ], + false, + false + ); + } + + return true; + } + + /** + * Refresh expired access token. + * + * Reads the refresh token from the SDK client's internal state (set by an earlier + * setAccessToken call) or from options as a fallback. Bails silently when no + * refresh token is available — the user will need to re-authorize. + * + * @since 4.9.0 + */ + private function refresh_access_token() { + + $refresh = $this->client->getRefreshToken(); + + if ( empty( $refresh ) && isset( $this->options['refresh_token'] ) ) { + $refresh = $this->options['refresh_token']; + } + + if ( empty( $refresh ) ) { + return; + } + + try { + $creds = $this->client->fetchAccessTokenWithRefreshToken( $refresh ); + } catch ( Exception $e ) { + $creds = [ 'error' => $e->getMessage() ]; + } + + if ( ! empty( $creds['error'] ) ) { + DebugEvents::add_throttled( + 'Mailer: Gmail' . WP::EOL . $creds['error'], + 'gmail_refresh_error_' . $this->connection->get_id() + ); + + return; + } + + $this->update_access_token( $this->client->getAccessToken() ); + $this->update_refresh_token( $this->client->getRefreshToken() ); + } + + /** + * Get the auth code from the $_GET and save it. + * Redirect user back to settings with an error message, if failed. + * + * @since 1.0.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/gmail'; + } + + if ( ! ( isset( $_GET['tab'] ) && $_GET['tab'] === 'auth' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + 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 that should be returned in the state parameter. + if ( ! wp_verify_nonce( $nonce, $this->state_key ) ) { + wp_safe_redirect( + add_query_arg( + 'error', + 'google_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', + 'google_no_clients', + $redirect_url + ) + ); + exit; + } + + $this->include_vendor_lib(); + + $code = ''; + $scope = ''; + $error = ''; + + if ( isset( $_GET['error'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $error = sanitize_key( $_GET['error'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + // In case of any error: display a message to a user. + if ( ! empty( $error ) ) { + DebugEvents::add_debug( + sprintf( /* Translators: %s the error code passed from Google. */ + esc_html__( 'There was an error while processing Google authorization: %s', 'wp-mail-smtp' ), + esc_html( $error ) + ) + ); + + wp_safe_redirect( + add_query_arg( + 'error', + 'google_' . $error, + $redirect_url + ) + ); + exit; + } + + if ( isset( $_GET['code'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $code = urldecode( $_GET['code'] ); + } + + if ( isset( $_GET['scope'] ) ) { + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $scope = $_GET['scope']; + + if ( self::use_self_oauth_redirect_url() ) { + $scope = urldecode( $scope ); + } else { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + $scope = urldecode( base64_decode( $scope ) ); + } + } + + // Validate code + scope. + $scope_is_valid = ( + $scope === Gmail::MAIL_GOOGLE_COM . ' ' . Gmail::GMAIL_SEND || + $scope === Gmail::GMAIL_SEND . ' ' . Gmail::MAIL_GOOGLE_COM || + $scope === Gmail::GMAIL_SEND || + $scope === Gmail::MAIL_GOOGLE_COM + ); + + if ( empty( $code ) || ! $scope_is_valid ) { + DebugEvents::add_debug( + esc_html__( 'There was an error while processing Google authorization: missing code or scope parameter.', 'wp-mail-smtp' ) + ); + + wp_safe_redirect( + add_query_arg( + 'error', + 'google_no_code_scope', + $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: Gmail' . WP::EOL . $result->get_error_message() ); + + wp_safe_redirect( + add_query_arg( + [ + 'error' => 'google_unsuccessful_oauth', + 'debug_event_id' => $event_id, + ], + $redirect_url + ) + ); + exit; + } + + wp_safe_redirect( + add_query_arg( + 'success', + 'google_site_linked', + $redirect_url + ) + ); + exit; + } + + /** + * Get the auth URL used to proceed to Provider to request access to send emails. + * + * @since 1.0.0 + * + * @return string + */ + public function get_auth_url() { + + if ( + ! empty( $this->client ) && + class_exists( 'WPMailSMTP\Vendor\Google_Client', false ) && + $this->client instanceof Google_Client + ) { + return filter_var( $this->client->createAuthUrl(), FILTER_SANITIZE_URL ); + } + + return '#'; + } + + /** + * Get and update user-related details (currently only email). + * + * @since 3.11.0 + * + * @param Google_Client $client The Google Client object (optional). + */ + private function update_user_details( $client = false ) { + + if ( $client === false ) { + $client = $this->get_client(); + } + + $gmail = new Gmail( $client ); + + try { + $email = $gmail->users->getProfile( 'me' )->getEmailAddress(); + + $user_details = [ + 'email' => $email, + ]; + + // To save in DB. + $updated_settings = [ + $this->mailer_slug => [ + 'user_details' => $user_details, + ], + ]; + + // To save in currently retrieved options array. + $this->options['user_details'] = $user_details; + + $this->connection_options->set( $updated_settings, false, false ); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing. + } + } + + /** + * Get user information (currently only email) that is associated with the current OAuth connection. + * + * @since 1.5.0 + * @since 3.11.0 Switched to DB stored value instead of API call. + * + * @return array + */ + public function get_user_info() { + + /* + * We need to populate user data on the fly for old users who already performed + * authorization before we switched to DB stored value. + */ + if ( ! isset( $this->options['user_details'] ) && ! $this->is_auth_required() ) { + $this->update_user_details(); + } + + return $this->connection_options->get( $this->mailer_slug, 'user_details' ); + } + + /** + * Get the registered email addresses that the user can use as the "from email". + * + * @since 2.2.0 + * + * @return array The list of possible from email addresses. + */ + public function get_user_possible_send_from_addresses() { + + if ( isset( $this->aliases ) ) { + return $this->aliases; + } + + $gmail = new Gmail( $this->get_client() ); + + try { + + // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $response = $gmail->users_settings_sendAs->listUsersSettingsSendAs( 'me' ); + + // phpcs:disable + $this->aliases = array_map( + function( $sendAsObject ) { + return $sendAsObject['sendAsEmail']; + }, + (array) $response->getSendAs() + ); + // phpcs:enable + + } catch ( Exception $exception ) { + DebugEvents::add_debug( + sprintf( /* Translators: %s the error message. */ + esc_html__( 'An error occurred when trying to get Gmail aliases: %s', 'wp-mail-smtp' ), + esc_html( $exception->getMessage() ) + ) + ); + + $this->aliases = []; + } + + return $this->aliases; + } + + /** + * Get the Google oAuth 2.0 redirect URL. + * + * This is the URL that Google will redirect after the access to the Gmail account is granted or rejected. + * The below endpoint will then redirect back to the user's WP site (to self::get_plugin_auth_url() URL). + * + * @since 2.5.0 + * + * @return string + */ + public static function get_oauth_redirect_url() { + + if ( self::use_self_oauth_redirect_url() ) { + return remove_query_arg( 'state', self::get_plugin_auth_url() ); + } else { + return 'https://connect.wpmailsmtp.com/google/'; + } + } + + /** + * Get the state parameter for the Google oAuth redirect URL. + * + * @since 3.10.0 + * + * @param ConnectionInterface $connection The Connection object. + * + * @return string + */ + private static function get_state_param( $connection ) { + + $state = [ + wp_create_nonce( 'wp_mail_smtp_provider_client_state' ), + $connection->get_id(), + ]; + + return implode( '-', $state ); + } + + /** + * Whether to use self website redirect URL for the Google oAuth. + * + * @since 3.10.0 + * + * @return bool + */ + private static function use_self_oauth_redirect_url() { + + /** + * Filter whether to use self website redirect URL for the Google oAuth. + * + * @since 3.10.0 + * + * @param bool $use Whether to use self website redirect URL for the Google oAuth. + */ + return apply_filters( 'wp_mail_smtp_providers_gmail_auth_use_self_oauth_redirect_url', false ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Logger.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Logger.php new file mode 100644 index 00000000..774c46f6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Logger.php @@ -0,0 +1,175 @@ +log( LogLevel::EMERGENCY, $message, $context ); + } + + /** + * Action must be taken immediately. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function alert( $message, array $context = [] ) { + + $this->log( LogLevel::ALERT, $message, $context ); + } + + /** + * Critical conditions. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function critical( $message, array $context = [] ) { + + $this->log( LogLevel::CRITICAL, $message, $context ); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function error( $message, array $context = [] ) { + + $this->log( LogLevel::ERROR, $message, $context ); + } + + /** + * Exceptional occurrences that are not errors. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function warning( $message, array $context = [] ) { + + $this->log( LogLevel::WARNING, $message, $context ); + } + + /** + * Normal but significant events. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function notice( $message, array $context = [] ) { + + $this->log( LogLevel::NOTICE, $message, $context ); + } + + /** + * Interesting events. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function info( $message, array $context = [] ) { + + $this->log( LogLevel::INFO, $message, $context ); + } + + /** + * Detailed debug information. + * + * @since 4.7.0 + * + * @param string $message The log message. + * @param array $context The log context. + */ + public function debug( $message, array $context = [] ) { + + $this->log( LogLevel::DEBUG, $message, $context ); + } + + /** + * Logs with an arbitrary level. + * + * @since 4.7.0 + * + * @param mixed $level The log level. + * @param string $message The log message. + * @param array $context The log context. + */ + public function log( $level, $message, array $context = [] ) { + + // Only log errors and warnings to avoid spam. + if ( ! in_array( $level, [ LogLevel::ERROR, LogLevel::WARNING, LogLevel::CRITICAL, LogLevel::EMERGENCY, LogLevel::ALERT ], true ) ) { + return; + } + + // Interpolate context values into the message placeholders. + $message = $this->interpolate( $message, $context ); + + // Format the log message. + $formatted_message = sprintf( + '[%s] Gmail API: %s', + strtoupper( $level ), + $message + ); + + DebugEvents::add( $formatted_message ); + } + + /** + * Interpolates context values into the message placeholders. + * + * @since 4.7.0 + * + * @param string $message The message with placeholders. + * @param array $context The context array. + * + * @return string The interpolated message. + */ + private function interpolate( $message, array $context = [] ) { + + // Build a replacement array with braces around the context keys. + $replace = []; + + foreach ( $context as $key => $val ) { + // Check that the value can be cast to string. + if ( ! is_array( $val ) && ( ! is_object( $val ) || method_exists( $val, '__toString' ) ) ) { + $replace[ '{' . $key . '}' ] = $val; + } + } + + // Interpolate replacement values into the message and return. + return strtr( $message, $replace ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Mailer.php new file mode 100644 index 00000000..7607a988 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Gmail/Mailer.php @@ -0,0 +1,303 @@ +is_valid_phpmailer( $phpmailer ) ) { + return; + } + + $this->phpmailer = $phpmailer; + } + + /** + * Use Google API Services to send emails. + * + * @since 1.0.0 + */ + public function send() { + + // Include the Google library. + require_once wp_mail_smtp()->plugin_path . '/vendor/autoload.php'; + + $auth = new Auth( $this->connection ); + $message = new Message(); + + // Set the authorized Gmail email address as the "from email" if the set email is not on the list of aliases. + $possible_from_emails = $auth->get_user_possible_send_from_addresses(); + + if ( ! in_array( $this->phpmailer->From, $possible_from_emails, true ) ) { + $user_info = $auth->get_user_info(); + + if ( ! empty( $user_info['email'] ) ) { + $this->phpmailer->From = $user_info['email']; + $this->phpmailer->Sender = $user_info['email']; + } + } + + try { + // 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( + [ '+', '/', '=' ], + [ '-', '_', '' ], + base64_encode( $this->phpmailer->getSentMIMEMessage() ) //phpcs:ignore + ); + + $message->setRaw( $base64 ); + + $service = new Gmail( $auth->get_client() ); + $response = $service->users_messages->send( 'me', $message ); + + DebugEvents::add_debug( + esc_html__( 'An email request was sent to the Gmail API.', 'wp-mail-smtp' ) + ); + + $this->process_response( $response ); + } catch ( \Exception $e ) { + $this->error_message = $this->process_exception_message( $e->getMessage() ); + + // Extract error code from Google API JSON response or exception code. + $error_data = json_decode( $e->getMessage() ); + + if ( ! empty( $error_data->error ) && is_string( $error_data->error ) ) { + $this->error_code = $error_data->error; + } elseif ( $e->getCode() ) { + $this->error_code = $e->getCode(); + } + } + } + + /** + * Save response from the API to use it later. + * + * @since 1.0.0 + * @since 1.5.0 Added action "wp_mail_smtp_providers_gmail_mailer_process_response" with $response. + * + * @param Message $response Instance of Gmail response. + */ + protected function process_response( $response ) { + + $this->response = $response; + + if ( empty( $this->response ) || ! method_exists( $this->response, 'getId' ) ) { + $this->error_message = esc_html__( 'The response object is invalid (missing getId method).', 'wp-mail-smtp' ); + } else { + $message_id = $this->response->getId(); + + if ( empty( $message_id ) ) { + $this->error_message = esc_html__( 'The email message ID is missing.', 'wp-mail-smtp' ); + } + } + + do_action( 'wp_mail_smtp_providers_gmail_mailer_process_response', $this->response, $this->phpmailer ); + } + + /** + * Check whether the email was sent. + * + * @since 1.0.0 + * + * @return bool + */ + public function is_email_sent() { + + $is_sent = false; + + if ( + ! empty( $this->response ) && + method_exists( $this->response, 'getId' ) && + ! empty( $this->response->getId() ) + ) { + $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 ); + } + + /** + * 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() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $gmail_text = array(); + + $gmail = $this->connection_options->get_group( 'gmail' ); + $curl_ver = 'No'; + if ( function_exists( 'curl_version' ) ) { + $curl = curl_version(); + $curl_ver = $curl['version']; + } + + $gmail_text[] = 'Client ID/Secret: ' . ( ! empty( $gmail['client_id'] ) && ! empty( $gmail['client_secret'] ) ? 'Yes' : 'No' ); + $gmail_text[] = 'Auth Code: ' . ( ! empty( $gmail['auth_code'] ) ? 'Yes' : 'No' ); + $gmail_text[] = 'Access Token: ' . ( ! empty( $gmail['access_token'] ) ? 'Yes' : 'No' ); + + $gmail_text[] = '
    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() ) : ?> +
    +
    + +
    +
    + 'wp-mail-smtp-setting-' . esc_attr( $this->get_slug() ) . '-one_click_setup_enabled-lite', + ] + ); + ?> +

    + +

    +
    +
    + + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_id' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-client_id" spellcheck="false" + /> +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'client_secret' ) ) : ?> + + display_const_set_message( 'WPMS_GMAIL_CLIENT_SECRET' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'client_secret' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][client_secret]", + 'id' => "wp-mail-smtp-setting-{$slug}-client_secret", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Client Secret', 'wp-mail-smtp' ), + ] + ); + ?> + +
    +
    + + +
    +
    + +
    +
    + + +

    + +

    +
    +
    + + +
    +
    + +
    +
    + display_auth_setting_action(); ?> +
    +
    + + process_provider_remove(); + + $auth = new Auth( $this->connection ); + ?> + + is_clients_saved() ) : ?> + + is_auth_required() ) : ?> + + + + +

    + +

    + + + + + + + + get_user_info(); + + if ( ! empty( $user['email'] ) ) { + printf( + /* translators: %s - email address, as received from Google API. */ + esc_html__( 'Connected as %s', 'wp-mail-smtp' ), + '' . esc_html( $user['email'] ) . '' + ); + } + ?> + +

    + 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' ) ) + ); + ?> +

    +

    + +

    +

    + +

    + + + + + +

    + +

    + + get_capability_manage_global_options() ) ) { + return; + } + + if ( + ! isset( $_GET['gmail_remove_nonce'] ) || + ! wp_verify_nonce( sanitize_key( $_GET['gmail_remove_nonce'] ), 'gmail_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() ]['access_token'] ); + unset( $old_opt[ $this->get_slug() ]['refresh_token'] ); + unset( $old_opt[ $this->get_slug() ]['user_details'] ); + unset( $old_opt[ $this->get_slug() ]['auth_code'] ); + + $this->connection_options->set( $old_opt ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Loader.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Loader.php new file mode 100644 index 00000000..ac3d9ace --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Loader.php @@ -0,0 +1,244 @@ + 'WPMailSMTP\Providers\Mail\\', + 'sendlayer' => 'WPMailSMTP\Providers\Sendlayer\\', + 'smtpcom' => 'WPMailSMTP\Providers\SMTPcom\\', + 'sendinblue' => 'WPMailSMTP\Providers\Sendinblue\\', + 'amazonses' => 'WPMailSMTP\Providers\AmazonSES\\', + 'elasticemail' => 'WPMailSMTP\Providers\ElasticEmail\\', + 'gmail' => 'WPMailSMTP\Providers\Gmail\\', + 'mailgun' => 'WPMailSMTP\Providers\Mailgun\\', + 'mailjet' => 'WPMailSMTP\Providers\Mailjet\\', + 'mailersend' => 'WPMailSMTP\Providers\MailerSend\\', + 'mandrill' => 'WPMailSMTP\Providers\Mandrill\\', + 'outlook' => 'WPMailSMTP\Providers\Outlook\\', + 'pepipostapi' => 'WPMailSMTP\Providers\PepipostAPI\\', + 'postmark' => 'WPMailSMTP\Providers\Postmark\\', + 'resend' => 'WPMailSMTP\Providers\Resend\\', + 'sendgrid' => 'WPMailSMTP\Providers\Sendgrid\\', + 'smtp2go' => 'WPMailSMTP\Providers\SMTP2GO\\', + 'sparkpost' => 'WPMailSMTP\Providers\SparkPost\\', + 'zoho' => 'WPMailSMTP\Providers\Zoho\\', + 'smtp' => 'WPMailSMTP\Providers\SMTP\\', + 'pepipost' => 'WPMailSMTP\Providers\Pepipost\\', + ]; + + /** + * Get all the supported providers. + * + * @since 1.0.0 + * + * @return array + */ + public function get_providers() { + + if ( ! Options::init()->is_mailer_active( 'pepipost' ) ) { + unset( $this->providers['pepipost'] ); + } + + if ( ! Options::init()->is_mailer_active( 'pepipostapi' ) ) { + unset( $this->providers['pepipostapi'] ); + } + + return apply_filters( 'wp_mail_smtp_providers_loader_get_providers', $this->providers ); + } + + /** + * Get a single provider FQN-path based on its name. + * + * @since 1.0.0 + * + * @param string $provider + * + * @return string|null + */ + public function get_provider_path( $provider ) { + + $provider = sanitize_key( $provider ); + + $providers = $this->get_providers(); + + return apply_filters( + 'wp_mail_smtp_providers_loader_get_provider_path', + isset( $providers[ $provider ] ) ? $providers[ $provider ] : null, + $provider + ); + } + + /** + * Get the provider options, if exists. + * + * @since 1.0.0 + * + * @param string $provider + * @param ConnectionInterface $connection The Connection object. + * + * @return OptionsAbstract|null + */ + public function get_options( $provider, $connection = null ) { + + return $this->get_entity( $provider, 'Options', [ $connection ] ); + } + + /** + * Get all options of all providers. + * + * @since 1.0.0 + * + * @param ConnectionInterface $connection The Connection object. + * + * @return OptionsAbstract[] + */ + public function get_options_all( $connection = null ) { + + $options = []; + + foreach ( $this->get_providers() as $provider => $path ) { + + $option = $this->get_options( $provider, $connection ); + + if ( ! $option instanceof OptionsAbstract ) { + continue; + } + + $slug = $option->get_slug(); + $title = $option->get_title(); + + if ( empty( $title ) || empty( $slug ) ) { + continue; + } + + $options[] = $option; + } + + return apply_filters( 'wp_mail_smtp_providers_loader_get_providers_all', $options ); + } + + /** + * Get the provider mailer, if exists. + * + * @since 1.0.0 + * + * @param string $provider The provider name. + * @param MailCatcherInterface $phpmailer The MailCatcher object. + * @param ConnectionInterface $connection The Connection object. + * + * @return MailerAbstract|null + */ + public function get_mailer( $provider, $phpmailer, $connection = null ) { + + return $this->get_entity( $provider, 'Mailer', [ $phpmailer, $connection ] ); + } + + /** + * Get the provider auth, if exists. + * + * @param string $provider + * @param ConnectionInterface $connection The Connection object. + * + * @return AuthAbstract|null + */ + public function get_auth( $provider, $connection = null ) { + + return $this->get_entity( $provider, 'Auth', [ $connection ] ); + } + + /** + * Get a generic entity based on the request. + * + * @since 1.0.0 + * + * @param string $provider + * @param string $request + * @param array $args Entity instantiation arguments. + * + * @return OptionsAbstract|MailerAbstract|AuthAbstract|null + * @uses \ReflectionClass + * + */ + protected function get_entity( $provider, $request, $args = [] ) { + + $provider = sanitize_key( $provider ); + $request = sanitize_text_field( $request ); + $path = $this->get_provider_path( $provider ); + $entity = null; + + if ( empty( $path ) ) { + return $entity; + } + + try { + $reflection = new \ReflectionClass( $path . $request ); + + if ( file_exists( $reflection->getFileName() ) ) { + $class = $path . $request; + $entity = new $class( ...$args ); + } + } catch ( \Exception $e ) { + DebugEvents::add( "There was a problem while retrieving {$request} for {$provider}: {$e->getMessage()}" ); + $entity = null; + } + + return apply_filters( 'wp_mail_smtp_providers_loader_get_entity', $entity, $provider, $request, $args ); + } + + /** + * Get supports options for all mailers. + * + * @since 2.3.0 + * + * @return array + */ + public function get_supports_all() { + + $supports = []; + + foreach ( $this->get_providers() as $provider => $path ) { + $option = $this->get_options( $provider ); + + if ( ! $option instanceof OptionsAbstract ) { + continue; + } + + $mailer_slug = $option->get_slug(); + $mailer_supports = $option->get_supports(); + + if ( empty( $mailer_slug ) || empty( $mailer_supports ) ) { + continue; + } + + $supports[ $mailer_slug ] = $mailer_supports; + } + + return apply_filters( 'wp_mail_smtp_providers_loader_get_supports_all', $supports ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Mailer.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Mailer.php new file mode 100644 index 00000000..51654bfc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mail/Mailer.php @@ -0,0 +1,49 @@ +Server:'; + + $disabled_functions = ini_get( 'disable_functions' ); + $disabled = (array) explode( ',', trim( $disabled_functions ) ); + + $mail_text[] = 'PHP.mail(): ' . ( in_array( 'mail', $disabled, true ) || ! function_exists( 'mail' ) ? 'No' : 'Yes' ); + if ( function_exists( 'apache_get_modules' ) ) { + $modules = apache_get_modules(); + $mail_text[] = 'Apache.mod_security: ' . ( in_array( 'mod_security', $modules, true ) || in_array( 'mod_security2', $modules, true ) ? 'Yes' : 'No' ); + } + if ( function_exists( 'selinux_is_enabled' ) ) { + $mail_text[] = 'OS.SELinux: ' . ( selinux_is_enabled() ? 'Yes' : 'No' ); + } + if ( function_exists( 'grsecurity_is_enabled' ) ) { + $mail_text[] = 'OS.grsecurity: ' . ( grsecurity_is_enabled() ? 'Yes' : 'No' ); + } + + return implode( '
    ', $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'; + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_MAILERSEND_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + ' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[' . $this->get_slug() . '][has_pro_plan]', + 'id' => 'wp-mail-smtp-setting-' . $this->get_slug() . '-has_pro_plan', + 'value' => 'true', + 'checked' => (bool) $this->connection_options->get( $this->get_slug(), 'has_pro_plan' ), + 'disabled' => $this->connection_options->is_const_defined( $this->get_slug(), 'has_pro_plan' ), + ] + ); + ?> +

    + ' . + esc_html__( 'pricing page', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + 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; + } + + /* + * Append the url with a domain, + * to avoid passing the domain name as a query parameter with all requests. + */ + $this->url .= sanitize_text_field( $this->connection_options->get( $this->mailer, 'domain' ) . '/messages' ); + + $this->set_header( 'Authorization', 'Basic ' . base64_encode( 'api:' . $this->connection_options->get( $this->mailer, 'api_key' ) ) ); + } + + /** + * @inheritdoc + */ + public function set_from( $email, $name = '' ) { + + if ( ! filter_var( $email, FILTER_VALIDATE_EMAIL ) ) { + return; + } + + if ( ! empty( $name ) ) { + $this->set_body_param( + array( + 'from' => $name . ' <' . $email . '>', + ) + ); + } else { + $this->set_body_param( + array( + 'from' => $email, + ) + ); + } + } + + /** + * @inheritdoc + */ + public function set_recipients( $recipients ) { + + if ( empty( $recipients ) ) { + return; + } + + $default = array( 'to', 'cc', 'bcc' ); + + foreach ( $recipients as $kind => $emails ) { + if ( + ! in_array( $kind, $default, true ) || + empty( $emails ) || + ! is_array( $emails ) + ) { + continue; + } + + $data = array(); + + foreach ( $emails as $email ) { + $addr = isset( $email[0] ) ? $email[0] : false; + $name = isset( $email[1] ) ? $email[1] : false; + + if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) { + continue; + } + + if ( ! empty( $name ) ) { + $data[] = $name . ' <' . $addr . '>'; + } else { + $data[] = $addr; + } + } + + if ( ! empty( $data ) ) { + $this->set_body_param( + array( + $kind => implode( ', ', $data ), + ) + ); + } + } + } + + /** + * @inheritdoc + */ + public function set_content( $content ) { + + if ( is_array( $content ) ) { + + $default = array( 'text', 'html' ); + + foreach ( $content as $type => $mail ) { + if ( + ! in_array( $type, $default, true ) || + empty( $mail ) + ) { + continue; + } + + $this->set_body_param( + array( + $type => $mail, + ) + ); + } + } else { + + $type = 'html'; + + if ( $this->phpmailer->ContentType === 'text/plain' ) { + $type = 'text'; + } + + if ( ! empty( $content ) ) { + $this->set_body_param( + array( + $type => $content, + ) + ); + } + } + } + + /** + * Redefine the way custom headers are process 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 with a special prefix "h:". + * + * @since 1.5.0 + */ + public function set_body_header( $name, $value ) { + + $name = sanitize_text_field( $name ); + + $this->set_body_param( + array( + 'h:' . $name => $this->sanitize_header_value( $name, $value ), + ) + ); + } + + /** + * It's the last one, so we can modify the whole body. + * + * @since 1.0.0 + * + * @param array $attachments The array of attachments data. + */ + public function set_attachments( $attachments ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded + + if ( empty( $attachments ) ) { + return; + } + + $payload = ''; + $data = []; + + foreach ( $attachments as $attachment ) { + $file = $this->get_attachment_file_content( $attachment ); + + if ( $file === false ) { + continue; + } + + $data[] = [ + 'content' => $file, + 'name' => $this->get_attachment_file_name( $attachment ), + ]; + } + + if ( ! empty( $data ) ) { + + // First, generate a boundary for the multipart message. + $boundary = $this->phpmailer->generate_id(); + + // Iterate through pre-built params and build a payload. + foreach ( $this->body as $key => $value ) { + if ( is_array( $value ) ) { + foreach ( $value as $child_value ) { + $payload .= '--' . $boundary; + $payload .= "\r\n"; + $payload .= 'Content-Disposition: form-data; name="' . $key . "\"\r\n\r\n"; + $payload .= $child_value; + $payload .= "\r\n"; + } + } else { + $payload .= '--' . $boundary; + $payload .= "\r\n"; + $payload .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n\r\n"; + $payload .= $value; + $payload .= "\r\n"; + } + } + + // Now iterate through our attachments, and add them too. + foreach ( $data as $key => $attachment ) { + $payload .= '--' . $boundary; + $payload .= "\r\n"; + $payload .= 'Content-Disposition: form-data; name="attachment[' . $key . ']"; filename="' . $attachment['name'] . '"' . "\r\n\r\n"; + $payload .= $attachment['content']; + $payload .= "\r\n"; + } + + $payload .= '--' . $boundary . '--'; + + // Redefine the body the "dirty way". + $this->body = $payload; + + $this->set_header( 'Content-Type', 'multipart/form-data; boundary=' . $boundary ); + } + } + + /** + * @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; + } + + if ( ! empty( $name ) ) { + $data[] = $name . ' <' . $addr . '>'; + } else { + $data[] = $addr; + } + } + + if ( ! empty( $data ) ) { + $this->set_body_param( + array( + 'h:Reply-To' => implode( ',', $data ), + ) + ); + } + } + + /** + * @inheritdoc + */ + public function set_return_path( $email ) { + + if ( + $this->connection_options->get( 'mail', 'return_path' ) !== true || + ! filter_var( $email, FILTER_VALIDATE_EMAIL ) + ) { + return; + } + + $this->set_body_param( + array( + 'sender' => $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 ) ) { + return; + } + + if ( ! empty( $this->response['body']->id ) ) { + $this->phpmailer->MessageID = $this->response['body']->id; + $this->verify_sent_status = true; + } + } + + /** + * 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. + * + * In Mailgun's case it looks like we have to check if the response body has the message ID. + * All successful API responses should have `id` key in the response body. + * + * @since 2.2.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; + } + + /** This filter is documented in src/Providers/MailerAbstract.php. */ + return apply_filters( 'wp_mail_smtp_providers_mailer_is_email_sent', $is_sent, $this->mailer ); + } + + /** + * Get a Mailgun-specific response with a helpful error. + * + * @since 1.2.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->message ) ) { + $error_text[] = Helpers::format_error_message( $body->message ); + } else { + $error_text[] = WP::wp_remote_get_response_error_message( $this->response ); + } + } + + return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) ); + } + + /** + * @inheritdoc + */ + public function get_debug_info() { + + $mg_text = array(); + + $mailgun = $this->connection_options->get_group( $this->mailer ); + + $mg_text[] = 'Api Key / Domain: ' . ( ! empty( $mailgun['api_key'] ) && ! empty( $mailgun['domain'] ) ? 'Yes' : 'No' ); + + return implode( '
    ', $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() { + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_MAILGUN_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', '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' => [], + ], + ] + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'domain' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-domain" spellcheck="false" + /> +

    + ' . + esc_html__( 'Get a Domain Name', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + + + + + +

    +
    + + More information on Mailgun.com.', 'wp-mail-smtp' ), + array( + 'a' => array( + 'href' => array(), + 'rel' => array(), + 'target' => array(), + ), + ) + ), + 'https://www.mailgun.com/regions' + ); + ?> +

    +
    +
    + + connection_options->get( $this->mailer, 'api_key' ); + $pass = $this->connection_options->get( $this->mailer, 'secret_key' ); + + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + $this->set_header( 'Authorization', 'Basic ' . base64_encode( "$user:$pass" ) ); + $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.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 ); + } + } + + /** + * This mailer supports email-related custom headers inside a body of the message. + * + * @since 4.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['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 4.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; + } + + $this->set_body_param( + [ + 'From' => $this->address_format( [ $email, $name ] ), + ] + ); + } + + /** + * Set email recipients: to, cc, bcc. + * + * @since 4.2.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 4.2.0 + * + * @param array $emails Reply To email addresses. + */ + public function set_reply_to( $emails ) { + + if ( empty( $emails ) ) { + return; + } + + $email = array_shift( $emails ); + + if ( ! isset( $email[0] ) || ! filter_var( $email[0], FILTER_VALIDATE_EMAIL ) ) { + return; + } + + $this->set_body_param( + [ + 'ReplyTo' => $this->address_format( $email ), + ] + ); + } + + /** + * Set email subject. + * + * @since 4.2.0 + * + * @param string $subject Email subject. + */ + public function set_subject( $subject ) { + + $this->set_body_param( + [ + 'Subject' => $subject, + ] + ); + } + + /** + * Set email content. + * + * @since 4.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( + [ + 'TextPart' => $content['text'], + ] + ); + } + + if ( ! empty( $content['html'] ) ) { + $this->set_body_param( + [ + 'HTMLPart' => $content['html'], + ] + ); + } + } else { + if ( $this->phpmailer->ContentType === 'text/plain' ) { + $this->set_body_param( + [ + 'TextPart' => $content, + ] + ); + } else { + $this->set_body_param( + [ + 'HTMLPart' => $content, + ] + ); + } + } + } + + /** + * Set attachments for an email. + * + * @since 4.2.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 "inline" groups. + foreach ( $attachments as $attachment ) { + $mode = in_array( $attachment[6], [ 'inline', 'attachment' ], true ) ? $attachment[6] : 'attachment'; + + if ( $mode === 'inline' ) { + $data['InlinedAttachments'][] = $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 ); + } + } + + /** + * Prepare attachments data for SendLayer API. + * + * @since 4.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; + } + + $filetype = str_replace( ';', '', trim( $attachment[4] ) ); + $entry = [ + 'Filename' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ), + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'Base64Content' => base64_encode( $file ), + 'ContentType' => $attachment[4], + ]; + + if ( $attachment[6] === 'inline' ) { + $entry['ContentID'] = $entry['Filename']; + } + + $data[] = $entry; + } + + return $data; + } + + /** + * Doesn't support this. + * So we do nothing. + * + * @since 4.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. + * This mailer requires a JSON, so we encode the body. + * + * @since 4.2.0 + */ + public function get_body() { + + $body = [ + 'Messages' => [ + parent::get_body(), + ], + ]; + + return wp_json_encode( $body ); + } + + /** + * Whether the email is sent or not. + * We check response code and a non-empty `email_id` field in the response body. + * + * @since 4.2.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; + } + + // 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 + } + + /** + * 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.2.0 + * + * @param mixed $response Response data. + */ + protected function process_response( $response ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + parent::process_response( $response ); + + // Bail if request failed, or has no message entries. + if ( + is_wp_error( $response ) || + empty( $this->response['body']->Messages ) || + ! is_array( $this->response['body']->Messages ) + ) { + return; + } + + // Only pick successful messages. + $messages = array_filter( + $this->response['body']->Messages, + function( $message ) { + return strtolower( $message->Status ) === 'success'; + } + ); + + // Bail if all messages failed. + if ( empty( $messages ) ) { + return; + } + + // Bail if no primary TO is defined. Unlikely, but just in case. + if ( ! isset( $this->body['To'] ) || + ! is_array( $this->body['To'] ) || + ! isset( $this->body['To'][0]['Email'] ) || + empty( $this->body['To'][0]['Email'] ) + ) { + return; + } + + $primary_to = $this->body['To'][0]['Email']; + + foreach ( $messages as $message ) { + foreach ( $message->To as $to ) { + if ( strtolower( $to->Email ) === $primary_to ) { + $this->phpmailer->addCustomHeader( 'X-Msg-ID', $to->MessageID ); + $this->verify_sent_status = true; + + return; + } + } + } + } + + /** + * Gather errors from a nested array. + * + * @since 4.2.0 + * + * @param array $data Array of data. + * @param int $recursion Current recursion step. + * + * @return array + */ + private function gather_response_errors( $data, $recursion = 10 ) { + + $errors = []; + + // Bail if recursion exceeds the limit. + if ( $recursion <= 0 ) { + return $errors; + } + + // Bail if provided data is not an array. + if ( ! is_array( $data ) ) { + return $errors; + } + + if ( ! empty( $data['ErrorMessage'] ) ) { + $errors[] = $data; + + return $errors; + } + + foreach ( $data as $datum ) { + $errors = array_merge( $errors, $this->gather_response_errors( $datum, $recursion - 1 ) ); + } + + return $errors; + } + + /** + * Get a mailer-specific response with a helpful error. + * + * @since 4.2.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 ); + $body = json_decode( wp_json_encode( $body ), true ); + $errors = $this->gather_response_errors( $body ); + + foreach ( $errors as $error ) { + $message = $error['ErrorMessage']; + $code = ! empty( $error['ErrorCode'] ) ? $error['ErrorCode'] : ''; + + if ( ! empty( $error['ErrorRelatedTo'] ) ) { + $related_to = implode( ', ', $error['ErrorRelatedTo'] ); + $message = "{$message} [{$related_to}]"; + } + + $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 mailer-specific error code from the 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 ); + $body = json_decode( wp_json_encode( $body ), true ); + $errors = $this->gather_response_errors( $body ); + + if ( ! empty( $errors[0]['ErrorCode'] ) ) { + return $errors[0]['ErrorCode']; + } + } + + return parent::get_response_error_code(); + } + + /** + * Whether the mailer has all its settings correctly set up and saved. + * + * @since 4.2.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'] ) && ! empty( $options['secret_key'] ) ) { + return true; + } + + return false; + } + + /** + * Prepare address param. + * + * @since 4.2.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/Mailjet/Options.php b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailjet/Options.php new file mode 100644 index 00000000..87d00d0f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Providers/Mailjet/Options.php @@ -0,0 +1,173 @@ +get_connections_manager()->get_primary_connection(); + } + + $description = sprintf( + wp_kses( /* translators: %1$s - URL to Mailjet.com site. */ + __( 'Mailjet is a cloud-based email service platform that enables businesses to send marketing and transactional emails, offering features like email automation, real-time analytics, and responsive design templates. If you\'re just starting out, you can send up to 200 emails per day without a credit card.', 'wp-mail-smtp' ) . + '

    ' . + /* 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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_MAILJET_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + + +

    + ' . + esc_html__( 'API Key Management', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'secret_key' ) ) : ?> + + display_const_set_message( 'WPMS_MAILJET_SECRET_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'secret_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][secret_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-secret_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Secret Key', 'wp-mail-smtp' ), + ] + ); + ?> + + +

    + ' . + esc_html__( 'API Key Management', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + set_header( 'Content-Type', 'application/json' ); + + $this->set_body_param( + [ + 'key' => $this->connection_options->get( $this->mailer, 'api_key' ), + 'message' => [], // Initialize the message structure. + ] + ); + } + + /** + * Get the email body. + * + * @since 4.6.0 + * + * @return string|array + */ + public function get_body() { + + return wp_json_encode( parent::get_body() ); + } + + /** + * Redefine the way custom headers are processed for this mailer - they should be in body. + * + * @since 4.6.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 the body of the message. + * + * @since 4.6.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['message']['headers'] ) ? (array) $this->body['message']['headers'] : []; + + $headers[ $name ] = $this->sanitize_header_value( $name, $value ); + + $this->set_body_param( + [ + 'message' => [ + 'headers' => $headers, + ], + ] + ); + } + + /** + * Set the From information for an email. + * + * @since 4.6.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( + [ + 'message' => [ + 'from_email' => $email, + 'from_name' => ! empty( $name ) ? $name : '', + ], + ] + ); + } + + /** + * Set email recipients: to, cc, bcc. + * + * @since 4.6.0 + * + * @param array $recipients Email recipients. + */ + public function set_recipients( $recipients ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( empty( $recipients ) ) { + return; + } + + $data = []; + $default = [ 'to', 'cc', 'bcc' ]; + + // Process all recipient types (to, cc, bcc). + foreach ( $recipients as $kind => $emails ) { + if ( + ! in_array( $kind, $default, true ) || + empty( $emails ) || + ! is_array( $emails ) + ) { + continue; + } + + // Process each email address for this type. + foreach ( $emails as $email ) { + $addr = isset( $email[0] ) ? $email[0] : false; + $name = isset( $email[1] ) ? $email[1] : false; + + if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) { + continue; + } + + $data[] = [ + 'email' => $addr, + 'name' => ! empty( $name ) ? $name : '', + 'type' => $kind, + ]; + } + } + + if ( ! empty( $data ) ) { + $this->set_body_param( + [ + 'message' => [ + 'to' => $data, + ], + ] + ); + } + } + + /** + * Set the Reply To information for an email. + * + * @since 4.6.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->phpmailer->addrFormat( $email ); + } + + if ( ! empty( $data ) ) { + $this->set_body_param( + [ + 'message' => [ + 'headers' => [ + 'Reply-To' => implode( ',', $data ), + ], + ], + ] + ); + } + } + + /** + * Set email subject. + * + * @since 4.6.0 + * + * @param string $subject Email subject. + */ + public function set_subject( $subject ) { + + $this->set_body_param( + [ + 'message' => [ + 'subject' => $subject, + ], + ] + ); + } + + /** + * Set email content. + * + * @since 4.6.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( + [ + 'message' => [ + 'text' => $content['text'], + ], + ] + ); + } + + if ( ! empty( $content['html'] ) ) { + $this->set_body_param( + [ + 'message' => [ + 'html' => $content['html'], + ], + ] + ); + } + } else { + if ( $this->phpmailer->ContentType === 'text/plain' ) { + $this->set_body_param( + [ + 'message' => [ + 'text' => $content, + ], + ] + ); + } else { + $this->set_body_param( + [ + 'message' => [ + 'html' => $content, + ], + ] + ); + } + } + } + + /** + * Set attachments for an email. + * + * @since 4.6.0 + * + * @param array $attachments Attachments array. + */ + public function set_attachments( $attachments ) { + + if ( empty( $attachments ) ) { + return; + } + + $data = $this->prepare_attachments( $attachments ); + + if ( ! empty( $data['attachments'] ) ) { + $this->set_body_param( + [ + 'message' => [ + 'attachments' => $data['attachments'], + ], + ] + ); + } + } + + /** + * Prepare attachments data for Mandrill API. + * + * @since 4.6.0 + * + * @param array $attachments Array of attachments. + * + * @return array + */ + protected function prepare_attachments( $attachments ) { + + $data = [ + 'attachments' => [], + ]; + + foreach ( $attachments as $attachment ) { + $file = $this->get_attachment_file_content( $attachment ); + + if ( $file === false ) { + continue; + } + + $filetype = str_replace( ';', '', trim( $attachment[4] ) ); + + $data['attachments'][] = [ + 'type' => $filetype, + 'name' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ), + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + 'content' => base64_encode( $file ), + ]; + } + + 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.6.0 + * + * @param mixed $response Response data. + */ + protected function process_response( $response ) { + + parent::process_response( $response ); + + if ( is_wp_error( $response ) ) { + return; + } + + $body = isset( $this->response['body'] ) ? $this->response['body'] : ''; + + if ( ! empty( $body ) && is_array( $body ) && ! empty( $body[0]->_id ) ) { + $this->phpmailer->addCustomHeader( 'X-Msg-ID', $body[0]->_id ); + + // Skip verification for failed emails. + if ( $this->is_email_sent() ) { + $this->verify_sent_status = true; + } + } + } + + /** + * Get a Mandrill-specific response with a helpful error message. + * + * @since 4.6.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 ( is_array( $body ) && ! empty( $body[0] ) && is_object( $body[0] ) && isset( $body[0]->status ) ) { + $reason = ! empty( $body[0]->reject_reason ) ? $body[0]->reject_reason : $body[0]->status; + $error_text[] = Helpers::format_error_message( + /* translators: %s - The reason the email was rejected. */ + sprintf( esc_html__( 'The email failed to be sent. Reason: %s', 'wp-mail-smtp' ), $reason ) + ); + } elseif ( is_object( $body ) && isset( $body->message ) && isset( $body->code ) ) { + $error_text[] = Helpers::format_error_message( $body->message, $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 Mandrill API response. + * + * @since 4.8.0 + * + * @return string + */ + public function get_response_error_code() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( ! empty( $this->response ) ) { + $body = wp_remote_retrieve_body( $this->response ); + + // Array response with reject_reason. + if ( is_array( $body ) && ! empty( $body[0] ) && is_object( $body[0] ) ) { + if ( ! empty( $body[0]->reject_reason ) ) { + return $body[0]->reject_reason; + } + + if ( ! empty( $body[0]->status ) ) { + return $body[0]->status; + } + } + + // Object response with error name/code. + if ( is_object( $body ) ) { + if ( ! empty( $body->name ) ) { + return $body->name; + } + + if ( ! empty( $body->code ) ) { + return $body->code; + } + } + } + + return parent::get_response_error_code(); + } + + /** + * Get mailer debug information that is helpful during support. + * + * @since 4.6.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 `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/'; + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_MANDRILL_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + ' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + connection = $connection; + } else { + $this->connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + } + + $this->connection_options = $this->connection->get_options(); + + if ( + empty( $params['slug'] ) || + empty( $params['title'] ) + ) { + return; + } + + $this->slug = sanitize_key( $params['slug'] ); + $this->title = sanitize_text_field( $params['title'] ); + + if ( ! empty( $params['description'] ) ) { + $this->description = wp_kses_post( $params['description'] ); + } + + if ( ! empty( $params['notices'] ) ) { + foreach ( (array) $params['notices'] as $key => $notice ) { + $key = sanitize_key( $key ); + if ( empty( $key ) ) { + continue; + } + + $notice = wp_kses( + $notice, + array( + 'br' => true, + 'strong' => true, + 'em' => true, + 'a' => array( + 'href' => true, + 'rel' => true, + 'target' => true, + ), + ) + ); + if ( empty( $notice ) ) { + continue; + } + + $this->notices[ $key ] = $notice; + } + } + + if ( isset( $params['recommended'] ) ) { + $this->recommended = (bool) $params['recommended']; + } + if ( isset( $params['disabled'] ) ) { + $this->disabled = (bool) $params['disabled']; + } + + if ( ! empty( $params['php'] ) ) { + $this->php = sanitize_text_field( $params['php'] ); + } + + if ( ! empty( $params['logo_url'] ) ) { + $this->logo_url = esc_url_raw( $params['logo_url'] ); + } + + $this->supports = ( ! empty( $params['supports'] ) ) ? $params['supports'] : $this->get_supports_defaults(); + + $this->options = Options::init(); + } + + /** + * @inheritdoc + */ + public function get_logo_url() { + return apply_filters( 'wp_mail_smtp_providers_provider_get_logo_url', $this->logo_url, $this ); + } + + /** + * @inheritdoc + */ + public function get_slug() { + return apply_filters( 'wp_mail_smtp_providers_provider_get_slug', $this->slug, $this ); + } + + /** + * @inheritdoc + */ + public function get_title() { + return apply_filters( 'wp_mail_smtp_providers_provider_get_title', $this->title, $this ); + } + + /** + * @inheritdoc + */ + public function get_description() { + return apply_filters( 'wp_mail_smtp_providers_provider_get_description', $this->description, $this ); + } + + /** + * Get the mailer provider notices. + * + * @since 4.3.0 + * + * @return array + */ + public function get_notices() { + + return apply_filters( 'wp_mail_smtp_providers_provider_get_notices', $this->notices, $this ); + } + + /** + * Some mailers may display a notice above its options. + * + * @since 1.6.0 + * + * @param string $type + * + * @return string + */ + public function get_notice( $type ) { + + $type = sanitize_key( $type ); + + return apply_filters( 'wp_mail_smtp_providers_provider_get_notice', isset( $this->notices[ $type ] ) ? $this->notices[ $type ] : '', $this ); + } + + /** + * @inheritdoc + */ + public function get_php_version() { + return apply_filters( 'wp_mail_smtp_providers_provider_get_php_version', $this->php, $this ); + } + + /** + * @inheritdoc + */ + public function display_options() { + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'host' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-host" spellcheck="false" + /> +
    +
    + + +
    +
    + +
    +
    + + + + + + + +

    + +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'port' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-port" class="small-text" spellcheck="false" + /> +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[' . $this->get_slug() . '][autotls]', + 'id' => 'wp-mail-smtp-setting-' . $this->get_slug() . '-autotls', + 'checked' => (bool) $this->connection_options->get( $this->get_slug(), 'autotls' ), + 'disabled' => $this->connection_options->is_const_defined( $this->get_slug(), 'autotls' ), + ] + ); + ?> +

    + +

    +
    +
    + + +
    +
    + +
    +
    + 'wp-mail-smtp[' . $this->get_slug() . '][auth]', + 'id' => 'wp-mail-smtp-setting-' . $this->get_slug() . '-auth', + 'checked' => (bool) $this->connection_options->get( $this->get_slug(), 'auth' ), + 'disabled' => $this->connection_options->is_const_defined( $this->get_slug(), 'auth' ), + ] + ); + ?> +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'user' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-user" spellcheck="false" autocomplete="new-password" + /> +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'pass' ) ) : ?> + + + display_const_set_message( 'WPMS_SMTP_PASS' ); ?> + +

    + define( \'WPMS_SMTP_PASS\', \'your_old_password\' );' + ); + ?> +
    + wp-config.php', + 'WPMS_ON' + ); + ?> +

    +
    +						define( 'WPMS_ON', false );
    +					
    +

    + +

    + + + get_slug(); + $value = $this->connection_options->get( $slug, 'pass' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][pass]", + 'id' => "wp-mail-smtp-setting-{$slug}-pass", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Password', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + +
    + %2$s', + esc_url( wp_mail_smtp()->get_utm_url( 'https://wpmailsmtp.com/docs/how-to-secure-smtp-settings-by-using-constants/', 'SMTP Password - Learn More' ) ), + esc_html__( 'Learn More', 'wp-mail-smtp' ) + ) + ?> +

    + +
    +
    + + recommended, $this ); + } + + /** + * Whether this mailer is disabled or not. + * Used for displaying Pro mailers inside Lite plugin. + * + * @since 1.7.0 + * + * @return bool + */ + public function is_disabled() { + + return (bool) apply_filters( 'wp_mail_smtp_providers_provider_is_disabled', $this->disabled, $this ); + } + + /** + * Check whether we can use this provider based on the PHP version. + * Valid for those, that use SDK. + * + * @since 1.0.0 + * + * @return bool + */ + public function is_php_correct() { + return version_compare( phpversion(), $this->php, '>=' ); + } + + /** + * Display a helpful message to those users, that are using an outdated version of PHP, + * which is not supported by the currently selected Provider. + * + * @since 1.0.0 + */ + protected function display_php_warning() { + ?> + +
    + 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( + '

    %2$s

    ', + '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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_PEPIPOST_API_KEY' ); ?> + + + + +

    + ' . + 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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'server_api_token' ) ) : ?> + + display_const_set_message( 'WPMS_POSTMARK_SERVER_API_TOKEN' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'server_api_token' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][server_api_token]", + 'id' => "wp-mail-smtp-setting-{$slug}-server_api_token", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove Server API Token', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + ' . + esc_html__( 'Get Server API Token', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'message_stream' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-message_stream" spellcheck="false"/> + connection_options->is_const_defined( $this->get_slug(), 'message_stream' ) ) { + $this->display_const_set_message( 'WPMS_POSTMARK_MESSAGE_STREAM' ); + } + ?> +

    + 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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_RESEND_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + + +

    + ' . + 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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_SMTP2GO_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + + +

    + ' . + 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( + '

    %2$s

    ', + '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() { + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_SMTPCOM_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + ' . + esc_html__( 'Get API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'channel' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-channel" spellcheck="false" + /> + connection_options->is_const_defined( $this->get_slug(), 'channel' ) ) { + $this->display_const_set_message( 'WPMS_SMTPCOM_CHANNEL' ); + } + ?> +

    + ' . + 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() { + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_SENDGRID_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + ' . + esc_html__( 'Create API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +
    + Mail Send' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'domain' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-domain" spellcheck="false" + /> +

    + 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( + '

    %2$s

    ', + '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; + } + ?> + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_SENDINBLUE_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + + +

    + ' . + esc_html__( 'Get v3 API Key', 'wp-mail-smtp' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + connection_options->is_const_defined( $this->get_slug(), 'domain' ) ? 'disabled' : ''; ?> + id="wp-mail-smtp-setting-get_slug() ); ?>-domain" spellcheck="false" + /> +

    + 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'; + ?> + +
    + style="display: none;" + > + +
    +

    +
    + + +

    + + + + +

    +
    + + + +
    + + + +
    + + + + +
    +
    + +
    +
    +

    + + + + +

    +

    + +

    +

    + 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' ] ) ) + ); + ?> +

    +
    +
    + + + +
    + style="display: none;" + > +
    + +
    +
    + + + display_const_set_message( 'WPMS_SENDLAYER_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $slug, 'api_key' ); + + $field_args = [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ]; + + if ( $is_quick_connect ) { + $field_args['clear_text'] = esc_html__( 'Disconnect', 'wp-mail-smtp' ); + $field_args['clear_url'] = wp_nonce_url( ( new ConnectionSettings( $this->connection ) )->get_admin_page_url(), 'sendlayer_quick_connect_disconnect', 'sendlayer_quick_connect_disconnect_nonce' ); + } + + UI::hidden_password_field( $field_args ); + ?> + +

    + ' . + 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' ] ); + ?> +
    +
    +
    +
    + + + + +
    +

    + SendLayer, our recommended mailer, and send your first 200 emails for free with just a few clicks!', 'wp-mail-smtp' ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + ], + 'strong' => [], + ] + ), + esc_url( $sendlayer_url ) + ); + ?> +

    +
    +
    + + + + + +
    +
    +
    + +
    + +

    +
    + 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'; + ?> + + 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->is_const_defined( $this->get_slug(), 'api_key' ) ) : ?> + + display_const_set_message( 'WPMS_SPARKPOST_API_KEY' ); ?> + + get_slug(); + $value = $this->connection_options->get( $this->get_slug(), 'api_key' ); + + UI::hidden_password_field( + [ + 'name' => "wp-mail-smtp[{$slug}][api_key]", + 'id' => "wp-mail-smtp-setting-{$slug}-api_key", + 'value' => $value, + 'clear_text' => esc_html__( 'Remove API Key', 'wp-mail-smtp' ), + ] + ); + ?> + +

    + 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' ) . + '' + ); + ?> +

    +
    +
    + + +
    +
    + +
    +
    + + + + + + connection_options->is_const_defined( $this->get_slug(), 'region' ) ) { + $this->display_const_set_message( 'WPMS_SPARKPOST_REGION' ); + } + ?> +

    + + 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(); + ?> + + + + + + + + + + + <?php esc_html_e( 'WP Mail SMTP Weekly Email Summary', 'wp-mail-smtp' ); ?> + + + + + + + +
    + + + + + + + + + + + + + + +
    +
    + <?php esc_attr_e( 'WP Mail SMTP Logo', 'wp-mail-smtp' ); ?> +
    + + + +
    + 'misc' ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG ) + ); + + ob_start(); + ?> +
    +
    + + + get_upgrade_link( + [ + 'medium' => 'weekly-email-summary', + 'content' => 'upgrade-to-wp-mail-smtp-pro-button', + ] + ); + + ob_start(); + ?> +
    + +
    +

    + +

    + + + + + + +
    + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Total Emails', 'wp-mail-smtp' ), + 'icon-email.png', + $reports->get_total_emails_sent(), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $reports->get_total_emails_sent() - $reports->get_total_weekly_emails_sent( 'previous' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#809EB0' + ); + ?> + + get_stats_total_html( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + __( 'Last week', 'wp-mail-smtp' ), + 'icon-check.png', + $reports->get_total_weekly_emails_sent( 'previous' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $reports->get_total_weekly_emails_sent( 'two_weeks_ago' ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '#6AA08B' + ); + ?> +
    + +
     
    + + + + + +
    + <?php esc_attr_e( 'Reports', 'wp-mail-smtp' ); ?> +

    +

    + WP Mail SMTP Pro and unlock Email Log and advanced Email Reports. Start measuring the success of your emails today!', 'wp-mail-smtp' ), + [ + 'b' => [], + 'u' => [], + ] + ); + ?> +

    +
    + + + + +
    + + + + +
    + + + +
    +
    +
    +
    + get_stats_total_item_width(); + + $percent_change = $this->calc_percent_change( $value, $prev_value ); + + $images_dir_url = wp_mail_smtp()->assets_url . '/images/reports/email/'; + $icon_width = ( $icon === 'icon-email.png' ) ? 34 : 32; + + ob_start(); + ?> + + + + +
    +

    + <?php echo esc_attr( $title ); ?> +

    +

    + +

    +

    + +

    +

    + <?php echo esc_attr( $title ); ?> + + + % + +

    +
    + $diff >= 0, + 'value' => round( $percent_change, 1 ), + ]; + } + + /** + * Set the HTML content type. + * + * @since 3.0.0 + * + * @return string + */ + public function set_html_content_type() { + + return 'text/html'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Reports/Reports.php b/web/app/plugins/wp-mail-smtp-pro/src/Reports/Reports.php new file mode 100644 index 00000000..85af417b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Reports/Reports.php @@ -0,0 +1,269 @@ +public_hooks(); + + if ( WP::in_wp_admin() ) { + $this->admin_hooks(); + } + } + + /** + * Frontend hooks. + * + * @since 3.0.0 + */ + private function public_hooks() { + + // Update sent email counter when SMTP mailer is used. + add_action( 'wp_mail_smtp_mailcatcher_smtp_send_after', [ $this, 'update_sent_emails_stats' ] ); + + // Update sent email counter when all other mailers are used. + add_action( 'wp_mail_smtp_mailcatcher_send_after', [ $this, 'update_sent_emails_stats' ] ); + } + + /** + * Admin hooks. + * + * @since 3.0.0 + */ + private function admin_hooks() { + + add_action( 'load-toplevel_page_wp-mail-smtp', [ $this, 'summary_report_email_preview' ] ); + + // Detect summary report email constant change. + if ( Options::init()->is_const_defined( 'general', SummaryReportEmail::SETTINGS_SLUG ) ) { + add_action( 'admin_init', [ $this, 'detect_summary_report_email_constant_change' ] ); + } + } + + /** + * Update all stats after email sent. + * + * @since 3.0.0 + */ + public function update_sent_emails_stats() { + + if ( wp_mail_smtp()->is_pro() ) { + return; + } + + $this->increment_sent_emails_counter(); + $this->increment_weekly_sent_emails_counter(); + } + + /** + * Increment the number of total emails sent by 1. + * + * @since 3.0.0 + */ + private function increment_sent_emails_counter() { + + $value = $this->get_total_emails_sent() + 1; + + update_option( self::SENT_EMAILS_COUNTER_OPTION_KEY, $value, true ); + } + + /** + * Get the number of total emails sent. + * + * @since 3.0.0 + * + * @return int + */ + public function get_total_emails_sent() { + + return get_option( self::SENT_EMAILS_COUNTER_OPTION_KEY, 0 ); + } + + /** + * Increment the number of total emails sent in this week by 1. + * + * @since 3.0.0 + */ + private function increment_weekly_sent_emails_counter() { + + $stats = $this->get_total_weekly_emails_sent(); + + $week = $this->get_current_week(); + + if ( ! isset( $stats[ $week ] ) ) { + $stats[ $week ] = 0; + } + + $stats[ $week ] ++; + + // Cleanup old stats. + $stats = array_slice( $stats, self::WEEKLY_COUNTER_RETENTION_PERIOD * - 1, null, true ); + + update_option( self::WEEKLY_SENT_EMAILS_COUNTER_OPTION_KEY, $stats, true ); + } + + /** + * Get the number of total emails sent by week. + * + * When `$week` is `'previous'` or `'two_weeks_ago'` and the calculated ISO week drops + * below 1 (year-boundary case), the target week number is derived from the dated + * offset (7 or 14 days ago) so it correctly resolves to the prior year's last weeks + * (52 or 53) regardless of whether intervening weeks have entries in the stats array. + * + * @since 3.0.0 + * @since 4.9.0 Added 'two_weeks_ago' identifier and year-boundary handling. + * + * @param int|string|null $week One of: null (full array), 'now', 'previous', + * 'two_weeks_ago', or an integer ISO week number. + * + * @return array|int + */ + public function get_total_weekly_emails_sent( $week = null ) { + + $stats = get_option( self::WEEKLY_SENT_EMAILS_COUNTER_OPTION_KEY, [] ); + + if ( is_null( $week ) ) { + return $stats; + } + + $current_week = $this->get_current_week(); + + if ( $week === 'now' ) { + return isset( $stats[ $current_week ] ) ? $stats[ $current_week ] : 0; + } + + if ( $week === 'previous' ) { + $target = $current_week - 1; + + if ( $target < 1 ) { + $target = (int) wp_date( 'W', strtotime( '-7 days' ) ); + } + + return isset( $stats[ $target ] ) ? $stats[ $target ] : 0; + } + + if ( $week === 'two_weeks_ago' ) { + $target = $current_week - 2; + + if ( $target < 1 ) { + $target = (int) wp_date( 'W', strtotime( '-14 days' ) ); + } + + return isset( $stats[ $target ] ) ? $stats[ $target ] : 0; + } + + // Explicit integer ISO week number. + return isset( $stats[ $week ] ) ? $stats[ $week ] : 0; + } + + /** + * Generate a summary report email preview and display it for users. + * + * @since 3.0.0 + */ + public function summary_report_email_preview() { + + if ( ! current_user_can( wp_mail_smtp()->get_admin()->get_logs_access_capability() ) ) { + return; + } + + if ( ! isset( $_GET['mode'] ) || $_GET['mode'] !== 'summary_report_email_preview' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return; + } + + $email = $this->get_summary_report_email(); + + echo $email->get_content(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + exit; + } + + /** + * Get emails stats weekly summary report email. + * + * @since 3.0.0 + * + * @return SummaryReportEmail + */ + public function get_summary_report_email() { + + return new SummaryReportEmail(); + } + + /** + * Detect summary report email constant change. + * + * @since 3.0.0 + */ + public function detect_summary_report_email_constant_change() { + + if ( ! WP::in_wp_admin() ) { + return; + } + + if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { + return; + } + + if ( Options::init()->is_const_changed( 'general', SummaryReportEmail::SETTINGS_SLUG ) ) { + ( new SummaryEmailTask() )->cancel(); + } + } + + /** + * Get current week number. + * + * @since 3.0.0 + * + * @return int + */ + public function get_current_week() { + + return current_time( 'W' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/SiteHealth.php b/web/app/plugins/wp-mail-smtp-pro/src/SiteHealth.php new file mode 100644 index 00000000..b4a272a1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/SiteHealth.php @@ -0,0 +1,428 @@ +assets_url . '/css/admin-site-health.min.css', + false, + WPMS_PLUGIN_VER + ); + } + + /** + * Register plugin WP site health tests. + * This will be displayed in the "Status" tab of the WP Site Health page. + * + * @since 1.9.0 + * + * @param array $tests The array with all WP site health tests. + * + * @return array + */ + public function register_site_status_tests( $tests ) { + + $tests['direct']['wp_mail_smtp_mailer_setup_complete'] = array( + 'label' => esc_html__( 'Is WP Mail SMTP mailer setup complete?', 'wp-mail-smtp' ), + 'test' => array( $this, 'mailer_setup_complete_test' ), + ); + + $tests['direct']['wp_mail_smtp_db_tables_exist'] = array( + 'label' => esc_html__( 'Do WP Mail SMTP DB tables exist?', 'wp-mail-smtp' ), + 'test' => [ $this, 'db_tables_test' ], + ); + + $tests['async']['wp_mail_smtp_email_domain_check'] = array( + 'label' => esc_html__( 'Is email domain configured properly?', 'wp-mail-smtp' ), + 'test' => 'email_domain_check_test', + ); + + return $tests; + } + + /** + * Register plugin WP Site Health debug information. + * This will be displayed in the "Info" tab of the WP Site Health page. + * + * @since 1.9.0 + * + * @param array $debug_info Array of existing debug information. + * + * @return array + */ + public function register_debug_information( $debug_info ) { + + $debug_notices = EmailSendingDebug::get_messages(); + $db_tables = $this->get_db_tables( 'existing' ); + + $debug_info[ self::DEBUG_INFO_SLUG ] = [ + 'label' => $this->get_label(), + 'fields' => [ + 'version' => [ + 'label' => esc_html__( 'Version', 'wp-mail-smtp' ), + 'value' => WPMS_PLUGIN_VER, + ], + 'license_key_type' => [ + 'label' => esc_html__( 'License key type', 'wp-mail-smtp' ), + 'value' => wp_mail_smtp()->get_license_type(), + ], + 'debug' => [ + 'label' => esc_html__( 'Debug', 'wp-mail-smtp' ), + 'value' => ! empty( $debug_notices ) ? implode( WP::EOL . WP::EOL, $debug_notices ) : esc_html__( 'No debug notices found.', 'wp-mail-smtp' ), + ], + 'db_tables' => [ + 'label' => esc_html__( 'DB tables', 'wp-mail-smtp' ), + 'value' => ! empty( $db_tables ) ? + implode( ', ', $db_tables ) : esc_html__( 'No DB tables found.', 'wp-mail-smtp' ), + 'private' => true, + ], + ], + ]; + + // Install date. + $activated = get_option( 'wp_mail_smtp_activated', [] ); + if ( ! empty( $activated['lite'] ) ) { + $date = $activated['lite'] + ( get_option( 'gmt_offset' ) * 3600 ); + + $debug_info[ self::DEBUG_INFO_SLUG ]['fields']['lite_install_date'] = [ + 'label' => esc_html__( 'Lite install date', 'wp-mail-smtp' ), + 'value' => date_i18n( esc_html__( 'M j, Y @ g:ia' ), $date ), + ]; + } + + return $debug_info; + } + + /** + * Perform the WP site health test for checking, if the mailer setup is complete. + * + * @since 1.9.0 + */ + public function mailer_setup_complete_test() { + + $mailer = Options::init()->get( 'mail', 'mailer' ); + $mailer_complete = false; + $mailer_title = esc_html__( 'None selected', 'wp-mail-smtp' ); + + if ( ! empty( $mailer ) ) { + $mailer_object = wp_mail_smtp() + ->get_providers() + ->get_mailer( + $mailer, + wp_mail_smtp()->get_processor()->get_phpmailer() + ); + + $mailer_complete = ! empty( $mailer_object ) ? $mailer_object->is_mailer_complete() : false; + + $mailer_title = wp_mail_smtp()->get_providers()->get_options( $mailer )->get_title(); + } + + // The default mailer should be considered as a non-complete mailer. + if ( $mailer === 'mail' ) { + $mailer_complete = false; + } + + $mailer_text = sprintf( + '%s: %s', + esc_html__( 'Current mailer', 'wp-mail-smtp' ), + esc_html( $mailer_title ) + ); + + $result = array( + 'label' => esc_html__( 'WP Mail SMTP mailer setup is complete', 'wp-mail-smtp' ), + 'status' => 'good', + 'badge' => array( + 'label' => $this->get_label(), + 'color' => self::BADGE_COLOR, + ), + 'description' => sprintf( + '

    %s

    %s

    ', + $mailer_text, + esc_html__( 'The WP Mail SMTP plugin mailer setup is complete. You can send a test email, to make sure it\'s working properly.', 'wp-mail-smtp' ) + ), + 'actions' => sprintf( + '

    %s

    ', + esc_url( add_query_arg( 'tab', 'test', wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) ) ), + esc_html__( 'Test email sending', 'wp-mail-smtp' ) + ), + 'test' => 'wp_mail_smtp_mailer_setup_complete', + ); + + if ( $mailer === 'mail' ) { + $mailer_text .= sprintf( /* translators: %s - explanation why default mailer is not a valid mailer option. */ + '

    %s

    ', + esc_html__( 'You currently have the default mailer selected, which means that you haven’t set up SMTP yet.', 'wp-mail-smtp' ) + ); + } + + if ( $mailer_complete === false ) { + $result['label'] = esc_html__( 'WP Mail SMTP mailer setup is incomplete', 'wp-mail-smtp' ); + $result['status'] = 'recommended'; + $result['badge']['color'] = 'orange'; + $result['description'] = sprintf( + '

    %s

    %s

    ', + $mailer_text, + esc_html__( 'The WP Mail SMTP plugin mailer setup is incomplete. Please click on the link below to access plugin settings and configure the mailer.', 'wp-mail-smtp' ) + ); + $result['actions'] = sprintf( + '

    %s

    ', + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ), + esc_html__( 'Configure mailer', 'wp-mail-smtp' ) + ); + } + + return $result; + } + + /** + * Perform the test for checking if all custom plugin DB tables exist. + * + * @since 2.1.2 + * + * @return array + */ + public function db_tables_test() { + + $result = array( + 'label' => esc_html__( 'WP Mail SMTP DB tables are created', 'wp-mail-smtp' ), + 'status' => 'good', + 'badge' => array( + 'label' => $this->get_label(), + 'color' => self::BADGE_COLOR, + ), + 'description' => esc_html__( '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 looks like they exist in your database.', 'wp-mail-smtp' ), + 'actions' => '', + 'test' => 'wp_mail_smtp_db_tables_exist', + ); + + $missing_tables = $this->get_db_tables( 'missing' ); + + if ( ! empty( $missing_tables ) ) { + $missing_tables_create_link = wp_nonce_url( + add_query_arg( + [ + 'create-missing-db-tables' => 1, + ], + wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG ) + ), + Area::SLUG . '-create-missing-db-tables' + ); + + $result['label'] = esc_html__( 'WP Mail SMTP DB tables check has failed', 'wp-mail-smtp' ); + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; + $result['description'] = sprintf( + '

    %s

    %s

    ', + sprintf( /* translators: %s - the list of missing tables separated by comma. */ + esc_html( _n( 'Missing table: %s', 'Missing tables: %s', count( $missing_tables ), 'wp-mail-smtp' ) ), + esc_html( implode( ', ', $missing_tables ) ) + ), + wp_kses( + sprintf( /* translators: %1$s - Settings Page URL; %2$s - The aria label; %3$s - The text that will appear on the link. */ + __( '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 %3$s. If this issue persists, please contact our support.', 'wp-mail-smtp' ), + esc_url( $missing_tables_create_link ), + esc_attr__( 'Go to WP Mail SMTP settings page.', 'wp-mail-smtp' ), + esc_attr__( 'create the missing DB tables by clicking on this link', 'wp-mail-smtp' ) + ), + [ + 'a' => [ + 'href' => [], + 'rel' => [], + 'target' => [], + 'aria-label' => [], + ], + ] + ) + ); + } + + return $result; + } + + /** + * Perform the test (async) for checking if email domain configured properly. + * + * @since 2.8.0 + */ + public function email_domain_check_test() { + + check_ajax_referer( 'health-check-site-status' ); + + if ( ! current_user_can( 'view_site_health_checks' ) ) { + wp_send_json_error(); + } + + $options = Options::init(); + $mailer = $options->get( 'mail', 'mailer' ); + $email = $options->get( 'mail', 'from_email' ); + $domain = ''; + + $email_domain_text = sprintf( + '%1$s: %2$s', + esc_html__( 'Current from email domain', 'wp-mail-smtp' ), + esc_html( WP::get_email_domain( $email ) ) + ); + + $result = array( + 'label' => esc_html__( 'Email domain is configured correctly', 'wp-mail-smtp' ), + 'status' => 'good', + 'badge' => array( + 'label' => $this->get_label(), + 'color' => self::BADGE_COLOR, + ), + 'description' => sprintf( + '

    %1$s

    %2$s

    ', + $email_domain_text, + esc_html__( 'All checks for your email domain were successful. It looks like everything is configured correctly.', 'wp-mail-smtp' ) + ), + 'actions' => sprintf( + '

    %2$s

    ', + esc_url( add_query_arg( 'tab', 'test', wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' ) ) ), + esc_html__( 'Send a Test Email', 'wp-mail-smtp' ) + ), + 'test' => 'wp_mail_smtp_email_domain_check', + ); + + // Add the optional sending domain parameter. + if ( in_array( $mailer, [ 'mailgun', 'sendinblue', 'sendgrid' ], true ) ) { + $domain = $options->get( $mailer, 'domain' ); + } + + $domain_checker = new DomainChecker( $mailer, $email, $domain ); + + if ( ! $domain_checker->no_issues() ) { + $result['label'] = esc_html__( 'Email domain issues detected', 'wp-mail-smtp' ); + $result['status'] = 'recommended'; + $result['description'] = sprintf( + '

    %1$s

    %2$s', + $email_domain_text, + $domain_checker->get_results_html() + ); + $result['actions'] = sprintf( + '

    %2$s

    ', + esc_url( wp_mail_smtp()->get_admin()->get_admin_page_url() ), + esc_html__( 'Configure mailer', 'wp-mail-smtp' ) + ); + } + + wp_send_json_success( $result ); + } + + /** + * Get the missing tables from the database. + * + * @since 3.6.0 + * + * @return array + */ + public function get_missing_db_tables() { + + return $this->get_db_tables( 'missing' ); + } + + /** + * Check DB: + * - if any required plugin DB table is missing, + * - which of the required plugin DB tables exist. + * + * @since 2.1.2 + * + * @param string $check Which type of tables to return: 'missing' or 'existing'. + * + * @return array Missing or existing tables. + */ + private function get_db_tables( $check = 'missing' ) { + + global $wpdb; + + $tables = wp_mail_smtp()->get_custom_db_tables(); + + $missing_tables = []; + $existing_tables = []; + + foreach ( $tables as $table ) { + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $db_result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); + + if ( is_null( $db_result ) || strtolower( $db_result ) !== strtolower( $table ) ) { + $missing_tables[] = $table; + } else { + $existing_tables[] = $table; + } + } + + return ( $check === 'existing' ) ? $existing_tables : $missing_tables; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/DebugEventsCleanupTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/DebugEventsCleanupTask.php new file mode 100644 index 00000000..81535a51 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/DebugEventsCleanupTask.php @@ -0,0 +1,129 @@ +get( 'debug_events', 'retention_period' ); + + // Exit if the retention period is not defined (set to "forever") or this task is already scheduled. + if ( empty( $retention_period ) || Tasks::is_scheduled( self::ACTION ) !== false ) { + return; + } + + // Schedule the task. + $this->recurring( + strtotime( 'tomorrow' ), + $this->get_debug_events_cleanup_interval() + ) + ->params( $retention_period ) + ->register(); + } + + /** + * Get the cleanup interval for the debug events. + * + * @since 3.6.0 + * + * @return int + */ + private function get_debug_events_cleanup_interval() { + + $day_in_seconds = DAY_IN_SECONDS; + + /** + * Filter for the debug events cleanup interval. + * + * @since 3.6.0 + * + * @param int $day_in_seconds Debug events cleanup interval. + */ + return (int) apply_filters( 'wpmailsmtp_tasks_get_debug_events_cleanup_interval', $day_in_seconds ); + } + + /** + * Perform the cleanup action: remove outdated debug events. + * + * @since 3.6.0 + * + * @param int $meta_id The Meta ID with the stored task parameters. + * + * @throws Exception Exception will be logged in the Action Scheduler logs table. + */ + public function process( $meta_id ) { + + $task_meta = new Meta(); + $meta = $task_meta->get( (int) $meta_id ); + + // We should actually receive the passed parameter. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) !== 1 ) { + return; + } + + /** + * Date in seconds (examples: 86400, 100500). + * Debug Events older than this period will be deleted. + * + * @var int $retention_period + */ + $retention_period = (int) $meta->data[0]; + + if ( empty( $retention_period ) ) { + return; + } + + // Bail if DB tables was not created. + if ( ! DebugEvents::is_valid_db() ) { + return; + } + + $wpdb = WP::wpdb(); + $table = DebugEvents::get_table_name(); + $date = ( new DateTime( "- $retention_period seconds" ) )->format( WP::datetime_mysql_format() ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->query( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->prepare( "DELETE FROM `$table` WHERE created_at < %s", $date ) + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Meta.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Meta.php new file mode 100644 index 00000000..9d84fe2d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Meta.php @@ -0,0 +1,533 @@ +191 chars in JSON to AS, + * so we need to store them somewhere (and clean from time to time). + * + * @since 2.1.0 + */ +class Meta { + + /** + * Database table name. + * + * @since 2.1.0 + * + * @var string + */ + public $table_name; + + /** + * Database version. + * + * @since 2.1.0 + * + * @var string + */ + public $version; + + /** + * Primary key (unique field) for the database table. + * + * @since 2.1.0 + * + * @var string + */ + public $primary_key = 'id'; + + /** + * Database type identifier. + * + * @since 2.1.0 + * + * @var string + */ + public $type = 'tasks_meta'; + + /** + * Primary class constructor. + * + * @since 2.1.0 + */ + public function __construct() { + + $this->table_name = self::get_table_name(); + } + + /** + * Get the DB table name. + * + * @since 2.1.0 + * + * @return string + */ + public static function get_table_name() { + + global $wpdb; + + return $wpdb->prefix . 'wpmailsmtp_tasks_meta'; + } + + /** + * Get table columns. + * + * @since 2.1.0 + */ + public function get_columns() { + + return array( + 'id' => '%d', + 'action' => '%s', + 'data' => '%s', + 'date' => '%s', + ); + } + + /** + * Default column values. + * + * @since 2.1.0 + * + * @return array + */ + public function get_column_defaults() { + + return array( + 'action' => '', + 'data' => '', + 'date' => gmdate( 'Y-m-d H:i:s' ), + ); + } + + /** + * Retrieve a row from the database based on a given row ID. + * + * @since 2.1.0 + * + * @param int $row_id Row ID. + * + * @return null|object + */ + private function get_from_db( $row_id ) { + + global $wpdb; + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$this->table_name} WHERE {$this->primary_key} = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $row_id + ) + ); + } + + /** + * Retrieve a row based on column and row ID. + * + * @since 2.1.0 + * + * @param string $column Column name. + * @param int|string $row_id Row ID. + * + * @return object|null|bool Database query result, object or null on failure. + */ + public function get_by( $column, $row_id ) { + + global $wpdb; + + if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) { + return false; + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM $this->table_name WHERE $column = '%s' LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + $row_id + ) + ); + } + + /** + * Retrieve a value based on column name and row ID. + * + * @since 2.1.0 + * + * @param string $column Column name. + * @param int|string $row_id Row ID. + * + * @return string|null Database query result (as string), or null on failure. + */ + public function get_column( $column, $row_id ) { + + global $wpdb; + + if ( empty( $row_id ) || ! array_key_exists( $column, $this->get_columns() ) ) { + return false; + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->get_var( + $wpdb->prepare( + "SELECT $column FROM $this->table_name WHERE $this->primary_key = '%s' LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + $row_id + ) + ); + } + + /** + * Retrieve one column value based on another given column and matching value. + * + * @since 2.1.0 + * + * @param string $column Column name. + * @param string $column_where Column to match against in the WHERE clause. + * @param string $column_value Value to match to the column in the WHERE clause. + * + * @return string|null Database query result (as string), or null on failure. + */ + public function get_column_by( $column, $column_where, $column_value ) { + + global $wpdb; + + if ( empty( $column ) || empty( $column_where ) || empty( $column_value ) || ! array_key_exists( $column, $this->get_columns() ) ) { + return false; + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + return $wpdb->get_var( + $wpdb->prepare( + "SELECT $column FROM $this->table_name WHERE $column_where = %s LIMIT 1;", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $column_value + ) + ); + } + + /** + * Insert a new record into the database. + * + * @since 2.1.0 + * + * @param array $data Column data. + * @param string $type Optional. Data type context. + * + * @return int ID for the newly inserted record. 0 otherwise. + */ + private function add_to_db( $data, $type = '' ) { + + global $wpdb; + + // Set default values. + $data = wp_parse_args( $data, $this->get_column_defaults() ); + + do_action( 'wp_mail_smtp_pre_insert_' . $type, $data ); + + // Initialise column format array. + $column_formats = $this->get_columns(); + + // Force fields to lower case. + $data = array_change_key_case( $data ); + + // White list columns. + $data = array_intersect_key( $data, $column_formats ); + + // Reorder $column_formats to match the order of columns given in $data. + $data_keys = array_keys( $data ); + $column_formats = array_merge( array_flip( $data_keys ), $column_formats ); + + $wpdb->insert( $this->table_name, $data, $column_formats ); + + do_action( 'wp_mail_smtp_post_insert_' . $type, $wpdb->insert_id, $data ); + + return $wpdb->insert_id; + } + + /** + * Update an existing record in the database. + * + * @since 2.1.0 + * + * @param int|string $row_id Row ID for the record being updated. + * @param array $data Optional. Array of columns and associated data to update. Default empty array. + * @param string $where Optional. Column to match against in the WHERE clause. If empty, $primary_key + * will be used. Default empty. + * @param string $type Optional. Data type context, e.g. 'affiliate', 'creative', etc. Default empty. + * + * @return bool False if the record could not be updated, true otherwise. + */ + public function update( $row_id, $data = array(), $where = '', $type = '' ) { + + global $wpdb; + + // Row ID must be a positive integer. + $row_id = absint( $row_id ); + + if ( empty( $row_id ) ) { + return false; + } + + if ( empty( $where ) ) { + $where = $this->primary_key; + } + + do_action( 'wp_mail_smtp_pre_update_' . $type, $data ); + + // Initialise column format array. + $column_formats = $this->get_columns(); + + // Force fields to lower case. + $data = array_change_key_case( $data ); + + // White list columns. + $data = array_intersect_key( $data, $column_formats ); + + // Reorder $column_formats to match the order of columns given in $data. + $data_keys = array_keys( $data ); + $column_formats = array_merge( array_flip( $data_keys ), $column_formats ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + if ( false === $wpdb->update( $this->table_name, $data, array( $where => $row_id ), $column_formats ) ) { + return false; + } + + do_action( 'wp_mail_smtp_post_update_' . $type, $data ); + + return true; + } + + /** + * Delete a record from the database. + * + * @since 2.1.0 + * + * @param int|string $row_id Row ID. + * + * @return bool False if the record could not be deleted, true otherwise. + */ + public function delete( $row_id = 0 ) { + + global $wpdb; + + // Row ID must be positive integer. + $row_id = absint( $row_id ); + + if ( empty( $row_id ) ) { + return false; + } + + do_action( 'wp_mail_smtp_pre_delete', $row_id ); + do_action( 'wp_mail_smtp_pre_delete_' . $this->type, $row_id ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name} WHERE {$this->primary_key} = %d", $row_id ) ) ) { + return false; + } + + do_action( 'wp_mail_smtp_post_delete', $row_id ); + do_action( 'wp_mail_smtp_post_delete_' . $this->type, $row_id ); + + return true; + } + + /** + * Delete a record from the database by column. + * + * @since 2.1.0 + * + * @param string $column Column name. + * @param int|string $column_value Column value. + * + * @return bool False if the record could not be deleted, true otherwise. + */ + public function delete_by( $column, $column_value ) { + + global $wpdb; + + if ( empty( $column ) || empty( $column_value ) || ! array_key_exists( $column, $this->get_columns() ) ) { + return false; + } + + do_action( 'wp_mail_smtp_pre_delete', $column_value ); + do_action( 'wp_mail_smtp_pre_delete_' . $this->type, $column_value ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared + if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name} WHERE $column = %s", $column_value ) ) ) { + return false; + } + + do_action( 'wp_mail_smtp_post_delete', $column_value ); + do_action( 'wp_mail_smtp_post_delete_' . $this->type, $column_value ); + + return true; + } + + /** + * Check if the given table exists. + * + * @since 2.1.0 + * + * @param string $table The table name. Defaults to the child class table name. + * + * @return string|null If the table name exists. + */ + public function table_exists( $table = '' ) { + + global $wpdb; + + if ( ! empty( $table ) ) { + $table = sanitize_text_field( $table ); + } else { + $table = $this->table_name; + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $db_result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); + + if ( is_null( $db_result ) ) { + return false; + } + + return strtolower( $db_result ) === strtolower( $table ); + } + + /** + * Create custom entry meta database table. + * Used in migration. + * + * @since 2.1.0 + */ + public function create_table() { + + global $wpdb; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + + $charset_collate = ''; + + if ( ! empty( $wpdb->charset ) ) { + $charset_collate .= "DEFAULT CHARACTER SET {$wpdb->charset}"; + } + if ( ! empty( $wpdb->collate ) ) { + $charset_collate .= " COLLATE {$wpdb->collate}"; + } + + $sql = "CREATE TABLE {$this->table_name} ( + id bigint(20) NOT NULL AUTO_INCREMENT, + action varchar(255) NOT NULL, + data longtext NOT NULL, + date datetime NOT NULL, + PRIMARY KEY (id) + ) {$charset_collate};"; + + dbDelta( $sql ); + } + + /** + * Remove queue records for a defined period of time in the past. + * Calling this method will remove queue records that are older than $period seconds. + * + * @since 2.1.0 + * + * @param string $action Action that should be cleaned up. + * @param int $interval Number of seconds from now. + * + * @return int Number of removed tasks meta records. + */ + public function clean_by( $action, $interval ) { + + global $wpdb; + + if ( empty( $action ) || empty( $interval ) ) { + return 0; + } + + $table = self::get_table_name(); + $action = sanitize_key( $action ); + $date = gmdate( 'Y-m-d H:i:s', time() - (int) $interval ); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + return (int) $wpdb->query( + $wpdb->prepare( + "DELETE FROM `$table` WHERE action = %s AND date < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $action, + $date + ) + ); + } + + /** + * Inserts a new record into the database. + * + * @since 2.1.0 + * + * @param array $data Column data. + * @param string $type Optional. Data type context. + * + * @return int ID for the newly inserted record. 0 otherwise. + */ + public function add( $data, $type = '' ) { + + if ( empty( $data['action'] ) || ! is_string( $data['action'] ) ) { + return 0; + } + + $data['action'] = sanitize_key( $data['action'] ); + + if ( isset( $data['data'] ) ) { + $string = wp_json_encode( $data['data'] ); + + if ( $string === false ) { + $string = ''; + } + + /* + * We are encoding the string representation of all the data + * to make sure that nothing can harm the database. + * This is not an encryption, and we need this data later as is, + * so we are using one of the fastest way to do that. + * This data is removed from DB on a daily basis. + */ + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + $data['data'] = base64_encode( $string ); + } + + if ( empty( $type ) ) { + $type = $this->type; + } + + return $this->add_to_db( $data, $type ); + } + + /** + * Retrieve a row from the database based on a given row ID. + * + * @since 2.1.0} + * + * @param int $meta_id Meta ID. + * + * @return null|object + */ + public function get( $meta_id ) { + + $meta = $this->get_from_db( $meta_id ); + + if ( empty( $meta ) || empty( $meta->data ) ) { + return $meta; + } + + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode + $decoded = base64_decode( $meta->data ); + + if ( $decoded === false || ! is_string( $decoded ) ) { + $meta->data = ''; + } else { + $meta->data = json_decode( $decoded, true ); + } + + return $meta; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/NotificationsUpdateTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/NotificationsUpdateTask.php new file mode 100644 index 00000000..0d39f835 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/NotificationsUpdateTask.php @@ -0,0 +1,75 @@ +get_notifications()->is_enabled() || + Tasks::is_scheduled( self::ACTION ) !== false + ) { + return; + } + + // Schedule the task. + $this->recurring( + strtotime( '+1 minute' ), + wp_mail_smtp()->get_notifications()->get_notification_update_task_interval() + ) + ->unique() + ->register(); + } + + /** + * Update the notification feed. + * + * @since 4.3.0 + */ + public function process() { + + // Delete task duplicates. + try { + $this->remove_pending( 1000 ); + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Do nothing. + } + + wp_mail_smtp()->get_notifications()->update(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/CleanupQueueTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/CleanupQueueTask.php new file mode 100644 index 00000000..2964de63 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/CleanupQueueTask.php @@ -0,0 +1,90 @@ +get_queue()->is_enabled() || + Tasks::is_scheduled( self::ACTION ) !== false + ) { + return; + } + + // Schedule the task. + $this->recurring( strtotime( 'now' ), DAY_IN_SECONDS ) + ->unique() + ->register(); + } + + /** + * Perform email sending. + * + * @since 4.0.0 + */ + public function process() { + + $queue = wp_mail_smtp()->get_queue(); + $attachments = new Attachments(); + + // Cleanup processed emails. + $queue->cleanup(); + + // Cleanup older-than-a-month attachments. + $attachments->delete_attachments( null, new DateTime( '1 month ago', new DateTimeZone( 'UTC' ) ) ); + + if ( ! $queue->is_enabled() ) { + // If the query has been disabled in the meanwhile, + // and there aren't any emails left, + // cancel the cleanup task. + $queued_emails_count = $queue->count_queued_emails(); + $processed_emails_count = $queue->count_processed_emails(); + + if ( $queued_emails_count === 0 && $processed_emails_count === 0 ) { + // Cleanup any remaining, older-than-an-hour attachments. + $attachments->delete_attachments( null, new DateTime( '1 hour ago', new DateTimeZone( 'UTC' ) ) ); + + $this->cancel_force(); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/ProcessQueueTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/ProcessQueueTask.php new file mode 100644 index 00000000..9f2281f9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/ProcessQueueTask.php @@ -0,0 +1,84 @@ +get_queue()->is_enabled() || + Tasks::is_scheduled( self::ACTION ) !== false + ) { + return; + } + + // Schedule the task. + $this->recurring( strtotime( 'now' ), MINUTE_IN_SECONDS ) + ->unique() + ->register(); + } + + /** + * Perform email sending. + * + * @since 4.0.0 + */ + public function process() { + + $queue = wp_mail_smtp()->get_queue(); + + $queue->process(); + + if ( ! $queue->is_enabled() ) { + $this->cancel_force(); + } + } + + /** + * Cleanup completed tasks. + * + * @since 4.1.0 + */ + public function cleanup() { + + $this->remove_completed( 10 ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/SendEnqueuedEmailTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/SendEnqueuedEmailTask.php new file mode 100644 index 00000000..c80a3871 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Queue/SendEnqueuedEmailTask.php @@ -0,0 +1,97 @@ +async() + ->params( $email_id ) + ->register(); + } + + /** + * Perform email sending. + * + * @since 4.0.0 + * + * @param int $meta_id The Meta ID with the stored task parameters. + */ + public function process( $meta_id ) { + + $task_meta = new Meta(); + $meta = $task_meta->get( (int) $meta_id ); + + // We should actually receive the passed parameter. + if ( empty( $meta ) || empty( $meta->data ) || count( $meta->data ) < 1 ) { + return; + } + + $email_id = $meta->data[0]; + + wp_mail_smtp()->get_queue()->send_email( $email_id ); + } + + /** + * Cleanup completed tasks. + * + * @since 4.1.0 + */ + public function cleanup() { + + $this->remove_completed( 10 ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Reports/SummaryEmailTask.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Reports/SummaryEmailTask.php new file mode 100644 index 00000000..83fb5c0c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Reports/SummaryEmailTask.php @@ -0,0 +1,104 @@ +recurring( $date->getTimestamp(), WEEK_IN_SECONDS ) + ->unique() + ->register(); + } + + /** + * Process summary report email send. + * + * @since 3.0.0 + * + * @param int $meta_id The Meta ID with the stored task parameters. + */ + public function process( $meta_id ) { + + // Prevent email sending if summary report email is disabled. + if ( SummaryReportEmail::is_disabled() || ! $this->is_allowed() ) { + return; + } + + // Update the last sent week at the top to prevent multiple emails in case of task failure and retry. + update_option( 'wp_mail_smtp_summary_report_email_last_sent_week', current_time( 'W' ) ); + + $reports = wp_mail_smtp()->get_reports(); + + $email = $reports->get_summary_report_email(); + + $email->send(); + } + + /** + * Check if the summary report email is allowed to be sent. + * + * The email is allowed to be sent if it was not sent in the current week. + * + * @since 4.1.1 + * + * @return bool + */ + private function is_allowed() { + + $last_sent_week = get_option( 'wp_mail_smtp_summary_report_email_last_sent_week' ); + $current_week = current_time( 'W' ); + + if ( $last_sent_week === false || ( (int) $current_week !== (int) $last_sent_week ) ) { + return true; + } + + return false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Task.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Task.php new file mode 100644 index 00000000..d581ba5f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Task.php @@ -0,0 +1,449 @@ +action = sanitize_key( $action ); + + if ( empty( $this->action ) ) { + throw new \UnexpectedValueException( 'Task action cannot be empty.' ); + } + } + + /** + * Define the type of the task as async. + * + * @since 2.1.0 + * + * @return Task + */ + public function async() { + + $this->type = self::TYPE_ASYNC; + + return $this; + } + + /** + * Define the type of the task as recurring. + * + * @since 2.1.0 + * + * @param int $timestamp When the first instance of the job will run. + * @param int $interval How long to wait between runs. + * + * @return Task + */ + public function recurring( $timestamp, $interval ) { + + $this->type = self::TYPE_RECURRING; + $this->timestamp = (int) $timestamp; + $this->interval = (int) $interval; + + return $this; + } + + /** + * Define the type of the task as one-time. + * + * @since 2.1.0 + * + * @param int $timestamp When the first instance of the job will run. + * + * @return Task + */ + public function once( $timestamp ) { + + $this->type = self::TYPE_ONCE; + $this->timestamp = (int) $timestamp; + + return $this; + } + + /** + * Set this task as unique. + * + * @since 4.0.0 + * + * @return Task + */ + public function unique() { + + $this->unique = true; + + return $this; + } + + /** + * Pass any number of params that should be saved to Meta table. + * + * @since 2.1.0 + * + * @return Task + */ + public function params() { + + $args = func_get_args(); + + if ( ! empty( $args ) ) { + $this->params = $args; + } + + return $this; + } + + /** + * Register the action. + * Should be the final call in a chain. + * + * @since 2.1.0 + * + * @return null|string Action ID. + */ + public function register() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $action_id = null; + + // No processing if ActionScheduler is not usable. + if ( ! Tasks::is_usable() ) { + return $action_id; + } + + // Save data to tasks meta table. + if ( ! is_null( $this->params ) ) { + $task_meta = new Meta(); + + // No processing if meta table was not created. + if ( ! $task_meta->table_exists() ) { + return $action_id; + } + + $this->meta_id = $task_meta->add( + [ + 'action' => $this->action, + 'data' => isset( $this->params ) ? $this->params : [], + ] + ); + + if ( empty( $this->meta_id ) ) { + return $action_id; + } + } + + // Prevent 500 errors when Action Scheduler tables don't exist. + try { + switch ( $this->type ) { + case self::TYPE_ASYNC: + $action_id = $this->register_async(); + break; + + case self::TYPE_RECURRING: + $action_id = $this->register_recurring(); + break; + + case self::TYPE_ONCE: + $action_id = $this->register_once(); + break; + } + } catch ( \RuntimeException $exception ) { + $action_id = null; + } + + return $action_id; + } + + /** + * Register the async task. + * + * @since 2.1.0 + * + * @return null|string Action ID. + */ + protected function register_async() { + + if ( ! function_exists( 'as_enqueue_async_action' ) ) { + return null; + } + + return as_enqueue_async_action( + $this->action, + [ $this->meta_id ], + Tasks::GROUP, + $this->unique + ); + } + + /** + * Register the recurring task. + * + * @since 2.1.0 + * + * @return null|string Action ID. + */ + protected function register_recurring() { + + if ( ! function_exists( 'as_schedule_recurring_action' ) ) { + return null; + } + + return as_schedule_recurring_action( + $this->timestamp, + $this->interval, + $this->action, + [ $this->meta_id ], + Tasks::GROUP, + $this->unique + ); + } + + /** + * Register the one-time task. + * + * @since 2.1.0 + * + * @return null|string Action ID. + */ + protected function register_once() { + + if ( ! function_exists( 'as_schedule_single_action' ) ) { + return null; + } + + return as_schedule_single_action( + $this->timestamp, + $this->action, + [ $this->meta_id ], + Tasks::GROUP, + $this->unique + ); + } + + /** + * Cancel all occurrences of this task. + * + * @since 2.1.0 + * + * @return null|bool|string Null if no matching action found, + * false if AS library is missing, + * string of the scheduled action ID if a scheduled action was found and unscheduled. + */ + public function cancel() { + + // Exit if AS function does not exist. + if ( ! function_exists( 'as_unschedule_all_actions' ) || ! Tasks::is_usable() ) { + return false; + } + + as_unschedule_all_actions( $this->action ); + + return true; + } + + /** + * Cancel all occurrences of this task, + * preventing it from re-registering itself. + * + * @since 4.0.0 + */ + public function cancel_force() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + add_action( 'shutdown', [ $this, 'cancel' ], PHP_INT_MAX ); + } + + /** + * Remove completed occurrences of this task. + * + * @since 4.1.0 + * + * @param int $limit The amount of rows to remove. + */ + protected function remove_completed( $limit = 0 ) { + + // Make sure that all used functions, classes, and methods exist. + if ( + ! function_exists( 'as_get_scheduled_actions' ) || + ! class_exists( 'ActionScheduler' ) || + ! method_exists( 'ActionScheduler', 'store' ) || + ! class_exists( 'ActionScheduler_Store' ) || + ! method_exists( 'ActionScheduler_Store', 'delete_action' ) + ) { + return; + } + + // Cap the query result to prevent performing a large number of individual delete actions at once. + $per_page = min( 10, max( 0, intval( $limit ) ) ); + + // Get completed occurrences of this task. + $action_ids = as_get_scheduled_actions( + [ + 'hook' => $this->action, + 'status' => 'complete', + 'per_page' => $per_page, + ], + 'ids' + ); + + if ( empty( $action_ids ) ) { + return; + } + + // Delete actions through the Action Scheduler API so that associated + // `actionscheduler_logs` rows are cleaned up via the + // `action_scheduler_deleted_action` hook. + foreach ( $action_ids as $action_id ) { + ActionScheduler::store()->delete_action( $action_id ); + } + } + + /** + * Remove pending occurrences of this task. + * + * @since 4.3.0 + * + * @param int $limit The amount of rows to remove. + */ + protected function remove_pending( $limit = 0 ) { + + // Make sure that all used functions, classes, and methods exist. + if ( + ! function_exists( 'as_get_scheduled_actions' ) || + ! class_exists( 'ActionScheduler' ) || + ! method_exists( 'ActionScheduler', 'store' ) || + ! class_exists( 'ActionScheduler_Store' ) || + ! method_exists( 'ActionScheduler_Store', 'delete_action' ) + ) { + return; + } + + $per_page = max( 0, intval( $limit ) ); + + // Get all pending license check actions. + $action_ids = as_get_scheduled_actions( + [ + 'hook' => $this->action, + 'status' => 'pending', + 'per_page' => $per_page, + ], + 'ids' + ); + + if ( empty( $action_ids ) ) { + return; + } + + // Delete all pending license check actions. + foreach ( $action_ids as $action_id ) { + ActionScheduler::store()->delete_action( $action_id ); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Tasks.php b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Tasks.php new file mode 100644 index 00000000..b4aa1ab0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Tasks/Tasks.php @@ -0,0 +1,321 @@ +get_tasks() as $task ) { + if ( ! is_subclass_of( $task, '\WPMailSMTP\Tasks\Task' ) ) { + continue; + } + + $new_task = new $task(); + + // Run the init method, if a task has one defined. + if ( method_exists( $new_task, 'init' ) ) { + $new_task->init(); + } + } + + // Remove scheduled action meta after action execution. + add_action( 'action_scheduler_after_execute', [ $this, 'clear_action_meta' ], PHP_INT_MAX, 2 ); + + // Cancel tasks on plugin deactivation. + register_deactivation_hook( WPMS_PLUGIN_FILE, [ $this, 'cancel_all' ] ); + } + + /** + * Get the list of default scheduled tasks. + * Tasks, that are fired under certain specific circumstances + * (like sending emails) are not listed here. + * + * @since 2.1.0 + * + * @return Task[] List of tasks classes. + */ + public function get_tasks() { + + $tasks = [ + SummaryEmailTask::class, + DebugEventsCleanupTask::class, + ProcessQueueTask::class, + CleanupQueueTask::class, + SendEnqueuedEmailTask::class, + NotificationsUpdateTask::class, + ]; + + /** + * Filters list of tasks classes. + * + * @since 2.1.2 + * + * @param Task[] $tasks List of tasks classes. + */ + return apply_filters( 'wp_mail_smtp_tasks_get_tasks', $tasks ); + } + + /** + * Hide Action Scheduler admin area when not in debug mode. + * + * @since 2.1.0 + */ + public function admin_hide_as_menu() { + + $plugin_exceptions = [ + 'woocommerce/woocommerce.php', + 'action-scheduler/action-scheduler.php', + ]; + + /** + * Filters the list of plugins for which + * the Action Scheduler Tools ->Scheduled Actions menu item + * should remain visible. + * + * @since 4.3.0 + * + * @param array $plugin_exceptions List of plugins exceptions. + */ + $plugin_exceptions = apply_filters( 'wp_mail_smtp_tasks_tasks_action_scheduler_tools_plugin_exceptions', $plugin_exceptions ); + $hide_as_menu = empty( array_filter( $plugin_exceptions, 'is_plugin_active' ) ); + + // Filter to redefine that WP Mail SMTP hides Tools > Action Scheduler menu item. + if ( apply_filters( 'wp_mail_smtp_tasks_admin_hide_as_menu', $hide_as_menu ) ) { + remove_submenu_page( 'tools.php', 'action-scheduler' ); + } + } + + /** + * Create a new task. + * Used for "inline" tasks, that require additional information + * from the plugin runtime before they can be scheduled. + * + * Example: + * wp_mail_smtp()->get( 'tasks' ) + * ->create( 'i_am_the_dude' ) + * ->async() + * ->params( 'The Big Lebowski', 1998 ) + * ->register(); + * + * This `i_am_the_dude` action will be later processed as: + * add_action( 'i_am_the_dude', 'thats_what_you_call_me' ); + * + * @since 2.1.0 + * + * @param string $action Action that will be used as a hook. + * + * @return Task + */ + public function create( $action ) { + + return new Task( $action ); + } + + /** + * Cancel all the AS actions for a group. + * + * @since 2.1.0 + * + * @param string $group Group to cancel all actions for. + */ + public function cancel_all( $group = '' ) { + + if ( empty( $group ) ) { + $group = self::GROUP; + } else { + $group = sanitize_key( $group ); + } + + if ( class_exists( 'ActionScheduler_DBStore' ) ) { + ActionScheduler_DBStore::instance()->cancel_actions_by_group( $group ); + } + } + + /** + * Remove all the AS actions for a group and remove group. + * + * @since 3.7.0 + * + * @param string $group Group to remove all actions for. + */ + public function remove_all( $group = '' ) { + + global $wpdb; + + if ( empty( $group ) ) { + $group = self::GROUP; + } else { + $group = sanitize_key( $group ); + } + + if ( + class_exists( 'ActionScheduler_DBStore' ) && + isset( $wpdb->actionscheduler_actions ) && + isset( $wpdb->actionscheduler_groups ) + ) { + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $group_id = $wpdb->get_var( + $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $group ) + ); + + if ( ! empty( $group_id ) ) { + // Delete actions. + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->delete( $wpdb->actionscheduler_actions, [ 'group_id' => (int) $group_id ], [ '%d' ] ); + + // Delete group. + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->delete( $wpdb->actionscheduler_groups, [ 'slug' => $group ], [ '%s' ] ); + } + } + } + + /** + * Clear the meta after action complete. + * Fired before an action is marked as completed. + * + * @since 3.5.0 + * + * @param integer $action_id Action ID. + * @param ActionScheduler_Action $action Action name. + */ + public function clear_action_meta( $action_id, $action ) { + + $action_schedule = $action->get_schedule(); + + if ( + $action_schedule === null || + $action_schedule->is_recurring() || + $action->get_group() !== self::GROUP + ) { + return; + } + + $hook_args = $action->get_args(); + + if ( ! is_numeric( $hook_args[0] ) ) { + return; + } + + $meta = new Meta(); + + $meta->delete( $hook_args[0] ); + } + + /** + * Whether ActionScheduler thinks that it has migrated or not. + * + * @since 2.1.0 + * + * @return bool + */ + public static function is_usable() { + + // No tasks if ActionScheduler wasn't loaded. + if ( ! class_exists( 'ActionScheduler_DataController' ) ) { + return false; + } + + return ActionScheduler_DataController::is_migration_complete(); + } + + /** + * Whether task has been scheduled and is pending. + * + * @since 2.1.0 + * + * @param string $hook Hook to check for. + * + * @return bool|null + */ + public static function is_scheduled( $hook ) { + + // If ActionScheduler wasn't loaded, then no tasks are scheduled. + if ( ! function_exists( 'as_next_scheduled_action' ) ) { + return null; + } + + if ( is_null( self::$active_actions ) ) { + self::$active_actions = self::get_active_actions(); + } + + if ( in_array( $hook, self::$active_actions, true ) ) { + return true; + } + + // Action is not in the array, so it is not scheduled or belongs to another group. + if ( function_exists( 'as_has_scheduled_action' ) ) { + // This function more performant than `as_next_scheduled_action`, but it is available only since AS 3.3.0. + return as_has_scheduled_action( $hook ); + } else { + return as_next_scheduled_action( $hook ) !== false; + } + } + + /** + * Get all WP Mail SMTP pending or in-progress actions. + * + * @since 3.3.0 + */ + private static function get_active_actions() { + + global $wpdb; + + $group = self::GROUP; + $sql = "SELECT a.hook FROM {$wpdb->prefix}actionscheduler_actions a + JOIN {$wpdb->prefix}actionscheduler_groups g ON g.group_id = a.group_id + WHERE g.slug = '$group' AND a.status IN ('in-progress', 'pending')"; + + // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( $sql, 'ARRAY_N' ); + // phpcs:enable WordPress.DB.DirectDatabaseQuery.NoCaching + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared + + return $results ? array_merge( ...$results ) : []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/TestEmail/TestEmail.php b/web/app/plugins/wp-mail-smtp-pro/src/TestEmail/TestEmail.php new file mode 100644 index 00000000..c0ac6550 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/TestEmail/TestEmail.php @@ -0,0 +1,521 @@ +connection = $connection; + + return $this; + } + + /** + * Set the caller context (drives the X-Mailer-Type header). + * + * @since 4.9.0 + * + * @param string $context One of the CONTEXT_* constants. + * + * @return self + */ + public function with_context( $context ) { + + $this->context = (string) $context; + + return $this; + } + + /** + * Toggle HTML vs plain text body. + * + * @since 4.9.0 + * + * @param bool $is_html True for HTML, false for plain text. + * + * @return self + */ + public function as_html( $is_html ) { + + $this->is_html = (bool) $is_html; + + return $this; + } + + /** + * Toggle DomainChecker invocation after a successful send. + * + * @since 4.9.0 + * + * @param bool $run True to run the domain check, false to skip. + * + * @return self + */ + public function with_domain_check( $run ) { + + $this->run_domain_check = (bool) $run; + + return $this; + } + + /** + * Whether the send succeeded (with or without domain-check issues). + * + * @since 4.9.0 + * + * @return bool + */ + public function is_successful() { + + return $this->result === self::SUCCESS || $this->result === self::FAILED_DOMAIN_CHECK; + } + + /** + * Get the raw result constant. + * + * @since 4.9.0 + * + * @return int|null + */ + public function get_result() { + + return $this->result; + } + + /** + * Get the DomainChecker instance populated after a successful send (when enabled). + * + * @since 4.9.0 + * + * @return DomainChecker|null + */ + public function get_domain_checker() { + + return $this->domain_checker; + } + + /** + * Get the connection used for the send. + * + * @since 4.9.0 + * + * @return ConnectionInterface|null + */ + public function get_connection() { + + return $this->connection; + } + + /** + * Send the test email. + * + * Lifted from TestTab::process_post() with the form-data plumbing replaced by + * fluent setters; the wp_mail() / ob_start / DomainChecker block stays identical. + * + * @since 4.9.0 + * + * @param string $recipient Recipient email address. + * + * @return int|null Result constant (SUCCESS, FAILED, FAILED_DOMAIN_CHECK), or null when validation failed. + */ + public function send( $recipient ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks -- Paired add_filter/remove_filter scope the HTML content-type to this single test send only; moving them to hooks() would change behavior. + + $recipient = filter_var( wp_unslash( $recipient ), FILTER_VALIDATE_EMAIL ); + + if ( empty( $recipient ) ) { + return null; + } + + if ( ! $this->connection ) { + $this->connection = wp_mail_smtp()->get_connections_manager()->get_primary_connection(); + } + + $phpmailer = wp_mail_smtp()->get_processor()->get_phpmailer(); + + /* translators: %s - email address a test email will be sent to. */ + $subject = 'WP Mail SMTP: ' . sprintf( esc_html__( 'Test email to %s', 'wp-mail-smtp' ), $recipient ); + $headers = [ 'X-Mailer-Type:' . $this->context ]; + + if ( $this->is_html ) { + add_filter( 'wp_mail_content_type', [ __CLASS__, 'set_test_html_content_type' ] ); + + /* translators: %s - email address a test email will be sent to. */ + $subject = 'WP Mail SMTP: HTML ' . sprintf( esc_html__( 'Test email to %s', 'wp-mail-smtp' ), $recipient ); + $headers[] = 'Content-Type: text/html'; + } + + // Send the test mail. + $result = wp_mail( + $recipient, + $subject, + $this->get_email_message( $this->is_html ), + $headers + ); + + if ( $this->is_html ) { + remove_filter( 'wp_mail_content_type', [ __CLASS__, 'set_test_html_content_type' ] ); + } + + /* + * Notify a user about the results. + */ + if ( $result ) { + if ( $this->run_domain_check ) { + $connection_options = $this->connection->get_options(); + $mailer = $connection_options->get( 'mail', 'mailer' ); + $email = $connection_options->get( 'mail', 'from_email' ); + $domain = ''; + + // Add the optional sending domain parameter. + if ( in_array( $mailer, [ 'mailgun', 'sendinblue', 'sendgrid' ], true ) ) { + $domain = $connection_options->get( $mailer, 'domain' ); + } + + $this->domain_checker = new DomainChecker( $mailer, $email, $domain ); + + $this->result = $this->domain_checker->no_issues() ? self::SUCCESS : self::FAILED_DOMAIN_CHECK; + } else { + $this->result = self::SUCCESS; + } + } else { + $this->result = self::FAILED; + } + + return $this->result; + } + + /** + * Get the email message that should be sent. + * + * @since 1.4.0 + * + * @param bool $is_html Whether to send an HTML email or plain text. + * + * @return string + */ + private function get_email_message( $is_html = true ) { + + // Default plain text version of the email. + $message = self::get_email_message_text(); + + if ( $is_html ) { + $message = $this->get_email_message_html(); + } + + return $message; + } + + /** + * Get the HTML prepared message for test email. + * + * @since 1.4.0 + * + * @return string + */ + private function get_email_message_html() { + + ob_start(); + ?> + + + + + + + WP Mail SMTP Test Email + + + + + + + + +
    + + + + + + + + + + +
    + WP Mail SMTP Logo +
    + + + + + + is_pro() ) : ?> + + + + + +
    +
    +

    + Success +

    +

    + Congrats, test email was sent successfully! +

    +

    + Thank you for trying out WP Mail SMTP. We're on a mission to make sure that your emails actually get delivered. +

    + is_pro() ) : ?> +

    + If you find this free plugin useful, please consider giving WP Mail SMTP Pro a try! +

    + +

    + Signature +

    +

    + Syed Balkhi
    Co-Founder, WP Mail SMTP +

    +
    +
    +
    + Unlock Powerful Features with WP Mail SMTP Pro +
    +

    + Email Logging with Email Resending
    + Open & Click Tracking
    + Email Reports with Weekly Summary
    + Backup Mailer
    + Failed Email Alerts via Email, Slack, and SMS
    + World-Class Support +

    +

    + WP Mail SMTP users get $50 off, automatically applied at checkout +

    +
    + + + + +
    + + + + +
    + + Upgrade to Pro Today + +
    +
    +
    +
    +
    +
    + + + + is_pro() ) { + // WP Mail SMTP Pro paid installed. + $message = +'Congrats, test email was sent successfully! + +Thank you for trying out WP Mail SMTP. We are on a mission to make sure your emails actually get delivered. + +- Syed Balkhi +Co-Founder, WP Mail SMTP'; + } else { + // Free WP Mail SMTP is installed. + $message = +'Congrats, test email was sent successfully! + +Thank you for trying out WP Mail SMTP. We are on a mission to make sure your emails actually get delivered. + +If you find this free plugin useful, please consider giving WP Mail SMTP Pro a try! + +https://wpmailsmtp.com/lite-upgrade/ + +Unlock These Powerful Features with WP Mail SMTP Pro: + ++ Log all emails and resend failed emails from your email log ++ Track opens and clicks to measure the engagement ++ Get email reports with a weekly summary of your email activity ++ Use a backup mailer if your mail service goes down ++ Get notified of failed emails via email, Slack, or SMS ++ Get help from our world-class support team + +- Syed Balkhi +Co-Founder, WP Mail SMTP'; + } + // phpcs:enable + + return $message; + } + + /** + * Set the HTML content type for a test email. + * + * @since 1.4.0 + * + * @return string + */ + public static function set_test_html_content_type() { + + return 'text/html'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Upgrade.php b/web/app/plugins/wp-mail-smtp-pro/src/Upgrade.php new file mode 100644 index 00000000..888b5e2e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Upgrade.php @@ -0,0 +1,91 @@ +upgrades(); + + if ( empty( $upgrades ) ) { + return; + } + + // Run any available upgrades. + foreach ( $upgrades as $upgrade ) { + if ( is_callable( $upgrade ) ) { + $upgrade(); + } + } + + // Update version post upgrade(s). + update_option( 'wp_mail_smtp_version', WPMS_PLUGIN_VER ); + } + + /** + * Whether we need to perform an upgrade. + * + * @since 1.1.0 + * + * @return array + */ + protected function upgrades() { + + $version = get_option( 'wp_mail_smtp_version' ); + + /** + * Filters the list of upgrade callbacks to run. + * + * @since 4.4.0 + * + * @param array $upgrades List of upgrade callbacks to run. + * @param string $version Latest installed version of the plugin. + */ + $upgrades = apply_filters( 'wp_mail_smtp_upgrade_upgrades', [], $version ); + + // Version 1.1.0 upgrade; prior to this the option was not available. + if ( empty( $version ) ) { + $upgrades[] = [ $this, 'v110_upgrade' ]; + } + + return $upgrades; + } + + /** + * Upgrade routine for v1.1.0. + * + * Set SMTPAutoTLS to true. + * + * @since 1.1.0 + */ + public function v110_upgrade() { + + // Enable SMTPAutoTLS option. + $values = [ + 'smtp' => [ + 'autotls' => true, + ], + ]; + + Options::init()->set( $values, false, false ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/Uploads.php b/web/app/plugins/wp-mail-smtp-pro/src/Uploads.php new file mode 100644 index 00000000..18f97f4d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/Uploads.php @@ -0,0 +1,237 @@ + $upload_root, + 'url' => trailingslashit( $upload_dir['baseurl'] ) . $dir, + ]; + } + + + /** + * Create .htaccess file in the WPMailSMTP upload directory. + * + * @since 2.8.0 + * + * @return bool True when the .htaccess file exists, false on failure. + */ + public static function create_upload_dir_htaccess_file() { + + /** + * Filters create upload dir htaccess file. + * + * @since 2.8.0 + * + * @param bool $is_create Creates upload dir htaccess file. + */ + if ( ! apply_filters( 'wp_mail_smtp_uploads_create_upload_dir_htaccess_file', true ) ) { + return false; + } + + $upload_dir = self::upload_dir(); + + if ( is_wp_error( $upload_dir ) ) { + return false; + } + + $htaccess_file = wp_normalize_path( trailingslashit( $upload_dir['path'] ) . '.htaccess' ); + $cache_key = 'wp_mail_smtp_upload_dir_htaccess_file'; + + if ( is_file( $htaccess_file ) ) { + $cached_stat = get_transient( $cache_key ); + $stat = array_intersect_key( + stat( $htaccess_file ), + [ + 'size' => 0, + 'mtime' => 0, + 'ctime' => 0, + ] + ); + + if ( $cached_stat === $stat ) { + return true; + } + + @unlink( $htaccess_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + } + + if ( ! function_exists( 'insert_with_markers' ) ) { + require_once ABSPATH . 'wp-admin/includes/misc.php'; + } + + /** + * Filters upload dir htaccess file content. + * + * @since 2.8.0 + * + * @param bool $content Upload dir htaccess file content. + */ + $contents = apply_filters( + 'wp_mail_smtp_uploads_create_upload_dir_htaccess_file_content', + '# Disable PHP and Python scripts parsing. + + SetHandler none + SetHandler default-handler + RemoveHandler .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo + RemoveType .cgi .php .php3 .php4 .php5 .phtml .pl .py .pyc .pyo + + + php_flag engine off + + + php_flag engine off + + + php_flag engine off + + + Header set X-Robots-Tag "noindex" +' + ); + + $created = insert_with_markers( $htaccess_file, 'WPMailSMTP', $contents ); + + if ( $created ) { + clearstatcache( true, $htaccess_file ); + $stat = array_intersect_key( + stat( $htaccess_file ), + [ + 'size' => 0, + 'mtime' => 0, + 'ctime' => 0, + ] + ); + + set_transient( $cache_key, $stat ); + } + + return $created; + } + + /** + * Create index.html file in the specified directory if it doesn't exist. + * + * @since 2.8.0 + * + * @param string $path Path to the directory. + * + * @return int|false Number of bytes that were written to the file, or false on failure. + */ + public static function create_index_html_file( $path ) { + + if ( ! is_dir( $path ) || is_link( $path ) ) { + return false; + } + + $index_file = wp_normalize_path( trailingslashit( $path ) . 'index.html' ); + + // Do nothing if index.html exists in the directory. + if ( file_exists( $index_file ) ) { + return false; + } + + // Create empty index.html. + return file_put_contents( $index_file, '' ); // phpcs:ignore WordPress.WP.AlternativeFunctions + } + + /** + * Delete the WPMailSMTP uploads directory. + * + * @since 4.1.1 + * + * @return void + */ + public static function delete_upload_dir() { + + // Get the upload dir. + $upload_dir = self::upload_dir(); + + // If there is an error, return. + if ( is_wp_error( $upload_dir ) ) { + return; + } + + $upload_root = $upload_dir['path']; + + // Get WP Filesystembase files. + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'; + require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php'; + + // Initialize WP_Filesystem_Direct. + $wp_filesystem = new WP_Filesystem_Direct( false ); + + // Delete the directory. + $wp_filesystem->delete( $upload_root, true ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/ErrorStats.php b/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/ErrorStats.php new file mode 100644 index 00000000..0d51d32d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/ErrorStats.php @@ -0,0 +1,329 @@ +resolve_mailer_slug( $mailer_slug ); + + $this->track_error( $mailer_slug, $error_key ); + } + + /** + * Track email delivery failure from webhooks or delivery verification. + * + * @since 4.8.0 + * + * @param string $mailer_slug Current mailer name. + * @param string $error_code Error code. + * @param string $error_message Error message. + */ + public function track_delivery_failed( $mailer_slug, $error_code, $error_message = '' ) { + + $error_key = $this->build_error_key( 'delivery', (string) $error_code, (string) $error_message ); + + $this->track_error( $mailer_slug, $error_key ); + } + + /** + * Track email sending error. + * + * Accumulates error in memory. All accumulated stats are flushed + * to the database on shutdown. + * + * @since 4.8.0 + * + * @param string $mailer_slug Current mailer name. + * @param string $error_key Normalized error key. + */ + private function track_error( $mailer_slug, $error_key ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks + + if ( ! isset( $this->pending_stats[ $mailer_slug ] ) ) { + $this->pending_stats[ $mailer_slug ] = []; + } + + if ( ! isset( $this->pending_stats[ $mailer_slug ][ $error_key ] ) ) { + $this->pending_stats[ $mailer_slug ][ $error_key ] = 0; + } + + $this->pending_stats[ $mailer_slug ][ $error_key ]++; + + // Register shutdown flush lazily on first tracked error. + if ( ! $this->shutdown_registered ) { + add_action( 'shutdown', [ $this, 'flush' ] ); + $this->shutdown_registered = true; + } + } + + /** + * Build error key in format: {prefix}:{error_code}:{sanitized_message}. + * + * Uses "-" for missing parts to keep the format parseable. + * + * @since 4.8.0 + * + * @param string $prefix First segment — HTTP response code or category (e.g. "401", "delivery"). + * @param string $error_code API-specific error code. + * @param string $error_message Error message text. + * + * @return string Normalized error key. + */ + private function build_error_key( $prefix, $error_code, $error_message ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $part_response = ! empty( $prefix ) ? $prefix : '-'; + $part_code = ! empty( $error_code ) && $error_code !== 'unknown' && (string) $error_code !== $part_response ? $error_code : '-'; + $part_message = ! empty( $error_message ) ? $this->sanitize_message( $error_message ) : '-'; + + // Remove response code and error code from message to save space. + if ( $part_response !== '-' ) { + $part_message = str_replace( sanitize_title( $part_response ), '', $part_message ); + } + + if ( $part_code !== '-' ) { + $part_message = str_replace( sanitize_title( $part_code ), '', $part_message ); + } + + $part_message = preg_replace( '/-{2,}/', '-', $part_message ); + $part_message = trim( $part_message, '-' ); + + if ( empty( $part_message ) ) { + $part_message = '-'; + } + + $key = $part_response . ':' . $part_code . ':' . $part_message; + + if ( ! function_exists( 'mb_strlen' ) ) { + Helpers::include_mbstring_polyfill(); + } + + if ( mb_strlen( $key ) > self::MAX_ERROR_CODE_LENGTH ) { + $key = mb_substr( $key, 0, self::MAX_ERROR_CODE_LENGTH ); + + // Cut at last hyphen to avoid partial words. + $last_hyphen = strrpos( $key, '-' ); + + if ( $last_hyphen !== false && $last_hyphen > strrpos( $key, ':' ) ) { + $key = substr( $key, 0, $last_hyphen ); + } + } + + return $key; + } + + /** + * Sanitize error message into a short aggregatable slug. + * + * Strips dynamic content (emails, domains, URLs, UUIDs, quoted strings), + * takes first ~5 words, and slugifies. + * + * @since 4.8.0 + * + * @param string $message Error message. + * + * @return string Sanitized message slug. + */ + private function sanitize_message( $message ) { + + // Strip HTML entities. + $message = html_entity_decode( $message, ENT_QUOTES, 'UTF-8' ); + + // Strip emails. + $message = preg_replace( '/\S+@\S+\.\S+/', '', $message ); + + // Strip URLs. + $message = preg_replace( '#https?://\S+#i', '', $message ); + + // Strip dot-separated strings (domains, namespaces, etc.). + $message = preg_replace( '/\b\S+\.\S+\b/', '', $message ); + + // Strip UUIDs. + $message = preg_replace( '/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i', '', $message ); + + // Strip quoted strings. + $message = preg_replace( '/["\'][^"\']*["\']/', '', $message ); + + // Collapse whitespace and trim. + $message = trim( preg_replace( '/\s+/', ' ', $message ) ); + + return sanitize_title( $message ); + } + + /** + * Resolve mailer slug, distinguishing one-click setups. + * + * @since 4.8.0 + * + * @param string $mailer_slug Original mailer slug. + * + * @return string Resolved mailer slug. + */ + private function resolve_mailer_slug( $mailer_slug ) { + + if ( $mailer_slug === 'gmail' && Options::init()->get( 'gmail', 'one_click_setup_enabled' ) ) { + return 'gmail_one_click'; + } + + if ( $mailer_slug === 'outlook' && Options::init()->get( 'outlook', 'one_click_setup_enabled' ) ) { + return 'outlook_one_click'; + } + + return $mailer_slug; + } + + /** + * Flush accumulated stats to the database. + * + * Reads current stored stats, merges with pending stats, and saves. + * Bails early if no errors were tracked during this request. + * + * @since 4.8.0 + */ + public function flush() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( empty( $this->pending_stats ) ) { + return; + } + + $stored = get_option( self::OPTION_NAME, [] ); + + if ( ! is_array( $stored ) ) { + $stored = []; + } + + foreach ( $this->pending_stats as $mailer_slug => $error_codes ) { + if ( ! isset( $stored[ $mailer_slug ] ) ) { + $stored[ $mailer_slug ] = []; + } + + foreach ( $error_codes as $error_code => $count ) { + // Apply overflow limit against the merged state. + if ( + count( $stored[ $mailer_slug ] ) >= 50 && + ! isset( $stored[ $mailer_slug ][ $error_code ] ) + ) { + $error_code = 'overflow'; + } + + if ( ! isset( $stored[ $mailer_slug ][ $error_code ] ) ) { + $stored[ $mailer_slug ][ $error_code ] = 0; + } + + $stored[ $mailer_slug ][ $error_code ] += $count; + } + } + + update_option( self::OPTION_NAME, $stored, false ); + + $this->pending_stats = []; + } + + /** + * Add usage stats data. + * + * @since 4.8.0 + * + * @param array $data Usage data. + * + * @return array + */ + public function add_usage_stats( $data ) { + + // Flush any pending stats before reading. + $this->flush(); + + $stats = get_option( self::OPTION_NAME, [] ); + + $data['email_sending_errors_stat'] = is_array( $stats ) ? $stats : []; + + // Reset after collecting. + delete_option( self::OPTION_NAME ); + + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/SendUsageTask.php b/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/SendUsageTask.php new file mode 100644 index 00000000..be503520 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/SendUsageTask.php @@ -0,0 +1,122 @@ +recurring( $this->generate_start_date(), WEEK_IN_SECONDS ) + ->register(); + } + + /** + * Randomly pick a timestamp + * which is not more than 1 week in the future + * starting from next sunday. + * + * @since 2.3.0 + * + * @return int + */ + private function generate_start_date() { + + $tracking = []; + + $tracking['days'] = wp_rand( 0, 6 ) * DAY_IN_SECONDS; + $tracking['hours'] = wp_rand( 0, 23 ) * HOUR_IN_SECONDS; + $tracking['minutes'] = wp_rand( 0, 59 ) * MINUTE_IN_SECONDS; + $tracking['seconds'] = wp_rand( 0, 59 ); + + return strtotime( 'next sunday' ) + array_sum( $tracking ); + } + + /** + * Send the actual data in a POST request. + * This will be executed in a separate process via Action Scheduler. + * + * @since 2.3.0 + */ + public function process() { + + $last_run = get_option( self::LAST_RUN ); + + // Make sure we do not run it more than once a day. + if ( + $last_run !== false && + ( time() - $last_run ) < DAY_IN_SECONDS + ) { + return; + } + + // Send data to the usage tracking API. + $ut = new UsageTracking(); + + wp_remote_post( + self::TRACK_URL, + [ + 'timeout' => 5, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => true, + 'body' => $ut->get_data(), + 'user-agent' => $ut->get_user_agent(), + ] + ); + + // Update the last run option to the current timestamp. + update_option( self::LAST_RUN, time() ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/UsageTracking.php b/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/UsageTracking.php new file mode 100644 index 00000000..b9304ac3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/UsageTracking/UsageTracking.php @@ -0,0 +1,420 @@ +get( 'general', self::SETTINGS_SLUG ) + ); + } + + /** + * Load usage tracking functionality. + * + * @since 2.3.0 + */ + public function load() { + + // Check if loading the usage tracking functionality is allowed. + if ( ! (bool) apply_filters( 'wp_mail_smtp_usage_tracking_load_allowed', true ) ) { + return; + } + + // Deregister the action if option is disabled. + add_action( + 'wp_mail_smtp_options_set_after', + function () { + + if ( ! $this->is_enabled() ) { + ( new SendUsageTask() )->cancel(); + } + } + ); + + // Register the action handler and error stats tracking only if enabled. + if ( $this->is_enabled() ) { + /** + * Filter whether to enable error stats collection. + * + * @since 4.8.0 + * + * @param bool $enabled Whether error stats collection is enabled. Default true. + */ + if ( apply_filters( 'wp_mail_smtp_usage_tracking_error_stats_enabled', true ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName + ( new ErrorStats() )->hooks(); + } + + add_filter( + 'wp_mail_smtp_tasks_get_tasks', + static function ( $tasks ) { + $tasks[] = SendUsageTask::class; + + return $tasks; + } + ); + } + } + + /** + * Get the User Agent string that will be sent to the API. + * + * @since 2.3.0 + * + * @return string + */ + public function get_user_agent() { + + return Helpers::get_default_user_agent(); + } + + /** + * Get data for sending to the server. + * + * @since 2.3.0 + * + * @return array + */ + public function get_data() { + + global $wpdb; + + $theme_data = wp_get_theme(); + $options = Options::init(); + $mailer = wp_mail_smtp()->get_providers()->get_mailer( + $options->get( 'mail', 'mailer' ), + wp_mail_smtp()->get_processor()->get_phpmailer() + ); + $setup_wizard_stats = SetupWizard::get_stats(); + + $data = array_merge( + $this->get_required_data(), + $this->get_additional_data(), + [ + // Generic data (environment). + 'mysql_version' => $wpdb->db_version(), + 'server_version' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '', + 'is_ssl' => is_ssl(), + 'is_multisite' => is_multisite(), + 'sites_count' => $this->get_sites_total(), + 'theme_name' => $theme_data->name, + 'theme_version' => $theme_data->version, + 'locale' => get_locale(), + 'timezone_offset' => $this->get_timezone_offset(), + // WP Mail SMTP - specific data. + 'wp_mail_smtp_version' => WPMS_PLUGIN_VER, + 'wp_mail_smtp_activated' => get_option( 'wp_mail_smtp_activated_time', 0 ), + 'wp_mail_smtp_mailer' => $options->get( 'mail', 'mailer' ), + 'wp_mail_smtp_setup_type' => $this->get_setup_type( $options ), + 'wp_mail_smtp_from_email_force' => (bool) $options->get( 'mail', 'from_email_force' ), + 'wp_mail_smtp_from_name_force' => (bool) $options->get( 'mail', 'from_name_force' ), + 'wp_mail_smtp_return_path' => (bool) $options->get( 'mail', 'return_path' ), + 'wp_mail_smtp_do_not_send' => (bool) $options->get( 'general', 'do_not_send' ), + 'wp_mail_smtp_is_white_labeled' => wp_mail_smtp()->is_white_labeled(), + 'wp_mail_smtp_is_const_enabled' => (bool) $options->is_const_enabled(), + 'wp_mail_smtp_conflicts_is_detected' => ( new Conflicts() )->is_detected(), + 'wp_mail_smtp_is_mailer_complete' => empty( $mailer ) ? false : $mailer->is_mailer_complete(), + 'wp_mail_smtp_setup_wizard_launched_time' => isset( $setup_wizard_stats['launched_time'] ) ? (int) $setup_wizard_stats['launched_time'] : 0, + 'wp_mail_smtp_setup_wizard_completed_time' => isset( $setup_wizard_stats['completed_time'] ) ? (int) $setup_wizard_stats['completed_time'] : 0, + 'wp_mail_smtp_setup_wizard_completed_successfully' => ! empty( $setup_wizard_stats['was_successful'] ), + 'wp_mail_smtp_setup_wizard_mailer' => isset( $setup_wizard_stats['mailer'] ) ? $setup_wizard_stats['mailer'] : '', + 'wp_mail_smtp_source' => sanitize_title( get_option( 'wp_mail_smtp_source', '' ) ), + 'wp_mail_smtp_optimize_email_sending' => (bool) OptimizedEmailSending::is_enabled(), + ] + ); + + if ( 'smtp' === $options->get( 'mail', 'mailer' ) ) { + $data['wp_mail_smtp_other_smtp_host'] = $options->get( 'smtp', 'host' ); + $data['wp_mail_smtp_other_smtp_encryption'] = $options->get( 'smtp', 'encryption' ); + $data['wp_mail_smtp_other_smtp_port'] = $options->get( 'smtp', 'port' ); + $data['wp_mail_smtp_other_smtp_auth'] = (bool) $options->get( 'smtp', 'auth' ); + $data['wp_mail_smtp_other_smtp_autotls'] = (bool) $options->get( 'smtp', 'autotls' ); + } + + if ( is_multisite() ) { + $use_global_settings = WP::use_global_plugin_settings(); + $data['wp_mail_smtp_multisite_network_wide'] = $use_global_settings; + + if ( ! $use_global_settings ) { + $data['wp_mail_smtp_multisite_is_subsite'] = ! is_main_site(); + } + } + + return apply_filters( 'wp_mail_smtp_usage_tracking_get_data', $data ); + } + + /** + * How the active mailer was set up: 'quick_connect' (Gmail/Outlook One-Click or + * SendLayer Quick Connect), 'manual' for any other configured mailer, or '' when none is set. + * + * @since 4.9.0 + * + * @param Options $options Plugin options. + * + * @return string + */ + private function get_setup_type( Options $options ) { + + $mailer = $options->get( 'mail', 'mailer' ); + + if ( empty( $mailer ) || $mailer === 'mail' ) { + return ''; + } + + $is_quick_connect = + ( + in_array( $mailer, [ 'gmail', 'outlook' ], true ) && + $options->get( $mailer, 'one_click_setup_enabled' ) + ) || + ( $mailer === 'sendlayer' && $options->get( 'sendlayer', 'quick_connect' ) ); + + return $is_quick_connect ? 'quick_connect' : 'manual'; + } + + /** + * Get the required request data. + * + * @since 3.1.0 + * + * @return array + */ + private function get_required_data() { + + return [ + 'url' => home_url(), + 'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, + 'wp_version' => get_bloginfo( 'version' ), + 'active_plugins' => $this->get_active_plugins(), + ]; + } + + /** + * Get the additional data required by the usage tracking API. + * + * @since 3.1.0 + * + * @return array + */ + private function get_additional_data() { + + $activated_dates = get_option( 'wp_mail_smtp_activated', [] ); + + return [ + 'wp_mail_smtp_license_key' => wp_mail_smtp()->get_license_key(), + 'wp_mail_smtp_license_type' => wp_mail_smtp()->get_license_type(), + 'wp_mail_smtp_is_pro' => wp_mail_smtp()->is_pro(), + 'wp_mail_smtp_lite_installed_date' => $this->get_installed( $activated_dates, 'lite' ), + 'wp_mail_smtp_pro_installed_date' => $this->get_installed( $activated_dates, 'pro' ), + ]; + } + + /** + * Get timezone offset. + * We use `wp_timezone_string()` when it's available (WP 5.3+), + * otherwise fallback to the same code, copy-pasted. + * + * @since 2.3.0 + * + * @return string + */ + private function get_timezone_offset() { + + // It was added in WordPress 5.3. + if ( function_exists( 'wp_timezone_string' ) ) { + return wp_timezone_string(); + } + + /* + * The code below is basically a copy-paste from that function. + */ + + $timezone_string = get_option( 'timezone_string' ); + + if ( $timezone_string ) { + return $timezone_string; + } + + $offset = (float) get_option( 'gmt_offset' ); + $hours = (int) $offset; + $minutes = ( $offset - $hours ); + + $sign = ( $offset < 0 ) ? '-' : '+'; + $abs_hour = abs( $hours ); + $abs_mins = abs( $minutes * 60 ); + + return sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); + } + + /** + * Get the list of active plugins. + * + * @since 2.3.0 + * + * @return array + */ + private function get_active_plugins() { + + if ( ! function_exists( 'get_plugins' ) ) { + include ABSPATH . '/wp-admin/includes/plugin.php'; + } + + $active_plugins = []; + + foreach ( get_mu_plugins() as $path => $plugin ) { + $active_plugins[ $path ] = isset( $plugin['Version'] ) ? $plugin['Version'] : 'Not Set'; + } + + foreach ( get_plugins() as $path => $plugin ) { + if ( is_plugin_active( $path ) ) { + $active_plugins[ $path ] = isset( $plugin['Version'] ) ? $plugin['Version'] : 'Not Set'; + } + } + + return $active_plugins; + } + + /** + * Installed date. + * + * @since 2.3.0 + * + * @param array $activated_dates Input array with dates. + * @param string $key Input key what you want to get. + * + * @return mixed + */ + private function get_installed( $activated_dates, $key ) { + + if ( ! empty( $activated_dates[ $key ] ) ) { + return $activated_dates[ $key ]; + } + + return false; + } + + /** + * Total number of sites. + * + * @since 2.3.0 + * + * @return int + */ + private function get_sites_total() { + + return function_exists( 'get_blog_count' ) ? (int) get_blog_count() : 1; + } + + /** + * Send failed Setup Wizard usage tracking data, if usage tracking is enabled. + * + * @since 3.1.0 + * + * @param null|DomainChecker $domain_checker The optional DomainChecker object. + */ + public function send_failed_setup_wizard_usage_tracking_data( $domain_checker = null ) { + + if ( ! $this->is_enabled() ) { + return; + } + + $options = Options::init(); + + $data = array_merge( + $this->get_required_data(), + $this->get_additional_data(), + [ + 'wp_mail_smtp_mailer' => $options->get( 'mail', 'mailer' ), + 'wp_mail_smtp_mail_error' => EmailSendingDebug::get_message( 'primary' ), + ], + $this->get_domain_checker_results( $domain_checker ) + ); + + wp_remote_post( + self::FAILED_SETUP_WIZARD_DATA_URL, + [ + 'timeout' => 5, + 'redirection' => 5, + 'httpversion' => '1.1', + 'blocking' => true, + 'body' => $data, + 'user-agent' => $this->get_user_agent(), + ] + ); + } + + /** + * Reformat the domain checker results, so it can be submitted to the usage tracking API. + * + * @since 3.1.0 + * + * @param DomainChecker|null $domain_checker The Domain Checker object. + * + * @return array + */ + private function get_domain_checker_results( $domain_checker ) { + + if ( ! is_a( $domain_checker, DomainChecker::class ) ) { + return []; + } + + $data = []; + $results = $domain_checker->get_results(); + + $data['wp_mail_smtp_domain_checker_success'] = isset( $results['success'] ) ? (bool) $results['success'] : false; + $data['wp_mail_smtp_domain_checker_message'] = isset( $results['message'] ) ? $results['message'] : ''; + + // Return early if checks are not available. + if ( empty( $results['checks'] ) || ! is_array( $results['checks'] ) ) { + return $data; + } + + foreach ( $results['checks'] as $check ) { + if ( empty( $check['type'] ) || empty( $check['state'] ) ) { + continue; + } + + $data[ 'wp_mail_smtp_domain_checker_check_' . $check['type'] ] = $check['state']; + } + + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WP.php b/web/app/plugins/wp-mail-smtp-pro/src/WP.php new file mode 100644 index 00000000..6ed65940 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WP.php @@ -0,0 +1,974 @@ + $message, + 'class' => $class, + 'is_dismissible' => (bool) $is_dismissible, + 'key' => sanitize_key( $key ), + 'error_code' => $error_code, + ]; + } + + /** + * Add an admin notice and append the DebugEvent referenced by `?debug_event_id=` in + * the current request URL (if any) on a new line below the notice copy. + * + * Use this from redirect-based flows where the upstream request constructed a URL + * like `?error=foo&debug_event_id=42` to carry the underlying technical detail to + * the landing page. The opt-in form (rather than auto-detection in add_admin_notice) + * prevents the detail from attaching to unrelated notices that happen to render on + * the same page. + * + * Signature mirrors add_admin_notice. + * + * @since 4.9.0 + * + * @param string $message Message text (HTML is OK). + * @param string $class Display class (severity). + * @param bool $is_dismissible Whether the message should be dismissible. + * @param string $key Unique key for the notice. + * @param string $error_code Optional error code displayed next to the notice. + */ + public static function add_admin_notice_with_debug( $message, $class = self::ADMIN_NOTICE_INFO, $is_dismissible = true, $key = '', $error_code = '' ) { + + self::add_admin_notice( + $message . self::get_debug_event_detail_html(), + $class, + $is_dismissible, + $key, + $error_code + ); + } + + /** + * Render the inline HTML for a debug event referenced by the current request URL. + * + * Cached per request — the DebugEvents lookup runs at most once. Returns an empty + * string when no `debug_event_id` is present in the URL or the event cannot be + * resolved. + * + * @since 4.9.0 + * + * @return string + */ + private static function get_debug_event_detail_html() { + + static $cache = []; + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $event_id = isset( $_GET['debug_event_id'] ) ? absint( wp_unslash( $_GET['debug_event_id'] ) ) : 0; + + if ( isset( $cache[ $event_id ] ) ) { + return $cache[ $event_id ]; + } + + $cache[ $event_id ] = ''; + + if ( $event_id <= 0 ) { + return $cache[ $event_id ]; + } + + $details = DebugEvents::get_debug_messages( $event_id ); + $detail = is_array( $details ) ? reset( $details ) : ''; + + if ( ! empty( $detail ) ) { + $cache[ $event_id ] = '' . esc_html( $detail ) . ''; + } + + return $cache[ $event_id ]; + } + + /** + * Display all notices. + * + * @since 1.0.0 + * @since 1.5.0 Allow the notice to be dismissible, remove the id attribute, which is not unique. + */ + public static function display_admin_notices() { + + $has_notices = false; + + foreach ( (array) self::$admin_notices as $notice ) : + $is_dismissible = $notice['is_dismissible']; + $dismissible = $is_dismissible ? 'is-dismissible' : ''; + + if ( + $is_dismissible && + ! empty( $notice['key'] ) && + (bool) get_user_meta( get_current_user_id(), "wp_mail_smtp_notice_{$notice['key']}_dismissed", true ) + ) { + continue; + } + + $has_notices = true; + ?> + +
    > +

    + +

    + +
    + + +
    + +
    + + assets_url . '/js/smtp-admin-notices' . self::asset_min() . '.js', + [ 'jquery' ], + WPMS_PLUGIN_VER, + true + ); + + wp_localize_script( + 'wp-mail-smtp-admin-notices', + 'wp_mail_smtp_admin_notices', + [ + 'nonce' => wp_create_nonce( 'wp-mail-smtp-admin' ), + ] + ); + } + } + + /** + * Check whether WP_DEBUG is active. + * + * @since 1.0.0 + * + * @return bool + */ + public static function is_debug() { + + return defined( 'WP_DEBUG' ) && WP_DEBUG; + } + + /** + * Shortcut to global $wpdb. + * + * @since 1.0.0 + * + * @return \wpdb + */ + public static function wpdb() { + global $wpdb; + + return $wpdb; + } + + /** + * Get the postfix for assets files - ".min" or empty. + * ".min" if in production mode. + * + * @since 1.0.0 + * + * @return string + */ + public static function asset_min() { + + $min = '.min'; + + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + $min = ''; + } + + return $min; + } + + /** + * Check whether the string is a JSON or not. + * + * @since 1.5.0 + * + * @param string $string String we want to test if it's json. + * + * @return bool + */ + public static function is_json( $string ) { + + return is_string( $string ) && is_array( json_decode( $string, true ) ) && ( json_last_error() === JSON_ERROR_NONE ) ? true : false; + } + + /** + * Get the full date format as per WP options. + * + * @since 1.5.0 + * + * @return string + */ + public static function datetime_format() { + + return sprintf( /* translators: %1$s - date, \a\t - specially escaped "at", %2$s - time. */ + esc_html__( '%1$s \a\t %2$s', 'wp-mail-smtp' ), + get_option( 'date_format' ), + get_option( 'time_format' ) + ); + } + + /** + * Get the full date form as per MySQL format. + * + * @since 1.5.0 + * + * @return string + */ + public static function datetime_mysql_format() { + + return 'Y-m-d H:i:s'; + } + + /** + * Sanitize the value, similar to `sanitize_text_field()`, but a bit differently. + * It preserves `<` and `>` for non-HTML tags. + * + * @since 1.5.0 + * + * @param string $value String we want to sanitize. + * + * @return string + */ + public static function sanitize_value( $value ) { + + // Remove HTML tags. + $filtered = wp_strip_all_tags( $value, false ); + // Remove multi-lines/tabs. + $filtered = preg_replace( '/[\r\n\t ]+/', ' ', $filtered ); + // Remove whitespaces. + $filtered = trim( $filtered ); + + // Remove octets. + $found = false; + while ( preg_match( '/%[a-f0-9]{2}/i', $filtered, $match ) ) { + $filtered = str_replace( $match[0], '', $filtered ); + $found = true; + } + + if ( $found ) { + // Strip out the whitespace that may now exist after removing the octets. + $filtered = trim( preg_replace( '/ +/', ' ', $filtered ) ); + } + + return $filtered; + } + + /** + * Get default email address. + * + * This is the same code as used in WP core for getting the default email address. + * + * @see https://github.com/WordPress/WordPress/blob/master/wp-includes/pluggable.php#L332 + * + * @since 2.2.0 + * @since 2.3.0 In WP 5.5 the core code changed and is now using `network_home_url`. + * + * @return string + */ + public static function get_default_email() { + + if ( version_compare( get_bloginfo( 'version' ), '5.5-alpha', '<' ) ) { + $sitename = ! empty( $_SERVER['SERVER_NAME'] ) ? + strtolower( sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) ) : + wp_parse_url( get_home_url( get_current_blog_id() ), PHP_URL_HOST ); + } else { + $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST ); + } + + if ( 'www.' === substr( $sitename, 0, 4 ) ) { + $sitename = substr( $sitename, 4 ); + } + + return 'wordpress@' . $sitename; + } + + /** + * Wrapper for the WP `admin_url` method that should be used in the plugin. + * + * We can filter into it, to maybe call `network_admin_url` for multisite support. + * + * @since 2.2.0 + * + * @param string $path Optional path relative to the admin URL. + * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl(). + * 'http' or 'https' can be passed to force those schemes. + * + * @return string Admin URL link with optional path appended. + */ + public static function admin_url( $path = '', $scheme = 'admin' ) { + + return apply_filters( 'wp_mail_smtp_admin_url', \admin_url( $path, $scheme ), $path, $scheme ); + } + + /** + * Check if the global plugin option in a multisite should be used. + * If the global plugin option "multisite" is set and true. + * + * @since 2.2.0 + * + * @return bool + */ + public static function use_global_plugin_settings() { + + if ( ! is_multisite() ) { + return false; + } + + $main_site_options = get_blog_option( get_main_site_id(), Options::META_KEY, [] ); + + return ! empty( $main_site_options['general']['network_wide'] ); + } + + /** + * Returns Jed-formatted localization data. + * This code was taken from a function removed from WP core: `wp_get_jed_locale_data`. + * + * @since 2.6.0 + * + * @param string $domain Translation domain. + * + * @return array + */ + public static function get_jed_locale_data( $domain ) { + + $translations = get_translations_for_domain( $domain ); + + $locale = array( + '' => array( + 'domain' => $domain, + 'lang' => is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(), + ), + ); + + if ( ! empty( $translations->headers['Plural-Forms'] ) ) { + $locale['']['plural_forms'] = $translations->headers['Plural-Forms']; + } + + foreach ( $translations->entries as $entry ) { + $locale[ $entry->singular ] = $entry->translations; + } + + return $locale; + } + + /** + * Check if plugins is activated. + * Replacement for is_plugin_active function as it works only in admin area + * + * @since 2.8.0 + * + * @param string $plugin_slug Plugin slug. + * + * @return bool + */ + public static function is_plugin_activated( $plugin_slug ) { + + static $active_plugins; + + if ( ! isset( $active_plugins ) ) { + $active_plugins = (array) get_option( 'active_plugins', [] ); + + if ( is_multisite() ) { + $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', [] ) ); + } + } + + return ( in_array( $plugin_slug, $active_plugins, true ) || array_key_exists( $plugin_slug, $active_plugins ) ); + } + + /** + * Get the ISO 639-2 Language Code from user/site locale. + * + * @see http://www.loc.gov/standards/iso639-2/php/code_list.php + * + * @since 2.8.0 + * + * @return string + */ + public static function get_language_code() { + + $default_lang = 'en'; + $locale = get_user_locale(); + + if ( ! empty( $locale ) ) { + $lang = explode( '_', $locale ); + if ( ! empty( $lang ) && is_array( $lang ) ) { + $default_lang = strtolower( $lang[0] ); + } + } + + return $default_lang; + } + + /** + * Get the certain date of a specified day in a specified format. + * + * @since 2.8.0 + * + * @param string $period Supported values: start, end. + * @param string $timestamp Default is the current timestamp, if left empty. + * @param string $format Default is a MySQL format. + * @param bool $use_gmt_offset Use GTM offset. + * + * @return string + */ + public static function get_day_period_date( $period, $timestamp = '', $format = 'Y-m-d H:i:s', $use_gmt_offset = false ) { + + $date = ''; + + if ( empty( $timestamp ) ) { + $timestamp = time(); + } + + $offset_sec = $use_gmt_offset ? get_option( 'gmt_offset' ) * 3600 : 0; + + switch ( $period ) { + case 'start_of_day': + $date = gmdate( $format, strtotime( 'today', $timestamp ) - $offset_sec ); + break; + + case 'end_of_day': + $date = gmdate( $format, strtotime( 'tomorrow', $timestamp ) - 1 - $offset_sec ); + break; + } + + return $date; + } + + /** + * Returns extracted domain from email address. + * + * @since 2.8.0 + * + * @param string $email Email address. + * + * @return string + */ + public static function get_email_domain( $email ) { + + return substr( strrchr( $email, '@' ), 1 ); + } + + /** + * Wrapper for set_time_limit to see if it is enabled. + * + * @since 2.8.0 + * + * @param int $limit Time limit. + */ + public static function set_time_limit( $limit = 0 ) { + + if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved + @set_time_limit( $limit ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + } + } + + /** + * Recursive arguments parsing. + * + * @since 2.8.0 + * + * @param array $args Arguments. + * @param array $defaults Defaults. + * + * @return array + */ + public static function parse_args_r( &$args, $defaults ) { + + $args = (array) $args; + $defaults = (array) $defaults; + $r = $defaults; + + foreach ( $args as $k => &$v ) { + if ( is_array( $v ) && isset( $r[ $k ] ) ) { + $r[ $k ] = self::parse_args_r( $v, $r[ $k ] ); + } else { + $r[ $k ] = $v; + } + } + + return $r; + } + + /** + * True if WP is processing plugin related AJAX call. + * + * @since 3.0.0 + * + * @return bool + */ + public static function is_doing_self_ajax() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false; + + return self::is_doing_ajax() && $action && substr( $action, 0, 12 ) === 'wp_mail_smtp'; + } + + /** + * Get the name of the plugin/theme/wp-core that initiated the desired function call. + * + * @since 3.0.0 + * + * @param string $file_path The absolute path of a file that that called the desired function. + * + * @return string + */ + public static function get_initiator_name( $file_path ) { + + return self::get_initiator( $file_path )['name']; + } + + /** + * Get the info of the plugin/theme/wp-core function. + * + * @since 3.5.0 + * + * @param string $file_path The absolute path of the function location. + * + * @return array + */ + public static function get_initiator( $file_path ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $cache_key = 'wp_mail_smtp_initiators_data'; + + // Mainly we have several initiators and we can cache them for better performance. + $initiators_cache = get_transient( $cache_key ); + $initiators_cache = is_array( $initiators_cache ) ? $initiators_cache : []; + + if ( isset( $initiators_cache[ $file_path ] ) ) { + return $initiators_cache[ $file_path ]; + } + + $initiator = self::get_initiator_plugin( $file_path ); + + // Change the initiator name if the email was sent from the reloaded method in the email controls. + if ( + ! empty( $initiator ) && + strpos( str_replace( '\\', '/', $file_path ), 'src/Pro/Emails/Control/Reload.php' ) + ) { + $initiator['name'] = sprintf( /* translators: %s - plugin name. */ + esc_html__( 'WP Core (%s)', 'wp-mail-smtp' ), + $initiator['name'] + ); + } + + if ( empty( $initiator ) ) { + $initiator = self::get_initiator_plugin( $file_path, true ); + } + + if ( empty( $initiator ) ) { + $initiator = self::get_initiator_theme( $file_path ); + } + + if ( empty( $initiator ) ) { + $initiator = self::get_initiator_wp_core( $file_path ); + } + + if ( empty( $initiator ) ) { + $initiator = []; + $initiator['name'] = esc_html__( 'N/A', 'wp-mail-smtp' ); + $initiator['slug'] = ''; + $initiator['type'] = 'unknown'; + } + + $initiators_cache[ $file_path ] = $initiator; + + set_transient( $cache_key, $initiators_cache, HOUR_IN_SECONDS ); + + return $initiator; + } + + /** + * Get the initiator's data, if it's a plugin (or mu plugin). + * + * @since 3.0.0 + * + * @param string $file_path The absolute path of a file. + * @param bool $check_mu_plugin Whether to check for mu plugins or not. + * + * @return false|array + */ + private static function get_initiator_plugin( $file_path, $check_mu_plugin = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded + + $constant = empty( $check_mu_plugin ) ? 'WP_PLUGIN_DIR' : 'WPMU_PLUGIN_DIR'; + + if ( ! defined( $constant ) ) { + return false; + } + + $root = basename( constant( $constant ) ); + $separator = defined( 'DIRECTORY_SEPARATOR' ) ? '\\' . DIRECTORY_SEPARATOR : '\/'; + + preg_match( "/$separator$root$separator(.[^$separator]+)($separator|\.php)/", $file_path, $result ); + + if ( ! empty( $result[1] ) ) { + if ( ! function_exists( 'get_plugins' ) ) { + include ABSPATH . '/wp-admin/includes/plugin.php'; + } + + $all_plugins = empty( $check_mu_plugin ) ? get_plugins() : get_mu_plugins(); + $plugin_slug = $result[1]; + + foreach ( $all_plugins as $plugin => $plugin_data ) { + if ( + 1 === preg_match( "/^$plugin_slug(\/|\.php)/", $plugin ) && + isset( $plugin_data['Name'] ) + ) { + return [ + 'name' => $plugin_data['Name'], + 'slug' => $plugin, + 'type' => $check_mu_plugin ? 'mu-plugin' : 'plugin', + ]; + } + } + + return [ + 'name' => $result[1], + 'slug' => '', + 'type' => $check_mu_plugin ? 'mu-plugin' : 'plugin', + ]; + } + + return false; + } + + /** + * Get the initiator's data, if it's a theme. + * + * @since 3.0.0 + * + * @param string $file_path The absolute path of a file. + * + * @return false|array + */ + private static function get_initiator_theme( $file_path ) { + + if ( ! defined( 'WP_CONTENT_DIR' ) ) { + return false; + } + + $root = basename( WP_CONTENT_DIR ); + $separator = defined( 'DIRECTORY_SEPARATOR' ) ? '\\' . DIRECTORY_SEPARATOR : '\/'; + + preg_match( "/$separator$root{$separator}themes{$separator}(.[^$separator]+)/", $file_path, $result ); + + if ( ! empty( $result[1] ) ) { + $theme = wp_get_theme( $result[1] ); + + return [ + 'name' => method_exists( $theme, 'get' ) ? $theme->get( 'Name' ) : $result[1], + 'slug' => $result[1], + 'type' => 'theme', + ]; + } + + return false; + } + + /** + * Return WP Core if the file path is from WP Core (wp-admin or wp-includes folders). + * + * @since 3.1.0 + * + * @param string $file_path The absolute path of a file. + * + * @return false|array + */ + private static function get_initiator_wp_core( $file_path ) { + + if ( ! defined( 'ABSPATH' ) ) { + return false; + } + + $wp_includes = defined( 'WPINC' ) ? trailingslashit( ABSPATH . WPINC ) : false; + $wp_admin = trailingslashit( ABSPATH . 'wp-admin' ); + + if ( + strpos( $file_path, $wp_includes ) === 0 || + strpos( $file_path, $wp_admin ) === 0 + ) { + return [ + 'name' => esc_html__( 'WP Core', 'wp-mail-smtp' ), + 'slug' => 'wp-core', + 'type' => 'wp-core', + ]; + } + + return false; + } + + /** + * Retrieves the timezone from site settings as a `DateTimeZone` object. + * + * Timezone can be based on a PHP timezone string or a ±HH:MM offset. + * + * We use `wp_timezone()` when it's available (WP 5.3+), + * otherwise fallback to the same code, copy-pasted. + * + * @since 3.0.2 + * + * @return \DateTimeZone Timezone object. + */ + public static function wp_timezone() { + + if ( function_exists( 'wp_timezone' ) ) { + return wp_timezone(); + } + + return new \DateTimeZone( self::wp_timezone_string() ); + } + + /** + * Retrieves the timezone from site settings as a string. + * + * Uses the `timezone_string` option to get a proper timezone if available, + * otherwise falls back to an offset. + * + * We use `wp_timezone_string()` when it's available (WP 5.3+), + * otherwise fallback to the same code, copy-pasted. + * + * @since 3.0.2 + * + * @return string PHP timezone string or a ±HH:MM offset. + */ + public static function wp_timezone_string() { + + if ( function_exists( 'wp_timezone_string' ) ) { + return wp_timezone_string(); + } + + $timezone_string = get_option( 'timezone_string' ); + + if ( $timezone_string ) { + return $timezone_string; + } + + $offset = (float) get_option( 'gmt_offset' ); + $hours = (int) $offset; + $minutes = ( $offset - $hours ); + + $sign = ( $offset < 0 ) ? '-' : '+'; + $abs_hour = abs( $hours ); + $abs_mins = abs( $minutes * 60 ); + $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins ); + + return $tz_offset; + } + + /** + * Get wp remote response error message. + * + * @since 3.4.0 + * + * @param array $response Response array. + */ + public static function wp_remote_get_response_error_message( $response ) { + + if ( is_wp_error( $response ) ) { + return ''; + } + + $body = wp_remote_retrieve_body( $response ); + $message = wp_remote_retrieve_response_message( $response ); + $code = wp_remote_retrieve_response_code( $response ); + $description = ''; + + if ( ! empty( $body ) ) { + $description = is_string( $body ) ? $body : wp_json_encode( $body ); + } + + return Helpers::format_error_message( $message, $code, $description ); + } + + /** + * Clean variables using sanitize_text_field. Arrays are cleaned recursively. + * Non-string values are ignored. + * + * @since 3.7.0 + * + * @param string|array $var Data to sanitize. + * + * @return string|array + */ + public static function sanitize_text( $var ) { + + if ( is_array( $var ) ) { + return array_map( [ __CLASS__, 'sanitize_text' ], $var ); + } else { + return is_string( $var ) ? sanitize_text_field( $var ) : $var; + } + } + + /** + * Get the current site URL, + * or the network URL if using network-wide settings. + * + * @since 4.4.0 + * + * @return string + */ + public static function get_site_url() { + + $site_id = null; + + if ( self::use_global_plugin_settings() ) { + $site_id = get_main_site_id(); + } + + /** + * Whether to return the unfiltered site URL. + * + * @since 4.6.0 + * + * @param bool $unfiltered Whether to return the unfiltered site URL. + * + * @return bool + */ + if ( apply_filters( 'wp_mail_smtp_wp_get_site_url_unfiltered', false ) ) { + return self::get_raw_site_url( $site_id ); + } + + return get_site_url( $site_id ); + } + + /** + * Get the raw/unfiltered site URL. + * + * @since 4.6.0 + * + * @param int $site_id The site ID. + * + * @return string + */ + private static function get_raw_site_url( $site_id ) { + + if ( empty( $site_id ) || ! is_multisite() ) { + $url = get_option( 'siteurl' ); + } else { + switch_to_blog( $site_id ); + + $url = get_option( 'siteurl' ); + + restore_current_blog(); + } + + $url = set_url_scheme( $url ); + + return $url; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Bootstrap.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Bootstrap.php new file mode 100644 index 00000000..89bb1a52 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Bootstrap.php @@ -0,0 +1,61 @@ + Commands\Setup::shortdesc() ]; + $option = [ 'shortdesc' => Commands\Option::shortdesc() ]; + + // longdesc is only displayed for help / our-namespace invocations, but + // building it walks the full arg registry; skip that work otherwise. + if ( $this->needs_longdesc() ) { + $registry = new Options\Registry(); + + $setup['longdesc'] = Commands\Setup::help( $registry ); + $option['longdesc'] = Commands\Option::help( $registry ); + } + + WP_CLI::add_command( 'wp-mail-smtp setup', Commands\Setup::class, $setup ); + WP_CLI::add_command( 'wp-mail-smtp option', Commands\Option::class, $option ); + } + + /** + * Whether this invocation will display a command longdesc: a + * `wp wp-mail-smtp ...` command (including `--help`) or a + * `wp help wp-mail-smtp ...` lookup. + * + * @since 4.9.0 + * + * @return bool + */ + private function needs_longdesc() { + + $args = WP_CLI::get_runner()->arguments; + + return ! empty( $args ) && ( $args[0] === 'wp-mail-smtp' || $args[0] === 'help' ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Manage.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Manage.php new file mode 100644 index 00000000..372aab05 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Manage.php @@ -0,0 +1,27 @@ +` + * for detailed help on each one. + * + * ## EXAMPLES + * + * wp wp-mail-smtp setup --mail.from_email=noreply@example.com --mail.from_name="Example" \ + * --mail.mailer=smtp --smtp.host=mail.example.com --smtp.port=587 \ + * --smtp.encryption=tls --smtp.auth=1 --smtp.user=foo --smtp.pass-file=/run/secret/smtp_pass + * + * wp wp-mail-smtp option get mail.from_email + * + * wp wp-mail-smtp test you@example.com + * + * @since 4.9.0 + */ +class Manage extends CommandNamespace { + +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Option.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Option.php new file mode 100644 index 00000000..e554324b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Option.php @@ -0,0 +1,386 @@ + +: {$action_desc} + +[] +: {$flag_desc} + +[] +: {$value_desc} + +[--value-file=] +: {$value_file_desc} + +[--show-sensitive] +: {$show_sensitive_desc} + +[--format=] +: {$format_desc} + +## EXAMPLES + + wp wp-mail-smtp option get mail.from_email + wp wp-mail-smtp option set smtp.host mail.example.com + wp wp-mail-smtp option set sendgrid.api_key --value-file=/run/secret/sg + wp wp-mail-smtp option list --format=json + +{$flags} +HELP; + } + + /** + * Execute the `wp wp-mail-smtp option` command. + * + * @since 4.9.0 + * + * @param array $args Positional args. + * @param array $assoc_args Associative args. + * + * @return void + */ + public function __invoke( $args, $assoc_args ) { + + $action = $args[0] ?? null; + + switch ( $action ) { + case 'get': + $this->cmd_get( $args, $assoc_args ); + break; + + case 'set': + $this->cmd_set( $args, $assoc_args ); + break; + + case 'list': + $this->cmd_list( $assoc_args ); + break; + + default: + WP_CLI::error( __( 'Action must be one of: get, set, list.', 'wp-mail-smtp' ) ); + } + } + + /** + * Print a single key's value. + * + * `smtp.pass` is decrypted via Options::get() before display. Keys absent + * from storage report "(not set)" via WP_CLI::log so empty-stdout scripts + * still work. + * + * @since 4.9.0 + * + * @param array $args Positional args. + * @param array $assoc_args Associative args. + * + * @return void + */ + private function cmd_get( array $args, array $assoc_args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $flag = $args[1] ?? null; + + if ( $flag === null ) { + WP_CLI::error( __( 'Usage: wp wp-mail-smtp option get .', 'wp-mail-smtp' ) ); + } + + $registry = new Registry(); + $arg = $registry->find( $flag ); + + if ( $arg === null ) { + WP_CLI::error( + sprintf( + /* translators: %s is the unknown dotted CLI flag (e.g. smtp.host). The flag itself is not translated. */ + __( 'Unknown flag: %s', 'wp-mail-smtp' ), + $flag + ) + ); + } + + $storage_path = $this->storage_path( $arg ); + $raw_options = get_option( Options::META_KEY, [] ); + + $sentinel = new stdClass(); + $value = Data::get( $raw_options, $storage_path, $sentinel ); + + if ( $value === $sentinel ) { + WP_CLI::log( __( '(not set)', 'wp-mail-smtp' ) ); + + return; + } + + // smtp.pass is the only encrypted key; route it through Options::get() + // so the stored ciphertext is decrypted before display. + if ( $storage_path === 'smtp.pass' ) { + $value = Options::init()->get( 'smtp', 'pass' ); + } + + $value = $this->reverse_enum_storage( $arg, $value ); + $display = $this->scalarize( $value ); + $mask = ! empty( $arg['sensitive'] ) && ! isset( $assoc_args['show-sensitive'] ) && $display !== ''; + + WP_CLI::log( $mask ? self::MASK : $display ); + } + + /** + * Write a single key's value to the stored options. + * + * @since 4.9.0 + * + * @param array $args Positional args. + * @param array $assoc_args Associative args. + * + * @return void + */ + private function cmd_set( array $args, array $assoc_args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $flag = $args[1] ?? null; + + if ( $flag === null ) { + WP_CLI::error( __( 'Usage: wp wp-mail-smtp option set . ', 'wp-mail-smtp' ) ); + } + + $registry = new Registry(); + $arg = $registry->find( $flag ); + + if ( $arg === null ) { + WP_CLI::error( + sprintf( + /* translators: %s is the unknown dotted CLI flag (e.g. smtp.host). The flag itself is not translated. */ + __( 'Unknown flag: %s', 'wp-mail-smtp' ), + $flag + ) + ); + } + + // Source the value: positional > --value-file > env var. + $assoc_args_for_writer = []; + + if ( isset( $args[2] ) ) { + $assoc_args_for_writer[ $flag ] = $args[2]; + } elseif ( isset( $assoc_args['value-file'] ) ) { + $assoc_args_for_writer[ $flag . '-file' ] = $assoc_args['value-file']; + } + + $writer = new Writer( $registry ); + $value = $writer->resolve_single( $flag, $assoc_args_for_writer ); + + if ( $value === null ) { + WP_CLI::error( + sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. smtp.host). %2$s is the environment variable name the operator can use (e.g. WPMS_SMTP_HOST). Flag and env var name are not translated. */ + __( 'No value provided for --%1$s (positional, --value-file, or env var %2$s).', 'wp-mail-smtp' ), + $flag, + $arg['env_var'] ?? ( 'WPMS_' . strtoupper( str_replace( '.', '_', $flag ) ) ) + ) + ); + } + + $writer->validate_single( $flag, $value ); + $result = $writer->write_single( $flag, $value ); + + // If the single flag we were asked to set is shadowed by a + // wp-config constant, write_single() already warned and stored + // nothing. Surface that as an error so shell pipelines like + // `wp ... option set X Y && echo ok` don't falsely succeed. + if ( empty( $result['written'] ) ) { + WP_CLI::error( + sprintf( + /* translators: %s is the dotted CLI flag whose underlying option is overridden by a wp-config constant (e.g. smtp.host). The flag itself is not translated. */ + __( 'Could not store %s — value is shadowed by a wp-config constant.', 'wp-mail-smtp' ), + $flag + ) + ); + } + + WP_CLI::success( + sprintf( + /* translators: %s is the dotted CLI flag that was updated (e.g. smtp.host). The flag itself is not translated. */ + __( 'Updated %s.', 'wp-mail-smtp' ), + $flag + ) + ); + } + + /** + * List stored option values under their operator-facing flag names. + * + * Only flags physically present in storage are surfaced, so output reflects + * what the operator has actually set. `smtp.pass` is decrypted via + * Options::get(). Sensitive values are masked unless --show-sensitive. + * + * @since 4.9.0 + * + * @param array $assoc_args Associative args. + * + * @return void + */ + private function cmd_list( array $assoc_args ) { + + $show_sensitive = isset( $assoc_args['show-sensitive'] ); + $format = $assoc_args['format'] ?? 'table'; + $registry = new Registry(); + + $rows = []; + $raw_options = get_option( Options::META_KEY, [] ); + $sentinel = new stdClass(); + + foreach ( $registry->get_args() as $arg ) { + $storage_path = $this->storage_path( $arg ); + $value = Data::get( $raw_options, $storage_path, $sentinel ); + + if ( $value === $sentinel ) { + continue; + } + + if ( $storage_path === 'smtp.pass' ) { + $value = Options::init()->get( 'smtp', 'pass' ); + } + + $value = $this->reverse_enum_storage( $arg, $value ); + $display = $this->scalarize( $value ); + + if ( ! empty( $arg['sensitive'] ) && ! $show_sensitive && $display !== '' ) { + $display = self::MASK; + } + + $rows[] = [ + 'flag' => $arg['flag'], + 'value' => $display, + ]; + } + + Utils\format_items( $format, $rows, [ 'flag', 'value' ] ); + } + + /** + * Flatten a value to its string display form. + * + * @since 4.9.0 + * + * @param mixed $value Raw value. + * + * @return string + */ + private function scalarize( $value ) { + + if ( is_bool( $value ) ) { + return $value ? '1' : '0'; + } + + if ( is_array( $value ) ) { + return wp_json_encode( $value ); + } + + return (string) $value; + } + + /** + * Resolve the storage path for an arg. Mirrors Writer::storage_path() — + * uses the arg's explicit `storage_path` if set, otherwise falls back to + * the flag itself. + * + * @since 4.9.0 + * + * @param array $arg Registry arg. + * + * @return string + */ + private function storage_path( array $arg ) { + + return ! empty( $arg['storage_path'] ) ? $arg['storage_path'] : $arg['flag']; + } + + /** + * Reverse the `enum_storage_map` translation for reads: when the stored + * value matches one of the map's storage values, return the corresponding + * operator-facing value. Values not in the map (and non-enum args) pass + * through unchanged, keeping operator-facing values consistent across + * writes and reads. + * + * @since 4.9.0 + * + * @param array $arg Registry arg. + * @param mixed $value Stored value. + * + * @return mixed + */ + private function reverse_enum_storage( array $arg, $value ) { + + if ( empty( $arg['enum_storage_map'] ) || ! is_array( $arg['enum_storage_map'] ) ) { + return $value; + } + + $reverse = array_flip( $arg['enum_storage_map'] ); + + if ( is_string( $value ) && array_key_exists( $value, $reverse ) ) { + return $reverse[ $value ]; + } + + return $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Setup.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Setup.php new file mode 100644 index 00000000..b4d3458c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Setup.php @@ -0,0 +1,124 @@ +get( 'mail', 'mailer' ); + + if ( ! $force && $current_mailer !== '' && $current_mailer !== 'mail' ) { + WP_CLI::error( + sprintf( + /* translators: %s is the currently configured mailer slug (e.g. smtp, sendgrid). The slug is not translated. */ + __( "WP Mail SMTP is already configured (mailer: %s).\nUse `wp wp-mail-smtp option set . ` to change individual settings, or pass --force to override this check.", 'wp-mail-smtp' ), + $current_mailer + ) + ); + } + + $resolved = $writer->resolve( $assoc_args ); + + if ( empty( $resolved ) ) { + WP_CLI::error( __( 'No configuration flags provided. Pass at least --mail.from_email, --mail.from_name, and --mail.mailer (plus the credentials for that mailer).', 'wp-mail-smtp' ) ); + } + + $writer->validate( $resolved ); + $result = $writer->write( $resolved ); + + // Nothing stored means every flag was shadowed by a wp-config constant. + if ( empty( $result['written'] ) ) { + WP_CLI::error( __( 'No settings were stored — every flag passed was shadowed by a wp-config constant. Remove the constants and re-run, or pass flags for non-shadowed settings.', 'wp-mail-smtp' ) ); + } + + // Skip the admin Setup Wizard redirect, mirroring wizard completion. + update_option( 'wp_mail_smtp_activation_prevent_redirect', true ); + + $mailer = $resolved['mail.mailer'] ?? Options::init()->get( 'mail', 'mailer' ); + + WP_CLI::success( + sprintf( + /* translators: %s is the configured mailer slug (e.g. smtp, sendgrid). The slug is not translated. */ + __( 'Configured WP Mail SMTP (mailer: %s). Run `wp wp-mail-smtp test ` to verify.', 'wp-mail-smtp' ), + $mailer + ) + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Test.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Test.php new file mode 100644 index 00000000..f8518587 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Commands/Test.php @@ -0,0 +1,91 @@ + + * : Email address to send the test message to. + * + * [--plain] + * : Send as plain text instead of HTML. + * + * ## EXAMPLES + * + * wp wp-mail-smtp test you@example.com + * wp wp-mail-smtp test you@example.com --plain + * + * @since 4.9.0 + * + * @param array $args Positional args. + * @param array $assoc_args Associative args. + * + * @return void + */ + public function __invoke( $args, $assoc_args ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks -- The wp_mail_failed capture hook is paired add/remove and scoped to this single send. + + $recipient = $args[0] ?? null; + + if ( $recipient === null || ! is_email( $recipient ) ) { + WP_CLI::error( __( 'Pass a valid recipient: wp wp-mail-smtp test ', 'wp-mail-smtp' ) ); + } + + $mailer = Options::init()->get( 'mail', 'mailer' ); + + if ( $mailer === '' || $mailer === 'mail' ) { + WP_CLI::error( __( 'No mailer is configured. Run `wp wp-mail-smtp setup ...` first.', 'wp-mail-smtp' ) ); + } + + // Capture wp_mail_failed to surface the underlying WP_Error on failure. + $captured_error = null; + $capture = static function ( $wp_error ) use ( &$captured_error ) { + $captured_error = $wp_error; + }; + add_action( 'wp_mail_failed', $capture ); + + $test = ( new TestEmail() )->as_html( ! isset( $assoc_args['plain'] ) ); + + $test->send( $recipient ); + + remove_action( 'wp_mail_failed', $capture ); + + if ( $test->is_successful() ) { + WP_CLI::success( + sprintf( + /* translators: %1$s is the recipient email address. %2$s is the mailer slug (e.g. smtp, sendgrid). Recipient and mailer slug are not translated. */ + __( 'Test email sent to %1$s via mailer "%2$s".', 'wp-mail-smtp' ), + $recipient, + $mailer + ) + ); + + return; + } + + $reason = $captured_error instanceof \WP_Error + ? $captured_error->get_error_message() + : __( 'wp_mail() returned false (no further detail available).', 'wp-mail-smtp' ); + + WP_CLI::error( + sprintf( + /* translators: %s is the underlying error message returned by wp_mail() / the mailer (already localized or quoted verbatim from the mailer response). */ + __( 'Test email failed: %s', 'wp-mail-smtp' ), + $reason + ) + ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Help.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Help.php new file mode 100644 index 00000000..82ce00e6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Help.php @@ -0,0 +1,230 @@ +get_args() as $arg ) { + $group = explode( '.', $arg['flag'], 2 )[0]; + $grouped[ $group ][] = $arg; + } + + // Order known groups first, then anything else in registry order. + $ordered = []; + + foreach ( self::$group_order as $group ) { + if ( isset( $grouped[ $group ] ) ) { + $ordered[ $group ] = $grouped[ $group ]; + + unset( $grouped[ $group ] ); + } + } + + foreach ( $grouped as $group => $args ) { + $ordered[ $group ] = $args; + } + + $lines = []; + + $lines[] = '## ' . __( 'CONFIGURATION FLAGS', 'wp-mail-smtp' ); + $lines[] = ''; + $lines[] = __( 'These flags accept their value as a literal (--flag=value), from a file (--flag-file=), or from an environment variable (WPMS_).', 'wp-mail-smtp' ); + $lines[] = ''; + + foreach ( $ordered as $group => $args ) { + $lines[] = self::group_label( $group ) . ':'; + $lines[] = ''; + + foreach ( $args as $arg ) { + $lines[] = '[--' . $arg['flag'] . '=' . self::value_shape( $arg ) . ']'; + $lines[] = ': ' . self::description( $arg ); + $lines[] = ''; + } + } + + return rtrim( implode( "\n", $lines ) ) . "\n"; + } + + /** + * The `` placeholder for an arg's `--flag=...` form. + * + * Enum shapes stay ``; the allowed values are listed in the description. + * + * @since 4.9.0 + * + * @param array $arg Registry arg. + * + * @return string + */ + private static function value_shape( array $arg ) { + + $shapes = [ + 'int' => '', + 'bool' => '', + 'email' => '', + 'enum' => '', + ]; + + return $shapes[ $arg['type'] ] ?? ''; + } + + /** + * Assemble the description line: registry description, then any + * required / required_if / sensitive notes appended in that order. + * + * @since 4.9.0 + * + * @param array $arg Registry arg. + * + * @return string + */ + private static function description( array $arg ) { + + $parts = [ wp_strip_all_tags( $arg['description'] ) ]; + + if ( ! empty( $arg['required'] ) ) { + $parts[] = __( 'Required.', 'wp-mail-smtp' ); + } + + if ( ! empty( $arg['required_if'] ) && is_array( $arg['required_if'] ) ) { + $conditions = []; + + foreach ( $arg['required_if'] as $flag => $value ) { + $conditions[] = '--' . $flag . '=' . self::format_required_if_value( $value ); + } + + $parts[] = sprintf( + /* translators: %s is a list of "--flag=value" conditions joined by " and " (e.g. "--mail.mailer=smtp and --smtp.auth=true"). Flags and values are not translated. */ + __( 'Required when %s.', 'wp-mail-smtp' ), + implode( ' ' . __( 'and', 'wp-mail-smtp' ) . ' ', $conditions ) + ); + } + + if ( ! empty( $arg['sensitive'] ) ) { + $env_var = ! empty( $arg['env_var'] ) + ? $arg['env_var'] + : 'WPMS_' . strtoupper( str_replace( '.', '_', $arg['flag'] ) ); + $parts[] = sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. smtp.pass). %2$s is the matching environment variable name (e.g. WPMS_SMTP_PASS). Flag and env var name are not translated. */ + __( 'Sensitive: also accepts --%1$s-file= or env var %2$s.', 'wp-mail-smtp' ), + $arg['flag'], + $env_var + ); + } + + return implode( ' ', $parts ); + } + + /** + * Map a group slug to its section-header label. + * + * Brand names stay literal (untranslated); generic labels are translatable; + * unknown groups fall back to `ucfirst`. + * + * @since 4.9.0 + * + * @param string $group Group slug. + * + * @return string + */ + private static function group_label( $group ) { + + $labels = [ + 'mail' => __( 'Mail', 'wp-mail-smtp' ), + 'general' => __( 'General', 'wp-mail-smtp' ), + 'smtp' => 'SMTP', + 'sendgrid' => 'SendGrid', + 'mailgun' => 'Mailgun', + 'postmark' => 'Postmark', + 'sendlayer' => 'SendLayer', + 'resend' => 'Resend', + 'smtpcom' => 'SMTP.com', + 'smtp2go' => 'SMTP2GO', + 'sparkpost' => 'SparkPost', + 'mailjet' => 'Mailjet', + 'mailersend' => 'MailerSend', + 'brevo' => 'Brevo', + 'elasticemail' => 'Elastic Email', + 'amazonses' => 'Amazon SES', + 'mandrill' => 'Mandrill', + 'logs' => __( 'Email Logs', 'wp-mail-smtp' ), + 'rate_limit' => __( 'Rate Limiting', 'wp-mail-smtp' ), + 'control' => __( 'Email Controls', 'wp-mail-smtp' ), + 'alert' => __( 'Alerts', 'wp-mail-smtp' ), + ]; + + return $labels[ $group ] ?? ucfirst( $group ); + } + + /** + * Render a `required_if` value for display. Booleans become the literal + * strings `true`/`false`; everything else passes through as-is. Matches + * how operators type the value on the command line. + * + * @since 4.9.0 + * + * @param mixed $value Condition value. + * + * @return string + */ + private static function format_required_if_value( $value ) { + + if ( is_bool( $value ) ) { + return $value ? 'true' : 'false'; + } + + return (string) $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Registry.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Registry.php new file mode 100644 index 00000000..9c85d4d5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Registry.php @@ -0,0 +1,631 @@ + storage value, applied on write and reversed on + * read. Keys not in the map pass through unchanged. + * - required (bool) Unconditionally required. + * - required_if (array|null) Map of flag => value pairs (AND semantics). + * - sensitive (bool) Masked in `option list`; accepts --flag-file / env var. + * - env_var (string|null) Explicit override; else derived as WPMS_. + * - description (string) Help text. + * + * @since 4.9.0 + * + * @return array + */ + public function get_args() { + + $args = array_merge( + $this->mail_args(), + $this->general_args(), + $this->smtp_args(), + $this->sendgrid_args(), + $this->mailgun_args(), + $this->postmark_args(), + $this->sendlayer_args(), + $this->resend_args(), + $this->smtpcom_args(), + $this->smtp2go_args(), + $this->sparkpost_args(), + $this->mailjet_args(), + $this->mailersend_args(), + $this->sendinblue_args(), + $this->elasticemail_args(), + $this->amazonses_args(), + $this->mandrill_args() + ); + + /** + * Filter the WP-CLI arg registry. Pro hooks here to register + * its own flags (logs, alerts, additional connections, etc.). + * + * @since 4.9.0 + * + * @param array $args List of arg definitions. + */ + return apply_filters( 'wp_mail_smtp_wpcli_options_registry_get_args', $args ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName + } + + /** + * Look up a single arg by its dotted flag. + * + * @since 4.9.0 + * + * @param string $flag Dotted flag, e.g. `smtp.host`. + * + * @return array|null + */ + public function find( $flag ) { + + foreach ( $this->get_args() as $arg ) { + if ( $arg['flag'] === $flag ) { + return $arg; + } + } + + return null; + } + + /** + * The non-OAuth mailers selectable via `mail.mailer`. + * + * @since 4.9.0 + * + * @return array + */ + public static function supported_mailers() { + + return [ + 'mail', + 'smtp', + 'sendgrid', + 'mailgun', + 'postmark', + 'sendlayer', + 'resend', + 'smtpcom', + 'smtp2go', + 'sparkpost', + 'mailjet', + 'mailersend', + 'brevo', + 'elasticemail', + 'amazonses', + 'mandrill', + ]; + } + + /** + * Args common to every mailer (`mail.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function mail_args() { + + return [ + [ + 'flag' => 'mail.from_email', + 'type' => 'email', + 'required' => true, + 'description' => __( 'From email address.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mail.from_name', + 'type' => 'string', + 'required' => true, + 'description' => __( 'From name.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mail.mailer', + 'type' => 'enum', + 'enum' => self::supported_mailers(), + 'enum_storage_map' => [ 'brevo' => 'sendinblue' ], + 'required' => true, + 'description' => sprintf( + /* translators: %s is a comma-separated list of supported mailer slugs (e.g. "mail, smtp, sendgrid"). The slugs themselves are not translated. */ + __( 'Mailer to use. One of: %s.', 'wp-mail-smtp' ), + implode( ', ', self::supported_mailers() ) + ), + ], + [ + 'flag' => 'mail.return_path', + 'type' => 'bool', + 'description' => __( 'Set Return-Path to match From email.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mail.from_email_force', + 'type' => 'bool', + 'description' => __( 'Force From email on every outgoing message.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mail.from_name_force', + 'type' => 'bool', + 'description' => __( 'Force From name on every outgoing message.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * General plugin settings args (`general.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function general_args() { + + return [ + [ + 'flag' => 'general.do_not_send', + 'type' => 'bool', + 'description' => __( 'Disable all outgoing email.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'general.hide_am_notifications', + 'storage_path' => 'general.am_notifications_hidden', + 'type' => 'bool', + 'description' => __( 'Hide Awesome Motive product notifications.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Other SMTP mailer args (`smtp.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function smtp_args() { + + $req = [ 'mail.mailer' => 'smtp' ]; + $req_auth = [ + 'mail.mailer' => 'smtp', + 'smtp.auth' => true, + ]; + + return [ + [ + 'flag' => 'smtp.host', + 'type' => 'string', + 'required_if' => $req, + 'description' => __( 'SMTP server hostname.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtp.port', + 'type' => 'int', + 'required_if' => $req, + 'description' => __( 'SMTP server port (e.g. 25, 465, 587).', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtp.encryption', + 'type' => 'enum', + 'enum' => [ 'none', 'ssl', 'tls' ], + 'description' => __( 'Encryption: none, ssl, or tls.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtp.autotls', + 'type' => 'bool', + 'description' => __( 'Auto TLS when supported by server.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtp.auth', + 'type' => 'bool', + 'description' => __( 'Whether the SMTP server requires authentication.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtp.user', + 'type' => 'string', + 'required_if' => $req_auth, + 'description' => __( 'SMTP username.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtp.pass', + 'type' => 'string', + 'required_if' => $req_auth, + 'sensitive' => true, + 'description' => __( 'SMTP password.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * SendGrid mailer args (`sendgrid.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function sendgrid_args() { + + $req = [ 'mail.mailer' => 'sendgrid' ]; + + return [ + [ + 'flag' => 'sendgrid.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'SendGrid API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'sendgrid.domain', + 'type' => 'string', + 'description' => __( 'Optional sending domain.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Mailgun mailer args (`mailgun.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function mailgun_args() { + + $req = [ 'mail.mailer' => 'mailgun' ]; + + return [ + [ + 'flag' => 'mailgun.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Mailgun API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mailgun.domain', + 'type' => 'string', + 'required_if' => $req, + 'description' => __( 'Mailgun sending domain.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mailgun.region', + 'type' => 'enum', + 'enum' => [ 'US', 'EU' ], + 'description' => __( 'Mailgun region (US or EU).', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Postmark mailer args (`postmark.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function postmark_args() { + + $req = [ 'mail.mailer' => 'postmark' ]; + + return [ + [ + 'flag' => 'postmark.api_key', + 'storage_path' => 'postmark.server_api_token', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Postmark server API token.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'postmark.message_stream', + 'type' => 'string', + 'description' => __( 'Optional Postmark message stream.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * SendLayer mailer args (`sendlayer.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function sendlayer_args() { + + $req = [ 'mail.mailer' => 'sendlayer' ]; + + return [ + [ + 'flag' => 'sendlayer.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'SendLayer API key.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Resend mailer args (`resend.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function resend_args() { + + $req = [ 'mail.mailer' => 'resend' ]; + + return [ + [ + 'flag' => 'resend.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Resend API key.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * SMTP.com mailer args (`smtpcom.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function smtpcom_args() { + + $req = [ 'mail.mailer' => 'smtpcom' ]; + + return [ + [ + 'flag' => 'smtpcom.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'SMTP.com API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'smtpcom.channel', + 'type' => 'string', + 'required_if' => $req, + 'description' => __( 'SMTP.com sender channel name.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * SMTP2GO mailer args (`smtp2go.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function smtp2go_args() { + + $req = [ 'mail.mailer' => 'smtp2go' ]; + + return [ + [ + 'flag' => 'smtp2go.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'SMTP2GO API key.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * SparkPost mailer args (`sparkpost.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function sparkpost_args() { + + $req = [ 'mail.mailer' => 'sparkpost' ]; + + return [ + [ + 'flag' => 'sparkpost.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'SparkPost API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'sparkpost.region', + 'type' => 'enum', + 'enum' => [ 'US', 'EU' ], + 'description' => __( 'SparkPost region (US or EU).', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Mailjet mailer args (`mailjet.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function mailjet_args() { + + $req = [ 'mail.mailer' => 'mailjet' ]; + + return [ + [ + 'flag' => 'mailjet.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Mailjet API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mailjet.secret_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Mailjet secret key.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * MailerSend mailer args (`mailersend.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function mailersend_args() { + + $req = [ 'mail.mailer' => 'mailersend' ]; + + return [ + [ + 'flag' => 'mailersend.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'MailerSend API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'mailersend.pro_plan', + 'storage_path' => 'mailersend.has_pro_plan', + 'type' => 'bool', + 'description' => __( 'Set if your MailerSend account is on a paid plan.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Brevo (formerly Sendinblue) mailer args (`brevo.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function sendinblue_args() { + + $req = [ 'mail.mailer' => 'brevo' ]; + + return [ + [ + 'flag' => 'brevo.api_key', + 'storage_path' => 'sendinblue.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Brevo (formerly Sendinblue) API key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'brevo.domain', + 'storage_path' => 'sendinblue.domain', + 'type' => 'string', + 'description' => __( 'Optional Brevo sending domain.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Elastic Email mailer args (`elasticemail.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function elasticemail_args() { + + $req = [ 'mail.mailer' => 'elasticemail' ]; + + return [ + [ + 'flag' => 'elasticemail.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Elastic Email API key.', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Amazon SES mailer args (`amazonses.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function amazonses_args() { + + $req = [ 'mail.mailer' => 'amazonses' ]; + + return [ + [ + 'flag' => 'amazonses.access_key_id', + 'storage_path' => 'amazonses.client_id', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'AWS access key ID.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'amazonses.secret_access_key', + 'storage_path' => 'amazonses.client_secret', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'AWS secret access key.', 'wp-mail-smtp' ), + ], + [ + 'flag' => 'amazonses.region', + 'type' => 'string', + 'required_if' => $req, + 'description' => __( 'AWS region (e.g. us-east-1).', 'wp-mail-smtp' ), + ], + ]; + } + + /** + * Mandrill mailer args (`mandrill.*`). + * + * @since 4.9.0 + * + * @return array + */ + private function mandrill_args() { + + $req = [ 'mail.mailer' => 'mandrill' ]; + + return [ + [ + 'flag' => 'mandrill.api_key', + 'type' => 'string', + 'required_if' => $req, + 'sensitive' => true, + 'description' => __( 'Mandrill API key.', 'wp-mail-smtp' ), + ], + ]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Writer.php b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Writer.php new file mode 100644 index 00000000..3362543e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPCLI/Options/Writer.php @@ -0,0 +1,506 @@ +registry = $registry !== null ? $registry : new Registry(); + } + + /** + * Resolve every registry arg against the assoc args passed on the CLI. + * + * Precedence per arg: literal flag value > ---file= > env var. + * Returns a map of flag => value for flags that resolved to something. + * + * @since 4.9.0 + * + * @param array $assoc_args WP-CLI assoc args. + * + * @return array + */ + public function resolve( array $assoc_args ) { + + $resolved = []; + + foreach ( $this->registry->get_args() as $arg ) { + $value = $this->resolve_one_arg( $arg, $assoc_args ); + + if ( $value !== null ) { + $resolved[ $arg['flag'] ] = $this->coerce( $arg, $value ); + } + } + + return $resolved; + } + + /** + * Resolve a single flag by name. + * + * @since 4.9.0 + * + * @param string $flag Dotted flag (e.g. `smtp.host`). + * @param array $assoc_args WP-CLI assoc args. + * + * @return mixed|null Coerced value, or null if not resolved. + */ + public function resolve_single( $flag, array $assoc_args ) { + + $arg = $this->registry->find( $flag ); + + if ( $arg === null ) { + WP_CLI::error( + sprintf( + /* translators: %s is the unknown dotted CLI flag (e.g. smtp.host). The flag itself is not translated. */ + __( 'Unknown flag: %s', 'wp-mail-smtp' ), + $flag + ) + ); + } + + $value = $this->resolve_one_arg( $arg, $assoc_args ); + + return $value === null ? null : $this->coerce( $arg, $value ); + } + + /** + * Validate the resolved map. Enforces required, required_if, type, enum. + * Accumulates ALL errors and reports them in one WP_CLI::error call. + * + * @since 4.9.0 + * + * @param array $resolved Map of flag => coerced value. + * + * @return void + */ + public function validate( array $resolved ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.MaxExceeded + + $errors = []; + $reported_gates = []; // De-dup partial-chain "missing gate" messages across all rules. + + foreach ( $this->registry->get_args() as $arg ) { + $flag = $arg['flag']; + $present = array_key_exists( $flag, $resolved ); + + // Required. + if ( ! empty( $arg['required'] ) && ! $present ) { + $errors[] = sprintf( + /* translators: %s is the dotted CLI flag (e.g. mail.from_email). The flag itself is not translated. */ + __( 'Missing required flag: --%s', 'wp-mail-smtp' ), + $flag + ); + + continue; + } + + // Required-if. + if ( ! empty( $arg['required_if'] ) ) { + $rule_match = $this->required_if_evaluate( $arg['required_if'], $resolved ); + + if ( $rule_match['all_match'] && ! $present ) { + $errors[] = sprintf( + /* translators: %1$s is the dotted CLI flag that is missing (e.g. smtp.host). %2$s is a human-readable condition (e.g. "--mail.mailer=smtp"). Flags and values are not translated. */ + __( 'Missing required flag: --%1$s (required when %2$s)', 'wp-mail-smtp' ), + $flag, + $this->required_if_human( $arg['required_if'] ) + ); + + continue; + } + + // Partial-chain match: at least one gating flag matched but others are missing; report each missing gate once. + if ( $rule_match['any_match'] && ! empty( $rule_match['missing'] ) ) { + foreach ( $rule_match['missing'] as $missing_gate ) { + if ( isset( $reported_gates[ $missing_gate ] ) ) { + continue; + } + + $reported_gates[ $missing_gate ] = true; + + $errors[] = sprintf( + /* translators: %s is the dotted CLI flag that is missing (e.g. smtp.auth). The flag itself is not translated. */ + __( 'Missing required flag: --%s (needed to fully specify the configuration when other gating flags are set)', 'wp-mail-smtp' ), + $missing_gate + ); + } + } + } + + if ( ! $present ) { + continue; + } + + // Enum. + if ( ( $arg['type'] ?? null ) === 'enum' && ! in_array( $resolved[ $flag ], $arg['enum'], true ) ) { + $errors[] = sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. mail.mailer). %2$s is the value the operator provided. %3$s is a comma-separated list of allowed values. Flags and values are not translated. */ + __( 'Invalid value for --%1$s: %2$s (allowed: %3$s)', 'wp-mail-smtp' ), + $flag, + $resolved[ $flag ], + implode( ', ', $arg['enum'] ) + ); + } + + // Email type sanity check. + if ( ( $arg['type'] ?? null ) === 'email' && ! is_email( $resolved[ $flag ] ) ) { + $errors[] = sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. mail.from_email). %2$s is the value the operator provided. The flag is not translated. */ + __( 'Invalid email for --%1$s: %2$s', 'wp-mail-smtp' ), + $flag, + $resolved[ $flag ] + ); + } + + // Int type sanity check. coerce() leaves non-numeric strings unmodified so they surface here instead of a silent 0. + if ( ( $arg['type'] ?? null ) === 'int' && ! is_numeric( $resolved[ $flag ] ) ) { + $errors[] = sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. smtp.port). %2$s is the value the operator provided. The flag is not translated. */ + __( 'Invalid integer for --%1$s: %2$s', 'wp-mail-smtp' ), + $flag, + $resolved[ $flag ] + ); + } + } + + if ( ! empty( $errors ) ) { + WP_CLI::error( __( 'Configuration errors:', 'wp-mail-smtp' ) . "\n - " . implode( "\n - ", $errors ) ); + } + } + + /** + * Validate a single resolved flag/value pair without applying required/required_if. + * Used by `option set` where the operator is updating one key in isolation. + * + * @since 4.9.0 + * + * @param string $flag Dotted flag. + * @param mixed $value Coerced value. + * + * @return void + */ + public function validate_single( $flag, $value ) { + + $arg = $this->registry->find( $flag ); + + if ( $arg === null ) { + WP_CLI::error( + sprintf( + /* translators: %s is the unknown dotted CLI flag (e.g. smtp.host). The flag itself is not translated. */ + __( 'Unknown flag: %s', 'wp-mail-smtp' ), + $flag + ) + ); + } + + if ( ( $arg['type'] ?? null ) === 'enum' && ! in_array( $value, $arg['enum'], true ) ) { + WP_CLI::error( + sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. mail.mailer). %2$s is the value the operator provided. %3$s is a comma-separated list of allowed values. Flags and values are not translated. */ + __( 'Invalid value for --%1$s: %2$s (allowed: %3$s)', 'wp-mail-smtp' ), + $flag, + $value, + implode( ', ', $arg['enum'] ) + ) + ); + } + + if ( ( $arg['type'] ?? null ) === 'email' && ! is_email( $value ) ) { + WP_CLI::error( + sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. mail.from_email). %2$s is the value the operator provided. The flag is not translated. */ + __( 'Invalid email for --%1$s: %2$s', 'wp-mail-smtp' ), + $flag, + $value + ) + ); + } + + if ( ( $arg['type'] ?? null ) === 'int' && ! is_numeric( $value ) ) { + WP_CLI::error( + sprintf( + /* translators: %1$s is the dotted CLI flag (e.g. smtp.port). %2$s is the value the operator provided. The flag is not translated. */ + __( 'Invalid integer for --%1$s: %2$s', 'wp-mail-smtp' ), + $flag, + $value + ) + ); + } + } + + /** + * Write the resolved map through Options::set(). + * + * Each flag is written to its `storage_path` (see ::storage_path()). Keys + * shadowed by a wp-config constant are skipped and warned about: Options::set() + * forces the stored value to '' whenever the constant is defined, which would + * silently wipe the value. + * + * @since 4.9.0 + * + * @param array $resolved Map of flag => coerced value. + * + * @return array Map with two keys: 'written' (flags actually stored) and + * 'shadowed' (flags skipped because a wp-config constant is set). + */ + public function write( array $resolved ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $result = [ + 'written' => [], + 'shadowed' => [], + ]; + + if ( empty( $resolved ) ) { + return $result; + } + + $options = Options::init(); + + $writable = []; + + foreach ( $resolved as $flag => $value ) { + $arg = $this->registry->find( $flag ); + $path = $this->storage_path( $arg !== null ? $arg : [ 'flag' => $flag ] ); + + // Translate operator-facing enum values to storage values before the shadow check, so it operates on storage values. + if ( $arg !== null && ! empty( $arg['enum_storage_map'] ) && is_array( $arg['enum_storage_map'] ) && array_key_exists( $value, $arg['enum_storage_map'] ) ) { + $value = $arg['enum_storage_map'][ $value ]; + } + + // Shadow check is limited to the top-level group/key pair, matching WPMS's shallow WPMS__ constants. + $segments = explode( Data::KEY_SEPARATOR, $path, 3 ); + + if ( isset( $segments[0], $segments[1] ) && $options->is_const_defined( $segments[0], $segments[1] ) ) { + $result['shadowed'][] = $flag; + } else { + $writable[ $flag ] = [ + 'value' => $value, + 'path' => $path, + ]; + } + } + + foreach ( $result['shadowed'] as $flag ) { + WP_CLI::warning( + sprintf( + /* translators: %s is the dotted CLI flag whose underlying option is overridden by a wp-config constant (e.g. smtp.host). The flag itself is not translated. */ + __( 'A wp-config constant is defined for %s. The value was not stored. Remove the constant first, then re-run this command.', 'wp-mail-smtp' ), + $flag + ) + ); + } + + if ( empty( $writable ) ) { + return $result; + } + + $new = $options->get_all_raw(); + + foreach ( $writable as $entry ) { + Data::set( $new, $entry['path'], $entry['value'] ); + } + + $options->set( $new ); + + $result['written'] = array_keys( $writable ); + + return $result; + } + + /** + * Write a single key (used by `option set`). + * + * @since 4.9.0 + * + * @param string $flag Dotted flag. + * @param mixed $value Coerced value. + * + * @return array See ::write(). + */ + public function write_single( $flag, $value ) { + + return $this->write( [ $flag => $value ] ); + } + + /** + * Resolve the storage path for an arg. + * + * Uses `storage_path` if set on the arg, otherwise falls back to the flag itself. + * + * @since 4.9.0 + * + * @param array $arg Registry arg shape. + * + * @return string Dotted storage path, e.g. `alert_slack_webhook.connections.0.webhook_url`. + */ + private function storage_path( array $arg ) { + + return ! empty( $arg['storage_path'] ) ? $arg['storage_path'] : $arg['flag']; + } + + /** + * Resolve a single arg's value from assoc args using + * literal > ---file > env var precedence. + * + * @since 4.9.0 + * + * @param array $arg Registry arg. + * @param array $assoc_args WP-CLI assoc args. + * + * @return string|null Raw string value, or null if not resolved. + */ + private function resolve_one_arg( array $arg, array $assoc_args ) { + + $flag = $arg['flag']; + $file_flag = $flag . '-file'; + + if ( array_key_exists( $flag, $assoc_args ) ) { + return (string) $assoc_args[ $flag ]; + } + + if ( array_key_exists( $file_flag, $assoc_args ) ) { + $path = $assoc_args[ $file_flag ]; + + if ( ! is_readable( $path ) ) { + WP_CLI::error( + sprintf( + /* translators: %1$s is the dotted CLI flag with a -file suffix (e.g. smtp.pass-file). %2$s is the absolute file path that could not be read. */ + __( 'Cannot read --%1$s file: %2$s', 'wp-mail-smtp' ), + $file_flag, + $path + ) + ); + } + + return rtrim( (string) file_get_contents( $path ) ); + } + + $env_var = $arg['env_var'] ?? ( 'WPMS_' . strtoupper( str_replace( '.', '_', $flag ) ) ); + $env = getenv( $env_var ); + + if ( $env !== false && $env !== '' ) { + return $env; + } + + return null; + } + + /** + * Coerce a raw string value to the type declared by the arg. + * + * @since 4.9.0 + * + * @param array $arg Registry arg. + * @param mixed $value Raw value. + * + * @return mixed Coerced value. + */ + private function coerce( array $arg, $value ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + $type = $arg['type'] ?? 'string'; + + if ( $type === 'bool' ) { + return in_array( strtolower( (string) $value ), [ '1', 'true', 'yes', 'on' ], true ); + } + + if ( $type === 'int' ) { + // Non-numeric values returned as-is so validate() can report them instead of silently casting to 0. + return is_numeric( $value ) ? (int) $value : (string) $value; + } + + return (string) $value; + } + + /** + * Evaluate a required_if rule set against the resolved map. + * + * Returns: + * - all_match: every gating flag is present AND equals its expected value. + * - any_match: at least one gating flag is present AND equals its expected value. + * - missing: gating flags that aren't present in the resolved map. + * + * Callers use any_match + missing to detect a partial-chain match (a likely forgotten gating flag). + * + * @since 4.9.0 + * + * @param array $rules Map of flag => expected value. + * @param array $resolved Map of flag => resolved value. + * + * @return array + */ + private function required_if_evaluate( array $rules, array $resolved ) { + + $any_match = false; + $all_match = true; + $missing = []; + + foreach ( $rules as $flag => $expected ) { + if ( ! array_key_exists( $flag, $resolved ) ) { + $missing[] = $flag; + $all_match = false; + + continue; + } + + if ( $resolved[ $flag ] === $expected ) { + $any_match = true; + } else { + $all_match = false; + } + } + + return [ + 'all_match' => $all_match, + 'any_match' => $any_match, + 'missing' => $missing, + ]; + } + + /** + * Render a required_if rule set as a human-readable "--flag=value and ..." string. + * + * @since 4.9.0 + * + * @param array $rules Map of flag => expected value. + * + * @return string + */ + private function required_if_human( array $rules ) { + + $parts = []; + + foreach ( $rules as $flag => $expected ) { + $rendered = is_bool( $expected ) ? ( $expected ? 'true' : 'false' ) : (string) $expected; + $parts[] = sprintf( '--%s=%s', $flag, $rendered ); + } + + return implode( ' and ', $parts ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPMailArgs.php b/web/app/plugins/wp-mail-smtp-pro/src/WPMailArgs.php new file mode 100644 index 00000000..a797709a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPMailArgs.php @@ -0,0 +1,252 @@ +args = $args; + } + + /** + * Get arguments. + * + * @since 3.7.0 + * + * @return array + */ + public function get_args() { + + return $this->args; + } + + /** + * Get to email. + * + * @since 3.7.0 + * + * @return string + */ + public function get_to_email() { + + return $this->get_arg( 'to' ); + } + + /** + * Get subject. + * + * @since 3.7.0 + * + * @return string + */ + public function get_subject() { + + return $this->get_arg( 'subject' ); + } + + /** + * Get message. + * + * @since 3.7.0 + * + * @return string + */ + public function get_message() { + + return $this->get_arg( 'message' ); + } + + /** + * Get from email. + * + * @since 3.7.0 + * + * @return string + */ + public function get_from_email() { + + $from = $this->get_from(); + + return $from['email']; + } + + /** + * Get from name. + * + * @since 3.7.0 + * + * @return string + */ + public function get_from_name() { + + $from = $this->get_from(); + + return $from['name']; + } + + /** + * Get parsed headers. + * + * @since 3.7.0 + * + * @return array + */ + public function get_headers() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + + if ( ! is_null( $this->headers ) ) { + return $this->headers; + } + + $this->headers = []; + + if ( ! empty( $this->args['headers'] ) ) { + if ( ! is_array( $this->args['headers'] ) ) { + $headers = explode( "\n", str_replace( "\r\n", "\n", $this->args['headers'] ) ); + } else { + $headers = $this->args['headers']; + } + + foreach ( (array) $headers as $header ) { + if ( strpos( $header, ':' ) === false ) { + continue; + } + + list( $name, $content ) = array_map( 'trim', explode( ':', trim( $header ), 2 ) ); + + $name = strtolower( $name ); + + if ( isset( $this->headers[ $name ] ) && in_array( $name, [ 'cc', 'bcc', 'reply-to' ], true ) ) { + $this->headers[ $name ] .= ', ' . $content; + } else { + $this->headers[ $name ] = $content; + } + } + } + + return $this->headers; + } + + /** + * Get particular header value. + * + * @since 3.7.0 + * + * @param string $name Header name. + * + * @return null|string + */ + public function get_header( $name ) { + + $name = strtolower( $name ); + $headers = $this->get_headers(); + + return isset( $headers[ $name ] ) ? $headers[ $name ] : null; + } + + /** + * Get argument value. + * + * @since 3.7.0 + * + * @param string $key Argument key. + * @param mixed $default Default value. + * + * @return string + */ + private function get_arg( $key, $default = '' ) { + + return isset( $this->args[ $key ] ) ? $this->args[ $key ] : $default; + } + + /** + * Get from address. + * + * @since 3.7.0 + * + * @return array + */ + private function get_from() { + + $from_email = ''; + $from_name = ''; + $value = $this->get_header( 'from' ); + $value = is_null( $value ) ? '' : $value; + + $bracket_pos = strpos( $value, '<' ); + + if ( $bracket_pos !== false ) { + // Text before the bracketed email is the "From" name. + if ( $bracket_pos > 0 ) { + $from_name = substr( $value, 0, $bracket_pos - 1 ); + $from_name = str_replace( '"', '', $from_name ); + $from_name = trim( $from_name ); + } + + $from_email = substr( $value, $bracket_pos + 1 ); + $from_email = str_replace( '>', '', $from_email ); + $from_email = trim( $from_email ); + + // Avoid setting an empty $from_email. + } elseif ( trim( $value ) !== '' ) { + $from_email = trim( $value ); + } + + return [ + 'email' => $from_email, + 'name' => $from_name, + ]; + } + + /** + * Get attachments. + * + * @since 4.0.0 + * + * @return array + */ + public function get_attachments() { + + return $this->get_arg( 'attachments', [] ); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/src/WPMailInitiator.php b/web/app/plugins/wp-mail-smtp-pro/src/WPMailInitiator.php new file mode 100644 index 00000000..035b79f8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/src/WPMailInitiator.php @@ -0,0 +1,271 @@ +set_initiator(); + } + + /** + * Get the path where the `wp_mail` function was called. + * + * @since 3.7.0 + * + * @return string + */ + public function get_file() { + + return $this->file; + } + + /** + * Get the line in the file where the `wp_mail` function was called. + * + * @since 3.7.0 + * + * @return int + */ + public function get_line() { + + return $this->line; + } + + /** + * Get the `wp_mail` function call backtrace. + * + * @since 3.7.0 + * + * @return array + */ + public function get_backtrace() { + + return $this->backtrace; + } + + /** + * Get the initiator name (plugin or theme name). + * + * @since 3.7.0 + * + * @return string + */ + public function get_name() { + + $this->lazy_init(); + + return $this->name; + } + + /** + * Get the initiator type. Available options: plugin, mu-plugin, theme, wp-core, unknown. + * + * @since 3.7.0 + * + * @return string + */ + public function get_type() { + + $this->lazy_init(); + + return $this->type; + } + + /** + * Get the initiator slug (plugin or theme slug). + * + * @since 3.7.0 + * + * @return string + */ + public function get_slug() { + + $this->lazy_init(); + + return $this->slug; + } + + /** + * Initialize initiator data. + * + * @since 3.7.0 + */ + public function set_initiator() { + + // Reset previous values. + $this->reset(); + + $backtrace = $this->get_wpmail_backtrace(); + + /** + * Filter the `wp_mail` function initiator data. + * + * @since 4.0.0 + * + * @param array $backtrace Backtrace data. + */ + $backtrace = apply_filters( 'wp_mail_smtp_wp_mail_initiator_set_initiator', $backtrace ); + + if ( empty( $backtrace['file'] ) ) { + return; + } + + $this->file = $backtrace['file']; + $this->backtrace = $backtrace['backtrace']; + + if ( ! empty( $backtrace['line'] ) ) { + $this->line = $backtrace['line']; + } + } + + /** + * Initialize performance-costly properties. + * + * @since 3.7.0 + */ + private function lazy_init() { + + if ( empty( $this->file ) || $this->initialized ) { + return; + } + + $data = WP::get_initiator( $this->file ); + + $this->name = $data['name']; + $this->type = $data['type']; + + if ( isset( $data['slug'] ) ) { + $this->slug = $data['slug']; + } + + $this->initialized = true; + } + + /** + * Reset previous initiator data before the new email sending. + * + * @since 3.7.0 + */ + private function reset() { + + $this->initialized = false; + $this->file = null; + $this->line = null; + $this->backtrace = null; + $this->name = null; + $this->type = null; + $this->slug = null; + } + + /** + * Get the `wp_mail` function backtrace data, if it exists. + * + * @since 3.7.0 + * + * @return array + */ + private function get_wpmail_backtrace() { + + $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace + + foreach ( $backtrace as $i => $item ) { + if ( $item['function'] === 'wp_mail' ) { + if ( isset( $item['function'] ) ) { + unset( $item['function'] ); + } + + $item['backtrace'] = array_slice( $backtrace, $i ); + + return $item; + } + } + + return []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/uninstall.php b/web/app/plugins/wp-mail-smtp-pro/uninstall.php new file mode 100644 index 00000000..2b909662 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/uninstall.php @@ -0,0 +1,263 @@ + [ 'amn_smtp' ], + 'post_status' => 'any', + 'numberposts' => - 1, + 'fields' => 'ids', +]; + +/** + * Disable Action Schedule Queue Runner, to prevent a fatal error on the shutdown WP hook. + */ +if ( class_exists( 'ActionScheduler_QueueRunner' ) ) { + $as_queue_runner = \ActionScheduler_QueueRunner::instance(); + + if ( method_exists( $as_queue_runner, 'unhook_dispatch_async_request' ) ) { + $as_queue_runner->unhook_dispatch_async_request(); + } +} + +// WP MS uninstall process. +//phpcs:disable WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine, WPForms.PHP.BackSlash.UseShortSyntax +if ( is_multisite() ) { + $main_site_settings = get_blog_option( get_main_site_id(), 'wp_mail_smtp', [] ); + $network_wide = ! empty( $main_site_settings['general']['network_wide'] ); + $network_uninstall = ! empty( $main_site_settings['general']['uninstall'] ); + + $sites = get_sites(); + + foreach ( $sites as $site ) { + $settings = get_blog_option( $site->blog_id, 'wp_mail_smtp', [] ); + + // Confirm network site admin has decided to remove all data, otherwise skip. + if ( + ( $network_wide && ! $network_uninstall ) || + ( ! $network_wide && empty( $settings['general']['uninstall'] ) ) + ) { + continue; + } + + /* + * Delete network site plugin options. + */ + foreach ( $options as $option ) { + delete_blog_option( $site->blog_id, $option ); + } + + // Switch to the current network site. + switch_to_blog( $site->blog_id ); + + // Delete plugin settings. + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'wp\_mail\_smtp%'" ); // phpcs:ignore WordPress.DB + + // Delete plugin user meta. + $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE 'wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + + // Remove any transients we've left behind. + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_site\_transient\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_timeout\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_site\_transient\_timeout\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + + // Delete debug events table. + $debug_events_table = \WPMailSMTP\Admin\DebugEvents\DebugEvents::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $debug_events_table;" ); // phpcs:ignore WordPress.DB + + /* + * Delete network site product announcements. + */ + $announcements = get_posts( $am_announcement_params ); + + if ( ! empty( $announcements ) ) { + foreach ( $announcements as $announcement ) { + wp_delete_post( $announcement, true ); + } + } + + // Delete queue table. + $queue_table = \WPMailSMTP\Queue\Queue::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $queue_table;" ); // phpcs:ignore WordPress.DB + + // Delete all queue attachments. + ( new \WPMailSMTP\Queue\Attachments() )->delete_attachments(); + + /* + * Cleanup network site data for Pro plugin only. + */ + if ( + function_exists( 'wp_mail_smtp' ) && + is_readable( wp_mail_smtp()->plugin_path . '/src/Pro/Pro.php' ) + ) { + + // Delete logs table. + $table = \WPMailSMTP\Pro\Emails\Logs\Logs::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $table;" ); // phpcs:ignore WordPress.DB + + // Delete attachments tables. + $attachment_files_table = \WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments::get_attachment_files_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $attachment_files_table;" ); // phpcs:ignore WordPress.DB + + $email_attachments_table = \WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments::get_email_attachments_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $email_attachments_table;" ); // phpcs:ignore WordPress.DB + + // Delete all attachments if any. + ( new \WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments() )->delete_all_attachments(); + + // Delete tracking tables. + $tracking_events_table = \WPMailSMTP\Pro\Emails\Logs\Tracking\Tracking::get_events_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $tracking_events_table;" ); // phpcs:ignore WordPress.DB + + $tracking_links_table = \WPMailSMTP\Pro\Emails\Logs\Tracking\Tracking::get_links_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $tracking_links_table;" ); // phpcs:ignore WordPress.DB + } + + /* + * Drop all Action Scheduler data and unschedule all plugin ActionScheduler actions. + */ + ( new \WPMailSMTP\Tasks\Tasks() )->remove_all(); + + $meta_table = \WPMailSMTP\Tasks\Meta::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $meta_table;" ); // phpcs:ignore WordPress.DB + + // Delete current sub-site wp-mail-smtp uploads folder. + \WPMailSMTP\Uploads::delete_upload_dir(); + + // Restore the current network site back to the original one. + restore_current_blog(); + } +} else { // Non WP MS uninstall process (for normal WP installs). + + // Confirm user has decided to remove all data, otherwise stop. + $settings = get_option( 'wp_mail_smtp', [] ); + if ( empty( $settings['general']['uninstall'] ) ) { + return; + } + + /* + * Delete plugin options. + */ + foreach ( $options as $option ) { + delete_option( $option ); + } + + // Delete plugin settings. + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE 'wp\_mail\_smtp%'" ); // phpcs:ignore WordPress.DB + + // Delete plugin user meta. + $wpdb->query( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE 'wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + + // Remove any transients we've left behind. + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_site\_transient\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_timeout\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_site\_transient\_timeout\_wp\_mail\_smtp\_%'" ); // phpcs:ignore WordPress.DB + + // Delete debug events table. + $debug_events_table = \WPMailSMTP\Admin\DebugEvents\DebugEvents::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $debug_events_table;" ); // phpcs:ignore WordPress.DB + + /* + * Remove product announcements. + */ + $announcements = get_posts( $am_announcement_params ); + if ( ! empty( $announcements ) ) { + foreach ( $announcements as $announcement ) { + wp_delete_post( $announcement, true ); + } + } + + // Delete queue table. + $queue_table = \WPMailSMTP\Queue\Queue::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $queue_table;" ); // phpcs:ignore WordPress.DB + + // Delete all queue attachments. + ( new \WPMailSMTP\Queue\Attachments() )->delete_attachments(); + + /* + * Cleanup data for Pro plugin only. + */ + if ( + function_exists( 'wp_mail_smtp' ) && + is_readable( wp_mail_smtp()->plugin_path . '/src/Pro/Pro.php' ) + ) { + + // Delete logs table. + $table = \WPMailSMTP\Pro\Emails\Logs\Logs::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $table;" ); // phpcs:ignore WordPress.DB + + // Delete attachments tables. + $attachment_files_table = \WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments::get_attachment_files_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $attachment_files_table;" ); // phpcs:ignore WordPress.DB + + $email_attachments_table = \WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments::get_email_attachments_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $email_attachments_table;" ); // phpcs:ignore WordPress.DB + + // Delete all attachments if any. + ( new \WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments() )->delete_all_attachments(); + + // Delete tracking tables. + $tracking_events_table = \WPMailSMTP\Pro\Emails\Logs\Tracking\Tracking::get_events_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $tracking_events_table;" ); // phpcs:ignore WordPress.DB + + $tracking_links_table = \WPMailSMTP\Pro\Emails\Logs\Tracking\Tracking::get_links_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $tracking_links_table;" ); // phpcs:ignore WordPress.DB + } + + /* + * Drop all Action Scheduler data and unschedule all plugin ActionScheduler actions. + */ + ( new \WPMailSMTP\Tasks\Tasks() )->remove_all(); + + $meta_table = \WPMailSMTP\Tasks\Meta::get_table_name(); + $wpdb->query( "DROP TABLE IF EXISTS $meta_table;" ); // phpcs:ignore WordPress.DB + + // Delete wp-mail-smtp uploads folder. + \WPMailSMTP\Uploads::delete_upload_dir(); +} +//phpcs:enable WPForms.Formatting.EmptyLineAfterAssigmentVariables.AddEmptyLine, WPForms.PHP.BackSlash.UseShortSyntax diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/LICENSE new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/NOTICE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/NOTICE new file mode 100644 index 00000000..616fc588 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/NOTICE @@ -0,0 +1 @@ +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/format-check.py b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/format-check.py new file mode 100644 index 00000000..35ac46e8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/format-check.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +import argparse +import os +from pathlib import Path +import re +from subprocess import list2cmdline, run +from tempfile import NamedTemporaryFile + +CLANG_FORMAT_VERSION = '18.1.6' + +INCLUDE_REGEX = re.compile(r'^ext/.*\.(c|h|inl)$') +EXCLUDE_REGEX = re.compile(r'^$') + +arg_parser = argparse.ArgumentParser(description="Check with clang-format") +arg_parser.add_argument('-i', '--inplace-edit', action='store_true', + help="Edit files inplace") +args = arg_parser.parse_args() + +os.chdir(Path(__file__).parent) + +# create file containing list of all files to format +filepaths_file = NamedTemporaryFile(delete=False) +for dirpath, dirnames, filenames in os.walk('.'): + for filename in filenames: + # our regexes expect filepath to use forward slash + filepath = Path(dirpath, filename).as_posix() + if not INCLUDE_REGEX.match(filepath): + continue + if EXCLUDE_REGEX.match(filepath): + continue + + filepaths_file.write(f"{filepath}\n".encode()) +filepaths_file.close() + +# use pipx to run clang-format from PyPI +# this is a simple way to run the same clang-format version regardless of OS +cmd = ['pipx', 'run', f'clang-format=={CLANG_FORMAT_VERSION}', + f'--files={filepaths_file.name}'] +if args.inplace_edit: + cmd += ['-i'] +else: + cmd += ['--Werror', '--dry-run'] + +print(f"{Path.cwd()}$ {list2cmdline(cmd)}") +if run(cmd).returncode: + exit(1) diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php new file mode 100644 index 00000000..79a24d51 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php @@ -0,0 +1,61 @@ + '', 'secret_access_key' => '', 'session_token' => '', 'expiration_timepoint_seconds' => 0]; + } + private $access_key_id; + private $secret_access_key; + private $session_token; + private $expiration_timepoint_seconds = 0; + public function __get($name) + { + return $this->{$name}; + } + function __construct(array $options = []) + { + parent::__construct(); + $options = new Options($options, self::defaults()); + $this->access_key_id = $options->access_key_id->asString(); + $this->secret_access_key = $options->secret_access_key->asString(); + $this->session_token = $options->session_token ? $options->session_token->asString() : null; + $this->expiration_timepoint_seconds = $options->expiration_timepoint_seconds->asInt(); + if (\strlen($this->access_key_id) == 0) { + throw new \InvalidArgumentException("access_key_id must be provided"); + } + if (\strlen($this->secret_access_key) == 0) { + throw new \InvalidArgumentException("secret_access_key must be provided"); + } + $creds_options = self::$crt->aws_credentials_options_new(); + self::$crt->aws_credentials_options_set_access_key_id($creds_options, $this->access_key_id); + self::$crt->aws_credentials_options_set_secret_access_key($creds_options, $this->secret_access_key); + self::$crt->aws_credentials_options_set_session_token($creds_options, $this->session_token); + self::$crt->aws_credentials_options_set_expiration_timepoint_seconds($creds_options, $this->expiration_timepoint_seconds); + $this->acquire(self::$crt->aws_credentials_new($creds_options)); + self::$crt->aws_credentials_options_release($creds_options); + } + function __destruct() + { + self::$crt->aws_credentials_release($this->release()); + parent::__destruct(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php new file mode 100644 index 00000000..07a9b2ac --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php @@ -0,0 +1,24 @@ +credentials_provider_release($this->release()); + parent::__destruct(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php new file mode 100644 index 00000000..b4b429c9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php @@ -0,0 +1,44 @@ +signable_new_from_http_request($http_message->native); + }); + } + public static function fromChunk($chunk_stream, $previous_signature = "") + { + if (!$chunk_stream instanceof InputStream) { + $chunk_stream = new InputStream($chunk_stream); + } + return new Signable(function () use($chunk_stream, $previous_signature) { + return self::$crt->signable_new_from_chunk($chunk_stream->native, $previous_signature); + }); + } + public static function fromCanonicalRequest($canonical_request) + { + return new Signable(function () use($canonical_request) { + return self::$crt->signable_new_from_canonical_request($canonical_request); + }); + } + protected function __construct($ctor) + { + parent::__construct(); + $this->acquire($ctor()); + } + function __destruct() + { + self::$crt->signable_release($this->release()); + parent::__destruct(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php new file mode 100644 index 00000000..00b84c60 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php @@ -0,0 +1,17 @@ +sign_request_aws($signable->native, $signing_config->native, function ($result, $error_code) use($on_complete) { + $signing_result = SigningResult::fromNative($result); + $on_complete($signing_result, $error_code); + }, null); + } + static function testVerifySigV4ASigning($signable, $signing_config, $expected_canonical_request, $signature, $ecc_key_pub_x, $ecc_key_pub_y) + { + return self::$crt->test_verify_sigv4a_signing($signable, $signing_config, $expected_canonical_request, $signature, $ecc_key_pub_x, $ecc_key_pub_y); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php new file mode 100644 index 00000000..3f5ca412 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php @@ -0,0 +1,13 @@ + SigningAlgorithm::SIGv4, 'signature_type' => SignatureType::HTTP_REQUEST_HEADERS, 'credentials_provider' => null, 'region' => null, 'service' => null, 'use_double_uri_encode' => \false, 'should_normalize_uri_path' => \false, 'omit_session_token' => \false, 'signed_body_value' => null, 'signed_body_header_type' => SignedBodyHeaderType::NONE, 'expiration_in_seconds' => 0, 'date' => \time(), 'should_sign_header' => null]; + } + private $options; + public function __construct(array $options = []) + { + parent::__construct(); + $this->options = $options = new Options($options, self::defaults()); + $sc = $this->acquire(self::$crt->signing_config_aws_new()); + self::$crt->signing_config_aws_set_algorithm($sc, $options->algorithm->asInt()); + self::$crt->signing_config_aws_set_signature_type($sc, $options->signature_type->asInt()); + if ($credentials_provider = $options->credentials_provider->asObject()) { + self::$crt->signing_config_aws_set_credentials_provider($sc, $credentials_provider->native); + } + self::$crt->signing_config_aws_set_region($sc, $options->region->asString()); + self::$crt->signing_config_aws_set_service($sc, $options->service->asString()); + self::$crt->signing_config_aws_set_use_double_uri_encode($sc, $options->use_double_uri_encode->asBool()); + self::$crt->signing_config_aws_set_should_normalize_uri_path($sc, $options->should_normalize_uri_path->asBool()); + self::$crt->signing_config_aws_set_omit_session_token($sc, $options->omit_session_token->asBool()); + self::$crt->signing_config_aws_set_signed_body_value($sc, $options->signed_body_value->asString()); + self::$crt->signing_config_aws_set_signed_body_header_type($sc, $options->signed_body_header_type->asInt()); + self::$crt->signing_config_aws_set_expiration_in_seconds($sc, $options->expiration_in_seconds->asInt()); + self::$crt->signing_config_aws_set_date($sc, $options->date->asInt()); + if ($should_sign_header = $options->should_sign_header->asCallable()) { + self::$crt->signing_config_aws_set_should_sign_header_fn($sc, $should_sign_header); + } + } + function __destruct() + { + self::$crt->signing_config_aws_release($this->release()); + parent::__destruct(); + } + public function __get($name) + { + return $this->options->get($name); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php new file mode 100644 index 00000000..5576d4e7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php @@ -0,0 +1,34 @@ +acquire($native); + } + function __destruct() + { + // No destruction necessary, SigningResults are transient, just release + $this->release(); + parent::__destruct(); + } + public static function fromNative($ptr) + { + return new SigningResult($ptr); + } + public function applyToHttpRequest(&$http_request) + { + self::$crt->signing_result_apply_to_http_request($this->native, $http_request->native); + // Update http_request from native + $http_request = Request::unmarshall($http_request->toBlob()); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php new file mode 100644 index 00000000..be5daf53 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php @@ -0,0 +1,35 @@ +{$name}; + } + function __construct(array $options = []) + { + parent::__construct(); + $this->credentials = new AwsCredentials($options); + $provider_options = self::$crt->credentials_provider_static_options_new(); + self::$crt->credentials_provider_static_options_set_access_key_id($provider_options, $this->credentials->access_key_id); + self::$crt->credentials_provider_static_options_set_secret_access_key($provider_options, $this->credentials->secret_access_key); + self::$crt->credentials_provider_static_options_set_session_token($provider_options, $this->credentials->session_token); + $this->acquire(self::$crt->credentials_provider_static_new($provider_options)); + self::$crt->credentials_provider_static_options_release($provider_options); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/CRT.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/CRT.php new file mode 100644 index 00000000..2b2e7572 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/CRT.php @@ -0,0 +1,356 @@ +aws_crt_last_error(); + } + /** + * @param integer $error Error code from the CRT, usually delivered via callback or {@see last_error} + * @return string Human-readable description of the provided error code + */ + public static function error_str($error) + { + return self::$impl->aws_crt_error_str((int) $error); + } + /** + * @param integer $error Error code from the CRT, usually delivered via callback or {@see last_error} + * @return string Name/enum identifier for the provided error code + */ + public static function error_name($error) + { + return self::$impl->aws_crt_error_name((int) $error); + } + public static function log_to_stdout() + { + return self::$impl->aws_crt_log_to_stdout(); + } + public static function log_to_stderr() + { + return self::$impl->aws_crt_log_to_stderr(); + } + public static function log_to_file($filename) + { + return self::$impl->aws_crt_log_to_file($filename); + } + public static function log_to_stream($stream) + { + return self::$impl->aws_crt_log_to_stream($stream); + } + public static function log_set_level($level) + { + return self::$impl->aws_crt_log_set_level($level); + } + public static function log_stop() + { + return self::$impl->aws_crt_log_stop(); + } + public static function log_message($level, $message) + { + return self::$impl->aws_crt_log_message($level, $message); + } + /** + * @return object Pointer to native event_loop_group_options + */ + function event_loop_group_options_new() + { + return self::$impl->aws_crt_event_loop_group_options_new(); + } + /** + * @param object $elg_options Pointer to native event_loop_group_options + */ + function event_loop_group_options_release($elg_options) + { + self::$impl->aws_crt_event_loop_group_options_release($elg_options); + } + /** + * @param object $elg_options Pointer to native event_loop_group_options + * @param integer $max_threads Maximum number of threads to allow the event loop group to use, default: 0/1 per CPU core + */ + function event_loop_group_options_set_max_threads($elg_options, $max_threads) + { + self::$impl->aws_crt_event_loop_group_options_set_max_threads($elg_options, (int) $max_threads); + } + /** + * @param object Pointer to event_loop_group_options, {@see event_loop_group_options_new} + * @return object Pointer to the new event loop group + */ + function event_loop_group_new($options) + { + return self::$impl->aws_crt_event_loop_group_new($options); + } + /** + * @param object $elg Pointer to the event loop group to release + */ + function event_loop_group_release($elg) + { + self::$impl->aws_crt_event_loop_group_release($elg); + } + /** + * return object Pointer to native AWS credentials options + */ + function aws_credentials_options_new() + { + return self::$impl->aws_crt_credentials_options_new(); + } + function aws_credentials_options_release($options) + { + self::$impl->aws_crt_credentials_options_release($options); + } + function aws_credentials_options_set_access_key_id($options, $access_key_id) + { + self::$impl->aws_crt_credentials_options_set_access_key_id($options, $access_key_id); + } + function aws_credentials_options_set_secret_access_key($options, $secret_access_key) + { + self::$impl->aws_crt_credentials_options_set_secret_access_key($options, $secret_access_key); + } + function aws_credentials_options_set_session_token($options, $session_token) + { + self::$impl->aws_crt_credentials_options_set_session_token($options, $session_token); + } + function aws_credentials_options_set_expiration_timepoint_seconds($options, $expiration_timepoint_seconds) + { + self::$impl->aws_crt_credentials_options_set_expiration_timepoint_seconds($options, $expiration_timepoint_seconds); + } + function aws_credentials_new($options) + { + return self::$impl->aws_crt_credentials_new($options); + } + function aws_credentials_release($credentials) + { + self::$impl->aws_crt_credentials_release($credentials); + } + function credentials_provider_release($provider) + { + self::$impl->aws_crt_credentials_provider_release($provider); + } + function credentials_provider_static_options_new() + { + return self::$impl->aws_crt_credentials_provider_static_options_new(); + } + function credentials_provider_static_options_release($options) + { + self::$impl->aws_crt_credentials_provider_static_options_release($options); + } + function credentials_provider_static_options_set_access_key_id($options, $access_key_id) + { + self::$impl->aws_crt_credentials_provider_static_options_set_access_key_id($options, $access_key_id); + } + function credentials_provider_static_options_set_secret_access_key($options, $secret_access_key) + { + self::$impl->aws_crt_credentials_provider_static_options_set_secret_access_key($options, $secret_access_key); + } + function credentials_provider_static_options_set_session_token($options, $session_token) + { + self::$impl->aws_crt_credentials_provider_static_options_set_session_token($options, $session_token); + } + function credentials_provider_static_new($options) + { + return self::$impl->aws_crt_credentials_provider_static_new($options); + } + function input_stream_options_new() + { + return self::$impl->aws_crt_input_stream_options_new(); + } + function input_stream_options_release($options) + { + self::$impl->aws_crt_input_stream_options_release($options); + } + function input_stream_options_set_user_data($options, $user_data) + { + self::$impl->aws_crt_input_stream_options_set_user_data($options, $user_data); + } + function input_stream_new($options) + { + return self::$impl->aws_crt_input_stream_new($options); + } + function input_stream_release($stream) + { + self::$impl->aws_crt_input_stream_release($stream); + } + function input_stream_seek($stream, $offset, $basis) + { + return self::$impl->aws_crt_input_stream_seek($stream, $offset, $basis); + } + function input_stream_read($stream, $length) + { + return self::$impl->aws_crt_input_stream_read($stream, $length); + } + function input_stream_eof($stream) + { + return self::$impl->aws_crt_input_stream_eof($stream); + } + function input_stream_get_length($stream) + { + return self::$impl->aws_crt_input_stream_get_length($stream); + } + function http_message_new_from_blob($blob) + { + return self::$impl->aws_crt_http_message_new_from_blob($blob); + } + function http_message_to_blob($message) + { + return self::$impl->aws_crt_http_message_to_blob($message); + } + function http_message_release($message) + { + self::$impl->aws_crt_http_message_release($message); + } + function signing_config_aws_new() + { + return self::$impl->aws_crt_signing_config_aws_new(); + } + function signing_config_aws_release($signing_config) + { + return self::$impl->aws_crt_signing_config_aws_release($signing_config); + } + function signing_config_aws_set_algorithm($signing_config, $algorithm) + { + self::$impl->aws_crt_signing_config_aws_set_algorithm($signing_config, (int) $algorithm); + } + function signing_config_aws_set_signature_type($signing_config, $signature_type) + { + self::$impl->aws_crt_signing_config_aws_set_signature_type($signing_config, (int) $signature_type); + } + function signing_config_aws_set_credentials_provider($signing_config, $credentials_provider) + { + self::$impl->aws_crt_signing_config_aws_set_credentials_provider($signing_config, $credentials_provider); + } + function signing_config_aws_set_region($signing_config, $region) + { + self::$impl->aws_crt_signing_config_aws_set_region($signing_config, $region); + } + function signing_config_aws_set_service($signing_config, $service) + { + self::$impl->aws_crt_signing_config_aws_set_service($signing_config, $service); + } + function signing_config_aws_set_use_double_uri_encode($signing_config, $use_double_uri_encode) + { + self::$impl->aws_crt_signing_config_aws_set_use_double_uri_encode($signing_config, $use_double_uri_encode); + } + function signing_config_aws_set_should_normalize_uri_path($signing_config, $should_normalize_uri_path) + { + self::$impl->aws_crt_signing_config_aws_set_should_normalize_uri_path($signing_config, $should_normalize_uri_path); + } + function signing_config_aws_set_omit_session_token($signing_config, $omit_session_token) + { + self::$impl->aws_crt_signing_config_aws_set_omit_session_token($signing_config, $omit_session_token); + } + function signing_config_aws_set_signed_body_value($signing_config, $signed_body_value) + { + self::$impl->aws_crt_signing_config_aws_set_signed_body_value($signing_config, $signed_body_value); + } + function signing_config_aws_set_signed_body_header_type($signing_config, $signed_body_header_type) + { + self::$impl->aws_crt_signing_config_aws_set_signed_body_header_type($signing_config, $signed_body_header_type); + } + function signing_config_aws_set_expiration_in_seconds($signing_config, $expiration_in_seconds) + { + self::$impl->aws_crt_signing_config_aws_set_expiration_in_seconds($signing_config, $expiration_in_seconds); + } + function signing_config_aws_set_date($signing_config, $timestamp) + { + self::$impl->aws_crt_signing_config_aws_set_date($signing_config, $timestamp); + } + function signing_config_aws_set_should_sign_header_fn($signing_config, $should_sign_header_fn) + { + self::$impl->aws_crt_signing_config_aws_set_should_sign_header_fn($signing_config, $should_sign_header_fn); + } + function signable_new_from_http_request($http_message) + { + return self::$impl->aws_crt_signable_new_from_http_request($http_message); + } + function signable_new_from_chunk($chunk_stream, $previous_signature) + { + return self::$impl->aws_crt_signable_new_from_chunk($chunk_stream, $previous_signature); + } + function signable_new_from_canonical_request($canonical_request) + { + return self::$impl->aws_crt_signable_new_from_canonical_request($canonical_request); + } + function signable_release($signable) + { + self::$impl->aws_crt_signable_release($signable); + } + function signing_result_release($signing_result) + { + self::$impl->aws_crt_signing_result_release($signing_result); + } + function signing_result_apply_to_http_request($signing_result, $http_message) + { + return self::$impl->aws_crt_signing_result_apply_to_http_request($signing_result, $http_message); + } + function sign_request_aws($signable, $signing_config, $on_complete, $user_data) + { + return self::$impl->aws_crt_sign_request_aws($signable, $signing_config, $on_complete, $user_data); + } + function test_verify_sigv4a_signing($signable, $signing_config, $expected_canonical_request, $signature, $ecc_key_pub_x, $ecc_key_pub_y) + { + return self::$impl->aws_crt_test_verify_sigv4a_signing($signable, $signing_config, $expected_canonical_request, $signature, $ecc_key_pub_x, $ecc_key_pub_y); + } + public static function crc32($input, $previous = 0) + { + return self::$impl->aws_crt_crc32($input, $previous); + } + public static function crc32c($input, $previous = 0) + { + return self::$impl->aws_crt_crc32c($input, $previous); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php new file mode 100644 index 00000000..e1b7a621 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php @@ -0,0 +1,51 @@ +headers = $headers; + } + public static function marshall($headers) + { + $buf = ""; + foreach ($headers->headers as $header => $value) { + $buf .= Encoding::encodeString($header); + $buf .= Encoding::encodeString($value); + } + return $buf; + } + public static function unmarshall($buf) + { + $strings = Encoding::readStrings($buf); + $headers = []; + for ($idx = 0; $idx < \count($strings);) { + $headers[$strings[$idx++]] = $strings[$idx++]; + } + return new Headers($headers); + } + public function count() + { + return \count($this->headers); + } + public function get($header) + { + return isset($this->headers[$header]) ? $this->headers[$header] : null; + } + public function set($header, $value) + { + $this->headers[$header] = $value; + } + public function toArray() + { + return $this->headers; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php new file mode 100644 index 00000000..38ae4c18 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php @@ -0,0 +1,94 @@ +method = $method; + $this->path = $path; + $this->query = $query; + $this->headers = new Headers($headers); + $this->acquire(self::$crt->http_message_new_from_blob(self::marshall($this))); + } + public function __destruct() + { + self::$crt->http_message_release($this->release()); + parent::__destruct(); + } + public function toBlob() + { + return self::$crt->http_message_to_blob($this->native); + } + protected static function marshall($msg) + { + $buf = ""; + $buf .= Encoding::encodeString($msg->method); + $buf .= Encoding::encodeString($msg->pathAndQuery()); + $buf .= Headers::marshall($msg->headers); + return $buf; + } + protected static function _unmarshall($buf, $class = Message::class) + { + $method = Encoding::readString($buf); + $path_and_query = Encoding::readString($buf); + $parts = \explode("?", $path_and_query, 2); + $path = isset($parts[0]) ? $parts[0] : ""; + $query = isset($parts[1]) ? $parts[1] : ""; + $headers = Headers::unmarshall($buf); + // Turn query params back into a dictionary + if (\strlen($query)) { + $query = \rawurldecode($query); + $query = \explode("&", $query); + $query = \array_reduce($query, function ($params, $pair) { + list($param, $value) = \explode("=", $pair, 2); + $params[$param] = $value; + return $params; + }, []); + } else { + $query = []; + } + return new $class($method, $path, $query, $headers->toArray()); + } + public function pathAndQuery() + { + $path = $this->path; + $queries = []; + foreach ($this->query as $param => $value) { + $queries[] = \urlencode($param) . "=" . \urlencode($value); + } + $query = \implode("&", $queries); + if (\strlen($query)) { + $path = \implode("?", [$path, $query]); + } + return $path; + } + public function method() + { + return $this->method; + } + public function path() + { + return $this->path; + } + public function query() + { + return $this->query; + } + public function headers() + { + return $this->headers; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php new file mode 100644 index 00000000..a429a449 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php @@ -0,0 +1,33 @@ +body_stream = $body_stream; + } + public static function marshall($request) + { + return parent::marshall($request); + } + public static function unmarshall($buf) + { + return parent::_unmarshall($buf, Request::class); + } + public function body_stream() + { + return $this->body_stream; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php new file mode 100644 index 00000000..82aa2f46 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php @@ -0,0 +1,29 @@ +status_code = $status_code; + } + public static function marshall($response) + { + return parent::marshall($response); + } + public static function unmarshall($buf) + { + return parent::_unmarshall($buf, Response::class); + } + public function status_code() + { + return $this->status_code; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php new file mode 100644 index 00000000..92913458 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php @@ -0,0 +1,38 @@ + 0]; + } + function __construct(array $options = []) + { + parent::__construct(); + $options = new Options($options, self::defaults()); + $elg_options = self::$crt->event_loop_group_options_new(); + self::$crt->event_loop_group_options_set_max_threads($elg_options, $options->getInt('max_threads')); + $this->acquire(self::$crt->event_loop_group_new($elg_options)); + self::$crt->event_loop_group_options_release($elg_options); + } + function __destruct() + { + self::$crt->event_loop_group_release($this->release()); + parent::__destruct(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php new file mode 100644 index 00000000..116804cb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php @@ -0,0 +1,50 @@ +stream = $stream; + $options = self::$crt->input_stream_options_new(); + // The stream implementation in native just converts the PHP stream into + // a native php_stream* and executes operations entirely in native + self::$crt->input_stream_options_set_user_data($options, $stream); + $this->acquire(self::$crt->input_stream_new($options)); + self::$crt->input_stream_options_release($options); + } + public function __destruct() + { + $this->release(); + parent::__destruct(); + } + public function eof() + { + return self::$crt->input_stream_eof($this->native); + } + public function length() + { + return self::$crt->input_stream_get_length($this->native); + } + public function read($length = 0) + { + if ($length == 0) { + $length = $this->length(); + } + return self::$crt->input_stream_read($this->native, $length); + } + public function seek($offset, $basis) + { + return self::$crt->input_stream_seek($this->native, $offset, $basis); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php new file mode 100644 index 00000000..a436b0e6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php @@ -0,0 +1,40 @@ += self::NONE && $level <= self::TRACE); + CRT::log_set_level($level); + } + public static function log($level, $message) + { + CRT::log_message($level, $message); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/NativeResource.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/NativeResource.php new file mode 100644 index 00000000..6598caef --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/NativeResource.php @@ -0,0 +1,42 @@ +native = $handle; + } + protected function release() + { + $native = $this->native; + $this->native = null; + return $native; + } + function __destruct() + { + // Should have been destroyed and released by derived resource + \assert($this->native == null); + unset(self::$resources[\spl_object_hash($this)]); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Options.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Options.php new file mode 100644 index 00000000..9f34f106 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-crt-php/src/AWS/CRT/Options.php @@ -0,0 +1,80 @@ +value = $value; + } + public function asObject() + { + return $this->value; + } + public function asMixed() + { + return $this->value; + } + public function asInt() + { + return empty($this->value) ? 0 : (int) $this->value; + } + public function asBool() + { + return \boolval($this->value); + } + public function asString() + { + return !empty($this->value) ? \strval($this->value) : ""; + } + public function asArray() + { + return \is_array($this->value) ? $this->value : (!empty($this->value) ? [$this->value] : []); + } + public function asCallable() + { + return \is_callable($this->value) ? $this->value : null; + } +} +final class Options +{ + private $options; + public function __construct($opts = [], $defaults = []) + { + $this->options = \array_replace($defaults, empty($opts) ? [] : $opts); + } + public function __get($name) + { + return $this->get($name); + } + public function asArray() + { + return $this->options; + } + public function toArray() + { + return \array_merge_recursive([], $this->options); + } + public function get($name) + { + return new OptionValue($this->options[$name]); + } + public function getInt($name) + { + return $this->get($name)->asInt(); + } + public function getString($name) + { + return $this->get($name)->asString(); + } + public function getBool($name) + { + return $this->get($name)->asBool(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/LICENSE new file mode 100644 index 00000000..8d53e9f5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/LICENSE @@ -0,0 +1,141 @@ +# Apache License +Version 2.0, January 2004 + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +## 1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 +through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the +License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled +by, or are under common control with that entity. For the purposes of this definition, "control" means +(i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract +or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial +ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software +source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, +including but not limited to compiled object code, generated documentation, and conversions to other media +types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, +as indicated by a copyright notice that is included in or attached to the work (an example is provided in the +Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) +the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, +as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not +include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work +and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any +modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to +Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to +submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to the Licensor or its representatives, including but not +limited to communication on electronic mailing lists, source code control systems, and issue tracking systems +that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been +received by Licensor and subsequently incorporated within the Work. + +## 2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare +Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +## 3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent +license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such +license applies only to those patent claims licensable by such Contributor that are necessarily infringed by +their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such +Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work +constitutes direct or contributory patent infringement, then any patent licenses granted to You under this +License for that Work shall terminate as of the date such litigation is filed. + +## 4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and + + 2. You must cause any modified files to carry prominent notices stating that You changed the files; and + + 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, + trademark, and attribution notices from the Source form of the Work, excluding those notices that do + not pertain to any part of the Derivative Works; and + + 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that + You distribute must include a readable copy of the attribution notices contained within such NOTICE + file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed as part of the Derivative Works; within + the Source form or documentation, if provided along with the Derivative Works; or, within a display + generated by the Derivative Works, if and wherever such third-party notices normally appear. The + contents of the NOTICE file are for informational purposes only and do not modify the License. You may + add Your own attribution notices within Derivative Works that You distribute, alongside or as an + addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be + construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license +terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative +Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the +conditions stated in this License. + +## 5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by +You to the Licensor shall be under the terms and conditions of this License, without any additional terms or +conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate +license agreement you may have executed with Licensor regarding such Contributions. + +## 6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of +the Licensor, except as required for reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +## 7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor +provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +## 8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless +required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any +Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential +damages of any character arising as a result of this License or out of the use or inability to use the Work +(including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has been advised of the possibility +of such damages. + +## 9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, +acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold +each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/NOTICE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/NOTICE new file mode 100644 index 00000000..e231afd7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/NOTICE @@ -0,0 +1,17 @@ +# AWS SDK for PHP + + + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"). +You may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/THIRD-PARTY-LICENSES b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/THIRD-PARTY-LICENSES new file mode 100644 index 00000000..6f7dc625 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/THIRD-PARTY-LICENSES @@ -0,0 +1,84 @@ +The AWS SDK for PHP includes the following third-party software/licensing: + + +** Guzzle - https://github.com/guzzle/guzzle + +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** jmespath.php - https://github.com/mtdowling/jmespath.php + +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------- + +** phpunit-mock-objects -- https://github.com/sebastianbergmann/phpunit-mock-objects + +Copyright (c) 2002-2018, Sebastian Bergmann . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Sebastian Bergmann nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AbstractConfigurationProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AbstractConfigurationProvider.php new file mode 100644 index 00000000..67f8f17a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AbstractConfigurationProvider.php @@ -0,0 +1,133 @@ +get($cacheKey); + if ($found instanceof static::$interfaceClass) { + return Promise\Create::promiseFor($found); + } + return $provider()->then(function ($config) use($cache, $cacheKey) { + $cache->set($cacheKey, $config); + return $config; + }); + }; + } + /** + * Creates an aggregate configuration provider that invokes the provided + * variadic providers one after the other until a provider returns + * configuration. + * + * @return callable + */ + public static function chain() + { + $links = \func_get_args(); + if (empty($links)) { + throw new \InvalidArgumentException('No providers in chain'); + } + return function () use($links) { + /** @var callable $parent */ + $parent = \array_shift($links); + $promise = $parent(); + while ($next = \array_shift($links)) { + $promise = $promise->otherwise($next); + } + return $promise; + }; + } + /** + * Gets the environment's HOME directory if available. + * + * @return null|string + */ + protected static function getHomeDir() + { + // On Linux/Unix-like systems, use the HOME environment variable + if ($homeDir = \getenv('HOME')) { + return $homeDir; + } + // Get the HOMEDRIVE and HOMEPATH values for Windows hosts + $homeDrive = \getenv('HOMEDRIVE'); + $homePath = \getenv('HOMEPATH'); + return $homeDrive && $homePath ? $homeDrive . $homePath : null; + } + /** + * Gets default config file location from environment, falling back to aws + * default location + * + * @return string + */ + protected static function getDefaultConfigFilename() + { + if ($filename = \getenv(self::ENV_CONFIG_FILE)) { + return $filename; + } + return self::getHomeDir() . '/.aws/config'; + } + /** + * Wraps a config provider and caches previously provided configuration. + * + * @param callable $provider Config provider function to wrap. + * + * @return callable + */ + public static function memoize(callable $provider) + { + return function () use($provider) { + static $result; + static $isConstant; + // Constant config will be returned constantly. + if ($isConstant) { + return $result; + } + // Create the initial promise that will be used as the cached value + if (null === $result) { + $result = $provider(); + } + // Return config and set flag that provider is already set + return $result->then(function ($config) use(&$isConstant) { + $isConstant = \true; + return $config; + }); + }; + } + /** + * Reject promise with standardized exception. + * + * @param $msg + * @return Promise\RejectedPromise + */ + protected static function reject($msg) + { + $exceptionClass = static::$exceptionClass; + return new Promise\RejectedPromise(new $exceptionClass($msg)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/AbstractModel.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/AbstractModel.php new file mode 100644 index 00000000..7e10bb1a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/AbstractModel.php @@ -0,0 +1,75 @@ +definition = $definition; + $this->shapeMap = $shapeMap; + if (isset($definition['contextParam'])) { + $this->contextParam = $definition['contextParam']; + } + } + public function toArray() + { + return $this->definition; + } + /** + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return isset($this->definition[$offset]) ? $this->definition[$offset] : null; + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->definition[$offset] = $value; + } + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->definition[$offset]); + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->definition[$offset]); + } + protected function shapeAt($key) + { + if (!isset($this->definition[$key])) { + throw new \InvalidArgumentException('Expected shape definition at ' . $key); + } + return $this->shapeFor($this->definition[$key]); + } + protected function shapeFor(array $definition) + { + return isset($definition['shape']) ? $this->shapeMap->resolve($definition) : Shape::create($definition, $this->shapeMap); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ApiProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ApiProvider.php new file mode 100644 index 00000000..33aa0d28 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ApiProvider.php @@ -0,0 +1,212 @@ + 'api-2', 'paginator' => 'paginators-1', 'waiter' => 'waiters-2', 'docs' => 'docs-2']; + /** @var array API manifest */ + private $manifest; + /** @var string The directory containing service models. */ + private $modelsDir; + /** + * Resolves an API provider and ensures a non-null return value. + * + * @param callable $provider Provider function to invoke. + * @param string $type Type of data ('api', 'waiter', 'paginator'). + * @param string $service Service name. + * @param string $version API version. + * + * @return array + * @throws UnresolvedApiException + */ + public static function resolve(callable $provider, $type, $service, $version) + { + // Execute the provider and return the result, if there is one. + $result = $provider($type, $service, $version); + if (\is_array($result)) { + if (!isset($result['metadata']['serviceIdentifier'])) { + $result['metadata']['serviceIdentifier'] = $service; + } + return $result; + } + // Throw an exception with a message depending on the inputs. + if (!isset(self::$typeMap[$type])) { + $msg = "The type must be one of: " . \implode(', ', self::$typeMap); + } elseif ($service) { + $msg = "The {$service} service does not have version: {$version}."; + } else { + $msg = "You must specify a service name to retrieve its API data."; + } + throw new UnresolvedApiException($msg); + } + /** + * Default SDK API provider. + * + * This provider loads pre-built manifest data from the `data` directory. + * + * @return self + */ + public static function defaultProvider() + { + return new self(__DIR__ . '/../data', \WPMailSMTP\Vendor\Aws\manifest()); + } + /** + * Loads API data after resolving the version to the latest, compatible, + * available version based on the provided manifest data. + * + * Manifest data is essentially an associative array of service names to + * associative arrays of API version aliases. + * + * [ + * ... + * 'ec2' => [ + * 'latest' => '2014-10-01', + * '2014-10-01' => '2014-10-01', + * '2014-09-01' => '2014-10-01', + * '2014-06-15' => '2014-10-01', + * ... + * ], + * 'ecs' => [...], + * 'elasticache' => [...], + * ... + * ] + * + * @param string $dir Directory containing service models. + * @param array $manifest The API version manifest data. + * + * @return self + */ + public static function manifest($dir, array $manifest) + { + return new self($dir, $manifest); + } + /** + * Loads API data from the specified directory. + * + * If "latest" is specified as the version, this provider must glob the + * directory to find which is the latest available version. + * + * @param string $dir Directory containing service models. + * + * @return self + * @throws \InvalidArgumentException if the provided `$dir` is invalid. + */ + public static function filesystem($dir) + { + return new self($dir); + } + /** + * Retrieves a list of valid versions for the specified service. + * + * @param string $service Service name + * + * @return array + */ + public function getVersions($service) + { + if (!isset($this->manifest)) { + $this->buildVersionsList($service); + } + if (!isset($this->manifest[$service]['versions'])) { + return []; + } + return \array_values(\array_unique($this->manifest[$service]['versions'])); + } + /** + * Execute the provider. + * + * @param string $type Type of data ('api', 'waiter', 'paginator'). + * @param string $service Service name. + * @param string $version API version. + * + * @return array|null + */ + public function __invoke($type, $service, $version) + { + // Resolve the type or return null. + if (isset(self::$typeMap[$type])) { + $type = self::$typeMap[$type]; + } else { + return null; + } + // Resolve the version or return null. + if (!isset($this->manifest)) { + $this->buildVersionsList($service); + } + if (!isset($this->manifest[$service]['versions'][$version])) { + return null; + } + $version = $this->manifest[$service]['versions'][$version]; + $path = "{$this->modelsDir}/{$service}/{$version}/{$type}.json"; + try { + return \WPMailSMTP\Vendor\Aws\load_compiled_json($path); + } catch (\InvalidArgumentException $e) { + return null; + } + } + /** + * @param string $modelsDir Directory containing service models. + * @param array $manifest The API version manifest data. + */ + private function __construct($modelsDir, ?array $manifest = null) + { + $this->manifest = $manifest; + $this->modelsDir = \rtrim($modelsDir, '/'); + if (!\is_dir($this->modelsDir)) { + throw new \InvalidArgumentException("The specified models directory, {$modelsDir}, was not found."); + } + } + /** + * Build the versions list for the specified service by globbing the dir. + */ + private function buildVersionsList($service) + { + $dir = "{$this->modelsDir}/{$service}/"; + if (!\is_dir($dir)) { + return; + } + // Get versions, remove . and .., and sort in descending order. + $results = \array_diff(\scandir($dir, \SCANDIR_SORT_DESCENDING), ['..', '.']); + if (!$results) { + $this->manifest[$service] = ['versions' => []]; + } else { + $this->manifest[$service] = ['versions' => ['latest' => $results[0]]]; + $this->manifest[$service]['versions'] += \array_combine($results, $results); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DateTimeResult.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DateTimeResult.php new file mode 100644 index 00000000..f6ba815b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DateTimeResult.php @@ -0,0 +1,115 @@ +format('Y-m-d H:i:s.u'), new DateTimeZone('UTC')); + } + /** + * @return DateTimeResult + */ + public static function fromISO8601($iso8601Timestamp) + { + if (\is_numeric($iso8601Timestamp) || !\is_string($iso8601Timestamp)) { + throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromISO8601'); + } + // Prior to 8.0.10, nanosecond precision is not supported + // Reduces to microsecond precision if nanosecond precision is detected + if (\PHP_VERSION_ID < 80010 && \preg_match(self::ISO8601_NANOSECOND_REGEX, $iso8601Timestamp, $matches)) { + $iso8601Timestamp = $matches[1] . ($matches[3] ?? ''); + } + return new DateTimeResult($iso8601Timestamp); + } + /** + * Create a new DateTimeResult from an unknown timestamp. + * + * @return DateTimeResult + * @throws Exception + */ + public static function fromTimestamp($timestamp, $expectedFormat = null) + { + if (empty($timestamp)) { + return self::fromEpoch(0); + } + if (!(\is_string($timestamp) || \is_numeric($timestamp))) { + throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromTimestamp'); + } + try { + if ($expectedFormat == 'iso8601') { + try { + return self::fromISO8601($timestamp); + } catch (Exception $exception) { + return self::fromEpoch($timestamp); + } + } else { + if ($expectedFormat == 'unixTimestamp') { + try { + return self::fromEpoch($timestamp); + } catch (Exception $exception) { + return self::fromISO8601($timestamp); + } + } else { + if (\WPMailSMTP\Vendor\Aws\is_valid_epoch($timestamp)) { + return self::fromEpoch($timestamp); + } + } + } + return self::fromISO8601($timestamp); + } catch (Exception $exception) { + throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromTimestamp'); + } + } + /** + * Serialize the DateTimeResult as an ISO 8601 date string. + * + * @return string + */ + public function __toString() + { + return $this->format('c'); + } + /** + * Serialize the date as an ISO 8601 date when serializing as JSON. + * + * @return string + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return (string) $this; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DocModel.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DocModel.php new file mode 100644 index 00000000..3712a2b5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/DocModel.php @@ -0,0 +1,107 @@ +docs = $docs; + } + /** + * Convert the doc model to an array. + * + * @return array + */ + public function toArray() + { + return $this->docs; + } + /** + * Retrieves documentation about the service. + * + * @return null|string + */ + public function getServiceDocs() + { + return isset($this->docs['service']) ? $this->docs['service'] : null; + } + /** + * Retrieves documentation about an operation. + * + * @param string $operation Name of the operation + * + * @return null|string + */ + public function getOperationDocs($operation) + { + return isset($this->docs['operations'][$operation]) ? $this->docs['operations'][$operation] : null; + } + /** + * Retrieves documentation about an error. + * + * @param string $error Name of the error + * + * @return null|string + */ + public function getErrorDocs($error) + { + return isset($this->docs['shapes'][$error]['base']) ? $this->docs['shapes'][$error]['base'] : null; + } + /** + * Retrieves documentation about a shape, specific to the context. + * + * @param string $shapeName Name of the shape. + * @param string $parentName Name of the parent/context shape. + * @param string $ref Name used by the context to reference the shape. + * + * @return null|string + */ + public function getShapeDocs($shapeName, $parentName, $ref) + { + if (!isset($this->docs['shapes'][$shapeName])) { + return ''; + } + $result = ''; + $d = $this->docs['shapes'][$shapeName]; + if (isset($d['refs']["{$parentName}\${$ref}"])) { + $result = $d['refs']["{$parentName}\${$ref}"]; + } elseif (isset($d['base'])) { + $result = $d['base']; + } + if (isset($d['append'])) { + if (!isset($d['excludeAppend']) || !\in_array($parentName, $d['excludeAppend'])) { + $result .= $d['append']; + } + } + if (isset($d['appendOnly']) && \in_array($parentName, $d['appendOnly']['shapes'])) { + $result .= $d['appendOnly']['message']; + } + return $this->clean($result); + } + private function clean($content) + { + if (!$content) { + return ''; + } + $tidy = new \tidy(); + $tidy->parseString($content, ['indent' => \true, 'doctype' => 'omit', 'output-html' => \true, 'show-body-only' => \true, 'drop-empty-paras' => \true, 'clean' => \true, 'drop-proprietary-attributes' => \true, 'hide-comments' => \true, 'logical-emphasis' => \true]); + $tidy->cleanRepair(); + return (string) $content; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/AbstractErrorParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/AbstractErrorParser.php new file mode 100644 index 00000000..ac752cb4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/AbstractErrorParser.php @@ -0,0 +1,70 @@ +api = $api; + } + protected abstract function payload(ResponseInterface $response, StructureShape $member); + protected function extractPayload(StructureShape $member, ResponseInterface $response) + { + if ($member instanceof StructureShape) { + // Structure members parse top-level data into a specific key. + return $this->payload($response, $member); + } else { + // Streaming data is just the stream from the response body. + return $response->getBody(); + } + } + protected function populateShape(array &$data, ResponseInterface $response, ?CommandInterface $command = null) + { + $data['body'] = []; + if (!empty($command) && !empty($this->api)) { + // If modeled error code is indicated, check for known error shape + if (!empty($data['code'])) { + $errors = $this->api->getOperation($command->getName())->getErrors(); + foreach ($errors as $key => $error) { + // If error code matches a known error shape, populate the body + if ($data['code'] == $error['name'] && $error instanceof StructureShape) { + $modeledError = $error; + $data['body'] = $this->extractPayload($modeledError, $response); + $data['error_shape'] = $modeledError; + foreach ($error->getMembers() as $name => $member) { + switch ($member['location']) { + case 'header': + $this->extractHeader($name, $member, $response, $data['body']); + break; + case 'headers': + $this->extractHeaders($name, $member, $response, $data['body']); + break; + case 'statusCode': + $this->extractStatus($name, $response, $data['body']); + break; + } + } + break; + } + } + } + } + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php new file mode 100644 index 00000000..861e0720 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php @@ -0,0 +1,37 @@ +getStatusCode(); + if ($this->api && !\is_null($this->api->getMetadata('awsQueryCompatible')) && $response->getHeaderLine('x-amzn-query-error')) { + $queryError = $response->getHeaderLine('x-amzn-query-error'); + $parts = \explode(';', $queryError); + if (isset($parts) && \count($parts) == 2 && $parts[0] && $parts[1]) { + $error_code = $parts[0]; + $error_type = $parts[1]; + } + } + if (!isset($error_type)) { + $error_type = $code[0] == '4' ? 'client' : 'server'; + } + return ['request_id' => (string) $response->getHeaderLine('x-amzn-requestid'), 'code' => isset($error_code) ? $error_code : null, 'message' => null, 'type' => $error_type, 'parsed' => $this->parseJson($response->getBody(), $response)]; + } + protected function payload(ResponseInterface $response, StructureShape $member) + { + $jsonBody = $this->parseJson($response->getBody(), $response); + if ($jsonBody) { + return $this->parser->parse($member, $jsonBody); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php new file mode 100644 index 00000000..12f505cd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php @@ -0,0 +1,38 @@ +parser = $parser ?: new JsonParser(); + } + public function __invoke(ResponseInterface $response, ?CommandInterface $command = null) + { + $data = $this->genericHandler($response); + // Make the casing consistent across services. + if ($data['parsed']) { + $data['parsed'] = \array_change_key_case($data['parsed']); + } + if (isset($data['parsed']['__type'])) { + if (!isset($data['code'])) { + $parts = \explode('#', $data['parsed']['__type']); + $data['code'] = isset($parts[1]) ? $parts[1] : $parts[0]; + } + $data['message'] = isset($data['parsed']['message']) ? $data['parsed']['message'] : null; + } + $this->populateShape($data, $response, $command); + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php new file mode 100644 index 00000000..24628a90 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php @@ -0,0 +1,43 @@ +parser = $parser ?: new JsonParser(); + } + public function __invoke(ResponseInterface $response, ?CommandInterface $command = null) + { + $data = $this->genericHandler($response); + // Merge in error data from the JSON body + if ($json = $data['parsed']) { + $data = \array_replace($data, $json); + } + // Correct error type from services like Amazon Glacier + if (!empty($data['type'])) { + $data['type'] = \strtolower($data['type']); + } + // Retrieve the error code from services like Amazon Elastic Transcoder + if ($code = $response->getHeaderLine('x-amzn-errortype')) { + $colon = \strpos($code, ':'); + $data['code'] = $colon ? \substr($code, 0, $colon) : $code; + } + // Retrieve error message directly + $data['message'] = isset($data['parsed']['message']) ? $data['parsed']['message'] : (isset($data['parsed']['Message']) ? $data['parsed']['Message'] : null); + $this->populateShape($data, $response, $command); + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php new file mode 100644 index 00000000..c72b9779 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php @@ -0,0 +1,82 @@ +parser = $parser ?: new XmlParser(); + } + public function __invoke(ResponseInterface $response, ?CommandInterface $command = null) + { + $code = (string) $response->getStatusCode(); + $data = ['type' => $code[0] == '4' ? 'client' : 'server', 'request_id' => null, 'code' => null, 'message' => null, 'parsed' => null]; + $body = $response->getBody(); + if ($body->getSize() > 0) { + $this->parseBody($this->parseXml($body, $response), $data); + } else { + $this->parseHeaders($response, $data); + } + $this->populateShape($data, $response, $command); + return $data; + } + private function parseHeaders(ResponseInterface $response, array &$data) + { + if ($response->getStatusCode() == '404') { + $data['code'] = 'NotFound'; + } + $data['message'] = $response->getStatusCode() . ' ' . $response->getReasonPhrase(); + if ($requestId = $response->getHeaderLine('x-amz-request-id')) { + $data['request_id'] = $requestId; + $data['message'] .= " (Request-ID: {$requestId})"; + } + } + private function parseBody(\SimpleXMLElement $body, array &$data) + { + $data['parsed'] = $body; + $prefix = $this->registerNamespacePrefix($body); + if ($tempXml = $body->xpath("//{$prefix}Code[1]")) { + $data['code'] = (string) $tempXml[0]; + } + if ($tempXml = $body->xpath("//{$prefix}Message[1]")) { + $data['message'] = (string) $tempXml[0]; + } + $tempXml = $body->xpath("//{$prefix}RequestId[1]"); + if (isset($tempXml[0])) { + $data['request_id'] = (string) $tempXml[0]; + } + } + protected function registerNamespacePrefix(\SimpleXMLElement $element) + { + $namespaces = $element->getDocNamespaces(); + if (!isset($namespaces[''])) { + return ''; + } + // Account for the default namespace being defined and PHP not + // being able to handle it :(. + $element->registerXPathNamespace('ns', $namespaces['']); + return 'ns:'; + } + protected function payload(ResponseInterface $response, StructureShape $member) + { + $xmlBody = $this->parseXml($response->getBody(), $response); + $prefix = $this->registerNamespacePrefix($xmlBody); + $errorBody = $xmlBody->xpath("//{$prefix}Error"); + if (\is_array($errorBody) && !empty($errorBody[0])) { + return $this->parser->parse($member, $errorBody[0]); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ListShape.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ListShape.php new file mode 100644 index 00000000..6c492167 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ListShape.php @@ -0,0 +1,30 @@ +member) { + if (!isset($this->definition['member'])) { + throw new \RuntimeException('No member attribute specified'); + } + $this->member = Shape::create($this->definition['member'], $this->shapeMap); + } + return $this->member; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/MapShape.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/MapShape.php new file mode 100644 index 00000000..ba269347 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/MapShape.php @@ -0,0 +1,43 @@ +value) { + if (!isset($this->definition['value'])) { + throw new \RuntimeException('No value specified'); + } + $this->value = Shape::create($this->definition['value'], $this->shapeMap); + } + return $this->value; + } + /** + * @return Shape + */ + public function getKey() + { + if (!$this->key) { + $this->key = isset($this->definition['key']) ? Shape::create($this->definition['key'], $this->shapeMap) : new Shape(['type' => 'string'], $this->shapeMap); + } + return $this->key; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Operation.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Operation.php new file mode 100644 index 00000000..9cb7f4ce --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Operation.php @@ -0,0 +1,138 @@ +staticContextParams = $definition['staticContextParams']; + } + if (isset($definition['operationContextParams'])) { + $this->operationContextParams = $definition['operationContextParams']; + } + parent::__construct($definition, $shapeMap); + $this->contextParams = $this->setContextParams(); + } + /** + * Returns an associative array of the HTTP attribute of the operation: + * + * - method: HTTP method of the operation + * - requestUri: URI of the request (can include URI template placeholders) + * + * @return array + */ + public function getHttp() + { + return $this->definition['http']; + } + /** + * Get the input shape of the operation. + * + * @return StructureShape + */ + public function getInput() + { + if (!$this->input) { + if ($input = $this['input']) { + $this->input = $this->shapeFor($input); + } else { + $this->input = new StructureShape([], $this->shapeMap); + } + } + return $this->input; + } + /** + * Get the output shape of the operation. + * + * @return StructureShape + */ + public function getOutput() + { + if (!$this->output) { + if ($output = $this['output']) { + $this->output = $this->shapeFor($output); + } else { + $this->output = new StructureShape([], $this->shapeMap); + } + } + return $this->output; + } + /** + * Get an array of operation error shapes. + * + * @return Shape[] + */ + public function getErrors() + { + if ($this->errors === null) { + if ($errors = $this['errors']) { + foreach ($errors as $key => $error) { + $errors[$key] = $this->shapeFor($error); + } + $this->errors = $errors; + } else { + $this->errors = []; + } + } + return $this->errors; + } + /** + * Gets static modeled static values used for + * endpoint resolution. + * + * @return array + */ + public function getStaticContextParams() + { + return $this->staticContextParams; + } + /** + * Gets definition of modeled dynamic values used + * for endpoint resolution + * + * @return array + */ + public function getContextParams() + { + return $this->contextParams; + } + /** + * Gets definition of modeled dynamic values used + * for endpoint resolution + * + * @return array + */ + public function getOperationContextParams() : array + { + return $this->operationContextParams; + } + private function setContextParams() + { + $members = $this->getInput()->getMembers(); + $contextParams = []; + foreach ($members as $name => $shape) { + if (!empty($contextParam = $shape->getContextParam())) { + $contextParams[$contextParam['name']] = ['shape' => $name, 'type' => $shape->getType()]; + } + } + return $contextParams; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php new file mode 100644 index 00000000..c9e76a1e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php @@ -0,0 +1,35 @@ +api = $api; + } + /** + * @param CommandInterface $command Command that was executed. + * @param ResponseInterface $response Response that was received. + * + * @return ResultInterface + */ + public abstract function __invoke(CommandInterface $command, ResponseInterface $response); + public abstract function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php new file mode 100644 index 00000000..967d0011 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php @@ -0,0 +1,140 @@ +api->getOperation($command->getName())->getOutput(); + $result = []; + if ($payload = $output['payload']) { + $this->extractPayload($payload, $output, $response, $result); + } + foreach ($output->getMembers() as $name => $member) { + switch ($member['location']) { + case 'header': + $this->extractHeader($name, $member, $response, $result); + break; + case 'headers': + $this->extractHeaders($name, $member, $response, $result); + break; + case 'statusCode': + $this->extractStatus($name, $response, $result); + break; + } + } + if (!$payload && $response->getBody()->getSize() > 0 && \count($output->getMembers()) > 0) { + // if no payload was found, then parse the contents of the body + $this->payload($response, $output, $result); + } + return new Result($result); + } + private function extractPayload($payload, StructureShape $output, ResponseInterface $response, array &$result) + { + $member = $output->getMember($payload); + if (!empty($member['eventstream'])) { + $result[$payload] = new EventParsingIterator($response->getBody(), $member, $this); + } else { + if ($member instanceof StructureShape) { + // Structure members parse top-level data into a specific key. + $result[$payload] = []; + $this->payload($response, $member, $result[$payload]); + } else { + // Streaming data is just the stream from the response body. + $result[$payload] = $response->getBody(); + } + } + } + /** + * Extract a single header from the response into the result. + */ + private function extractHeader($name, Shape $shape, ResponseInterface $response, &$result) + { + $value = $response->getHeaderLine($shape['locationName'] ?: $name); + switch ($shape->getType()) { + case 'float': + case 'double': + $value = (float) $value; + break; + case 'long': + $value = (int) $value; + break; + case 'boolean': + $value = \filter_var($value, \FILTER_VALIDATE_BOOLEAN); + break; + case 'blob': + $value = \base64_decode($value); + break; + case 'timestamp': + try { + $value = DateTimeResult::fromTimestamp($value, !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null); + break; + } catch (\Exception $e) { + // If the value cannot be parsed, then do not add it to the + // output structure. + return; + } + case 'string': + try { + if ($shape['jsonvalue']) { + $value = $this->parseJson(\base64_decode($value), $response); + } + // If value is not set, do not add to output structure. + if (!isset($value)) { + return; + } + break; + } catch (\Exception $e) { + //If the value cannot be parsed, then do not add it to the + //output structure. + return; + } + } + $result[$name] = $value; + } + /** + * Extract a map of headers with an optional prefix from the response. + */ + private function extractHeaders($name, Shape $shape, ResponseInterface $response, &$result) + { + // Check if the headers are prefixed by a location name + $result[$name] = []; + $prefix = $shape['locationName']; + $prefixLen = $prefix !== null ? \strlen($prefix) : 0; + foreach ($response->getHeaders() as $k => $values) { + if (!$prefixLen) { + $result[$name][$k] = \implode(', ', $values); + } elseif (\stripos($k, $prefix) === 0) { + $result[$name][\substr($k, $prefixLen)] = \implode(', ', $values); + } + } + } + /** + * Places the status code of the response into the result array. + */ + private function extractStatus($name, ResponseInterface $response, array &$result) + { + $result[$name] = (int) $response->getStatusCode(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php new file mode 100644 index 00000000..6141ed7f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php @@ -0,0 +1,38 @@ +parser = $parser; + } + public function __invoke(CommandInterface $command, ResponseInterface $response) + { + if ($expected = $response->getHeaderLine('x-amz-crc32')) { + $hash = \hexdec(Psr7\Utils::hash($response->getBody(), 'crc32b')); + if ($expected != $hash) { + throw new AwsException("crc32 mismatch. Expected {$expected}, found {$hash}.", $command, ['code' => 'ClientChecksumMismatch', 'connection_error' => \true, 'response' => $response]); + } + } + $fn = $this->parser; + return $fn($command, $response); + } + public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) + { + return $this->parser->parseMemberFromStream($stream, $member, $response); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php new file mode 100644 index 00000000..ae46ad42 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php @@ -0,0 +1,257 @@ + 'decodeUint32', self::LENGTH_HEADERS => 'decodeUint32', self::CRC_PRELUDE => 'decodeUint32']; + private static $lengthFormatMap = [1 => 'decodeUint8', 2 => 'decodeUint16', 4 => 'decodeUint32', 8 => 'decodeUint64']; + private static $headerTypeMap = [0 => 'decodeBooleanTrue', 1 => 'decodeBooleanFalse', 2 => 'decodeInt8', 3 => 'decodeInt16', 4 => 'decodeInt32', 5 => 'decodeInt64', 6 => 'decodeBytes', 7 => 'decodeString', 8 => 'decodeTimestamp', 9 => 'decodeUuid']; + /** @var StreamInterface Stream of eventstream shape to parse. */ + protected $stream; + /** @var array Currently parsed event. */ + protected $currentEvent; + /** @var int Current in-order event key. */ + protected $key; + /** @var resource|\HashContext CRC32 hash context for event validation */ + protected $hashContext; + /** @var int $currentPosition */ + protected $currentPosition; + /** + * DecodingEventStreamIterator constructor. + * + * @param StreamInterface $stream + */ + public function __construct(StreamInterface $stream) + { + $this->stream = $stream; + $this->rewind(); + } + protected function parseHeaders($headerBytes) + { + $headers = []; + $bytesRead = 0; + while ($bytesRead < $headerBytes) { + list($key, $numBytes) = $this->decodeString(1); + $bytesRead += $numBytes; + list($type, $numBytes) = $this->decodeUint8(); + $bytesRead += $numBytes; + $f = self::$headerTypeMap[$type]; + list($value, $numBytes) = $this->{$f}(); + $bytesRead += $numBytes; + if (isset($headers[$key])) { + throw new ParserException('Duplicate key in event headers.'); + } + $headers[$key] = $value; + } + return [$headers, $bytesRead]; + } + protected function parsePrelude() + { + $prelude = []; + $bytesRead = 0; + $calculatedCrc = null; + foreach (self::$preludeFormat as $key => $decodeFunction) { + if ($key === self::CRC_PRELUDE) { + $hashCopy = \hash_copy($this->hashContext); + $calculatedCrc = \hash_final($this->hashContext, \true); + $this->hashContext = $hashCopy; + } + list($value, $numBytes) = $this->{$decodeFunction}(); + $bytesRead += $numBytes; + $prelude[$key] = $value; + } + if (\unpack('N', $calculatedCrc)[1] !== $prelude[self::CRC_PRELUDE]) { + throw new ParserException('Prelude checksum mismatch.'); + } + return [$prelude, $bytesRead]; + } + /** + * This method decodes an event from the stream. + * + * @return array + */ + protected function parseEvent() + { + $event = []; + if ($this->stream->tell() < $this->stream->getSize()) { + $this->hashContext = \hash_init('crc32b'); + $bytesLeft = $this->stream->getSize() - $this->stream->tell(); + list($prelude, $numBytes) = $this->parsePrelude(); + if ($prelude[self::LENGTH_TOTAL] > $bytesLeft) { + throw new ParserException('Message length too long.'); + } + $bytesLeft -= $numBytes; + if ($prelude[self::LENGTH_HEADERS] > $bytesLeft) { + throw new ParserException('Headers length too long.'); + } + list($event[self::HEADERS], $numBytes) = $this->parseHeaders($prelude[self::LENGTH_HEADERS]); + $event[self::PAYLOAD] = Psr7\Utils::streamFor($this->readAndHashBytes($prelude[self::LENGTH_TOTAL] - self::BYTES_PRELUDE - $numBytes - self::BYTES_TRAILING)); + $calculatedCrc = \hash_final($this->hashContext, \true); + $messageCrc = $this->stream->read(4); + if ($calculatedCrc !== $messageCrc) { + throw new ParserException('Message checksum mismatch.'); + } + } + return $event; + } + // Iterator Functionality + /** + * @return array + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->currentEvent; + } + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function key() + { + return $this->key; + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function next() + { + $this->currentPosition = $this->stream->tell(); + if ($this->valid()) { + $this->key++; + $this->currentEvent = $this->parseEvent(); + } + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function rewind() + { + $this->stream->rewind(); + $this->key = 0; + $this->currentPosition = 0; + $this->currentEvent = $this->parseEvent(); + } + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() + { + return $this->currentPosition < $this->stream->getSize(); + } + // Decoding Utilities + protected function readAndHashBytes($num) + { + $bytes = $this->stream->read($num); + \hash_update($this->hashContext, $bytes); + return $bytes; + } + private function decodeBooleanTrue() + { + return [\true, 0]; + } + private function decodeBooleanFalse() + { + return [\false, 0]; + } + private function uintToInt($val, $size) + { + $signedCap = \pow(2, $size - 1); + if ($val > $signedCap) { + $val -= 2 * $signedCap; + } + return $val; + } + private function decodeInt8() + { + $val = (int) \unpack('C', $this->readAndHashBytes(1))[1]; + return [$this->uintToInt($val, 8), 1]; + } + private function decodeUint8() + { + return [\unpack('C', $this->readAndHashBytes(1))[1], 1]; + } + private function decodeInt16() + { + $val = (int) \unpack('n', $this->readAndHashBytes(2))[1]; + return [$this->uintToInt($val, 16), 2]; + } + private function decodeUint16() + { + return [\unpack('n', $this->readAndHashBytes(2))[1], 2]; + } + private function decodeInt32() + { + $val = (int) \unpack('N', $this->readAndHashBytes(4))[1]; + return [$this->uintToInt($val, 32), 4]; + } + private function decodeUint32() + { + return [\unpack('N', $this->readAndHashBytes(4))[1], 4]; + } + private function decodeInt64() + { + $val = $this->unpackInt64($this->readAndHashBytes(8))[1]; + return [$this->uintToInt($val, 64), 8]; + } + private function decodeUint64() + { + return [$this->unpackInt64($this->readAndHashBytes(8))[1], 8]; + } + private function unpackInt64($bytes) + { + if (\version_compare(\PHP_VERSION, '5.6.3', '<')) { + $d = \unpack('N2', $bytes); + return [1 => $d[1] << 32 | $d[2]]; + } + return \unpack('J', $bytes); + } + private function decodeBytes($lengthBytes = 2) + { + if (!isset(self::$lengthFormatMap[$lengthBytes])) { + throw new ParserException('Undefined variable length format.'); + } + $f = self::$lengthFormatMap[$lengthBytes]; + list($len, $bytes) = $this->{$f}(); + return [$this->readAndHashBytes($len), $len + $bytes]; + } + private function decodeString($lengthBytes = 2) + { + if (!isset(self::$lengthFormatMap[$lengthBytes])) { + throw new ParserException('Undefined variable length format.'); + } + $f = self::$lengthFormatMap[$lengthBytes]; + list($len, $bytes) = $this->{$f}(); + return [$this->readAndHashBytes($len), $len + $bytes]; + } + private function decodeTimestamp() + { + list($val, $bytes) = $this->decodeInt64(); + return [DateTimeResult::createFromFormat('U.u', $val / 1000), $bytes]; + } + private function decodeUuid() + { + $val = \unpack('H32', $this->readAndHashBytes(16))[1]; + return [\substr($val, 0, 8) . '-' . \substr($val, 8, 4) . '-' . \substr($val, 12, 4) . '-' . \substr($val, 16, 4) . '-' . \substr($val, 20, 12), 16]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/EventParsingIterator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/EventParsingIterator.php new file mode 100644 index 00000000..ef8e8ef8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/EventParsingIterator.php @@ -0,0 +1,158 @@ +decodingIterator = $this->chooseDecodingIterator($stream); + $this->shape = $shape; + $this->parser = $parser; + } + /** + * This method choose a decoding iterator implementation based on if the stream + * is seekable or not. + * + * @param $stream + * + * @return Iterator + */ + private function chooseDecodingIterator($stream) + { + if ($stream->isSeekable()) { + return new DecodingEventStreamIterator($stream); + } else { + return new NonSeekableStreamDecodingEventStreamIterator($stream); + } + } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->parseEvent($this->decodingIterator->current()); + } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function key() + { + return $this->decodingIterator->key(); + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function next() + { + $this->decodingIterator->next(); + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function rewind() + { + $this->decodingIterator->rewind(); + } + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() + { + return $this->decodingIterator->valid(); + } + private function parseEvent(array $event) + { + if (!empty($event['headers'][':message-type'])) { + if ($event['headers'][':message-type'] === 'error') { + return $this->parseError($event); + } + if ($event['headers'][':message-type'] !== 'event') { + throw new ParserException('Failed to parse unknown message type.'); + } + } + $eventType = $event['headers'][':event-type'] ?? null; + if (empty($eventType)) { + throw new ParserException('Failed to parse without event type.'); + } + $eventPayload = $event['payload']; + if ($eventType === 'initial-response') { + return $this->parseInitialResponseEvent($eventPayload); + } + $eventShape = $this->shape->getMember($eventType); + return [$eventType => \array_merge($this->parseEventHeaders($event['headers'], $eventShape), $this->parseEventPayload($eventPayload, $eventShape))]; + } + /** + * @param $headers + * @param $eventShape + * + * @return array + */ + private function parseEventHeaders($headers, $eventShape) : array + { + $parsedHeaders = []; + foreach ($eventShape->getMembers() as $memberName => $memberProps) { + if (isset($memberProps['eventheader'])) { + $parsedHeaders[$memberName] = $headers[$memberName]; + } + } + return $parsedHeaders; + } + /** + * @param $payload + * @param $eventShape + * + * @return array + */ + private function parseEventPayload($payload, $eventShape) : array + { + $parsedPayload = []; + foreach ($eventShape->getMembers() as $memberName => $memberProps) { + $memberShape = $eventShape->getMember($memberName); + if (isset($memberProps['eventpayload'])) { + if ($memberShape->getType() === 'blob') { + $parsedPayload[$memberName] = $payload; + } else { + $parsedPayload[$memberName] = $this->parser->parseMemberFromStream($payload, $memberShape, null); + } + break; + } + } + if (empty($parsedPayload) && !empty($payload->getContents())) { + /** + * If we did not find a member with an eventpayload trait, then we should deserialize the payload + * using the event's shape. + */ + $parsedPayload = $this->parser->parseMemberFromStream($payload, $eventShape, null); + } + return $parsedPayload; + } + private function parseError(array $event) + { + throw new EventStreamDataException($event['headers'][':error-code'], $event['headers'][':error-message']); + } + private function parseInitialResponseEvent($payload) : array + { + return ['initial-response' => \json_decode($payload, \true)]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php new file mode 100644 index 00000000..0c2d40db --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php @@ -0,0 +1,49 @@ +errorCode = isset($context['error_code']) ? $context['error_code'] : null; + $this->requestId = isset($context['request_id']) ? $context['request_id'] : null; + $this->response = isset($context['response']) ? $context['response'] : null; + parent::__construct($message, $code, $previous); + } + /** + * Get the error code, if any. + * + * @return string|null + */ + public function getErrorCode() + { + return $this->errorCode; + } + /** + * Get the request ID, if any. + * + * @return string|null + */ + public function getRequestId() + { + return $this->requestId; + } + /** + * Get the received HTTP response if any. + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonParser.php new file mode 100644 index 00000000..1b572ada --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonParser.php @@ -0,0 +1,57 @@ +getMembers() as $name => $member) { + $locationName = $member['locationName'] ?: $name; + if (isset($value[$locationName])) { + $target[$name] = $this->parse($member, $value[$locationName]); + } + } + if (isset($shape['union']) && $shape['union'] && \is_array($value) && empty($target)) { + foreach ($value as $key => $val) { + $target['Unknown'][$key] = $val; + } + } + return $target; + case 'list': + $member = $shape->getMember(); + $target = []; + foreach ($value as $v) { + $target[] = $this->parse($member, $v); + } + return $target; + case 'map': + $values = $shape->getValue(); + $target = []; + foreach ($value as $k => $v) { + $target[$k] = $this->parse($values, $v); + } + return $target; + case 'timestamp': + return DateTimeResult::fromTimestamp($value, !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null); + case 'blob': + return \base64_decode($value); + default: + return $value; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php new file mode 100644 index 00000000..5ee2c303 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php @@ -0,0 +1,59 @@ +parser = $parser ?: new JsonParser(); + } + public function __invoke(CommandInterface $command, ResponseInterface $response) + { + $operation = $this->api->getOperation($command->getName()); + return $this->parseResponse($response, $operation); + } + /** + * This method parses a response based on JSON RPC protocol. + * + * @param ResponseInterface $response the response to parse. + * @param Operation $operation the operation which holds information for + * parsing the response. + * + * @return Result + */ + private function parseResponse(ResponseInterface $response, Operation $operation) + { + if (null === $operation['output']) { + return new Result([]); + } + $outputShape = $operation->getOutput(); + foreach ($outputShape->getMembers() as $memberName => $memberProps) { + if (!empty($memberProps['eventstream'])) { + return new Result([$memberName => new EventParsingIterator($response->getBody(), $outputShape->getMember($memberName), $this)]); + } + } + $result = $this->parseMemberFromStream($response->getBody(), $operation->getOutput(), $response); + return new Result(\is_null($result) ? [] : $result); + } + public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) + { + return $this->parser->parse($member, $this->parseJson($stream, $response)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/MetadataParserTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/MetadataParserTrait.php new file mode 100644 index 00000000..caf6e57a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/MetadataParserTrait.php @@ -0,0 +1,71 @@ +getHeaderLine($shape['locationName'] ?: $name); + switch ($shape->getType()) { + case 'float': + case 'double': + $value = (float) $value; + break; + case 'long': + $value = (int) $value; + break; + case 'boolean': + $value = \filter_var($value, \FILTER_VALIDATE_BOOLEAN); + break; + case 'blob': + $value = \base64_decode($value); + break; + case 'timestamp': + try { + $value = DateTimeResult::fromTimestamp($value, !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null); + break; + } catch (\Exception $e) { + // If the value cannot be parsed, then do not add it to the + // output structure. + return; + } + case 'string': + if ($shape['jsonvalue']) { + $value = $this->parseJson(\base64_decode($value), $response); + } + break; + } + $result[$name] = $value; + } + /** + * Extract a map of headers with an optional prefix from the response. + */ + protected function extractHeaders($name, Shape $shape, ResponseInterface $response, &$result) + { + // Check if the headers are prefixed by a location name + $result[$name] = []; + $prefix = $shape['locationName']; + $prefixLen = \strlen($prefix); + foreach ($response->getHeaders() as $k => $values) { + if (!$prefixLen) { + $result[$name][$k] = \implode(', ', $values); + } elseif (\stripos($k, $prefix) === 0) { + $result[$name][\substr($k, $prefixLen)] = \implode(', ', $values); + } + } + } + /** + * Places the status code of the response into the result array. + */ + protected function extractStatus($name, ResponseInterface $response, array &$result) + { + $result[$name] = (int) $response->getStatusCode(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/NonSeekableStreamDecodingEventStreamIterator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/NonSeekableStreamDecodingEventStreamIterator.php new file mode 100644 index 00000000..b31e32bf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/NonSeekableStreamDecodingEventStreamIterator.php @@ -0,0 +1,81 @@ +stream = $stream; + if ($this->stream->isSeekable()) { + throw new \InvalidArgumentException('The stream provided must be not seekable.'); + } + $this->tempBuffer = []; + } + /** + * @inheritDoc + * + * @return array + */ + protected function parseEvent() : array + { + $event = []; + $this->hashContext = \hash_init('crc32b'); + $prelude = $this->parsePrelude()[0]; + list($event[self::HEADERS], $numBytes) = $this->parseHeaders($prelude[self::LENGTH_HEADERS]); + $event[self::PAYLOAD] = Psr7\Utils::streamFor($this->readAndHashBytes($prelude[self::LENGTH_TOTAL] - self::BYTES_PRELUDE - $numBytes - self::BYTES_TRAILING)); + $calculatedCrc = \hash_final($this->hashContext, \true); + $messageCrc = $this->stream->read(4); + if ($calculatedCrc !== $messageCrc) { + throw new ParserException('Message checksum mismatch.'); + } + return $event; + } + protected function readAndHashBytes($num) : string + { + $bytes = ''; + while (!empty($this->tempBuffer) && $num > 0) { + $byte = \array_shift($this->tempBuffer); + $bytes .= $byte; + $num = $num - 1; + } + $bytes = $bytes . $this->stream->read($num); + \hash_update($this->hashContext, $bytes); + return $bytes; + } + // Iterator Functionality + #[\ReturnTypeWillChange] + public function rewind() + { + $this->currentEvent = $this->parseEvent(); + } + public function next() + { + $this->tempBuffer[] = $this->stream->read(1); + if ($this->valid()) { + $this->key++; + $this->currentEvent = $this->parseEvent(); + } + } + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() + { + return !$this->stream->eof(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php new file mode 100644 index 00000000..98f5785f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php @@ -0,0 +1,47 @@ + $response]); + } + return $jsonPayload; + } + /** + * @param string $xml + * + * @throws ParserException + * + * @return \SimpleXMLElement + */ + protected function parseXml($xml, $response) + { + $priorSetting = \libxml_use_internal_errors(\true); + try { + \libxml_clear_errors(); + $xmlPayload = new \SimpleXMLElement($xml); + if ($error = \libxml_get_last_error()) { + throw new \RuntimeException($error->message); + } + } catch (\Exception $e) { + throw new ParserException("Error parsing XML: {$e->getMessage()}", 0, $e, ['response' => $response]); + } finally { + \libxml_use_internal_errors($priorSetting); + } + return $xmlPayload; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/QueryParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/QueryParser.php new file mode 100644 index 00000000..a65a3fd5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/QueryParser.php @@ -0,0 +1,46 @@ +parser = $xmlParser ?: new XmlParser(); + $this->honorResultWrapper = $honorResultWrapper; + } + public function __invoke(CommandInterface $command, ResponseInterface $response) + { + $output = $this->api->getOperation($command->getName())->getOutput(); + $xml = $this->parseXml($response->getBody(), $response); + if ($this->honorResultWrapper && $output['resultWrapper']) { + $xml = $xml->{$output['resultWrapper']}; + } + return new Result($this->parser->parse($output, $xml)); + } + public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) + { + $xml = $this->parseXml($stream, $response); + return $this->parser->parse($member, $xml); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestJsonParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestJsonParser.php new file mode 100644 index 00000000..2b749f8f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestJsonParser.php @@ -0,0 +1,39 @@ +parser = $parser ?: new JsonParser(); + } + protected function payload(ResponseInterface $response, StructureShape $member, array &$result) + { + $jsonBody = $this->parseJson($response->getBody(), $response); + if ($jsonBody) { + $result += $this->parser->parse($member, $jsonBody); + } + } + public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) + { + $jsonBody = $this->parseJson($stream, $response); + if ($jsonBody) { + return $this->parser->parse($member, $jsonBody); + } + return []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestXmlParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestXmlParser.php new file mode 100644 index 00000000..07adbd20 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/RestXmlParser.php @@ -0,0 +1,33 @@ +parser = $parser ?: new XmlParser(); + } + protected function payload(ResponseInterface $response, StructureShape $member, array &$result) + { + $result += $this->parseMemberFromStream($response->getBody(), $member, $response); + } + public function parseMemberFromStream(StreamInterface $stream, StructureShape $member, $response) + { + $xml = $this->parseXml($stream, $response); + return $this->parser->parse($member, $xml); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/XmlParser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/XmlParser.php new file mode 100644 index 00000000..cc3bff1e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Parser/XmlParser.php @@ -0,0 +1,125 @@ +dispatch($shape, $value); + } + private function dispatch($shape, \SimpleXMLElement $value) + { + static $methods = ['structure' => 'parse_structure', 'list' => 'parse_list', 'map' => 'parse_map', 'blob' => 'parse_blob', 'boolean' => 'parse_boolean', 'integer' => 'parse_integer', 'float' => 'parse_float', 'double' => 'parse_float', 'timestamp' => 'parse_timestamp']; + $type = $shape['type']; + if (isset($methods[$type])) { + return $this->{$methods[$type]}($shape, $value); + } + return (string) $value; + } + private function parse_structure(StructureShape $shape, \SimpleXMLElement $value) + { + $target = []; + foreach ($shape->getMembers() as $name => $member) { + // Extract the name of the XML node + $node = $this->memberKey($member, $name); + if (isset($value->{$node})) { + $target[$name] = $this->dispatch($member, $value->{$node}); + } else { + $memberShape = $shape->getMember($name); + if (!empty($memberShape['xmlAttribute'])) { + $target[$name] = $this->parse_xml_attribute($shape, $memberShape, $value); + } + } + } + if (isset($shape['union']) && $shape['union'] && empty($target)) { + foreach ($value as $key => $val) { + $name = $val->children()->getName(); + $target['Unknown'][$name] = $val->{$name}; + } + } + return $target; + } + private function memberKey(Shape $shape, $name) + { + if (null !== $shape['locationName']) { + return $shape['locationName']; + } + if ($shape instanceof ListShape && $shape['flattened']) { + return $shape->getMember()['locationName'] ?: $name; + } + return $name; + } + private function parse_list(ListShape $shape, \SimpleXMLElement $value) + { + $target = []; + $member = $shape->getMember(); + if (!$shape['flattened']) { + $value = $value->{$member['locationName'] ?: 'member'}; + } + foreach ($value as $v) { + $target[] = $this->dispatch($member, $v); + } + return $target; + } + private function parse_map(MapShape $shape, \SimpleXMLElement $value) + { + $target = []; + if (!$shape['flattened']) { + $value = $value->entry; + } + $mapKey = $shape->getKey(); + $mapValue = $shape->getValue(); + $keyName = $shape->getKey()['locationName'] ?: 'key'; + $valueName = $shape->getValue()['locationName'] ?: 'value'; + foreach ($value as $node) { + $key = $this->dispatch($mapKey, $node->{$keyName}); + $value = $this->dispatch($mapValue, $node->{$valueName}); + $target[$key] = $value; + } + return $target; + } + private function parse_blob(Shape $shape, $value) + { + return \base64_decode((string) $value); + } + private function parse_float(Shape $shape, $value) + { + return (float) (string) $value; + } + private function parse_integer(Shape $shape, $value) + { + return (int) (string) $value; + } + private function parse_boolean(Shape $shape, $value) + { + return $value == 'true'; + } + private function parse_timestamp(Shape $shape, $value) + { + if (\is_string($value) || \is_int($value) || \is_object($value) && \method_exists($value, '__toString')) { + return DateTimeResult::fromTimestamp((string) $value, !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : null); + } + throw new ParserException('Invalid timestamp value passed to XmlParser::parse_timestamp'); + } + private function parse_xml_attribute(Shape $shape, Shape $memberShape, $value) + { + $namespace = $shape['xmlNamespace']['uri'] ? $shape['xmlNamespace']['uri'] : ''; + $prefix = $shape['xmlNamespace']['prefix'] ? $shape['xmlNamespace']['prefix'] : ''; + if (!empty($prefix)) { + $prefix .= ':'; + } + $key = \str_replace($prefix, '', $memberShape['locationName']); + $attributes = $value->attributes($namespace); + return isset($attributes[$key]) ? (string) $attributes[$key] : null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/Ec2ParamBuilder.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/Ec2ParamBuilder.php new file mode 100644 index 00000000..6be399f3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/Ec2ParamBuilder.php @@ -0,0 +1,30 @@ +getMember(); + foreach ($value as $k => $v) { + $this->format($items, $v, $prefix . '.' . ($k + 1), $query); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonBody.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonBody.php new file mode 100644 index 00000000..9e538959 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonBody.php @@ -0,0 +1,94 @@ +api = $api; + } + /** + * Gets the JSON Content-Type header for a service API + * + * @param Service $service + * + * @return string + */ + public static function getContentType(Service $service) + { + if ($service->getMetadata('protocol') === 'rest-json') { + return 'application/json'; + } + $jsonVersion = $service->getMetadata('jsonVersion'); + if (empty($jsonVersion)) { + throw new \InvalidArgumentException('invalid json'); + } else { + return 'application/x-amz-json-' . @\number_format($service->getMetadata('jsonVersion'), 1); + } + } + /** + * Builds the JSON body based on an array of arguments. + * + * @param Shape $shape Operation being constructed + * @param array $args Associative array of arguments + * + * @return string + */ + public function build(Shape $shape, array $args) + { + $result = \json_encode($this->format($shape, $args)); + return $result == '[]' ? '{}' : $result; + } + private function format(Shape $shape, $value) + { + switch ($shape['type']) { + case 'structure': + $data = []; + if (isset($shape['document']) && $shape['document']) { + return $value; + } + foreach ($value as $k => $v) { + if ($v !== null && $shape->hasMember($k)) { + $valueShape = $shape->getMember($k); + $data[$valueShape['locationName'] ?: $k] = $this->format($valueShape, $v); + } + } + if (empty($data)) { + return new \stdClass(); + } + return $data; + case 'list': + $items = $shape->getMember(); + foreach ($value as $k => $v) { + $value[$k] = $this->format($items, $v); + } + return $value; + case 'map': + if (empty($value)) { + return new \stdClass(); + } + $values = $shape->getValue(); + foreach ($value as $k => $v) { + $value[$k] = $this->format($values, $v); + } + return $value; + case 'blob': + return \base64_encode($value); + case 'timestamp': + $timestampFormat = !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : 'unixTimestamp'; + return TimestampShape::format($value, $timestampFormat); + default: + return $value; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonRpcSerializer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonRpcSerializer.php new file mode 100644 index 00000000..97c9b9fc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/JsonRpcSerializer.php @@ -0,0 +1,59 @@ +endpoint = $endpoint; + $this->api = $api; + $this->jsonFormatter = $jsonFormatter ?: new JsonBody($this->api); + $this->contentType = JsonBody::getContentType($api); + } + /** + * When invoked with an AWS command, returns a serialization array + * containing "method", "uri", "headers", and "body" key value pairs. + * + * @param CommandInterface $command Command to serialize into a request. + * @param $endpointProvider Provider used for dynamic endpoint resolution. + * @param $clientArgs Client arguments used for dynamic endpoint resolution. + * + * @return RequestInterface + */ + public function __invoke(CommandInterface $command, $endpoint = null) + { + $operationName = $command->getName(); + $operation = $this->api->getOperation($operationName); + $commandArgs = $command->toArray(); + $headers = ['X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $operationName, 'Content-Type' => $this->contentType]; + if ($endpoint instanceof RulesetEndpoint) { + $this->setEndpointV2RequestOptions($endpoint, $headers); + } + return new Request($operation['http']['method'], $this->endpoint, $headers, $this->jsonFormatter->build($operation->getInput(), $commandArgs)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QueryParamBuilder.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QueryParamBuilder.php new file mode 100644 index 00000000..21266746 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QueryParamBuilder.php @@ -0,0 +1,112 @@ +isFlat($shape) && !empty($shape['member']['locationName'])) { + return $shape['member']['locationName']; + } + return $default; + } + protected function isFlat(Shape $shape) + { + return $shape['flattened'] === \true; + } + public function __invoke(StructureShape $shape, array $params) + { + if (!$this->methods) { + $this->methods = \array_fill_keys(\get_class_methods($this), \true); + } + $query = []; + $this->format_structure($shape, $params, '', $query); + return $query; + } + protected function format(Shape $shape, $value, $prefix, array &$query) + { + $type = 'format_' . $shape['type']; + if (isset($this->methods[$type])) { + $this->{$type}($shape, $value, $prefix, $query); + } else { + $query[$prefix] = (string) $value; + } + } + protected function format_structure(StructureShape $shape, array $value, $prefix, &$query) + { + if ($prefix) { + $prefix .= '.'; + } + foreach ($value as $k => $v) { + if ($shape->hasMember($k)) { + $member = $shape->getMember($k); + $this->format($member, $v, $prefix . $this->queryName($member, $k), $query); + } + } + } + protected function format_list(ListShape $shape, array $value, $prefix, &$query) + { + // Handle empty list serialization + if (!$value) { + $query[$prefix] = ''; + return; + } + $items = $shape->getMember(); + if (!$this->isFlat($shape)) { + $locationName = $shape->getMember()['locationName'] ?: 'member'; + $prefix .= ".{$locationName}"; + } elseif ($name = $this->queryName($items)) { + $parts = \explode('.', $prefix); + $parts[\count($parts) - 1] = $name; + $prefix = \implode('.', $parts); + } + foreach ($value as $k => $v) { + $this->format($items, $v, $prefix . '.' . ($k + 1), $query); + } + } + protected function format_map(MapShape $shape, array $value, $prefix, array &$query) + { + $vals = $shape->getValue(); + $keys = $shape->getKey(); + if (!$this->isFlat($shape)) { + $prefix .= '.entry'; + } + $i = 0; + $keyName = '%s.%d.' . $this->queryName($keys, 'key'); + $valueName = '%s.%s.' . $this->queryName($vals, 'value'); + foreach ($value as $k => $v) { + $i++; + $this->format($keys, $k, \sprintf($keyName, $prefix, $i), $query); + $this->format($vals, $v, \sprintf($valueName, $prefix, $i), $query); + } + } + protected function format_blob(Shape $shape, $value, $prefix, array &$query) + { + $query[$prefix] = \base64_encode($value); + } + protected function format_timestamp(TimestampShape $shape, $value, $prefix, array &$query) + { + $timestampFormat = !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : 'iso8601'; + $query[$prefix] = TimestampShape::format($value, $timestampFormat); + } + protected function format_boolean(Shape $shape, $value, $prefix, array &$query) + { + $query[$prefix] = $value ? 'true' : 'false'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QuerySerializer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QuerySerializer.php new file mode 100644 index 00000000..91b07710 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/QuerySerializer.php @@ -0,0 +1,54 @@ +api = $api; + $this->endpoint = $endpoint; + $this->paramBuilder = $paramBuilder ?: new QueryParamBuilder(); + } + /** + * When invoked with an AWS command, returns a serialization array + * containing "method", "uri", "headers", and "body" key value pairs. + * + * @param CommandInterface $command Command to serialize into a request. + * @param $endpointProvider Provider used for dynamic endpoint resolution. + * @param $clientArgs Client arguments used for dynamic endpoint resolution. + * + * @return RequestInterface + */ + public function __invoke(CommandInterface $command, $endpoint = null) + { + $operation = $this->api->getOperation($command->getName()); + $body = ['Action' => $command->getName(), 'Version' => $this->api->getMetadata('apiVersion')]; + $commandArgs = $command->toArray(); + // Only build up the parameters when there are parameters to build + if ($commandArgs) { + $body += \call_user_func($this->paramBuilder, $operation->getInput(), $commandArgs); + } + $body = \http_build_query($body, '', '&', \PHP_QUERY_RFC3986); + $headers = ['Content-Length' => \strlen($body), 'Content-Type' => 'application/x-www-form-urlencoded']; + if ($endpoint instanceof RulesetEndpoint) { + $this->setEndpointV2RequestOptions($endpoint, $headers); + } + return new Request('POST', $this->endpoint, $headers, $body); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestJsonSerializer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestJsonSerializer.php new file mode 100644 index 00000000..7e69af1f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestJsonSerializer.php @@ -0,0 +1,34 @@ +contentType = JsonBody::getContentType($api); + $this->jsonFormatter = $jsonFormatter ?: new JsonBody($api); + } + protected function payload(StructureShape $member, array $value, array &$opts) + { + $body = isset($value) ? (string) $this->jsonFormatter->build($member, $value) : "{}"; + $opts['headers']['Content-Type'] = $this->contentType; + $opts['body'] = $body; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php new file mode 100644 index 00000000..576850e4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php @@ -0,0 +1,254 @@ +api = $api; + $this->endpoint = Psr7\Utils::uriFor($endpoint); + } + /** + * @param CommandInterface $command Command to serialize into a request. + * @param $clientArgs Client arguments used for dynamic endpoint resolution. + * + * @return RequestInterface + */ + public function __invoke(CommandInterface $command, $endpoint = null) + { + $operation = $this->api->getOperation($command->getName()); + $commandArgs = $command->toArray(); + $opts = $this->serialize($operation, $commandArgs); + $headers = isset($opts['headers']) ? $opts['headers'] : []; + if ($endpoint instanceof RulesetEndpoint) { + $this->setEndpointV2RequestOptions($endpoint, $headers); + } + $uri = $this->buildEndpoint($operation, $commandArgs, $opts); + return new Request($operation['http']['method'], $uri, $headers, isset($opts['body']) ? $opts['body'] : null); + } + /** + * Modifies a hash of request options for a payload body. + * + * @param StructureShape $member Member to serialize + * @param array $value Value to serialize + * @param array $opts Request options to modify. + */ + protected abstract function payload(StructureShape $member, array $value, array &$opts); + private function serialize(Operation $operation, array $args) + { + $opts = []; + $input = $operation->getInput(); + // Apply the payload trait if present + if ($payload = $input['payload']) { + $this->applyPayload($input, $payload, $args, $opts); + } + foreach ($args as $name => $value) { + if ($input->hasMember($name)) { + $member = $input->getMember($name); + $location = $member['location']; + if (!$payload && !$location) { + $bodyMembers[$name] = $value; + } elseif ($location == 'header') { + $this->applyHeader($name, $member, $value, $opts); + } elseif ($location == 'querystring') { + $this->applyQuery($name, $member, $value, $opts); + } elseif ($location == 'headers') { + $this->applyHeaderMap($name, $member, $value, $opts); + } + } + } + if (isset($bodyMembers)) { + $this->payload($operation->getInput(), $bodyMembers, $opts); + } else { + if (!isset($opts['body']) && $this->hasPayloadParam($input, $payload)) { + $this->payload($operation->getInput(), [], $opts); + } + } + return $opts; + } + private function applyPayload(StructureShape $input, $name, array $args, array &$opts) + { + if (!isset($args[$name])) { + return; + } + $m = $input->getMember($name); + if ($m['streaming'] || ($m['type'] == 'string' || $m['type'] == 'blob')) { + // Streaming bodies or payloads that are strings are + // always just a stream of data. + $opts['body'] = Psr7\Utils::streamFor($args[$name]); + return; + } + $this->payload($m, $args[$name], $opts); + } + private function applyHeader($name, Shape $member, $value, array &$opts) + { + if ($member->getType() === 'timestamp') { + $timestampFormat = !empty($member['timestampFormat']) ? $member['timestampFormat'] : 'rfc822'; + $value = TimestampShape::format($value, $timestampFormat); + } elseif ($member->getType() === 'boolean') { + $value = $value ? 'true' : 'false'; + } + if ($member['jsonvalue']) { + $value = \json_encode($value); + if (empty($value) && \JSON_ERROR_NONE !== \json_last_error()) { + throw new \InvalidArgumentException('Unable to encode the provided value' . ' with \'json_encode\'. ' . \json_last_error_msg()); + } + $value = \base64_encode($value); + } + $opts['headers'][$member['locationName'] ?: $name] = $value; + } + /** + * Note: This is currently only present in the Amazon S3 model. + */ + private function applyHeaderMap($name, Shape $member, array $value, array &$opts) + { + $prefix = $member['locationName']; + foreach ($value as $k => $v) { + $opts['headers'][$prefix . $k] = $v; + } + } + private function applyQuery($name, Shape $member, $value, array &$opts) + { + if ($member instanceof MapShape) { + $opts['query'] = isset($opts['query']) && \is_array($opts['query']) ? $opts['query'] + $value : $value; + } elseif ($value !== null) { + $type = $member->getType(); + if ($type === 'boolean') { + $value = $value ? 'true' : 'false'; + } elseif ($type === 'timestamp') { + $timestampFormat = !empty($member['timestampFormat']) ? $member['timestampFormat'] : 'iso8601'; + $value = TimestampShape::format($value, $timestampFormat); + } + $opts['query'][$member['locationName'] ?: $name] = $value; + } + } + private function buildEndpoint(Operation $operation, array $args, array $opts) + { + $isModifiedModel = $this->api->isModifiedModel(); + $serviceName = $this->api->getServiceName(); + // Create an associative array of variable definitions used in expansions + $varDefinitions = $this->getVarDefinitions($operation, $args); + $relative = \preg_replace_callback('/\\{([^\\}]+)\\}/', function (array $matches) use($varDefinitions) { + $isGreedy = \substr($matches[1], -1, 1) == '+'; + $k = $isGreedy ? \substr($matches[1], 0, -1) : $matches[1]; + if (!isset($varDefinitions[$k])) { + return ''; + } + if ($isGreedy) { + return \str_replace('%2F', '/', \rawurlencode($varDefinitions[$k])); + } + return \rawurlencode($varDefinitions[$k]); + }, $operation['http']['requestUri']); + // Add the query string variables or appending to one if needed. + if (!empty($opts['query'])) { + $relative = $this->appendQuery($opts['query'], $relative); + } + $path = $this->endpoint->getPath(); + if ($isModifiedModel && $serviceName === 's3') { + if (\substr($path, -1) === '/' && $relative[0] === '/') { + $path = \rtrim($path, '/'); + } + $relative = $path . $relative; + if (\strpos($relative, '../') !== \false || \substr($relative, -2) === '..') { + if ($relative[0] !== '/') { + $relative = '/' . $relative; + } + return new Uri($this->endpoint->withPath('') . $relative); + } + } + if (!empty($relative) && $relative !== '/' && !$isModifiedModel && $serviceName !== 's3') { + $this->normalizePath($path); + } + // If endpoint has path, remove leading '/' to preserve URI resolution. + if ($path && $relative[0] === '/') { + $relative = \substr($relative, 1); + } + //Append path to endpoint when leading '//...' + // present as uri cannot be properly resolved + if ($isModifiedModel && \strpos($relative, '//') === 0) { + return new Uri($this->endpoint . $relative); + } + // Expand path place holders using Amazon's slightly different URI + // template syntax. + return UriResolver::resolve($this->endpoint, new Uri($relative)); + } + /** + * @param StructureShape $input + */ + private function hasPayloadParam(StructureShape $input, $payload) + { + if ($payload) { + $potentiallyEmptyTypes = ['blob', 'string']; + if ($this->api->getMetadata('protocol') == 'rest-xml') { + $potentiallyEmptyTypes[] = 'structure'; + } + $payloadMember = $input->getMember($payload); + if (\in_array($payloadMember['type'], $potentiallyEmptyTypes)) { + return \false; + } + } + foreach ($input->getMembers() as $member) { + if (!isset($member['location'])) { + return \true; + } + } + return \false; + } + private function appendQuery($query, $endpoint) + { + $append = Psr7\Query::build($query); + return $endpoint .= \strpos($endpoint, '?') !== \false ? "&{$append}" : "?{$append}"; + } + private function getVarDefinitions($command, $args) + { + $varDefinitions = []; + foreach ($command->getInput()->getMembers() as $name => $member) { + if ($member['location'] == 'uri') { + $varDefinitions[$member['locationName'] ?: $name] = isset($args[$name]) ? $args[$name] : null; + } + } + return $varDefinitions; + } + /** + * Appends trailing slash to non-empty paths with at least one segment + * to ensure proper URI resolution + * + * @param string $path + * + * @return void + */ + private function normalizePath(string $path) : void + { + if (!empty($path) && $path !== '/' && \substr($path, -1) !== '/') { + $this->endpoint = $this->endpoint->withPath($path . '/'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestXmlSerializer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestXmlSerializer.php new file mode 100644 index 00000000..327d0cb2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/RestXmlSerializer.php @@ -0,0 +1,42 @@ +xmlBody = $xmlBody ?: new XmlBody($api); + } + protected function payload(StructureShape $member, array $value, array &$opts) + { + $opts['headers']['Content-Type'] = 'application/xml'; + $opts['body'] = $this->getXmlBody($member, $value); + } + /** + * @param StructureShape $member + * @param array $value + * @return string + */ + private function getXmlBody(StructureShape $member, array $value) + { + $xmlBody = (string) $this->xmlBody->build($member, $value); + $xmlBody = \str_replace("'", "'", $xmlBody); + $xmlBody = \str_replace('\\r', " ", $xmlBody); + $xmlBody = \str_replace('\\n', " ", $xmlBody); + return $xmlBody; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/XmlBody.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/XmlBody.php new file mode 100644 index 00000000..46be3c14 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Serializer/XmlBody.php @@ -0,0 +1,148 @@ +api = $api; + } + /** + * Builds the XML body based on an array of arguments. + * + * @param Shape $shape Operation being constructed + * @param array $args Associative array of arguments + * + * @return string + */ + public function build(Shape $shape, array $args) + { + $xml = new XMLWriter(); + $xml->openMemory(); + $xml->startDocument('1.0', 'UTF-8'); + $this->format($shape, $shape['locationName'] ?: $shape['name'], $args, $xml); + $xml->endDocument(); + return $xml->outputMemory(); + } + private function startElement(Shape $shape, $name, XMLWriter $xml) + { + $xml->startElement($name); + if ($ns = $shape['xmlNamespace']) { + $xml->writeAttribute(isset($ns['prefix']) ? "xmlns:{$ns['prefix']}" : 'xmlns', $shape['xmlNamespace']['uri']); + } + } + private function format(Shape $shape, $name, $value, XMLWriter $xml) + { + // Any method mentioned here has a custom serialization handler. + static $methods = ['add_structure' => \true, 'add_list' => \true, 'add_blob' => \true, 'add_timestamp' => \true, 'add_boolean' => \true, 'add_map' => \true, 'add_string' => \true]; + $type = 'add_' . $shape['type']; + if (isset($methods[$type])) { + $this->{$type}($shape, $name, $value, $xml); + } else { + $this->defaultShape($shape, $name, $value, $xml); + } + } + private function defaultShape(Shape $shape, $name, $value, XMLWriter $xml) + { + $this->startElement($shape, $name, $xml); + $xml->text($value); + $xml->endElement(); + } + private function add_structure(StructureShape $shape, $name, array $value, \XMLWriter $xml) + { + $this->startElement($shape, $name, $xml); + foreach ($this->getStructureMembers($shape, $value) as $k => $definition) { + $this->format($definition['member'], $definition['member']['locationName'] ?: $k, $definition['value'], $xml); + } + $xml->endElement(); + } + private function getStructureMembers(StructureShape $shape, array $value) + { + $members = []; + foreach ($value as $k => $v) { + if ($v !== null && $shape->hasMember($k)) { + $definition = ['member' => $shape->getMember($k), 'value' => $v]; + if ($definition['member']['xmlAttribute']) { + // array_unshift_associative + $members = [$k => $definition] + $members; + } else { + $members[$k] = $definition; + } + } + } + return $members; + } + private function add_list(ListShape $shape, $name, array $value, XMLWriter $xml) + { + $items = $shape->getMember(); + if ($shape['flattened']) { + $elementName = $name; + } else { + $this->startElement($shape, $name, $xml); + $elementName = $items['locationName'] ?: 'member'; + } + foreach ($value as $v) { + $this->format($items, $elementName, $v, $xml); + } + if (!$shape['flattened']) { + $xml->endElement(); + } + } + private function add_map(MapShape $shape, $name, array $value, XMLWriter $xml) + { + $xmlEntry = $shape['flattened'] ? $shape['locationName'] : 'entry'; + $xmlKey = $shape->getKey()['locationName'] ?: 'key'; + $xmlValue = $shape->getValue()['locationName'] ?: 'value'; + $this->startElement($shape, $name, $xml); + foreach ($value as $key => $v) { + $this->startElement($shape, $xmlEntry, $xml); + $this->format($shape->getKey(), $xmlKey, $key, $xml); + $this->format($shape->getValue(), $xmlValue, $v, $xml); + $xml->endElement(); + } + $xml->endElement(); + } + private function add_blob(Shape $shape, $name, $value, XMLWriter $xml) + { + $this->startElement($shape, $name, $xml); + $xml->writeRaw(\base64_encode($value)); + $xml->endElement(); + } + private function add_timestamp(TimestampShape $shape, $name, $value, XMLWriter $xml) + { + $this->startElement($shape, $name, $xml); + $timestampFormat = !empty($shape['timestampFormat']) ? $shape['timestampFormat'] : 'iso8601'; + $xml->writeRaw(TimestampShape::format($value, $timestampFormat)); + $xml->endElement(); + } + private function add_boolean(Shape $shape, $name, $value, XMLWriter $xml) + { + $this->startElement($shape, $name, $xml); + $xml->writeRaw($value ? 'true' : 'false'); + $xml->endElement(); + } + private function add_string(Shape $shape, $name, $value, XMLWriter $xml) + { + if ($shape['xmlAttribute']) { + $xml->writeAttribute($shape['locationName'] ?: $name, $value); + } else { + $this->defaultShape($shape, $name, $value, $xml); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Service.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Service.php new file mode 100644 index 00000000..2aaaa974 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Service.php @@ -0,0 +1,416 @@ + [], 'shapes' => [], 'metadata' => [], 'clientContextParams' => []], $defaultMeta = ['apiVersion' => null, 'serviceFullName' => null, 'serviceId' => null, 'endpointPrefix' => null, 'signingName' => null, 'signatureVersion' => null, 'protocol' => null, 'uid' => null]; + $definition += $defaults; + $definition['metadata'] += $defaultMeta; + $this->definition = $definition; + $this->apiProvider = $provider; + parent::__construct($definition, new ShapeMap($definition['shapes'])); + if (isset($definition['metadata']['serviceIdentifier'])) { + $this->serviceName = $this->getServiceName(); + } else { + $this->serviceName = $this->getEndpointPrefix(); + } + $this->apiVersion = $this->getApiVersion(); + if (isset($definition['clientContextParams'])) { + $this->clientContextParams = $definition['clientContextParams']; + } + } + /** + * Creates a request serializer for the provided API object. + * + * @param Service $api API that contains a protocol. + * @param string $endpoint Endpoint to send requests to. + * + * @return callable + * @throws \UnexpectedValueException + */ + public static function createSerializer(Service $api, $endpoint) + { + static $mapping = ['json' => Serializer\JsonRpcSerializer::class, 'query' => Serializer\QuerySerializer::class, 'rest-json' => Serializer\RestJsonSerializer::class, 'rest-xml' => Serializer\RestXmlSerializer::class]; + $proto = $api->getProtocol(); + if (isset($mapping[$proto])) { + return new $mapping[$proto]($api, $endpoint); + } + if ($proto == 'ec2') { + return new Serializer\QuerySerializer($api, $endpoint, new Serializer\Ec2ParamBuilder()); + } + throw new \UnexpectedValueException('Unknown protocol: ' . $api->getProtocol()); + } + /** + * Creates an error parser for the given protocol. + * + * Redundant method signature to preserve backwards compatibility. + * + * @param string $protocol Protocol to parse (e.g., query, json, etc.) + * + * @return callable + * @throws \UnexpectedValueException + */ + public static function createErrorParser($protocol, ?Service $api = null) + { + static $mapping = ['json' => ErrorParser\JsonRpcErrorParser::class, 'query' => ErrorParser\XmlErrorParser::class, 'rest-json' => ErrorParser\RestJsonErrorParser::class, 'rest-xml' => ErrorParser\XmlErrorParser::class, 'ec2' => ErrorParser\XmlErrorParser::class]; + if (isset($mapping[$protocol])) { + return new $mapping[$protocol]($api); + } + throw new \UnexpectedValueException("Unknown protocol: {$protocol}"); + } + /** + * Applies the listeners needed to parse client models. + * + * @param Service $api API to create a parser for + * @return callable + * @throws \UnexpectedValueException + */ + public static function createParser(Service $api) + { + static $mapping = ['json' => Parser\JsonRpcParser::class, 'query' => Parser\QueryParser::class, 'rest-json' => Parser\RestJsonParser::class, 'rest-xml' => Parser\RestXmlParser::class]; + $proto = $api->getProtocol(); + if (isset($mapping[$proto])) { + return new $mapping[$proto]($api); + } + if ($proto == 'ec2') { + return new Parser\QueryParser($api, null, \false); + } + throw new \UnexpectedValueException('Unknown protocol: ' . $api->getProtocol()); + } + /** + * Get the full name of the service + * + * @return string + */ + public function getServiceFullName() + { + return $this->definition['metadata']['serviceFullName']; + } + /** + * Get the service id + * + * @return string + */ + public function getServiceId() + { + return $this->definition['metadata']['serviceId']; + } + /** + * Get the API version of the service + * + * @return string + */ + public function getApiVersion() + { + return $this->definition['metadata']['apiVersion']; + } + /** + * Get the API version of the service + * + * @return string + */ + public function getEndpointPrefix() + { + return $this->definition['metadata']['endpointPrefix']; + } + /** + * Get the signing name used by the service. + * + * @return string + */ + public function getSigningName() + { + return $this->definition['metadata']['signingName'] ?: $this->definition['metadata']['endpointPrefix']; + } + /** + * Get the service name. + * + * @return string + */ + public function getServiceName() + { + return $this->definition['metadata']['serviceIdentifier'] ?? null; + } + /** + * Get the default signature version of the service. + * + * Note: this method assumes "v4" when not specified in the model. + * + * @return string + */ + public function getSignatureVersion() + { + return $this->definition['metadata']['signatureVersion'] ?: 'v4'; + } + /** + * Get the protocol used by the service. + * + * @return string + */ + public function getProtocol() + { + return $this->definition['metadata']['protocol']; + } + /** + * Get the uid string used by the service + * + * @return string + */ + public function getUid() + { + return $this->definition['metadata']['uid']; + } + /** + * Check if the description has a specific operation by name. + * + * @param string $name Operation to check by name + * + * @return bool + */ + public function hasOperation($name) + { + return isset($this['operations'][$name]); + } + /** + * Get an operation by name. + * + * @param string $name Operation to retrieve by name + * + * @return Operation + * @throws \InvalidArgumentException If the operation is not found + */ + public function getOperation($name) + { + if (!isset($this->operations[$name])) { + if (!isset($this->definition['operations'][$name])) { + throw new \InvalidArgumentException("Unknown operation: {$name}"); + } + $this->operations[$name] = new Operation($this->definition['operations'][$name], $this->shapeMap); + } elseif ($this->modifiedModel) { + $this->operations[$name] = new Operation($this->definition['operations'][$name], $this->shapeMap); + } + return $this->operations[$name]; + } + /** + * Get all of the operations of the description. + * + * @return Operation[] + */ + public function getOperations() + { + $result = []; + foreach ($this->definition['operations'] as $name => $definition) { + $result[$name] = $this->getOperation($name); + } + return $result; + } + /** + * Get all of the error shapes of the service + * + * @return array + */ + public function getErrorShapes() + { + $result = []; + foreach ($this->definition['shapes'] as $name => $definition) { + if (!empty($definition['exception'])) { + $definition['name'] = $name; + $result[] = new StructureShape($definition, $this->getShapeMap()); + } + } + return $result; + } + /** + * Get all of the service metadata or a specific metadata key value. + * + * @param string|null $key Key to retrieve or null to retrieve all metadata + * + * @return mixed Returns the result or null if the key is not found + */ + public function getMetadata($key = null) + { + if (!$key) { + return $this['metadata']; + } + if (isset($this->definition['metadata'][$key])) { + return $this->definition['metadata'][$key]; + } + return null; + } + /** + * Gets an associative array of available paginator configurations where + * the key is the name of the paginator, and the value is the paginator + * configuration. + * + * @return array + * @unstable The configuration format of paginators may change in the future + */ + public function getPaginators() + { + if (!isset($this->paginators)) { + $res = \call_user_func($this->apiProvider, 'paginator', $this->serviceName, $this->apiVersion); + $this->paginators = isset($res['pagination']) ? $res['pagination'] : []; + } + return $this->paginators; + } + /** + * Determines if the service has a paginator by name. + * + * @param string $name Name of the paginator. + * + * @return bool + */ + public function hasPaginator($name) + { + return isset($this->getPaginators()[$name]); + } + /** + * Retrieve a paginator by name. + * + * @param string $name Paginator to retrieve by name. This argument is + * typically the operation name. + * @return array + * @throws \UnexpectedValueException if the paginator does not exist. + * @unstable The configuration format of paginators may change in the future + */ + public function getPaginatorConfig($name) + { + static $defaults = ['input_token' => null, 'output_token' => null, 'limit_key' => null, 'result_key' => null, 'more_results' => null]; + if ($this->hasPaginator($name)) { + return $this->paginators[$name] + $defaults; + } + throw new \UnexpectedValueException("There is no {$name} " . "paginator defined for the {$this->serviceName} service."); + } + /** + * Gets an associative array of available waiter configurations where the + * key is the name of the waiter, and the value is the waiter + * configuration. + * + * @return array + */ + public function getWaiters() + { + if (!isset($this->waiters)) { + $res = \call_user_func($this->apiProvider, 'waiter', $this->serviceName, $this->apiVersion); + $this->waiters = isset($res['waiters']) ? $res['waiters'] : []; + } + return $this->waiters; + } + /** + * Determines if the service has a waiter by name. + * + * @param string $name Name of the waiter. + * + * @return bool + */ + public function hasWaiter($name) + { + return isset($this->getWaiters()[$name]); + } + /** + * Get a waiter configuration by name. + * + * @param string $name Name of the waiter by name. + * + * @return array + * @throws \UnexpectedValueException if the waiter does not exist. + */ + public function getWaiterConfig($name) + { + // Error if the waiter is not defined + if ($this->hasWaiter($name)) { + return $this->waiters[$name]; + } + throw new \UnexpectedValueException("There is no {$name} waiter " . "defined for the {$this->serviceName} service."); + } + /** + * Get the shape map used by the API. + * + * @return ShapeMap + */ + public function getShapeMap() + { + return $this->shapeMap; + } + /** + * Get all the context params of the description. + * + * @return array + */ + public function getClientContextParams() + { + return $this->clientContextParams; + } + /** + * Get the service's api provider. + * + * @return callable + */ + public function getProvider() + { + return $this->apiProvider; + } + /** + * Get the service's definition. + * + * @return callable + */ + public function getDefinition() + { + return $this->definition; + } + /** + * Sets the service's api definition. + * Intended for internal use only. + * + * @return void + * + * @internal + */ + public function setDefinition($definition) + { + $this->definition = $definition; + $this->shapeMap = new ShapeMap($definition['shapes']); + $this->modifiedModel = \true; + } + /** + * Denotes whether or not a service's definition has + * been modified. Intended for internal use only. + * + * @return bool + * + * @internal + */ + public function isModifiedModel() + { + return $this->modifiedModel; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Shape.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Shape.php new file mode 100644 index 00000000..59b0b6d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Shape.php @@ -0,0 +1,56 @@ + StructureShape::class, 'map' => MapShape::class, 'list' => ListShape::class, 'timestamp' => TimestampShape::class, 'integer' => Shape::class, 'double' => Shape::class, 'float' => Shape::class, 'long' => Shape::class, 'string' => Shape::class, 'byte' => Shape::class, 'character' => Shape::class, 'blob' => Shape::class, 'boolean' => Shape::class]; + if (isset($definition['shape'])) { + return $shapeMap->resolve($definition); + } + if (!isset($map[$definition['type']])) { + throw new \RuntimeException('Invalid type: ' . \print_r($definition, \true)); + } + $type = $map[$definition['type']]; + return new $type($definition, $shapeMap); + } + /** + * Get the type of the shape + * + * @return string + */ + public function getType() + { + return $this->definition['type']; + } + /** + * Get the name of the shape + * + * @return string + */ + public function getName() + { + return $this->definition['name']; + } + /** + * Get a context param definition. + */ + public function getContextParam() + { + return $this->contextParam; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ShapeMap.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ShapeMap.php new file mode 100644 index 00000000..c0a075e0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/ShapeMap.php @@ -0,0 +1,59 @@ +definitions = $shapeModels; + } + /** + * Get an array of shape names. + * + * @return array + */ + public function getShapeNames() + { + return \array_keys($this->definitions); + } + /** + * Resolve a shape reference + * + * @param array $shapeRef Shape reference shape + * + * @return Shape + * @throws \InvalidArgumentException + */ + public function resolve(array $shapeRef) + { + $shape = $shapeRef['shape']; + if (!isset($this->definitions[$shape])) { + throw new \InvalidArgumentException('Shape not found: ' . $shape); + } + $isSimple = \count($shapeRef) == 1; + if ($isSimple && isset($this->simple[$shape])) { + return $this->simple[$shape]; + } + $definition = $shapeRef + $this->definitions[$shape]; + $definition['name'] = $definition['shape']; + if (isset($definition['shape'])) { + unset($definition['shape']); + } + $result = Shape::create($definition, $this); + if ($isSimple) { + $this->simple[$shape] = $result; + } + return $result; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/StructureShape.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/StructureShape.php new file mode 100644 index 00000000..7550ba1b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/StructureShape.php @@ -0,0 +1,68 @@ +members)) { + $this->generateMembersHash(); + } + return $this->members; + } + /** + * Check if a specific member exists by name. + * + * @param string $name Name of the member to check + * + * @return bool + */ + public function hasMember($name) + { + return isset($this->definition['members'][$name]); + } + /** + * Retrieve a member by name. + * + * @param string $name Name of the member to retrieve + * + * @return Shape + * @throws \InvalidArgumentException if the member is not found. + */ + public function getMember($name) + { + $members = $this->getMembers(); + if (!isset($members[$name])) { + throw new \InvalidArgumentException('Unknown member ' . $name); + } + return $members[$name]; + } + private function generateMembersHash() + { + $this->members = []; + foreach ($this->definition['members'] as $name => $definition) { + $this->members[$name] = $this->shapeFor($definition); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/TimestampShape.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/TimestampShape.php new file mode 100644 index 00000000..bafbf6d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/TimestampShape.php @@ -0,0 +1,45 @@ +getTimestamp(); + } elseif (\is_string($value)) { + $value = \strtotime($value); + } elseif (!\is_int($value)) { + throw new \InvalidArgumentException('Unable to handle the provided' . ' timestamp type: ' . \gettype($value)); + } + switch ($format) { + case 'iso8601': + return \gmdate('Y-m-d\\TH:i:s\\Z', $value); + case 'rfc822': + return \gmdate('D, d M Y H:i:s \\G\\M\\T', $value); + case 'unixTimestamp': + return $value; + default: + throw new \UnexpectedValueException('Unknown timestamp format: ' . $format); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Validator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Validator.php new file mode 100644 index 00000000..c3914539 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Api/Validator.php @@ -0,0 +1,246 @@ + \true, 'min' => \true, 'max' => \false, 'pattern' => \false]; + /** + * @param array $constraints Associative array of constraints to enforce. + * Accepts the following keys: "required", "min", + * "max", and "pattern". If a key is not + * provided, the constraint will assume false. + */ + public function __construct(?array $constraints = null) + { + static $assumedFalseValues = ['required' => \false, 'min' => \false, 'max' => \false, 'pattern' => \false]; + $this->constraints = empty($constraints) ? self::$defaultConstraints : $constraints + $assumedFalseValues; + } + /** + * Validates the given input against the schema. + * + * @param string $name Operation name + * @param Shape $shape Shape to validate + * @param array $input Input to validate + * + * @throws \InvalidArgumentException if the input is invalid. + */ + public function validate($name, Shape $shape, array $input) + { + $this->dispatch($shape, $input); + if ($this->errors) { + $message = \sprintf("Found %d error%s while validating the input provided for the " . "%s operation:\n%s", \count($this->errors), \count($this->errors) > 1 ? 's' : '', $name, \implode("\n", $this->errors)); + $this->errors = []; + throw new \InvalidArgumentException($message); + } + } + private function dispatch(Shape $shape, $value) + { + static $methods = ['structure' => 'check_structure', 'list' => 'check_list', 'map' => 'check_map', 'blob' => 'check_blob', 'boolean' => 'check_boolean', 'integer' => 'check_numeric', 'float' => 'check_numeric', 'long' => 'check_numeric', 'string' => 'check_string', 'byte' => 'check_string', 'char' => 'check_string']; + $type = $shape->getType(); + if (isset($methods[$type])) { + $this->{$methods[$type]}($shape, $value); + } + } + private function check_structure(StructureShape $shape, $value) + { + $isDocument = isset($shape['document']) && $shape['document']; + $isUnion = isset($shape['union']) && $shape['union']; + if ($isDocument) { + if (!$this->checkDocumentType($value)) { + $this->addError("is not a valid document type"); + return; + } + } elseif ($isUnion) { + if (!$this->checkUnion($value)) { + $this->addError("is a union type and must have exactly one non null value"); + return; + } + } elseif (!$this->checkAssociativeArray($value)) { + return; + } + if ($this->constraints['required'] && $shape['required']) { + foreach ($shape['required'] as $req) { + if (!isset($value[$req])) { + $this->path[] = $req; + $this->addError('is missing and is a required parameter'); + \array_pop($this->path); + } + } + } + if (!$isDocument) { + foreach ($value as $name => $v) { + if ($shape->hasMember($name)) { + $this->path[] = $name; + $this->dispatch($shape->getMember($name), isset($value[$name]) ? $value[$name] : null); + \array_pop($this->path); + } + } + } + } + private function check_list(ListShape $shape, $value) + { + if (!\is_array($value)) { + $this->addError('must be an array. Found ' . Aws\describe_type($value)); + return; + } + $this->validateRange($shape, \count($value), "list element count"); + $items = $shape->getMember(); + foreach ($value as $index => $v) { + $this->path[] = $index; + $this->dispatch($items, $v); + \array_pop($this->path); + } + } + private function check_map(MapShape $shape, $value) + { + if (!$this->checkAssociativeArray($value)) { + return; + } + $values = $shape->getValue(); + foreach ($value as $key => $v) { + $this->path[] = $key; + $this->dispatch($values, $v); + \array_pop($this->path); + } + } + private function check_blob(Shape $shape, $value) + { + static $valid = ['string' => \true, 'integer' => \true, 'double' => \true, 'resource' => \true]; + $type = \gettype($value); + if (!isset($valid[$type])) { + if ($type != 'object' || !\method_exists($value, '__toString')) { + $this->addError('must be an fopen resource, a ' . 'GuzzleHttp\\Stream\\StreamInterface object, or something ' . 'that can be cast to a string. Found ' . Aws\describe_type($value)); + } + } + } + private function check_numeric(Shape $shape, $value) + { + if (!\is_numeric($value)) { + $this->addError('must be numeric. Found ' . Aws\describe_type($value)); + return; + } + $this->validateRange($shape, $value, "numeric value"); + } + private function check_boolean(Shape $shape, $value) + { + if (!\is_bool($value)) { + $this->addError('must be a boolean. Found ' . Aws\describe_type($value)); + } + } + private function check_string(Shape $shape, $value) + { + if ($shape['jsonvalue']) { + if (!self::canJsonEncode($value)) { + $this->addError('must be a value encodable with \'json_encode\'.' . ' Found ' . Aws\describe_type($value)); + } + return; + } + if (!$this->checkCanString($value)) { + $this->addError('must be a string or an object that implements ' . '__toString(). Found ' . Aws\describe_type($value)); + return; + } + $value = isset($value) ? $value : ''; + $this->validateRange($shape, \strlen($value), "string length"); + if ($this->constraints['pattern']) { + $pattern = $shape['pattern']; + if ($pattern && !\preg_match("/{$pattern}/", $value)) { + $this->addError("Pattern /{$pattern}/ failed to match '{$value}'"); + } + } + } + private function validateRange(Shape $shape, $length, $descriptor) + { + if ($this->constraints['min']) { + $min = $shape['min']; + if ($min && $length < $min) { + $this->addError("expected {$descriptor} to be >= {$min}, but " . "found {$descriptor} of {$length}"); + } + } + if ($this->constraints['max']) { + $max = $shape['max']; + if ($max && $length > $max) { + $this->addError("expected {$descriptor} to be <= {$max}, but " . "found {$descriptor} of {$length}"); + } + } + } + private function checkArray($arr) + { + return $this->isIndexed($arr) || $this->isAssociative($arr); + } + private function isAssociative($arr) + { + return \count(\array_filter(\array_keys($arr), "is_string")) == \count($arr); + } + private function isIndexed(array $arr) + { + return $arr == \array_values($arr); + } + private function checkCanString($value) + { + static $valid = ['string' => \true, 'integer' => \true, 'double' => \true, 'NULL' => \true]; + $type = \gettype($value); + return isset($valid[$type]) || $type == 'object' && \method_exists($value, '__toString'); + } + private function checkAssociativeArray($value) + { + $isAssociative = \false; + if (\is_array($value)) { + $expectedIndex = 0; + $key = \key($value); + do { + $isAssociative = $key !== $expectedIndex++; + \next($value); + $key = \key($value); + } while (!$isAssociative && null !== $key); + } + if (!$isAssociative) { + $this->addError('must be an associative array. Found ' . Aws\describe_type($value)); + return \false; + } + return \true; + } + private function checkDocumentType($value) + { + if (\is_array($value)) { + $typeOfFirstKey = \gettype(\key($value)); + foreach ($value as $key => $val) { + if (!$this->checkDocumentType($val) || \gettype($key) != $typeOfFirstKey) { + return \false; + } + } + return $this->checkArray($value); + } + return \is_null($value) || \is_numeric($value) || \is_string($value) || \is_bool($value); + } + private function checkUnion($value) + { + if (\is_array($value)) { + $nonNullCount = 0; + foreach ($value as $key => $val) { + if (!\is_null($val) && !(\strpos($key, "@") === 0)) { + $nonNullCount++; + } + } + return $nonNullCount == 1; + } + return !\is_null($value); + } + private function addError($message) + { + $this->errors[] = \implode('', \array_map(function ($s) { + return "[{$s}]"; + }, $this->path)) . ' ' . $message; + } + private function canJsonEncode($data) + { + return !\is_resource($data); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArn.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArn.php new file mode 100644 index 00000000..8710f506 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArn.php @@ -0,0 +1,55 @@ +data); + } + public static function parse($string) + { + $data = parent::parse($string); + $data = self::parseResourceTypeAndId($data); + $data['accesspoint_name'] = $data['resource_id']; + return $data; + } + public function getAccesspointName() + { + return $this->data['accesspoint_name']; + } + /** + * Validation specific to AccessPointArn + * + * @param array $data + */ + protected static function validate(array $data) + { + self::validateRegion($data, 'access point ARN'); + self::validateAccountId($data, 'access point ARN'); + if ($data['resource_type'] !== 'accesspoint') { + throw new InvalidArnException("The 6th component of an access point ARN" . " represents the resource type and must be 'accesspoint'."); + } + if (empty($data['resource_id'])) { + throw new InvalidArnException("The 7th component of an access point ARN" . " represents the resource ID and must not be empty."); + } + if (\strpos($data['resource_id'], ':') !== \false) { + throw new InvalidArnException("The resource ID component of an access" . " point ARN must not contain additional components" . " (delimited by ':')."); + } + if (!self::isValidHostLabel($data['resource_id'])) { + throw new InvalidArnException("The resource ID in an access point ARN" . " must be a valid host label value."); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArnInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArnInterface.php new file mode 100644 index 00000000..802511b8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/AccessPointArnInterface.php @@ -0,0 +1,11 @@ + null, 'partition' => null, 'service' => null, 'region' => null, 'account_id' => null, 'resource' => null]; + $length = \strlen($string); + $lastDelim = 0; + $numComponents = 0; + for ($i = 0; $i < $length; $i++) { + if ($numComponents < 5 && $string[$i] === ':') { + // Split components between delimiters + $data[\key($data)] = \substr($string, $lastDelim, $i - $lastDelim); + // Do not include delimiter character itself + $lastDelim = $i + 1; + \next($data); + $numComponents++; + } + if ($i === $length - 1) { + // Put the remainder in the last component. + if (\in_array($numComponents, [5])) { + $data['resource'] = \substr($string, $lastDelim); + } else { + // If there are < 5 components, put remainder in current + // component. + $data[\key($data)] = \substr($string, $lastDelim); + } + } + } + return $data; + } + public function __construct($data) + { + if (\is_array($data)) { + $this->data = $data; + } elseif (\is_string($data)) { + $this->data = static::parse($data); + } else { + throw new InvalidArnException('Constructor accepts a string or an' . ' array as an argument.'); + } + static::validate($this->data); + } + public function __toString() + { + if (!isset($this->string)) { + $components = [$this->getPrefix(), $this->getPartition(), $this->getService(), $this->getRegion(), $this->getAccountId(), $this->getResource()]; + $this->string = \implode(':', $components); + } + return $this->string; + } + public function getPrefix() + { + return $this->data['arn']; + } + public function getPartition() + { + return $this->data['partition']; + } + public function getService() + { + return $this->data['service']; + } + public function getRegion() + { + return $this->data['region']; + } + public function getAccountId() + { + return $this->data['account_id']; + } + public function getResource() + { + return $this->data['resource']; + } + public function toArray() + { + return $this->data; + } + /** + * Minimally restrictive generic ARN validation + * + * @param array $data + */ + protected static function validate(array $data) + { + if ($data['arn'] !== 'arn') { + throw new InvalidArnException("The 1st component of an ARN must be" . " 'arn'."); + } + if (empty($data['partition'])) { + throw new InvalidArnException("The 2nd component of an ARN" . " represents the partition and must not be empty."); + } + if (empty($data['service'])) { + throw new InvalidArnException("The 3rd component of an ARN" . " represents the service and must not be empty."); + } + if (empty($data['resource'])) { + throw new InvalidArnException("The 6th component of an ARN" . " represents the resource information and must not be empty." . " Individual service ARNs may include additional delimiters" . " to further qualify resources."); + } + } + protected static function validateAccountId($data, $arnName) + { + if (!self::isValidHostLabel($data['account_id'])) { + throw new InvalidArnException("The 5th component of a {$arnName}" . " is required, represents the account ID, and" . " must be a valid host label."); + } + } + protected static function validateRegion($data, $arnName) + { + if (empty($data['region'])) { + throw new InvalidArnException("The 4th component of a {$arnName}" . " represents the region and must not be empty."); + } + } + /** + * Validates whether a string component is a valid host label + * + * @param $string + * @return bool + */ + protected static function isValidHostLabel($string) + { + if (empty($string) || \strlen($string) > 63) { + return \false; + } + if ($value = \preg_match("/^[a-zA-Z0-9-]+\$/", $string)) { + return \true; + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ArnInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ArnInterface.php new file mode 100644 index 00000000..0ec7ed28 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/ArnInterface.php @@ -0,0 +1,30 @@ +data['resource_type']; + } + public function getResourceId() + { + return $this->data['resource_id']; + } + protected static function parseResourceTypeAndId(array $data) + { + $resourceData = \preg_split("/[\\/:]/", $data['resource'], 2); + $data['resource_type'] = isset($resourceData[0]) ? $resourceData[0] : null; + $data['resource_id'] = isset($resourceData[1]) ? $resourceData[1] : null; + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/AccessPointArn.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/AccessPointArn.php new file mode 100644 index 00000000..bc409587 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/AccessPointArn.php @@ -0,0 +1,26 @@ +data['outpost_id']; + } + public function getAccesspointName() + { + return $this->data['accesspoint_name']; + } + private static function parseOutpostData(array $data) + { + $resourceData = \preg_split("/[\\/:]/", $data['resource_id']); + $data['outpost_id'] = isset($resourceData[0]) ? $resourceData[0] : null; + $data['accesspoint_type'] = isset($resourceData[1]) ? $resourceData[1] : null; + $data['accesspoint_name'] = isset($resourceData[2]) ? $resourceData[2] : null; + if (isset($resourceData[3])) { + $data['resource_extra'] = \implode(':', \array_slice($resourceData, 3)); + } + return $data; + } + /** + * Validation specific to OutpostsAccessPointArn. Note this uses the base Arn + * class validation instead of the direct parent due to it having slightly + * differing requirements from its parent. + * + * @param array $data + */ + public static function validate(array $data) + { + Arn::validate($data); + if ($data['service'] !== 's3-outposts') { + throw new InvalidArnException("The 3rd component of an S3 Outposts" . " access point ARN represents the service and must be" . " 's3-outposts'."); + } + self::validateRegion($data, 'S3 Outposts access point ARN'); + self::validateAccountId($data, 'S3 Outposts access point ARN'); + if ($data['resource_type'] !== 'outpost') { + throw new InvalidArnException("The 6th component of an S3 Outposts" . " access point ARN represents the resource type and must be" . " 'outpost'."); + } + if (!self::isValidHostLabel($data['outpost_id'])) { + throw new InvalidArnException("The 7th component of an S3 Outposts" . " access point ARN is required, represents the outpost ID, and" . " must be a valid host label."); + } + if ($data['accesspoint_type'] !== 'accesspoint') { + throw new InvalidArnException("The 8th component of an S3 Outposts" . " access point ARN must be 'accesspoint'"); + } + if (!self::isValidHostLabel($data['accesspoint_name'])) { + throw new InvalidArnException("The 9th component of an S3 Outposts" . " access point ARN is required, represents the accesspoint name," . " and must be a valid host label."); + } + if (!empty($data['resource_extra'])) { + throw new InvalidArnException("An S3 Outposts access point ARN" . " should only have 9 components, delimited by the characters" . " ':' and '/'. '{$data['resource_extra']}' was found after the" . " 9th component."); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/OutpostsArnInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/OutpostsArnInterface.php new file mode 100644 index 00000000..1e2902be --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Arn/S3/OutpostsArnInterface.php @@ -0,0 +1,12 @@ +data['bucket_name']; + } + public function getOutpostId() + { + return $this->data['outpost_id']; + } + private static function parseOutpostData(array $data) + { + $resourceData = \preg_split("/[\\/:]/", $data['resource_id'], 3); + $data['outpost_id'] = isset($resourceData[0]) ? $resourceData[0] : null; + $data['bucket_label'] = isset($resourceData[1]) ? $resourceData[1] : null; + $data['bucket_name'] = isset($resourceData[2]) ? $resourceData[2] : null; + return $data; + } + /** + * + * @param array $data + */ + public static function validate(array $data) + { + Arn::validate($data); + if ($data['service'] !== 's3-outposts') { + throw new InvalidArnException("The 3rd component of an S3 Outposts" . " bucket ARN represents the service and must be 's3-outposts'."); + } + self::validateRegion($data, 'S3 Outposts bucket ARN'); + self::validateAccountId($data, 'S3 Outposts bucket ARN'); + if ($data['resource_type'] !== 'outpost') { + throw new InvalidArnException("The 6th component of an S3 Outposts" . " bucket ARN represents the resource type and must be" . " 'outpost'."); + } + if (!self::isValidHostLabel($data['outpost_id'])) { + throw new InvalidArnException("The 7th component of an S3 Outposts" . " bucket ARN is required, represents the outpost ID, and" . " must be a valid host label."); + } + if ($data['bucket_label'] !== 'bucket') { + throw new InvalidArnException("The 8th component of an S3 Outposts" . " bucket ARN must be 'bucket'"); + } + if (empty($data['bucket_name'])) { + throw new InvalidArnException("The 9th component of an S3 Outposts" . " bucket ARN represents the bucket name and must not be empty."); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolver.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolver.php new file mode 100644 index 00000000..ed1fa4bc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolver.php @@ -0,0 +1,134 @@ + 'v4', 'aws.auth#sigv4a' => 'v4a', 'smithy.api#httpBearerAuth' => 'bearer', 'smithy.api#noAuth' => 'anonymous']; + /** + * @var array Mapping of auth schemes to signature versions used in + * resolving a signature version. + */ + private $authSchemeMap; + private $tokenProvider; + private $credentialProvider; + public function __construct(callable $credentialProvider, ?callable $tokenProvider = null, array $authSchemeMap = []) + { + $this->credentialProvider = $credentialProvider; + $this->tokenProvider = $tokenProvider; + $this->authSchemeMap = empty($authSchemeMap) ? self::$defaultAuthSchemeMap : $authSchemeMap; + } + /** + * Accepts a priority-ordered list of auth schemes and an Identity + * and selects the first compatible auth schemes, returning a normalized + * signature version. For example, based on the default auth scheme mapping, + * if `aws.auth#sigv4` is selected, `v4` will be returned. + * + * @param array $authSchemes + * @param $identity + * + * @return string + * @throws UnresolvedAuthSchemeException + */ + public function selectAuthScheme(array $authSchemes, array $args = []) : string + { + $failureReasons = []; + foreach ($authSchemes as $authScheme) { + $normalizedAuthScheme = $this->authSchemeMap[$authScheme] ?? $authScheme; + if ($this->isCompatibleAuthScheme($normalizedAuthScheme)) { + if ($normalizedAuthScheme === 'v4' && !empty($args['unsigned_payload'])) { + return $normalizedAuthScheme . self::UNSIGNED_BODY; + } + return $normalizedAuthScheme; + } else { + $failureReasons[] = $this->getIncompatibilityMessage($normalizedAuthScheme); + } + } + throw new UnresolvedAuthSchemeException('Could not resolve an authentication scheme: ' . \implode('; ', $failureReasons)); + } + /** + * Determines compatibility based on either Identity or the availability + * of the CRT extension. + * + * @param $authScheme + * + * @return bool + */ + private function isCompatibleAuthScheme($authScheme) : bool + { + switch ($authScheme) { + case 'v4': + case 'anonymous': + return $this->hasAwsCredentialIdentity(); + case 'v4a': + return \extension_loaded('awscrt') && $this->hasAwsCredentialIdentity(); + case 'bearer': + return $this->hasBearerTokenIdentity(); + default: + return \false; + } + } + /** + * Provides incompatibility messages in the event an incompatible auth scheme + * is encountered. + * + * @param $authScheme + * + * @return string + */ + private function getIncompatibilityMessage($authScheme) : string + { + switch ($authScheme) { + case 'v4': + return 'Signature V4 requires AWS credentials for request signing'; + case 'anonymous': + return 'Anonymous signatures require AWS credentials for request signing'; + case 'v4a': + return 'The aws-crt-php extension and AWS credentials are required to use Signature V4A'; + case 'bearer': + return 'Bearer token credentials must be provided to use Bearer authentication'; + default: + return "The service does not support `{$authScheme}` authentication."; + } + } + /** + * @return bool + */ + private function hasAwsCredentialIdentity() : bool + { + $fn = $this->credentialProvider; + $result = $fn(); + if ($result instanceof PromiseInterface) { + return $result->wait() instanceof AwsCredentialIdentity; + } + return $result instanceof AwsCredentialIdentity; + } + /** + * @return bool + */ + private function hasBearerTokenIdentity() : bool + { + if ($this->tokenProvider) { + $fn = $this->tokenProvider; + $result = $fn(); + if ($result instanceof PromiseInterface) { + return $result->wait() instanceof BearerTokenIdentity; + } + return $result instanceof BearerTokenIdentity; + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolverInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolverInterface.php new file mode 100644 index 00000000..c194ca7f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/AuthSchemeResolverInterface.php @@ -0,0 +1,20 @@ +nextHandler = $nextHandler; + $this->authResolver = $authResolver; + $this->api = $api; + } + /** + * @param CommandInterface $command + * + * @return Promise + */ + public function __invoke(CommandInterface $command) + { + $nextHandler = $this->nextHandler; + $serviceAuth = $this->api->getMetadata('auth') ?: []; + $operation = $this->api->getOperation($command->getName()); + $operationAuth = $operation['auth'] ?? []; + $unsignedPayload = $operation['unsignedpayload'] ?? \false; + $resolvableAuth = $operationAuth ?: $serviceAuth; + if (!empty($resolvableAuth)) { + if (isset($command['@context']['auth_scheme_resolver']) && $command['@context']['auth_scheme_resolver'] instanceof AuthSchemeResolverInterface) { + $resolver = $command['@context']['auth_scheme_resolver']; + } else { + $resolver = $this->authResolver; + } + $selectedAuthScheme = $resolver->selectAuthScheme($resolvableAuth, ['unsigned_payload' => $unsignedPayload]); + if (!empty($selectedAuthScheme)) { + $command['@context']['signature_version'] = $selectedAuthScheme; + } + } + return $nextHandler($command); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/Exception/UnresolvedAuthSchemeException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/Exception/UnresolvedAuthSchemeException.php new file mode 100644 index 00000000..aa871ad6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Auth/Exception/UnresolvedAuthSchemeException.php @@ -0,0 +1,13 @@ +parseClass(); + if (!isset($args['service'])) { + $args['service'] = manifest($service)['endpoint']; + } + if (!isset($args['exception_class'])) { + $args['exception_class'] = $exceptionClass; + } + $this->handlerList = new HandlerList(); + $resolver = new ClientResolver(static::getArguments()); + $config = $resolver->resolve($args, $this->handlerList); + $this->api = $config['api']; + $this->signatureProvider = $config['signature_provider']; + $this->authSchemeResolver = $config['auth_scheme_resolver']; + $this->endpoint = new Uri($config['endpoint']); + $this->credentialProvider = $config['credentials']; + $this->tokenProvider = $config['token']; + $this->region = $config['region'] ?? null; + $this->signingRegionSet = $config['sigv4a_signing_region_set'] ?? null; + $this->config = $config['config']; + $this->setClientBuiltIns($args, $config); + $this->clientContextParams = $this->setClientContextParams($args); + $this->defaultRequestOptions = $config['http']; + $this->endpointProvider = $config['endpoint_provider']; + $this->serializer = $config['serializer']; + $this->addSignatureMiddleware($args); + $this->addInvocationId(); + $this->addEndpointParameterMiddleware($args); + $this->addEndpointDiscoveryMiddleware($config, $args); + $this->addRequestCompressionMiddleware($config); + $this->loadAliases(); + $this->addStreamRequestPayload(); + $this->addRecursionDetection(); + if ($this->isUseEndpointV2()) { + $this->addEndpointV2Middleware(); + } + $this->addAuthSelectionMiddleware(); + if (!\is_null($this->api->getMetadata('awsQueryCompatible'))) { + $this->addQueryCompatibleInputMiddleware($this->api); + $this->addQueryModeHeader(); + } + if (isset($args['with_resolved'])) { + $args['with_resolved']($config); + } + $this->addUserAgentMiddleware($config); + } + public function getHandlerList() + { + return $this->handlerList; + } + public function getConfig($option = null) + { + return $option === null ? $this->config : $this->config[$option] ?? null; + } + public function getCredentials() + { + $fn = $this->credentialProvider; + return $fn(); + } + public function getEndpoint() + { + return $this->endpoint; + } + public function getRegion() + { + return $this->region; + } + public function getApi() + { + return $this->api; + } + public function getCommand($name, array $args = []) + { + // Fail fast if the command cannot be found in the description. + if (!isset($this->getApi()['operations'][$name])) { + $name = \ucfirst($name); + if (!isset($this->getApi()['operations'][$name])) { + throw new \InvalidArgumentException("Operation not found: {$name}"); + } + } + if (!isset($args['@http'])) { + $args['@http'] = $this->defaultRequestOptions; + } else { + $args['@http'] += $this->defaultRequestOptions; + } + return new Command($name, $args, clone $this->getHandlerList()); + } + public function getEndpointProvider() + { + return $this->endpointProvider; + } + /** + * Provides the set of service context parameter + * key-value pairs used for endpoint resolution. + * + * @return array + */ + public function getClientContextParams() + { + return $this->clientContextParams; + } + /** + * Provides the set of built-in keys and values + * used for endpoint resolution + * + * @return array + */ + public function getClientBuiltIns() + { + return $this->clientBuiltIns; + } + public function __sleep() + { + throw new \RuntimeException('Instances of ' . static::class . ' cannot be serialized'); + } + /** + * Get the signature_provider function of the client. + * + * @return callable + */ + public final function getSignatureProvider() + { + return $this->signatureProvider; + } + /** + * Parse the class name and setup the custom exception class of the client + * and return the "service" name of the client and "exception_class". + * + * @return array + */ + private function parseClass() + { + $klass = \get_class($this); + if ($klass === __CLASS__) { + return ['', AwsException::class]; + } + $service = \substr($klass, \strrpos($klass, '\\') + 1, -6); + return [\strtolower($service), "WPMailSMTP\\Vendor\\Aws\\{$service}\\Exception\\{$service}Exception"]; + } + private function addEndpointParameterMiddleware($args) + { + if (empty($args['disable_host_prefix_injection'])) { + $list = $this->getHandlerList(); + $list->appendBuild(EndpointParameterMiddleware::wrap($this->api), 'endpoint_parameter'); + } + } + private function addEndpointDiscoveryMiddleware($config, $args) + { + $list = $this->getHandlerList(); + if (!isset($args['endpoint'])) { + $list->appendBuild(EndpointDiscoveryMiddleware::wrap($this, $args, $config['endpoint_discovery']), 'EndpointDiscoveryMiddleware'); + } + } + private function addSignatureMiddleware(array $args) + { + $api = $this->getApi(); + $provider = $this->signatureProvider; + $signatureVersion = $this->config['signature_version']; + $name = $this->config['signing_name']; + $region = $this->config['signing_region']; + $signingRegionSet = $this->signingRegionSet; + if (isset($args['signature_version']) || isset($this->config['configured_signature_version'])) { + $configuredSignatureVersion = \true; + } else { + $configuredSignatureVersion = \false; + } + $resolver = static function (CommandInterface $command) use($api, $provider, $name, $region, $signatureVersion, $configuredSignatureVersion, $signingRegionSet) { + if (!$configuredSignatureVersion) { + if (!empty($command['@context']['signing_region'])) { + $region = $command['@context']['signing_region']; + } + if (!empty($command['@context']['signing_service'])) { + $name = $command['@context']['signing_service']; + } + if (!empty($command['@context']['signature_version'])) { + $signatureVersion = $command['@context']['signature_version']; + } + $authType = $api->getOperation($command->getName())['authtype']; + switch ($authType) { + case 'none': + $signatureVersion = 'anonymous'; + break; + case 'v4-unsigned-body': + $signatureVersion = 'v4-unsigned-body'; + break; + case 'bearer': + $signatureVersion = 'bearer'; + break; + } + } + if ($signatureVersion === 'v4a') { + $commandSigningRegionSet = !empty($command['@context']['signing_region_set']) ? \implode(', ', $command['@context']['signing_region_set']) : null; + $region = $signingRegionSet ?? $commandSigningRegionSet ?? $region; + } + // Capture signature metric + $command->getMetricsBuilder()->identifyMetricByValueAndAppend('signature', $signatureVersion); + return SignatureProvider::resolve($provider, $signatureVersion, $name, $region); + }; + $this->handlerList->appendSign(Middleware::signer($this->credentialProvider, $resolver, $this->tokenProvider, $this->getConfig()), 'signer'); + } + private function addRequestCompressionMiddleware($config) + { + if (empty($config['disable_request_compression'])) { + $list = $this->getHandlerList(); + $list->appendBuild(RequestCompressionMiddleware::wrap($config), 'request-compression'); + } + } + private function addQueryCompatibleInputMiddleware(Service $api) + { + $list = $this->getHandlerList(); + $list->appendValidate(QueryCompatibleInputMiddleware::wrap($api), 'query-compatible-input'); + } + private function addQueryModeHeader() : void + { + $list = $this->getHandlerList(); + $list->appendBuild(Middleware::mapRequest(function (RequestInterface $r) { + return $r->withHeader('x-amzn-query-mode', \true); + }), 'x-amzn-query-mode-header'); + } + private function addInvocationId() + { + // Add invocation id to each request + $this->handlerList->prependSign(Middleware::invocationId(), 'invocation-id'); + } + private function loadAliases($file = null) + { + if (!isset($this->aliases)) { + if (\is_null($file)) { + $file = __DIR__ . '/data/aliases.json'; + } + $aliases = \WPMailSMTP\Vendor\Aws\load_compiled_json($file); + $serviceId = $this->api->getServiceId(); + $version = $this->getApi()->getApiVersion(); + if (!empty($aliases['operations'][$serviceId][$version])) { + $this->aliases = \array_flip($aliases['operations'][$serviceId][$version]); + } + } + } + private function addStreamRequestPayload() + { + $streamRequestPayloadMiddleware = StreamRequestPayloadMiddleware::wrap($this->api); + $this->handlerList->prependSign($streamRequestPayloadMiddleware, 'StreamRequestPayloadMiddleware'); + } + private function addRecursionDetection() + { + // Add recursion detection header to requests + // originating in supported Lambda runtimes + $this->handlerList->appendBuild(Middleware::recursionDetection(), 'recursion-detection'); + } + private function addAuthSelectionMiddleware() + { + $list = $this->getHandlerList(); + $list->prependBuild(AuthSelectionMiddleware::wrap($this->authSchemeResolver, $this->getApi()), 'auth-selection'); + } + private function addEndpointV2Middleware() + { + $list = $this->getHandlerList(); + $endpointArgs = $this->getEndpointProviderArgs(); + $list->prependBuild(EndpointV2Middleware::wrap($this->endpointProvider, $this->getApi(), $endpointArgs, $this->credentialProvider), 'endpoint-resolution'); + } + /** + * Appends the user agent middleware. + * This middleware MUST be appended after the + * signature middleware `addSignatureMiddleware`, + * so that metrics around signatures are properly + * captured. + * + * @param $args + * @return void + */ + private function addUserAgentMiddleware($args) + { + $this->getHandlerList()->appendSign(UserAgentMiddleware::wrap($args), 'user-agent'); + } + /** + * Retrieves client context param definition from service model, + * creates mapping of client context param names with client-provided + * values. + * + * @return array + */ + private function setClientContextParams($args) + { + $api = $this->getApi(); + $resolvedParams = []; + if (!empty($paramDefinitions = $api->getClientContextParams())) { + foreach ($paramDefinitions as $paramName => $paramValue) { + if (isset($args[$paramName])) { + $resolvedParams[$paramName] = $args[$paramName]; + } + } + } + return $resolvedParams; + } + /** + * Retrieves and sets default values used for endpoint resolution. + */ + private function setClientBuiltIns($args, $resolvedConfig) + { + $builtIns = []; + $config = $resolvedConfig['config']; + $service = $args['service']; + $builtIns['SDK::Endpoint'] = null; + if (!empty($args['endpoint'])) { + $builtIns['SDK::Endpoint'] = $args['endpoint']; + } elseif (isset($config['configured_endpoint_url'])) { + $builtIns['SDK::Endpoint'] = (string) $this->getEndpoint(); + } + $builtIns['AWS::Region'] = $this->getRegion(); + $builtIns['AWS::UseFIPS'] = $config['use_fips_endpoint']->isUseFipsEndpoint(); + $builtIns['AWS::UseDualStack'] = $config['use_dual_stack_endpoint']->isUseDualstackEndpoint(); + if ($service === 's3' || $service === 's3control') { + $builtIns['AWS::S3::UseArnRegion'] = $config['use_arn_region']->isUseArnRegion(); + } + if ($service === 's3') { + $builtIns['AWS::S3::UseArnRegion'] = $config['use_arn_region']->isUseArnRegion(); + $builtIns['AWS::S3::Accelerate'] = $config['use_accelerate_endpoint']; + $builtIns['AWS::S3::ForcePathStyle'] = $config['use_path_style_endpoint']; + $builtIns['AWS::S3::DisableMultiRegionAccessPoints'] = $config['disable_multiregion_access_points']; + } + $builtIns['AWS::Auth::AccountIdEndpointMode'] = $resolvedConfig['account_id_endpoint_mode']; + $this->clientBuiltIns += $builtIns; + } + /** + * Retrieves arguments to be used in endpoint resolution. + * + * @return array + */ + public function getEndpointProviderArgs() + { + return $this->normalizeEndpointProviderArgs(); + } + /** + * Combines built-in and client context parameter values in + * order of specificity. Client context parameter values supersede + * built-in values. + * + * @return array + */ + private function normalizeEndpointProviderArgs() + { + $normalizedBuiltIns = []; + foreach ($this->clientBuiltIns as $name => $value) { + $normalizedName = \explode('::', $name); + $normalizedName = $normalizedName[\count($normalizedName) - 1]; + $normalizedBuiltIns[$normalizedName] = $value; + } + return \array_merge($normalizedBuiltIns, $this->getClientContextParams()); + } + protected function isUseEndpointV2() + { + return $this->endpointProvider instanceof EndpointProviderV2; + } + public static function emitDeprecationWarning() + { + \trigger_error("This method is deprecated. It will be removed in an upcoming release.", \E_USER_DEPRECATED); + $phpVersion = \PHP_VERSION_ID; + if ($phpVersion < 70205) { + $phpVersionString = \phpversion(); + @\trigger_error("This installation of the SDK is using PHP version" . " {$phpVersionString}, which will be deprecated on August" . " 15th, 2023. Please upgrade your PHP version to a minimum of" . " 7.2.5 before then to continue receiving updates to the AWS" . " SDK for PHP. To disable this warning, set" . " suppress_php_deprecation_warning to true on the client constructor" . " or set the environment variable AWS_SUPPRESS_PHP_DEPRECATION_WARNING" . " to true.", \E_USER_DEPRECATED); + } + } + /** + * Returns a service model and doc model with any necessary changes + * applied. + * + * @param array $api Array of service data being documented. + * @param array $docs Array of doc model data. + * + * @return array Tuple containing a [Service, DocModel] + * + * @internal This should only used to document the service API. + * @codeCoverageIgnore + */ + public static function applyDocFilters(array $api, array $docs) + { + $aliases = \WPMailSMTP\Vendor\Aws\load_compiled_json(__DIR__ . '/data/aliases.json'); + $serviceId = $api['metadata']['serviceId'] ?? ''; + $version = $api['metadata']['apiVersion']; + // Replace names for any operations with SDK aliases + if (!empty($aliases['operations'][$serviceId][$version])) { + foreach ($aliases['operations'][$serviceId][$version] as $op => $alias) { + $api['operations'][$alias] = $api['operations'][$op]; + $docs['operations'][$alias] = $docs['operations'][$op]; + unset($api['operations'][$op], $docs['operations'][$op]); + } + } + \ksort($api['operations']); + return [new Service($api, ApiProvider::defaultProvider()), new DocModel($docs)]; + } + /** + * @deprecated + * @return static + */ + public static function factory(array $config = []) + { + return new static($config); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientInterface.php new file mode 100644 index 00000000..e86e62c9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientInterface.php @@ -0,0 +1,156 @@ +getWaiter('foo', ['bar' => 'baz']); + * $waiter->promise()->then(function () { echo 'Done!'; }); + * + * @param string|callable $name Name of the waiter that defines the wait + * configuration and conditions. + * @param array $args Args to be used with each command executed + * by the waiter. Waiter configuration options + * can be provided in an associative array in + * the @waiter key. + * @return \Aws\Waiter + * @throws \UnexpectedValueException if the waiter is invalid. + */ + public function getWaiter($name, array $args = []); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientTrait.php new file mode 100644 index 00000000..9f8a734f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/AwsClientTrait.php @@ -0,0 +1,76 @@ +getApi()->getPaginatorConfig($name); + return new ResultPaginator($this, $name, $args, $config); + } + public function getIterator($name, array $args = []) + { + $config = $this->getApi()->getPaginatorConfig($name); + if (!$config['result_key']) { + throw new \UnexpectedValueException(\sprintf('There are no resources to iterate for the %s operation of %s', $name, $this->getApi()['serviceFullName'])); + } + $key = \is_array($config['result_key']) ? $config['result_key'][0] : $config['result_key']; + if ($config['output_token'] && $config['input_token']) { + return $this->getPaginator($name, $args)->search($key); + } + $result = $this->execute($this->getCommand($name, $args))->search($key); + return new \ArrayIterator((array) $result); + } + public function waitUntil($name, array $args = []) + { + return $this->getWaiter($name, $args)->promise()->wait(); + } + public function getWaiter($name, array $args = []) + { + $config = isset($args['@waiter']) ? $args['@waiter'] : []; + $config += $this->getApi()->getWaiterConfig($name); + return new Waiter($this, $name, $args, $config); + } + public function execute(CommandInterface $command) + { + return $this->executeAsync($command)->wait(); + } + public function executeAsync(CommandInterface $command) + { + $handler = $command->getHandlerList()->resolve(); + return $handler($command); + } + public function __call($name, array $args) + { + if (\substr($name, -5) === 'Async') { + $name = \substr($name, 0, -5); + $isAsync = \true; + } + if (!empty($this->aliases[\ucfirst($name)])) { + $name = $this->aliases[\ucfirst($name)]; + } + $params = isset($args[0]) ? $args[0] : []; + if (!empty($isAsync)) { + return $this->executeAsync($this->getCommand($name, $params)); + } + return $this->execute($this->getCommand($name, $params)); + } + /** + * @param string $name + * @param array $args + * + * @return CommandInterface + */ + public abstract function getCommand($name, array $args = []); + /** + * @return Service + */ + public abstract function getApi(); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CacheInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CacheInterface.php new file mode 100644 index 00000000..1bb44a01 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CacheInterface.php @@ -0,0 +1,33 @@ + 'is_resource', 'callable' => 'is_callable', 'int' => 'is_int', 'bool' => 'is_bool', 'boolean' => 'is_bool', 'string' => 'is_string', 'object' => 'is_object', 'array' => 'is_array']; + private static $defaultArgs = ['service' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'Name of the service to utilize. This value will be supplied by default when using one of the SDK clients (e.g., Aws\\S3\\S3Client).', 'required' => \true, 'internal' => \true], 'exception_class' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'Exception class to create when an error occurs.', 'default' => AwsException::class, 'internal' => \true], 'scheme' => ['type' => 'value', 'valid' => ['string'], 'default' => 'https', 'doc' => 'URI scheme to use when connecting connect. The SDK will utilize "https" endpoints (i.e., utilize SSL/TLS connections) by default. You can attempt to connect to a service over an unencrypted "http" endpoint by setting ``scheme`` to "http".'], 'disable_host_prefix_injection' => ['type' => 'value', 'valid' => ['bool'], 'doc' => 'Set to true to disable host prefix injection logic for services that use it. This disables the entire prefix injection, including the portions supplied by user-defined parameters. Setting this flag will have no effect on services that do not use host prefix injection.', 'default' => \false], 'ignore_configured_endpoint_urls' => ['type' => 'value', 'valid' => ['bool'], 'doc' => 'Set to true to disable endpoint urls configured using `AWS_ENDPOINT_URL` and `endpoint_url` shared config option.', 'fn' => [__CLASS__, '_apply_ignore_configured_endpoint_urls'], 'default' => self::DEFAULT_FROM_ENV_INI], 'endpoint' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'The full URI of the webservice. This is only required when connecting to a custom endpoint (e.g., a local version of S3).', 'fn' => [__CLASS__, '_apply_endpoint'], 'default' => [__CLASS__, '_default_endpoint']], 'region' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'Region to connect to. See http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of available regions.', 'fn' => [__CLASS__, '_apply_region'], 'default' => self::DEFAULT_FROM_ENV_INI], 'version' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'The version of the webservice to utilize (e.g., 2006-03-01).', 'default' => 'latest'], 'signature_provider' => ['type' => 'value', 'valid' => ['callable'], 'doc' => 'A callable that accepts a signature version name (e.g., "v4"), a service name, and region, and returns a SignatureInterface object or null. This provider is used to create signers utilized by the client. See Aws\\Signature\\SignatureProvider for a list of built-in providers', 'default' => [__CLASS__, '_default_signature_provider']], 'api_provider' => ['type' => 'value', 'valid' => ['callable'], 'doc' => 'An optional PHP callable that accepts a type, service, and version argument, and returns an array of corresponding configuration data. The type value can be one of api, waiter, or paginator.', 'fn' => [__CLASS__, '_apply_api_provider'], 'default' => [ApiProvider::class, 'defaultProvider']], 'configuration_mode' => ['type' => 'value', 'valid' => [ConfigModeInterface::class, CacheInterface::class, 'string', 'closure'], 'doc' => "Sets the default configuration mode. Otherwise provide an instance of Aws\\DefaultsMode\\ConfigurationInterface, an instance of Aws\\CacheInterface, or a string containing a valid mode", 'fn' => [__CLASS__, '_apply_defaults'], 'default' => [ConfigModeProvider::class, 'defaultProvider']], 'use_fips_endpoint' => ['type' => 'value', 'valid' => ['bool', UseFipsEndpointConfiguration::class, CacheInterface::class, 'callable'], 'doc' => 'Set to true to enable the use of FIPS pseudo regions', 'fn' => [__CLASS__, '_apply_use_fips_endpoint'], 'default' => [__CLASS__, '_default_use_fips_endpoint']], 'use_dual_stack_endpoint' => ['type' => 'value', 'valid' => ['bool', UseDualStackEndpointConfiguration::class, CacheInterface::class, 'callable'], 'doc' => 'Set to true to enable the use of dual-stack endpoints', 'fn' => [__CLASS__, '_apply_use_dual_stack_endpoint'], 'default' => [__CLASS__, '_default_use_dual_stack_endpoint']], 'endpoint_provider' => ['type' => 'value', 'valid' => ['callable', EndpointV2\EndpointProviderV2::class], 'fn' => [__CLASS__, '_apply_endpoint_provider'], 'doc' => 'An optional PHP callable that accepts a hash of options including a "service" and "region" key and returns NULL or a hash of endpoint data, of which the "endpoint" key is required. See Aws\\Endpoint\\EndpointProvider for a list of built-in providers.', 'default' => [__CLASS__, '_default_endpoint_provider']], 'serializer' => ['default' => [__CLASS__, '_default_serializer'], 'fn' => [__CLASS__, '_apply_serializer'], 'internal' => \true, 'type' => 'value', 'valid' => ['callable']], 'signature_version' => ['type' => 'config', 'valid' => ['string'], 'doc' => 'A string representing a custom signature version to use with a service (e.g., v4). Note that per/operation signature version MAY override this requested signature version.', 'default' => [__CLASS__, '_default_signature_version']], 'signing_name' => ['type' => 'config', 'valid' => ['string'], 'doc' => 'A string representing a custom service name to be used when calculating a request signature.', 'default' => [__CLASS__, '_default_signing_name']], 'signing_region' => ['type' => 'config', 'valid' => ['string'], 'doc' => 'A string representing a custom region name to be used when calculating a request signature.', 'default' => [__CLASS__, '_default_signing_region']], 'profile' => ['type' => 'config', 'valid' => ['string'], 'doc' => 'Allows you to specify which profile to use when credentials are created from the AWS credentials file in your HOME directory. This setting overrides the AWS_PROFILE environment variable. Note: Specifying "profile" will cause the "credentials" and "use_aws_shared_config_files" keys to be ignored.', 'fn' => [__CLASS__, '_apply_profile']], 'credentials' => ['type' => 'value', 'valid' => [CredentialsInterface::class, CacheInterface::class, 'array', 'bool', 'callable'], 'doc' => 'Specifies the credentials used to sign requests. Provide an Aws\\Credentials\\CredentialsInterface object, an associative array of "key", "secret", and an optional "token" key, `false` to use null credentials, or a callable credentials provider used to create credentials or return null. See Aws\\Credentials\\CredentialProvider for a list of built-in credentials providers. If no credentials are provided, the SDK will attempt to load them from the environment.', 'fn' => [__CLASS__, '_apply_credentials'], 'default' => [__CLASS__, '_default_credential_provider']], 'token' => ['type' => 'value', 'valid' => [TokenInterface::class, CacheInterface::class, 'array', 'bool', 'callable'], 'doc' => 'Specifies the token used to authorize requests. Provide an Aws\\Token\\TokenInterface object, an associative array of "token", and an optional "expiration" key, `false` to use a null token, or a callable token provider used to fetch a token or return null. See Aws\\Token\\TokenProvider for a list of built-in credentials providers. If no token is provided, the SDK will attempt to load one from the environment.', 'fn' => [__CLASS__, '_apply_token'], 'default' => [__CLASS__, '_default_token_provider']], 'auth_scheme_resolver' => ['type' => 'value', 'valid' => [AuthSchemeResolverInterface::class], 'doc' => 'An instance of Aws\\Auth\\AuthSchemeResolverInterface which selects a modeled auth scheme and returns a signature version', 'default' => [__CLASS__, '_default_auth_scheme_resolver']], 'endpoint_discovery' => ['type' => 'value', 'valid' => [ConfigurationInterface::class, CacheInterface::class, 'array', 'callable'], 'doc' => 'Specifies settings for endpoint discovery. Provide an instance of Aws\\EndpointDiscovery\\ConfigurationInterface, an instance Aws\\CacheInterface, a callable that provides a promise for a Configuration object, or an associative array with the following keys: enabled: (bool) Set to true to enable endpoint discovery, false to explicitly disable it. Defaults to false; cache_limit: (int) The maximum number of keys in the endpoints cache. Defaults to 1000.', 'fn' => [__CLASS__, '_apply_endpoint_discovery'], 'default' => [__CLASS__, '_default_endpoint_discovery_provider']], 'stats' => ['type' => 'value', 'valid' => ['bool', 'array'], 'default' => \false, 'doc' => 'Set to true to gather transfer statistics on requests sent. Alternatively, you can provide an associative array with the following keys: retries: (bool) Set to false to disable reporting on retries attempted; http: (bool) Set to true to enable collecting statistics from lower level HTTP adapters (e.g., values returned in GuzzleHttp\\TransferStats). HTTP handlers must support an http_stats_receiver option for this to have an effect; timer: (bool) Set to true to enable a command timer that reports the total wall clock time spent on an operation in seconds.', 'fn' => [__CLASS__, '_apply_stats']], 'retries' => ['type' => 'value', 'valid' => ['int', RetryConfigInterface::class, CacheInterface::class, 'callable', 'array'], 'doc' => "Configures the retry mode and maximum number of allowed retries for a client (pass 0 to disable retries). Provide an integer for 'legacy' mode with the specified number of retries. Otherwise provide an instance of Aws\\Retry\\ConfigurationInterface, an instance of Aws\\CacheInterface, a callable function, or an array with the following keys: mode: (string) Set to 'legacy', 'standard' (uses retry quota management), or 'adapative' (an experimental mode that adds client-side rate limiting to standard mode); max_attempts: (int) The maximum number of attempts for a given request. ", 'fn' => [__CLASS__, '_apply_retries'], 'default' => [RetryConfigProvider::class, 'defaultProvider']], 'validate' => ['type' => 'value', 'valid' => ['bool', 'array'], 'default' => \true, 'doc' => 'Set to false to disable client-side parameter validation. Set to true to utilize default validation constraints. Set to an associative array of validation options to enable specific validation constraints.', 'fn' => [__CLASS__, '_apply_validate']], 'debug' => ['type' => 'value', 'valid' => ['bool', 'array'], 'doc' => 'Set to true to display debug information when sending requests. Alternatively, you can provide an associative array with the following keys: logfn: (callable) Function that is invoked with log messages; stream_size: (int) When the size of a stream is greater than this number, the stream data will not be logged (set to "0" to not log any stream data); scrub_auth: (bool) Set to false to disable the scrubbing of auth data from the logged messages; http: (bool) Set to false to disable the "debug" feature of lower level HTTP adapters (e.g., verbose curl output).', 'fn' => [__CLASS__, '_apply_debug']], 'disable_request_compression' => ['type' => 'value', 'valid' => ['bool', 'callable'], 'doc' => 'Set to true to disable request compression for supported operations', 'fn' => [__CLASS__, '_apply_disable_request_compression'], 'default' => self::DEFAULT_FROM_ENV_INI], 'request_min_compression_size_bytes' => ['type' => 'value', 'valid' => ['int', 'callable'], 'doc' => 'Set to a value between between 0 and 10485760 bytes, inclusive. This value will be ignored if `disable_request_compression` is set to `true`', 'fn' => [__CLASS__, '_apply_min_compression_size'], 'default' => [__CLASS__, '_default_min_compression_size']], 'csm' => ['type' => 'value', 'valid' => [\WPMailSMTP\Vendor\Aws\ClientSideMonitoring\ConfigurationInterface::class, 'callable', 'array', 'bool'], 'doc' => 'CSM options for the client. Provides a callable wrapping a promise, a boolean "false", an instance of ConfigurationInterface, or an associative array of "enabled", "host", "port", and "client_id".', 'fn' => [__CLASS__, '_apply_csm'], 'default' => [\WPMailSMTP\Vendor\Aws\ClientSideMonitoring\ConfigurationProvider::class, 'defaultProvider']], 'http' => ['type' => 'value', 'valid' => ['array'], 'default' => [], 'doc' => 'Set to an array of SDK request options to apply to each request (e.g., proxy, verify, etc.).'], 'http_handler' => ['type' => 'value', 'valid' => ['callable'], 'doc' => 'An HTTP handler is a function that accepts a PSR-7 request object and returns a promise that is fulfilled with a PSR-7 response object or rejected with an array of exception data. NOTE: This option supersedes any provided "handler" option.', 'fn' => [__CLASS__, '_apply_http_handler']], 'handler' => ['type' => 'value', 'valid' => ['callable'], 'doc' => 'A handler that accepts a command object, request object and returns a promise that is fulfilled with an Aws\\ResultInterface object or rejected with an Aws\\Exception\\AwsException. A handler does not accept a next handler as it is terminal and expected to fulfill a command. If no handler is provided, a default Guzzle handler will be utilized.', 'fn' => [__CLASS__, '_apply_handler'], 'default' => [__CLASS__, '_default_handler']], 'app_id' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'app_id(AppId) is an optional application specific identifier that can be set. + When set it will be appended to the User-Agent header of every request in the form of App/{AppId}. + This value is also sourced from environment variable AWS_SDK_UA_APP_ID or the shared config profile attribute sdk_ua_app_id.', 'fn' => [__CLASS__, '_apply_app_id'], 'default' => [__CLASS__, '_default_app_id']], 'ua_append' => ['type' => 'value', 'valid' => ['string', 'array'], 'doc' => 'Provide a string or array of strings to send in the User-Agent header.', 'fn' => [__CLASS__, '_apply_user_agent'], 'default' => []], 'idempotency_auto_fill' => ['type' => 'value', 'valid' => ['bool', 'callable'], 'doc' => 'Set to false to disable SDK to populate parameters that enabled \'idempotencyToken\' trait with a random UUID v4 value on your behalf. Using default value \'true\' still allows parameter value to be overwritten when provided. Note: auto-fill only works when cryptographically secure random bytes generator functions(random_bytes, openssl_random_pseudo_bytes or mcrypt_create_iv) can be found. You may also provide a callable source of random bytes.', 'default' => \true, 'fn' => [__CLASS__, '_apply_idempotency_auto_fill']], 'use_aws_shared_config_files' => ['type' => 'value', 'valid' => ['bool'], 'doc' => 'Set to false to disable checking for shared aws config files usually located in \'~/.aws/config\' and \'~/.aws/credentials\'. This will be ignored if you set the \'profile\' setting.', 'default' => \true], 'suppress_php_deprecation_warning' => ['type' => 'value', 'valid' => ['bool'], 'doc' => 'Set to true to suppress PHP runtime deprecation warnings. The current deprecation campaign is PHP versions 8.0.x and below, taking effect on 1/13/2025.', 'default' => \false, 'fn' => [__CLASS__, '_apply_suppress_php_deprecation_warning']], 'account_id_endpoint_mode' => ['type' => 'value', 'valid' => ['string'], 'doc' => 'Decides whether account_id must a be a required resolved credentials property. If this configuration is set to disabled, then account_id is not required. If set to preferred a warning will be logged when account_id is not resolved, and when set to required an exception will be thrown if account_id is not resolved.', 'default' => [__CLASS__, '_default_account_id_endpoint_mode'], 'fn' => [__CLASS__, '_apply_account_id_endpoint_mode']], 'sigv4a_signing_region_set' => ['type' => 'value', 'valid' => ['string', 'array'], 'doc' => 'A comma-delimited list of supported regions sent in sigv4a requests.', 'fn' => [__CLASS__, '_apply_sigv4a_signing_region_set'], 'default' => self::DEFAULT_FROM_ENV_INI]]; + /** + * Gets an array of default client arguments, each argument containing a + * hash of the following: + * + * - type: (string, required) option type described as follows: + * - value: The default option type. + * - config: The provided value is made available in the client's + * getConfig() method. + * - valid: (array, required) Valid PHP types or class names. Note: null + * is not an allowed type. + * - required: (bool, callable) Whether or not the argument is required. + * Provide a function that accepts an array of arguments and returns a + * string to provide a custom error message. + * - default: (mixed) The default value of the argument if not provided. If + * a function is provided, then it will be invoked to provide a default + * value. The function is provided the array of options and is expected + * to return the default value of the option. The default value can be a + * closure and can not be a callable string that is not part of the + * defaultArgs array. + * - doc: (string) The argument documentation string. + * - fn: (callable) Function used to apply the argument. The function + * accepts the provided value, array of arguments by reference, and an + * event emitter. + * + * Note: Order is honored and important when applying arguments. + * + * @return array + */ + public static function getDefaultArguments() + { + return self::$defaultArgs; + } + /** + * @param array $argDefinitions Client arguments. + */ + public function __construct(array $argDefinitions) + { + $this->argDefinitions = $argDefinitions; + } + /** + * Resolves client configuration options and attached event listeners. + * Check for missing keys in passed arguments + * + * @param array $args Provided constructor arguments. + * @param HandlerList $list Handler list to augment. + * + * @return array Returns the array of provided options. + * @throws \InvalidArgumentException + * @see Aws\AwsClient::__construct for a list of available options. + */ + public function resolve(array $args, HandlerList $list) + { + $args['config'] = []; + foreach ($this->argDefinitions as $key => $a) { + // Add defaults, validate required values, and skip if not set. + if (!isset($args[$key])) { + if (isset($a['default'])) { + // Merge defaults in when not present. + if (\is_callable($a['default']) && (\is_array($a['default']) || $a['default'] instanceof \Closure)) { + if ($a['default'] === self::DEFAULT_FROM_ENV_INI) { + $args[$key] = $a['default']($key, $a['valid'][0] ?? 'string', $args); + } else { + $args[$key] = $a['default']($args); + } + } else { + $args[$key] = $a['default']; + } + } elseif (empty($a['required'])) { + continue; + } else { + $this->throwRequired($args); + } + } + // Validate the types against the provided value. + foreach ($a['valid'] as $check) { + if (isset(self::$typeMap[$check])) { + $fn = self::$typeMap[$check]; + if ($fn($args[$key])) { + goto is_valid; + } + } elseif ($args[$key] instanceof $check) { + goto is_valid; + } + } + $this->invalidType($key, $args[$key]); + // Apply the value + is_valid: + if (isset($a['fn'])) { + $a['fn']($args[$key], $args, $list); + } + if ($a['type'] === 'config') { + $args['config'][$key] = $args[$key]; + } + } + $this->_apply_client_context_params($args); + return $args; + } + /** + * Creates a verbose error message for an invalid argument. + * + * @param string $name Name of the argument that is missing. + * @param array $args Provided arguments + * @param bool $useRequired Set to true to show the required fn text if + * available instead of the documentation. + * @return string + */ + private function getArgMessage($name, $args = [], $useRequired = \false) + { + $arg = $this->argDefinitions[$name]; + $msg = ''; + $modifiers = []; + if (isset($arg['valid'])) { + $modifiers[] = \implode('|', $arg['valid']); + } + if (isset($arg['choice'])) { + $modifiers[] = 'One of ' . \implode(', ', $arg['choice']); + } + if ($modifiers) { + $msg .= '(' . \implode('; ', $modifiers) . ')'; + } + $msg = \wordwrap("{$name}: {$msg}", 75, "\n "); + if ($useRequired && \is_callable($arg['required'])) { + $msg .= "\n\n "; + $msg .= \str_replace("\n", "\n ", \call_user_func($arg['required'], $args)); + } elseif (isset($arg['doc'])) { + $msg .= \wordwrap("\n\n {$arg['doc']}", 75, "\n "); + } + return $msg; + } + /** + * Throw when an invalid type is encountered. + * + * @param string $name Name of the value being validated. + * @param mixed $provided The provided value. + * @throws \InvalidArgumentException + */ + private function invalidType($name, $provided) + { + $expected = \implode('|', $this->argDefinitions[$name]['valid']); + $msg = "Invalid configuration value " . "provided for \"{$name}\". Expected {$expected}, but got " . describe_type($provided) . "\n\n" . $this->getArgMessage($name); + throw new IAE($msg); + } + /** + * Throws an exception for missing required arguments. + * + * @param array $args Passed in arguments. + * @throws \InvalidArgumentException + */ + private function throwRequired(array $args) + { + $missing = []; + foreach ($this->argDefinitions as $k => $a) { + if (empty($a['required']) || isset($a['default']) || isset($args[$k])) { + continue; + } + $missing[] = $this->getArgMessage($k, $args, \true); + } + $msg = "Missing required client configuration options: \n\n"; + $msg .= \implode("\n\n", $missing); + throw new IAE($msg); + } + public static function _apply_retries($value, array &$args, HandlerList $list) + { + // A value of 0 for the config option disables retries + if ($value) { + $config = RetryConfigProvider::unwrap($value); + if ($config->getMode() === 'legacy') { + // # of retries is 1 less than # of attempts + $decider = RetryMiddleware::createDefaultDecider($config->getMaxAttempts() - 1); + $list->appendSign(Middleware::retry($decider, null, $args['stats']['retries']), 'retry'); + } else { + $list->appendSign(RetryMiddlewareV2::wrap($config, ['collect_stats' => $args['stats']['retries']]), 'retry'); + } + } + } + public static function _apply_defaults($value, array &$args, HandlerList $list) + { + $config = ConfigModeProvider::unwrap($value); + if ($config->getMode() !== 'legacy') { + if (!isset($args['retries']) && !\is_null($config->getRetryMode())) { + $args['retries'] = ['mode' => $config->getRetryMode()]; + } + if (!isset($args['sts_regional_endpoints']) && !\is_null($config->getStsRegionalEndpoints())) { + $args['sts_regional_endpoints'] = ['mode' => $config->getStsRegionalEndpoints()]; + } + if (!isset($args['s3_us_east_1_regional_endpoint']) && !\is_null($config->getS3UsEast1RegionalEndpoints())) { + $args['s3_us_east_1_regional_endpoint'] = ['mode' => $config->getS3UsEast1RegionalEndpoints()]; + } + if (!isset($args['http'])) { + $args['http'] = []; + } + if (!isset($args['http']['connect_timeout']) && !\is_null($config->getConnectTimeoutInMillis())) { + $args['http']['connect_timeout'] = $config->getConnectTimeoutInMillis() / 1000; + } + if (!isset($args['http']['timeout']) && !\is_null($config->getHttpRequestTimeoutInMillis())) { + $args['http']['timeout'] = $config->getHttpRequestTimeoutInMillis() / 1000; + } + } + } + public static function _apply_disable_request_compression($value, array &$args) + { + if (\is_callable($value)) { + $value = $value(); + } + if (!\is_bool($value)) { + throw new IAE("Invalid configuration value provided for 'disable_request_compression'." . " value must be a bool."); + } + $args['config']['disable_request_compression'] = $value; + } + public static function _apply_min_compression_size($value, array &$args) + { + if (\is_callable($value)) { + $value = $value(); + } + if (!\is_int($value) || \is_int($value) && ($value < 0 || $value > 10485760)) { + throw new IAE(" Invalid configuration value provided for 'min_compression_size_bytes'." . " value must be an integer between 0 and 10485760, inclusive."); + } + $args['config']['request_min_compression_size_bytes'] = $value; + } + public static function _default_min_compression_size(array &$args) + { + return ConfigurationResolver::resolve('request_min_compression_size_bytes', 10240, 'int', $args); + } + public static function _apply_credentials($value, array &$args) + { + if (\is_callable($value)) { + return; + } + if ($value instanceof CredentialsInterface) { + $args['credentials'] = CredentialProvider::fromCredentials($value); + } elseif (\is_array($value) && isset($value['key']) && isset($value['secret'])) { + $args['credentials'] = CredentialProvider::fromCredentials(new Credentials($value['key'], $value['secret'], $value['token'] ?? null, $value['expires'] ?? null, $value['accountId'] ?? null)); + } elseif ($value === \false) { + $args['credentials'] = CredentialProvider::fromCredentials(new Credentials('', '')); + $args['config']['signature_version'] = 'anonymous'; + $args['config']['configured_signature_version'] = \true; + } elseif ($value instanceof CacheInterface) { + $args['credentials'] = CredentialProvider::defaultProvider($args); + } else { + throw new IAE('Credentials must be an instance of ' . "'" . CredentialsInterface::class . ', an associative ' . 'array that contains "key", "secret", and an optional "token" ' . 'key-value pairs, a credentials provider function, or false.'); + } + } + public static function _default_credential_provider(array $args) + { + return CredentialProvider::defaultProvider($args); + } + public static function _apply_token($value, array &$args) + { + if (\is_callable($value)) { + return; + } + if ($value instanceof Token) { + $args['token'] = TokenProvider::fromToken($value); + } elseif (\is_array($value) && isset($value['token'])) { + $args['token'] = TokenProvider::fromToken(new Token($value['token'], $value['expires'] ?? null)); + } elseif ($value instanceof CacheInterface) { + $args['token'] = TokenProvider::defaultProvider($args); + } else { + throw new IAE('Token must be an instance of ' . TokenInterface::class . ', an associative ' . 'array that contains "token" and an optional "expires" ' . 'key-value pairs, a token provider function, or false.'); + } + } + public static function _default_token_provider(array $args) + { + return TokenProvider::defaultProvider($args); + } + public static function _apply_csm($value, array &$args, HandlerList $list) + { + if ($value === \false) { + $value = new Configuration(\false, \WPMailSMTP\Vendor\Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_HOST, \WPMailSMTP\Vendor\Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_PORT, \WPMailSMTP\Vendor\Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_CLIENT_ID); + $args['csm'] = $value; + } + $list->appendBuild(ApiCallMonitoringMiddleware::wrap($args['credentials'], $value, $args['region'], $args['api']->getServiceId()), 'ApiCallMonitoringMiddleware'); + $list->appendAttempt(ApiCallAttemptMonitoringMiddleware::wrap($args['credentials'], $value, $args['region'], $args['api']->getServiceId()), 'ApiCallAttemptMonitoringMiddleware'); + } + public static function _apply_api_provider(callable $value, array &$args) + { + $api = new Service(ApiProvider::resolve($value, 'api', $args['service'], $args['version']), $value); + if (empty($args['config']['signing_name']) && isset($api['metadata']['signingName'])) { + $args['config']['signing_name'] = $api['metadata']['signingName']; + } + $args['api'] = $api; + $args['parser'] = Service::createParser($api); + $args['error_parser'] = Service::createErrorParser($api->getProtocol(), $api); + } + public static function _apply_endpoint_provider($value, array &$args) + { + if (!isset($args['endpoint'])) { + if ($value instanceof \WPMailSMTP\Vendor\Aws\EndpointV2\EndpointProviderV2) { + $options = self::getEndpointProviderOptions($args); + $value = PartitionEndpointProvider::defaultProvider($options)->getPartition($args['region'], $args['service']); + } + $endpointPrefix = $args['api']['metadata']['endpointPrefix'] ?? $args['service']; + // Check region is a valid host label when it is being used to + // generate an endpoint + if (!self::isValidRegion($args['region'])) { + throw new InvalidRegionException('Region must be a valid RFC' . ' host label.'); + } + $serviceEndpoints = \is_array($value) && isset($value['services'][$args['service']]['endpoints']) ? $value['services'][$args['service']]['endpoints'] : null; + if (isset($serviceEndpoints[$args['region']]['deprecated'])) { + \trigger_error("The service " . $args['service'] . "has " . " deprecated the region " . $args['region'] . ".", \E_USER_WARNING); + } + $args['region'] = \WPMailSMTP\Vendor\Aws\strip_fips_pseudo_regions($args['region']); + // Invoke the endpoint provider and throw if it does not resolve. + $result = EndpointProvider::resolve($value, ['service' => $endpointPrefix, 'region' => $args['region'], 'scheme' => $args['scheme'], 'options' => self::getEndpointProviderOptions($args)]); + $args['endpoint'] = $result['endpoint']; + if (empty($args['config']['signature_version'])) { + if (isset($args['api']) && $args['api']->getSignatureVersion() == 'bearer') { + $args['config']['signature_version'] = 'bearer'; + } elseif (isset($result['signatureVersion'])) { + $args['config']['signature_version'] = $result['signatureVersion']; + } + } + if (empty($args['config']['signing_region']) && isset($result['signingRegion'])) { + $args['config']['signing_region'] = $result['signingRegion']; + } + if (empty($args['config']['signing_name']) && isset($result['signingName'])) { + $args['config']['signing_name'] = $result['signingName']; + } + } + } + public static function _apply_endpoint_discovery($value, array &$args) + { + $args['endpoint_discovery'] = $value; + } + public static function _default_endpoint_discovery_provider(array $args) + { + return ConfigurationProvider::defaultProvider($args); + } + public static function _apply_use_fips_endpoint($value, array &$args) + { + if ($value instanceof CacheInterface) { + $value = UseFipsConfigProvider::defaultProvider($args); + } + if (\is_callable($value)) { + $value = $value(); + } + if ($value instanceof PromiseInterface) { + $value = $value->wait(); + } + if ($value instanceof UseFipsEndpointConfigurationInterface) { + $args['config']['use_fips_endpoint'] = $value; + } else { + // The Configuration class itself will validate other inputs + $args['config']['use_fips_endpoint'] = new UseFipsEndpointConfiguration($value); + } + } + public static function _default_use_fips_endpoint(array &$args) + { + return UseFipsConfigProvider::defaultProvider($args); + } + public static function _apply_use_dual_stack_endpoint($value, array &$args) + { + if ($value instanceof CacheInterface) { + $value = UseDualStackConfigProvider::defaultProvider($args); + } + if (\is_callable($value)) { + $value = $value(); + } + if ($value instanceof PromiseInterface) { + $value = $value->wait(); + } + if ($value instanceof UseDualStackEndpointConfigurationInterface) { + $args['config']['use_dual_stack_endpoint'] = $value; + } else { + // The Configuration class itself will validate other inputs + $args['config']['use_dual_stack_endpoint'] = new UseDualStackEndpointConfiguration($value, $args['region']); + } + } + public static function _default_use_dual_stack_endpoint(array &$args) + { + return UseDualStackConfigProvider::defaultProvider($args); + } + public static function _apply_serializer($value, array &$args, HandlerList $list) + { + $list->prependBuild(Middleware::requestBuilder($value), 'builder'); + } + public static function _apply_debug($value, array &$args, HandlerList $list) + { + if ($value !== \false) { + $list->interpose(new TraceMiddleware($value === \true ? [] : $value, $args['api'])); + } + } + public static function _apply_stats($value, array &$args, HandlerList $list) + { + // Create an array of stat collectors that are disabled (set to false) + // by default. If the user has passed in true, enable all stat + // collectors. + $defaults = \array_fill_keys(['http', 'retries', 'timer'], $value === \true); + $args['stats'] = \is_array($value) ? \array_replace($defaults, $value) : $defaults; + if ($args['stats']['timer']) { + $list->prependInit(Middleware::timer(), 'timer'); + } + } + public static function _apply_profile($_, array &$args) + { + $args['credentials'] = CredentialProvider::ini($args['profile']); + } + public static function _apply_validate($value, array &$args, HandlerList $list) + { + if ($value === \false) { + return; + } + $validator = $value === \true ? new Validator() : new Validator($value); + $list->appendValidate(Middleware::validation($args['api'], $validator), 'validation'); + } + public static function _apply_handler($value, array &$args, HandlerList $list) + { + $list->setHandler($value); + } + public static function _default_handler(array &$args) + { + return new WrappedHttpHandler(default_http_handler(), $args['parser'], $args['error_parser'], $args['exception_class'], $args['stats']['http']); + } + public static function _apply_http_handler($value, array &$args, HandlerList $list) + { + $args['handler'] = new WrappedHttpHandler($value, $args['parser'], $args['error_parser'], $args['exception_class'], $args['stats']['http']); + } + public static function _apply_app_id($value, array &$args) + { + // AppId should not be longer than 50 chars + static $MAX_APP_ID_LENGTH = 50; + if (\strlen($value) > $MAX_APP_ID_LENGTH) { + \trigger_error("The provided or configured value for `AppId`, " . "which is an user agent parameter, exceeds the maximum length of " . "{$MAX_APP_ID_LENGTH} characters.", \E_USER_WARNING); + } + $args['app_id'] = $value; + } + public static function _default_app_id(array $args) + { + return ConfigurationResolver::resolve('sdk_ua_app_id', '', 'string', $args); + } + public static function _apply_user_agent($inputUserAgent, array &$args, HandlerList $list) : void + { + // Add endpoint discovery if set + $userAgent = []; + // Add the input to the end + if ($inputUserAgent) { + if (!\is_array($inputUserAgent)) { + $inputUserAgent = [$inputUserAgent]; + } + $inputUserAgent = \array_map('strval', $inputUserAgent); + $userAgent = \array_merge($userAgent, $inputUserAgent); + } + $args['ua_append'] = $userAgent; + $list->appendBuild(Middleware::mapRequest(function (RequestInterface $request) use($userAgent) { + return $request->withHeader('X-Amz-User-Agent', \implode(' ', \array_merge($userAgent, $request->getHeader('X-Amz-User-Agent')))); + })); + } + public static function _apply_endpoint($value, array &$args, HandlerList $list) + { + if (empty($value)) { + unset($args['endpoint']); + return; + } + $args['endpoint_override'] = \true; + $args['endpoint'] = $value; + } + public static function _apply_idempotency_auto_fill($value, array &$args, HandlerList $list) + { + $enabled = \false; + $generator = null; + if (\is_bool($value)) { + $enabled = $value; + } elseif (\is_callable($value)) { + $enabled = \true; + $generator = $value; + } + if ($enabled) { + $list->prependInit(IdempotencyTokenMiddleware::wrap($args['api'], $generator), 'idempotency_auto_fill'); + } + } + public static function _default_account_id_endpoint_mode($args) + { + return ConfigurationResolver::resolve('account_id_endpoint_mode', 'preferred', 'string', $args); + } + public static function _apply_account_id_endpoint_mode($value, array &$args) + { + static $accountIdEndpointModes = ['disabled', 'required', 'preferred']; + if (!\in_array($value, $accountIdEndpointModes)) { + throw new IAE("The value provided for the config account_id_endpoint_mode is invalid." . "Valid values are: " . \implode(", ", $accountIdEndpointModes)); + } + $args['account_id_endpoint_mode'] = $value; + } + public static function _default_endpoint_provider(array $args) + { + $service = $args['api'] ?? null; + $serviceName = isset($service) ? $service->getServiceName() : null; + $apiVersion = isset($service) ? $service->getApiVersion() : null; + if (self::isValidService($serviceName) && self::isValidApiVersion($serviceName, $apiVersion)) { + $ruleset = EndpointDefinitionProvider::getEndpointRuleset($service->getServiceName(), $service->getApiVersion()); + return new \WPMailSMTP\Vendor\Aws\EndpointV2\EndpointProviderV2($ruleset, EndpointDefinitionProvider::getPartitions()); + } + $options = self::getEndpointProviderOptions($args); + return PartitionEndpointProvider::defaultProvider($options)->getPartition($args['region'], $args['service']); + } + public static function _default_serializer(array $args) + { + return Service::createSerializer($args['api'], $args['endpoint']); + } + public static function _default_signature_provider() + { + return SignatureProvider::defaultProvider(); + } + public static function _default_auth_scheme_resolver(array $args) + { + return new AuthSchemeResolver($args['credentials'], $args['token']); + } + public static function _default_signature_version(array &$args) + { + if (isset($args['config']['signature_version'])) { + return $args['config']['signature_version']; + } + $args['__partition_result'] = isset($args['__partition_result']) ? isset($args['__partition_result']) : \call_user_func(PartitionEndpointProvider::defaultProvider(), ['service' => $args['service'], 'region' => $args['region']]); + return isset($args['__partition_result']['signatureVersion']) ? $args['__partition_result']['signatureVersion'] : $args['api']->getSignatureVersion(); + } + public static function _default_signing_name(array &$args) + { + if (isset($args['config']['signing_name'])) { + return $args['config']['signing_name']; + } + $args['__partition_result'] = isset($args['__partition_result']) ? isset($args['__partition_result']) : \call_user_func(PartitionEndpointProvider::defaultProvider(), ['service' => $args['service'], 'region' => $args['region']]); + if (isset($args['__partition_result']['signingName'])) { + return $args['__partition_result']['signingName']; + } + if ($signingName = $args['api']->getSigningName()) { + return $signingName; + } + return $args['service']; + } + public static function _default_signing_region(array &$args) + { + if (isset($args['config']['signing_region'])) { + return $args['config']['signing_region']; + } + $args['__partition_result'] = isset($args['__partition_result']) ? isset($args['__partition_result']) : \call_user_func(PartitionEndpointProvider::defaultProvider(), ['service' => $args['service'], 'region' => $args['region']]); + return $args['__partition_result']['signingRegion'] ?? $args['region']; + } + public static function _apply_ignore_configured_endpoint_urls($value, array &$args) + { + $args['config']['ignore_configured_endpoint_urls'] = $value; + } + public static function _apply_suppress_php_deprecation_warning($value, &$args) + { + if ($value) { + $args['suppress_php_deprecation_warning'] = \true; + } elseif (!empty(\getenv("AWS_SUPPRESS_PHP_DEPRECATION_WARNING"))) { + $args['suppress_php_deprecation_warning'] = \WPMailSMTP\Vendor\Aws\boolean_value(\getenv("AWS_SUPPRESS_PHP_DEPRECATION_WARNING")); + } elseif (!empty($_SERVER["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"])) { + $args['suppress_php_deprecation_warning'] = \WPMailSMTP\Vendor\Aws\boolean_value($_SERVER["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"]); + } elseif (!empty($_ENV["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"])) { + $args['suppress_php_deprecation_warning'] = \WPMailSMTP\Vendor\Aws\boolean_value($_ENV["AWS_SUPPRESS_PHP_DEPRECATION_WARNING"]); + } + if ($args['suppress_php_deprecation_warning'] === \false && \PHP_VERSION_ID < 80100) { + self::emitDeprecationWarning(); + } + } + public static function _default_endpoint(array &$args) + { + if ($args['config']['ignore_configured_endpoint_urls'] || !self::isValidService($args['service'])) { + return ''; + } + $serviceIdentifier = \WPMailSMTP\Vendor\Aws\manifest($args['service'])['serviceIdentifier']; + $value = ConfigurationResolver::resolve('endpoint_url_' . $serviceIdentifier, '', 'string', $args + ['ini_resolver_options' => ['section' => 'services', 'subsection' => $serviceIdentifier, 'key' => 'endpoint_url']]); + if (empty($value)) { + $value = ConfigurationResolver::resolve('endpoint_url', '', 'string', $args); + } + if (!empty($value)) { + $args['config']['configured_endpoint_url'] = \true; + } + return $value; + } + public static function _apply_sigv4a_signing_region_set($value, array &$args) + { + if (empty($value)) { + $args['sigv4a_signing_region_set'] = null; + } elseif (\is_array($value)) { + $args['sigv4a_signing_region_set'] = \implode(', ', $value); + } else { + $args['sigv4a_signing_region_set'] = $value; + } + } + public static function _apply_region($value, array &$args) + { + if (empty($value)) { + self::_missing_region($args); + } + $args['region'] = $value; + } + public static function _missing_region(array $args) + { + $service = $args['service'] ?? ''; + $msg = << 0, 'bool' => \false, 'boolean' => \false, 'string' => '']; + return ConfigurationResolver::resolve($key, $typeDefaultMap[$expectedType] ?? '', $expectedType, $args); + } + /** + * Extracts client options for the endpoint provider to its own array + * + * @param array $args + * @return array + */ + private static function getEndpointProviderOptions(array $args) + { + $options = []; + $optionKeys = ['sts_regional_endpoints', 's3_us_east_1_regional_endpoint']; + $configKeys = ['use_dual_stack_endpoint', 'use_fips_endpoint']; + foreach ($optionKeys as $key) { + if (isset($args[$key])) { + $options[$key] = $args[$key]; + } + } + foreach ($configKeys as $key) { + if (isset($args['config'][$key])) { + $options[$key] = $args['config'][$key]; + } + } + return $options; + } + /** + * Validates a region to be used for endpoint construction + * + * @param $region + * @return bool + */ + private static function isValidRegion($region) + { + return is_valid_hostlabel($region); + } + private function _apply_client_context_params(array $args) + { + if (isset($args['api']) && !empty($args['api']->getClientContextParams())) { + $clientContextParams = $args['api']->getClientContextParams(); + foreach ($clientContextParams as $paramName => $paramDefinition) { + $definition = ['type' => 'value', 'valid' => [$paramDefinition['type']], 'doc' => $paramDefinition['documentation'] ?? null]; + $this->argDefinitions[$paramName] = $definition; + if (isset($args[$paramName])) { + $fn = self::$typeMap[$paramDefinition['type']]; + if (!$fn($args[$paramName])) { + $this->invalidType($paramName, $args[$paramName]); + } + } + } + } + } + private static function isValidService($service) + { + if (\is_null($service)) { + return \false; + } + $services = \WPMailSMTP\Vendor\Aws\manifest(); + return isset($services[$service]); + } + private static function isValidApiVersion($service, $apiVersion) + { + if (\is_null($apiVersion)) { + return \false; + } + return \is_dir(__DIR__ . "/data/{$service}/{$apiVersion}"); + } + private static function emitDeprecationWarning() + { + $phpVersionString = \phpversion(); + \trigger_error("This installation of the SDK is using PHP version" . " {$phpVersionString}, which will be deprecated on January" . " 13th, 2025.\nPlease upgrade your PHP version to a minimum of" . " 8.1.x to continue receiving updates for the AWS" . " SDK for PHP.\nTo disable this warning, set" . " suppress_php_deprecation_warning to true on the client constructor" . " or set the environment variable AWS_SUPPRESS_PHP_DEPRECATION_WARNING" . " to true.\nMore information can be found at: " . "https://aws.amazon.com/blogs/developer/announcing-the-end-of-support-for-php-runtimes-8-0-x-and-below-in-the-aws-sdk-for-php/\n", \E_USER_DEPRECATED); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/AbstractMonitoringMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/AbstractMonitoringMiddleware.php new file mode 100644 index 00000000..b120d68b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/AbstractMonitoringMiddleware.php @@ -0,0 +1,244 @@ +getResponse(); + if ($response !== null) { + $header = $response->getHeader($headerName); + if (!empty($header[0])) { + return $header[0]; + } + } + return null; + } + protected static function getResultHeader(ResultInterface $result, $headerName) + { + if (isset($result['@metadata']['headers'][$headerName])) { + return $result['@metadata']['headers'][$headerName]; + } + return null; + } + protected static function getExceptionHeader(\Exception $e, $headerName) + { + if ($e instanceof ResponseContainerInterface) { + $response = $e->getResponse(); + if ($response instanceof ResponseInterface) { + $header = $response->getHeader($headerName); + if (!empty($header[0])) { + return $header[0]; + } + } + } + return null; + } + /** + * Constructor stores the passed in handler and options. + * + * @param callable $handler + * @param callable $credentialProvider + * @param $options + * @param $region + * @param $service + */ + public function __construct(callable $handler, callable $credentialProvider, $options, $region, $service) + { + $this->nextHandler = $handler; + $this->credentialProvider = $credentialProvider; + $this->options = $options; + $this->region = $region; + $this->service = $service; + } + /** + * Standard invoke pattern for middleware execution to be implemented by + * child classes. + * + * @param CommandInterface $cmd + * @param RequestInterface $request + * @return Promise\PromiseInterface + */ + public function __invoke(CommandInterface $cmd, RequestInterface $request) + { + $handler = $this->nextHandler; + $eventData = null; + $enabled = $this->isEnabled(); + if ($enabled) { + $cmd['@http']['collect_stats'] = \true; + $eventData = $this->populateRequestEventData($cmd, $request, $this->getNewEvent($cmd, $request)); + } + $g = function ($value) use($eventData, $enabled) { + if ($enabled) { + $eventData = $this->populateResultEventData($value, $eventData); + $this->sendEventData($eventData); + if ($value instanceof MonitoringEventsInterface) { + $value->appendMonitoringEvent($eventData); + } + } + if ($value instanceof \Exception || $value instanceof \Throwable) { + return Promise\Create::rejectionFor($value); + } + return $value; + }; + return Promise\Create::promiseFor($handler($cmd, $request))->then($g, $g); + } + private function getClientId() + { + return $this->unwrappedOptions()->getClientId(); + } + private function getNewEvent(CommandInterface $cmd, RequestInterface $request) + { + $event = ['Api' => $cmd->getName(), 'ClientId' => $this->getClientId(), 'Region' => $this->getRegion(), 'Service' => $this->getService(), 'Timestamp' => (int) \floor(\microtime(\true) * 1000), 'UserAgent' => \substr($request->getHeaderLine('User-Agent') . ' ' . \WPMailSMTP\Vendor\Aws\default_user_agent(), 0, 256), 'Version' => 1]; + return $event; + } + private function getHost() + { + return $this->unwrappedOptions()->getHost(); + } + private function getPort() + { + return $this->unwrappedOptions()->getPort(); + } + private function getRegion() + { + return $this->region; + } + private function getService() + { + return $this->service; + } + /** + * Returns enabled flag from options, unwrapping options if necessary. + * + * @return bool + */ + private function isEnabled() + { + return $this->unwrappedOptions()->isEnabled(); + } + /** + * Returns $eventData array with information from the request and command. + * + * @param CommandInterface $cmd + * @param RequestInterface $request + * @param array $event + * @return array + */ + protected function populateRequestEventData(CommandInterface $cmd, RequestInterface $request, array $event) + { + $dataFormat = static::getRequestData($request); + foreach ($dataFormat as $eventKey => $value) { + if ($value !== null) { + $event[$eventKey] = $value; + } + } + return $event; + } + /** + * Returns $eventData array with information from the response, including + * the calculation for attempt latency. + * + * @param ResultInterface|\Exception $result + * @param array $event + * @return array + */ + protected function populateResultEventData($result, array $event) + { + $dataFormat = static::getResponseData($result); + foreach ($dataFormat as $eventKey => $value) { + if ($value !== null) { + $event[$eventKey] = $value; + } + } + return $event; + } + /** + * Checks if the socket is created. If PHP version is greater or equals to 8 then, + * it will check if the var is instance of \Socket otherwise it will check if is + * a resource. + * + * @return bool Returns true if the socket is created, false otherwise. + */ + private function isSocketCreated() : bool + { + // Before version 8, sockets are resources + // After version 8, sockets are instances of Socket + if (\PHP_MAJOR_VERSION >= 8) { + $socketClass = '\\Socket'; + return self::$socket instanceof $socketClass; + } else { + return \is_resource(self::$socket); + } + } + /** + * Creates a UDP socket resource and stores it with the class, or retrieves + * it if already instantiated and connected. Handles error-checking and + * re-connecting if necessary. If $forceNewConnection is set to true, a new + * socket will be created. + * + * @param bool $forceNewConnection + * @return Resource + */ + private function prepareSocket($forceNewConnection = \false) + { + if (!$this->isSocketCreated() || $forceNewConnection || \socket_last_error(self::$socket)) { + self::$socket = \socket_create(\AF_INET, \SOCK_DGRAM, \SOL_UDP); + \socket_clear_error(self::$socket); + \socket_connect(self::$socket, $this->getHost(), $this->getPort()); + } + return self::$socket; + } + /** + * Sends formatted monitoring event data via the UDP socket connection to + * the CSM agent endpoint. + * + * @param array $eventData + * @return int + */ + private function sendEventData(array $eventData) + { + $socket = $this->prepareSocket(); + $datagram = \json_encode($eventData); + $result = \socket_write($socket, $datagram, \strlen($datagram)); + if ($result === \false) { + $this->prepareSocket(\true); + } + return $result; + } + /** + * Unwraps options, if needed, and returns them. + * + * @return ConfigurationInterface + */ + private function unwrappedOptions() + { + if (!$this->options instanceof ConfigurationInterface) { + try { + $this->options = ConfigurationProvider::unwrap($this->options); + } catch (\Exception $e) { + // Errors unwrapping CSM config defaults to disabling it + $this->options = new Configuration(\false, ConfigurationProvider::DEFAULT_HOST, ConfigurationProvider::DEFAULT_PORT); + } + } + return $this->options; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallAttemptMonitoringMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallAttemptMonitoringMiddleware.php new file mode 100644 index 00000000..f5942dcd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallAttemptMonitoringMiddleware.php @@ -0,0 +1,181 @@ + $request->getUri()->getHost()]; + } + /** + * {@inheritdoc} + */ + public static function getResponseData($klass) + { + if ($klass instanceof ResultInterface) { + return ['AttemptLatency' => self::getResultAttemptLatency($klass), 'DestinationIp' => self::getResultDestinationIp($klass), 'DnsLatency' => self::getResultDnsLatency($klass), 'HttpStatusCode' => self::getResultHttpStatusCode($klass), 'XAmzId2' => self::getResultHeader($klass, 'x-amz-id-2'), 'XAmzRequestId' => self::getResultHeader($klass, 'x-amz-request-id'), 'XAmznRequestId' => self::getResultHeader($klass, 'x-amzn-RequestId')]; + } + if ($klass instanceof AwsException) { + return ['AttemptLatency' => self::getAwsExceptionAttemptLatency($klass), 'AwsException' => \substr(self::getAwsExceptionErrorCode($klass), 0, 128), 'AwsExceptionMessage' => \substr(self::getAwsExceptionMessage($klass), 0, 512), 'DestinationIp' => self::getAwsExceptionDestinationIp($klass), 'DnsLatency' => self::getAwsExceptionDnsLatency($klass), 'HttpStatusCode' => self::getAwsExceptionHttpStatusCode($klass), 'XAmzId2' => self::getAwsExceptionHeader($klass, 'x-amz-id-2'), 'XAmzRequestId' => self::getAwsExceptionHeader($klass, 'x-amz-request-id'), 'XAmznRequestId' => self::getAwsExceptionHeader($klass, 'x-amzn-RequestId')]; + } + if ($klass instanceof \Exception) { + return ['HttpStatusCode' => self::getExceptionHttpStatusCode($klass), 'SdkException' => \substr(self::getExceptionCode($klass), 0, 128), 'SdkExceptionMessage' => \substr(self::getExceptionMessage($klass), 0, 512), 'XAmzId2' => self::getExceptionHeader($klass, 'x-amz-id-2'), 'XAmzRequestId' => self::getExceptionHeader($klass, 'x-amz-request-id'), 'XAmznRequestId' => self::getExceptionHeader($klass, 'x-amzn-RequestId')]; + } + throw new \InvalidArgumentException('Parameter must be an instance of ResultInterface, AwsException or Exception.'); + } + private static function getResultAttemptLatency(ResultInterface $result) + { + if (isset($result['@metadata']['transferStats']['http'])) { + $attempt = \end($result['@metadata']['transferStats']['http']); + if (isset($attempt['total_time'])) { + return (int) \floor($attempt['total_time'] * 1000); + } + } + return null; + } + private static function getResultDestinationIp(ResultInterface $result) + { + if (isset($result['@metadata']['transferStats']['http'])) { + $attempt = \end($result['@metadata']['transferStats']['http']); + if (isset($attempt['primary_ip'])) { + return $attempt['primary_ip']; + } + } + return null; + } + private static function getResultDnsLatency(ResultInterface $result) + { + if (isset($result['@metadata']['transferStats']['http'])) { + $attempt = \end($result['@metadata']['transferStats']['http']); + if (isset($attempt['namelookup_time'])) { + return (int) \floor($attempt['namelookup_time'] * 1000); + } + } + return null; + } + private static function getResultHttpStatusCode(ResultInterface $result) + { + return $result['@metadata']['statusCode']; + } + private static function getAwsExceptionAttemptLatency(AwsException $e) + { + $attempt = $e->getTransferInfo(); + if (isset($attempt['total_time'])) { + return (int) \floor($attempt['total_time'] * 1000); + } + return null; + } + private static function getAwsExceptionErrorCode(AwsException $e) + { + return $e->getAwsErrorCode(); + } + private static function getAwsExceptionMessage(AwsException $e) + { + return $e->getAwsErrorMessage(); + } + private static function getAwsExceptionDestinationIp(AwsException $e) + { + $attempt = $e->getTransferInfo(); + if (isset($attempt['primary_ip'])) { + return $attempt['primary_ip']; + } + return null; + } + private static function getAwsExceptionDnsLatency(AwsException $e) + { + $attempt = $e->getTransferInfo(); + if (isset($attempt['namelookup_time'])) { + return (int) \floor($attempt['namelookup_time'] * 1000); + } + return null; + } + private static function getAwsExceptionHttpStatusCode(AwsException $e) + { + $response = $e->getResponse(); + if ($response !== null) { + return $response->getStatusCode(); + } + return null; + } + private static function getExceptionHttpStatusCode(\Exception $e) + { + if ($e instanceof ResponseContainerInterface) { + $response = $e->getResponse(); + if ($response instanceof ResponseInterface) { + return $response->getStatusCode(); + } + } + return null; + } + private static function getExceptionCode(\Exception $e) + { + if (!$e instanceof AwsException) { + return \get_class($e); + } + return null; + } + private static function getExceptionMessage(\Exception $e) + { + if (!$e instanceof AwsException) { + return $e->getMessage(); + } + return null; + } + /** + * {@inheritdoc} + */ + protected function populateRequestEventData(CommandInterface $cmd, RequestInterface $request, array $event) + { + $event = parent::populateRequestEventData($cmd, $request, $event); + $event['Type'] = 'ApiCallAttempt'; + return $event; + } + /** + * {@inheritdoc} + */ + protected function populateResultEventData($result, array $event) + { + $event = parent::populateResultEventData($result, $event); + $provider = $this->credentialProvider; + /** @var CredentialsInterface $credentials */ + $credentials = $provider()->wait(); + $event['AccessKey'] = $credentials->getAccessKeyId(); + $sessionToken = $credentials->getSecurityToken(); + if ($sessionToken !== null) { + $event['SessionToken'] = $sessionToken; + } + if (empty($event['AttemptLatency'])) { + $event['AttemptLatency'] = (int) (\floor(\microtime(\true) * 1000) - $event['Timestamp']); + } + return $event; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallMonitoringMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallMonitoringMiddleware.php new file mode 100644 index 00000000..4ca5fbb1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ApiCallMonitoringMiddleware.php @@ -0,0 +1,126 @@ + 'AwsException', 'FinalAwsExceptionMessage' => 'AwsExceptionMessage', 'FinalSdkException' => 'SdkException', 'FinalSdkExceptionMessage' => 'SdkExceptionMessage', 'FinalHttpStatusCode' => 'HttpStatusCode']; + /** + * Standard middleware wrapper function with CSM options passed in. + * + * @param callable $credentialProvider + * @param mixed $options + * @param string $region + * @param string $service + * @return callable + */ + public static function wrap(callable $credentialProvider, $options, $region, $service) + { + return function (callable $handler) use($credentialProvider, $options, $region, $service) { + return new static($handler, $credentialProvider, $options, $region, $service); + }; + } + /** + * {@inheritdoc} + */ + public static function getRequestData(RequestInterface $request) + { + return []; + } + /** + * {@inheritdoc} + */ + public static function getResponseData($klass) + { + if ($klass instanceof ResultInterface) { + $data = ['AttemptCount' => self::getResultAttemptCount($klass), 'MaxRetriesExceeded' => 0]; + } elseif ($klass instanceof \Exception) { + $data = ['AttemptCount' => self::getExceptionAttemptCount($klass), 'MaxRetriesExceeded' => self::getMaxRetriesExceeded($klass)]; + } else { + throw new \InvalidArgumentException('Parameter must be an instance of ResultInterface or Exception.'); + } + return $data + self::getFinalAttemptData($klass); + } + private static function getResultAttemptCount(ResultInterface $result) + { + if (isset($result['@metadata']['transferStats']['http'])) { + return \count($result['@metadata']['transferStats']['http']); + } + return 1; + } + private static function getExceptionAttemptCount(\Exception $e) + { + $attemptCount = 0; + if ($e instanceof MonitoringEventsInterface) { + foreach ($e->getMonitoringEvents() as $event) { + if (isset($event['Type']) && $event['Type'] === 'ApiCallAttempt') { + $attemptCount++; + } + } + } + return $attemptCount; + } + private static function getFinalAttemptData($klass) + { + $data = []; + if ($klass instanceof MonitoringEventsInterface) { + $finalAttempt = self::getFinalAttempt($klass->getMonitoringEvents()); + if (!empty($finalAttempt)) { + foreach (self::$eventKeys as $callKey => $attemptKey) { + if (isset($finalAttempt[$attemptKey])) { + $data[$callKey] = $finalAttempt[$attemptKey]; + } + } + } + } + return $data; + } + private static function getFinalAttempt(array $events) + { + for (\end($events); \key($events) !== null; \prev($events)) { + $current = \current($events); + if (isset($current['Type']) && $current['Type'] === 'ApiCallAttempt') { + return $current; + } + } + return null; + } + private static function getMaxRetriesExceeded($klass) + { + if ($klass instanceof AwsException && $klass->isMaxRetriesExceeded()) { + return 1; + } + return 0; + } + /** + * {@inheritdoc} + */ + protected function populateRequestEventData(CommandInterface $cmd, RequestInterface $request, array $event) + { + $event = parent::populateRequestEventData($cmd, $request, $event); + $event['Type'] = 'ApiCall'; + return $event; + } + /** + * {@inheritdoc} + */ + protected function populateResultEventData($result, array $event) + { + $event = parent::populateResultEventData($result, $event); + $event['Latency'] = (int) (\floor(\microtime(\true) * 1000) - $event['Timestamp']); + return $event; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Configuration.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Configuration.php new file mode 100644 index 00000000..8218e91c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Configuration.php @@ -0,0 +1,65 @@ +host = $host; + $this->port = \filter_var($port, \FILTER_VALIDATE_INT); + if ($this->port === \false) { + throw new \InvalidArgumentException("CSM 'port' value must be an integer!"); + } + // Unparsable $enabled flag errors on the side of disabling CSM + $this->enabled = \filter_var($enabled, \FILTER_VALIDATE_BOOLEAN); + $this->clientId = \trim($clientId); + } + /** + * {@inheritdoc} + */ + public function isEnabled() + { + return $this->enabled; + } + /** + * {@inheritdoc} + */ + public function getClientId() + { + return $this->clientId; + } + /** + * /{@inheritdoc} + */ + public function getHost() + { + return $this->host; + } + /** + * {@inheritdoc} + */ + public function getPort() + { + return $this->port; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['client_id' => $this->getClientId(), 'enabled' => $this->isEnabled(), 'host' => $this->getHost(), 'port' => $this->getPort()]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ConfigurationInterface.php new file mode 100644 index 00000000..818d9a6d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/ConfigurationInterface.php @@ -0,0 +1,41 @@ + + * use Aws\ClientSideMonitoring\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see Aws\ClientSideMonitoring\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const DEFAULT_CLIENT_ID = ''; + const DEFAULT_ENABLED = \false; + const DEFAULT_HOST = '127.0.0.1'; + const DEFAULT_PORT = 31000; + const ENV_CLIENT_ID = 'AWS_CSM_CLIENT_ID'; + const ENV_ENABLED = 'AWS_CSM_ENABLED'; + const ENV_HOST = 'AWS_CSM_HOST'; + const ENV_PORT = 'AWS_CSM_PORT'; + const ENV_PROFILE = 'AWS_PROFILE'; + public static $cacheKey = 'aws_cached_csm_config'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $configProviders = [self::env()]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini(); + } + $configProviders[] = self::fallback(); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['csm']) && $config['csm'] instanceof CacheInterface) { + return self::cache($memo, $config['csm'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates CSM config from environment variables. + * + * @return callable + */ + public static function env() + { + return function () { + // Use credentials from environment variables, if available + $enabled = \getenv(self::ENV_ENABLED); + if ($enabled !== \false) { + return Promise\Create::promiseFor(new Configuration($enabled, \getenv(self::ENV_HOST) ?: self::DEFAULT_HOST, \getenv(self::ENV_PORT) ?: self::DEFAULT_PORT, \getenv(self::ENV_CLIENT_ID) ?: self::DEFAULT_CLIENT_ID)); + } + return self::reject('Could not find environment variable CSM config' . ' in ' . self::ENV_ENABLED . '/' . self::ENV_HOST . '/' . self::ENV_PORT . '/' . self::ENV_CLIENT_ID); + }; + } + /** + * Fallback config options when other sources are not set. + * + * @return callable + */ + public static function fallback() + { + return function () { + return Promise\Create::promiseFor(new Configuration(self::DEFAULT_ENABLED, self::DEFAULT_HOST, self::DEFAULT_PORT, self::DEFAULT_CLIENT_ID)); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * + * @return callable + */ + public static function ini($profile = null, $filename = null) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'aws_csm'); + return function () use($profile, $filename) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read CSM config from {$filename}"); + } + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile]['csm_enabled'])) { + return self::reject("Required CSM config values not present in\n INI profile '{$profile}' ({$filename})"); + } + // host is optional + if (empty($data[$profile]['csm_host'])) { + $data[$profile]['csm_host'] = self::DEFAULT_HOST; + } + // port is optional + if (empty($data[$profile]['csm_port'])) { + $data[$profile]['csm_port'] = self::DEFAULT_PORT; + } + // client_id is optional + if (empty($data[$profile]['csm_client_id'])) { + $data[$profile]['csm_client_id'] = self::DEFAULT_CLIENT_ID; + } + return Promise\Create::promiseFor(new Configuration($data[$profile]['csm_enabled'], $data[$profile]['csm_host'], $data[$profile]['csm_port'], $data[$profile]['csm_client_id'])); + }; + } + /** + * Unwraps a configuration object in whatever valid form it is in, + * always returning a ConfigurationInterface object. + * + * @param mixed $config + * @return ConfigurationInterface + * @throws \InvalidArgumentException + */ + public static function unwrap($config) + { + if (\is_callable($config)) { + $config = $config(); + } + if ($config instanceof PromiseInterface) { + $config = $config->wait(); + } + if ($config instanceof ConfigurationInterface) { + return $config; + } elseif (\is_array($config) && isset($config['enabled'])) { + $client_id = isset($config['client_id']) ? $config['client_id'] : self::DEFAULT_CLIENT_ID; + $host = isset($config['host']) ? $config['host'] : self::DEFAULT_HOST; + $port = isset($config['port']) ? $config['port'] : self::DEFAULT_PORT; + return new Configuration($config['enabled'], $host, $port, $client_id); + } + throw new \InvalidArgumentException('Not a valid CSM configuration ' . 'argument.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Exception/ConfigurationException.php new file mode 100644 index 00000000..ae015b6a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ClientSideMonitoring/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +name = $name; + $this->data = $args; + $this->handlerList = $list ?: new HandlerList(); + if (!isset($this->data['@http'])) { + $this->data['@http'] = []; + } + if (!isset($this->data['@context'])) { + $this->data['@context'] = []; + } + $this->metricsBuilder = $metricsBuilder ?: new MetricsBuilder(); + } + public function __clone() + { + $this->handlerList = clone $this->handlerList; + } + public function getName() + { + return $this->name; + } + public function hasParam($name) + { + return \array_key_exists($name, $this->data); + } + public function getHandlerList() + { + return $this->handlerList; + } + /** + * For overriding auth schemes on a per endpoint basis when using + * EndpointV2 provider. Intended for internal use only. + * + * @param array $authSchemes + * + * @deprecated In favor of using the @context property bag. + * Auth Schemes are now accessible via the `signature_version` key + * in a Command's context, if applicable. Auth Schemes set using + * This method are no longer consumed. + * + * @internal + */ + public function setAuthSchemes(array $authSchemes) + { + \trigger_error(__METHOD__ . ' is deprecated. Auth schemes ' . 'resolved using the service `auth` trait or via endpoint resolution ' . 'are now set in the command `@context` property.`', \E_USER_WARNING); + $this->authSchemes = $authSchemes; + } + /** + * Get auth schemes added to command as required + * for endpoint resolution + * + * @returns array + * + * @deprecated In favor of using the @context property bag. + * Auth schemes are now accessible via the `signature_version` key + * in a Command's context, if applicable. + */ + public function getAuthSchemes() + { + \trigger_error(__METHOD__ . ' is deprecated. Auth schemes ' . 'resolved using the service `auth` trait or via endpoint resolution ' . 'can now be found in the command `@context` property.`', \E_USER_WARNING); + return $this->authSchemes ?: []; + } + /** @deprecated */ + public function get($name) + { + return $this[$name]; + } + /** + * Returns the metrics builder instance tied up to this command. + * + * @internal + * + * @return MetricsBuilder + */ + public function getMetricsBuilder() : MetricsBuilder + { + return $this->metricsBuilder; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CommandInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CommandInterface.php new file mode 100644 index 00000000..d2d47bc6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/CommandInterface.php @@ -0,0 +1,40 @@ +getBefore($config); + $mapFn = function ($commands) use($client, $before, $config) { + foreach ($commands as $key => $command) { + if (!$command instanceof CommandInterface) { + throw new \InvalidArgumentException('Each value yielded by ' . 'the iterator must be an Aws\\CommandInterface.'); + } + if ($before) { + $before($command, $key); + } + if (!empty($config['preserve_iterator_keys'])) { + (yield $key => $client->executeAsync($command)); + } else { + (yield $client->executeAsync($command)); + } + } + }; + $this->each = new EachPromise($mapFn($commands), $config); + } + /** + * @return PromiseInterface + */ + public function promise() : PromiseInterface + { + return $this->each->promise(); + } + /** + * Executes a pool synchronously and aggregates the results of the pool + * into an indexed array in the same order as the passed in array. + * + * @param AwsClientInterface $client Client used to execute commands. + * @param mixed $commands Iterable that yields commands. + * @param array $config Configuration options. + * + * @return array + * @see \Aws\CommandPool::__construct for available configuration options. + */ + public static function batch(AwsClientInterface $client, $commands, array $config = []) + { + $results = []; + self::cmpCallback($config, 'fulfilled', $results); + self::cmpCallback($config, 'rejected', $results); + return (new self($client, $commands, $config))->promise()->then(static function () use(&$results) { + \ksort($results); + return $results; + })->wait(); + } + /** + * @return callable + */ + private function getBefore(array $config) + { + if (!isset($config['before'])) { + return null; + } + if (\is_callable($config['before'])) { + return $config['before']; + } + throw new \InvalidArgumentException('before must be callable'); + } + /** + * Adds an onFulfilled or onRejected callback that aggregates results into + * an array. If a callback is already present, it is replaced with the + * composed function. + * + * @param array $config + * @param $name + * @param array $results + */ + private static function cmpCallback(array &$config, $name, array &$results) + { + if (!isset($config[$name])) { + $config[$name] = function ($v, $k) use(&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $config[$name]; + $config[$name] = function ($v, $k) use(&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Configuration/ConfigurationResolver.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Configuration/ConfigurationResolver.php new file mode 100644 index 00000000..06fc615a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Configuration/ConfigurationResolver.php @@ -0,0 +1,176 @@ +client = $config['client']; + $this->assumeRoleParams = $config['assume_role_params']; + } + /** + * Loads assume role credentials. + * + * @return PromiseInterface + */ + public function __invoke() + { + $client = $this->client; + return $client->assumeRoleAsync($this->assumeRoleParams)->then(function (Result $result) { + return $this->client->createCredentials($result, CredentialSources::STS_ASSUME_ROLE); + })->otherwise(function (\RuntimeException $exception) { + throw new CredentialsException("Error in retrieving assume role credentials.", 0, $exception); + }); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/AssumeRoleWithWebIdentityCredentialProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/AssumeRoleWithWebIdentityCredentialProvider.php new file mode 100644 index 00000000..cbcbcb28 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/AssumeRoleWithWebIdentityCredentialProvider.php @@ -0,0 +1,125 @@ +arn = $config['RoleArn']; + if (!isset($config['WebIdentityTokenFile'])) { + throw new \InvalidArgumentException(self::ERROR_MSG . "'WebIdentityTokenFile'."); + } + $this->tokenFile = $config['WebIdentityTokenFile']; + if (!\preg_match("/^\\w\\:|^\\/|^\\\\/", $this->tokenFile)) { + throw new \InvalidArgumentException("'WebIdentityTokenFile' must be an absolute path."); + } + $this->retries = (int) \getenv(self::ENV_RETRIES) ?: (isset($config['retries']) ? $config['retries'] : 3); + $this->authenticationAttempts = 0; + $this->tokenFileReadAttempts = 0; + $this->session = $config['SessionName'] ?? 'aws-sdk-php-' . \round(\microtime(\true) * 1000); + $region = $config['region'] ?? 'us-east-1'; + if (isset($config['client'])) { + $this->client = $config['client']; + } else { + $this->client = new StsClient(['credentials' => \false, 'region' => $region, 'version' => 'latest']); + } + $this->source = $config['source'] ?? CredentialSources::STS_WEB_ID_TOKEN; + } + /** + * Loads assume role with web identity credentials. + * + * @return Promise\PromiseInterface + */ + public function __invoke() + { + return Promise\Coroutine::of(function () { + $client = $this->client; + $result = null; + while ($result == null) { + try { + $token = @\file_get_contents($this->tokenFile); + if (\false === $token) { + \clearstatcache(\true, \dirname($this->tokenFile) . "/" . \readlink($this->tokenFile)); + \clearstatcache(\true, \dirname($this->tokenFile) . "/" . \dirname(\readlink($this->tokenFile))); + \clearstatcache(\true, $this->tokenFile); + if (!@\is_readable($this->tokenFile)) { + throw new CredentialsException("Unreadable tokenfile at location {$this->tokenFile}"); + } + $token = @\file_get_contents($this->tokenFile); + } + if (empty($token)) { + if ($this->tokenFileReadAttempts < $this->retries) { + \sleep((int) \pow(1.2, $this->tokenFileReadAttempts)); + $this->tokenFileReadAttempts++; + continue; + } + throw new CredentialsException("InvalidIdentityToken from file: {$this->tokenFile}"); + } + } catch (\Exception $exception) { + throw new CredentialsException("Error reading WebIdentityTokenFile from " . $this->tokenFile, 0, $exception); + } + $assumeParams = ['RoleArn' => $this->arn, 'RoleSessionName' => $this->session, 'WebIdentityToken' => $token]; + try { + $result = $client->assumeRoleWithWebIdentity($assumeParams); + } catch (AwsException $e) { + if ($e->getAwsErrorCode() == 'InvalidIdentityToken') { + if ($this->authenticationAttempts < $this->retries) { + \sleep((int) \pow(1.2, $this->authenticationAttempts)); + } else { + throw new CredentialsException("InvalidIdentityToken, retries exhausted"); + } + } else { + throw new CredentialsException("Error assuming role from web identity credentials", 0, $e); + } + } catch (\Exception $e) { + throw new CredentialsException("Error retrieving web identity credentials: " . $e->getMessage() . " (" . $e->getCode() . ")"); + } + $this->authenticationAttempts++; + } + (yield $this->client->createCredentials($result, $this->source)); + }); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialProvider.php new file mode 100644 index 00000000..cc77e840 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialProvider.php @@ -0,0 +1,704 @@ + + * use Aws\Credentials\CredentialProvider; + * $provider = CredentialProvider::defaultProvider(); + * // Returns a CredentialsInterface or throws. + * $creds = $provider()->wait(); + * + * + * Credential providers can be composed to create credentials using conditional + * logic that can create different credentials in different environments. You + * can compose multiple providers into a single provider using + * {@see Aws\Credentials\CredentialProvider::chain}. This function accepts + * providers as variadic arguments and returns a new function that will invoke + * each provider until a successful set of credentials is returned. + * + * + * // First try an INI file at this location. + * $a = CredentialProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = CredentialProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = CredentialProvider::env(); + * // Combine the three providers together. + * $composed = CredentialProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with credentials or throws. + * $promise = $composed(); + * // Wait on the credentials to resolve. + * $creds = $promise->wait(); + * + */ +class CredentialProvider +{ + const ENV_ARN = 'AWS_ROLE_ARN'; + const ENV_KEY = 'AWS_ACCESS_KEY_ID'; + const ENV_PROFILE = 'AWS_PROFILE'; + const ENV_ROLE_SESSION_NAME = 'AWS_ROLE_SESSION_NAME'; + const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY'; + const ENV_ACCOUNT_ID = 'AWS_ACCOUNT_ID'; + const ENV_SESSION = 'AWS_SESSION_TOKEN'; + const ENV_TOKEN_FILE = 'AWS_WEB_IDENTITY_TOKEN_FILE'; + const ENV_SHARED_CREDENTIALS_FILE = 'AWS_SHARED_CREDENTIALS_FILE'; + /** + * Create a default credential provider that + * first checks for environment variables, + * then checks for assumed role via web identity, + * then checks for cached SSO credentials from the CLI, + * then check for credential_process in the "default" profile in ~/.aws/credentials, + * then checks for the "default" profile in ~/.aws/credentials, + * then for credential_process in the "default profile" profile in ~/.aws/config, + * then checks for "profile default" profile in ~/.aws/config (which is + * the default profile of AWS CLI), + * then tries to make a GET Request to fetch credentials if ECS environment variable is presented, + * finally checks for EC2 instance profile credentials. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided credentials. + * + * @param array $config Optional array of ecs/instance profile credentials + * provider options. + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $cacheable = ['web_identity', 'sso', 'process_credentials', 'process_config', 'ecs', 'instance']; + $profileName = \getenv(self::ENV_PROFILE) ?: 'default'; + $defaultChain = ['env' => self::env(), 'web_identity' => self::assumeRoleWithWebIdentityCredentialProvider($config)]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] !== \false) { + $defaultChain['sso'] = self::sso($profileName, self::getHomeDir() . '/.aws/config', $config); + $defaultChain['process_credentials'] = self::process(); + $defaultChain['ini'] = self::ini(); + $defaultChain['process_config'] = self::process('profile ' . $profileName, self::getHomeDir() . '/.aws/config'); + $defaultChain['ini_config'] = self::ini('profile ' . $profileName, self::getHomeDir() . '/.aws/config'); + } + if (self::shouldUseEcs()) { + $defaultChain['ecs'] = self::ecsCredentials($config); + } else { + $defaultChain['instance'] = self::instanceProfile($config); + } + if (isset($config['credentials']) && $config['credentials'] instanceof CacheInterface) { + foreach ($cacheable as $provider) { + if (isset($defaultChain[$provider])) { + $defaultChain[$provider] = self::cache($defaultChain[$provider], $config['credentials'], 'aws_cached_' . $provider . '_credentials'); + } + } + } + return self::memoize(\call_user_func_array([CredentialProvider::class, 'chain'], \array_values($defaultChain))); + } + /** + * Create a credential provider function from a set of static credentials. + * + * @param CredentialsInterface $creds + * + * @return callable + */ + public static function fromCredentials(CredentialsInterface $creds) + { + $promise = Promise\Create::promiseFor($creds); + return function () use($promise) { + return $promise; + }; + } + /** + * Creates an aggregate credentials provider that invokes the provided + * variadic providers one after the other until a provider returns + * credentials. + * + * @return callable + */ + public static function chain() + { + $links = \func_get_args(); + if (empty($links)) { + throw new \InvalidArgumentException('No providers in chain'); + } + return function ($previousCreds = null) use($links) { + /** @var callable $parent */ + $parent = \array_shift($links); + $promise = $parent(); + while ($next = \array_shift($links)) { + if ($next instanceof InstanceProfileProvider && $previousCreds instanceof Credentials) { + $promise = $promise->otherwise(function () use($next, $previousCreds) { + return $next($previousCreds); + }); + } else { + $promise = $promise->otherwise($next); + } + } + return $promise; + }; + } + /** + * Wraps a credential provider and caches previously provided credentials. + * + * Ensures that cached credentials are refreshed when they expire. + * + * @param callable $provider Credentials provider function to wrap. + * + * @return callable + */ + public static function memoize(callable $provider) + { + return function () use($provider) { + static $result; + static $isConstant; + // Constant credentials will be returned constantly. + if ($isConstant) { + return $result; + } + // Create the initial promise that will be used as the cached value + // until it expires. + if (null === $result) { + $result = $provider(); + } + // Return credentials that could expire and refresh when needed. + return $result->then(function (CredentialsInterface $creds) use($provider, &$isConstant, &$result) { + // Determine if these are constant credentials. + if (!$creds->getExpiration()) { + $isConstant = \true; + return $creds; + } + // Refresh expired credentials. + if (!$creds->isExpired()) { + return $creds; + } + // Refresh the result and forward the promise. + return $result = $provider($creds); + })->otherwise(function ($reason) use(&$result) { + // Cleanup rejected promise. + $result = null; + return new Promise\RejectedPromise($reason); + }); + }; + } + /** + * Wraps a credential provider and saves provided credentials in an + * instance of Aws\CacheInterface. Forwards calls when no credentials found + * in cache and updates cache with the results. + * + * @param callable $provider Credentials provider function to wrap + * @param CacheInterface $cache Cache to store credentials + * @param string|null $cacheKey (optional) Cache key to use + * + * @return callable + */ + public static function cache(callable $provider, CacheInterface $cache, $cacheKey = null) + { + $cacheKey = $cacheKey ?: 'aws_cached_credentials'; + return function () use($provider, $cache, $cacheKey) { + $found = $cache->get($cacheKey); + if ($found instanceof CredentialsInterface && !$found->isExpired()) { + return Promise\Create::promiseFor($found); + } + return $provider()->then(function (CredentialsInterface $creds) use($cache, $cacheKey) { + $cache->set($cacheKey, $creds, null === $creds->getExpiration() ? 0 : $creds->getExpiration() - \time()); + return $creds; + }); + }; + } + /** + * Provider that creates credentials from environment variables + * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN. + * + * @return callable + */ + public static function env() + { + return function () { + // Use credentials from environment variables, if available + $key = \getenv(self::ENV_KEY); + $secret = \getenv(self::ENV_SECRET); + $accountId = \getenv(self::ENV_ACCOUNT_ID) ?: null; + $token = \getenv(self::ENV_SESSION) ?: null; + if ($key && $secret) { + return Promise\Create::promiseFor(new Credentials($key, $secret, $token, null, $accountId, CredentialSources::ENVIRONMENT)); + } + return self::reject('Could not find environment variable ' . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET); + }; + } + /** + * Credential provider that creates credentials using instance profile + * credentials. + * + * @param array $config Array of configuration data. + * + * @return InstanceProfileProvider + * @see Aws\Credentials\InstanceProfileProvider for $config details. + */ + public static function instanceProfile(array $config = []) + { + return new InstanceProfileProvider($config); + } + /** + * Credential provider that retrieves cached SSO credentials from the CLI + * + * @return callable + */ + public static function sso($ssoProfileName = 'default', $filename = null, $config = []) + { + $filename = $filename ?: self::getHomeDir() . '/.aws/config'; + return function () use($ssoProfileName, $filename, $config) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read credentials from {$filename}"); + } + $profiles = self::loadProfiles($filename); + if (isset($profiles[$ssoProfileName])) { + $ssoProfile = $profiles[$ssoProfileName]; + } elseif (isset($profiles['profile ' . $ssoProfileName])) { + $ssoProfileName = 'profile ' . $ssoProfileName; + $ssoProfile = $profiles[$ssoProfileName]; + } else { + return self::reject("Profile {$ssoProfileName} does not exist in {$filename}."); + } + if (!empty($ssoProfile['sso_session'])) { + return CredentialProvider::getSsoCredentials($profiles, $ssoProfileName, $filename, $config); + } else { + return CredentialProvider::getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config); + } + }; + } + /** + * Credential provider that creates credentials using + * ecs credentials by a GET request, whose uri is specified + * by environment variable + * + * @param array $config Array of configuration data. + * + * @return EcsCredentialProvider + * @see Aws\Credentials\EcsCredentialProvider for $config details. + */ + public static function ecsCredentials(array $config = []) + { + return new EcsCredentialProvider($config); + } + /** + * Credential provider that creates credentials using assume role + * + * @param array $config Array of configuration data + * @return callable + * @see Aws\Credentials\AssumeRoleCredentialProvider for $config details. + */ + public static function assumeRole(array $config = []) + { + return new AssumeRoleCredentialProvider($config); + } + /** + * Credential provider that creates credentials by assuming role from a + * Web Identity Token + * + * @param array $config Array of configuration data + * @return callable + * @see Aws\Credentials\AssumeRoleWithWebIdentityCredentialProvider for + * $config details. + */ + public static function assumeRoleWithWebIdentityCredentialProvider(array $config = []) + { + return function () use($config) { + $arnFromEnv = \getenv(self::ENV_ARN); + $tokenFromEnv = \getenv(self::ENV_TOKEN_FILE); + $stsClient = isset($config['stsClient']) ? $config['stsClient'] : null; + $region = isset($config['region']) ? $config['region'] : null; + if ($tokenFromEnv && $arnFromEnv) { + $sessionName = \getenv(self::ENV_ROLE_SESSION_NAME) ? \getenv(self::ENV_ROLE_SESSION_NAME) : null; + $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $arnFromEnv, 'WebIdentityTokenFile' => $tokenFromEnv, 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region, 'source' => CredentialSources::ENVIRONMENT_STS_WEB_ID_TOKEN]); + return $provider(); + } + $profileName = \getenv(self::ENV_PROFILE) ?: 'default'; + if (isset($config['filename'])) { + $profiles = self::loadProfiles($config['filename']); + } else { + $profiles = self::loadDefaultProfiles(); + } + if (isset($profiles[$profileName])) { + $profile = $profiles[$profileName]; + if (isset($profile['region'])) { + $region = $profile['region']; + } + if (isset($profile['web_identity_token_file']) && isset($profile['role_arn'])) { + $sessionName = isset($profile['role_session_name']) ? $profile['role_session_name'] : null; + $provider = new AssumeRoleWithWebIdentityCredentialProvider(['RoleArn' => $profile['role_arn'], 'WebIdentityTokenFile' => $profile['web_identity_token_file'], 'SessionName' => $sessionName, 'client' => $stsClient, 'region' => $region, 'source' => CredentialSources::PROFILE_STS_WEB_ID_TOKEN]); + return $provider(); + } + } else { + return self::reject("Unknown profile: {$profileName}"); + } + return self::reject("No RoleArn or WebIdentityTokenFile specified"); + }; + } + /** + * Credentials provider that creates credentials using an ini file stored + * in the current user's home directory. A source can be provided + * in this file for assuming a role using the credential_source config option. + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile in "~/.aws/credentials". + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the home directory. + * @param array|null $config If provided, may contain the following: + * preferStaticCredentials: If true, prefer static + * credentials to role_arn if both are present + * disableAssumeRole: If true, disable support for + * roles that assume an IAM role. If true and role profile + * is selected, an error is raised. + * stsClient: StsClient used to assume role specified in profile + * + * @return callable + */ + public static function ini($profile = null, $filename = null, array $config = []) + { + $filename = self::getFileName($filename); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename, $config) { + $preferStaticCredentials = isset($config['preferStaticCredentials']) ? $config['preferStaticCredentials'] : \false; + $disableAssumeRole = isset($config['disableAssumeRole']) ? $config['disableAssumeRole'] : \false; + $stsClient = isset($config['stsClient']) ? $config['stsClient'] : null; + if (!@\is_readable($filename)) { + return self::reject("Cannot read credentials from {$filename}"); + } + $data = self::loadProfiles($filename); + if ($data === \false) { + return self::reject("Invalid credentials file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in credentials file"); + } + /* + In the CLI, the presence of both a role_arn and static credentials have + different meanings depending on how many profiles have been visited. For + the first profile processed, role_arn takes precedence over any static + credentials, but for all subsequent profiles, static credentials are + used if present, and only in their absence will the profile's + source_profile and role_arn keys be used to load another set of + credentials. This bool is intended to yield compatible behaviour in this + sdk. + */ + $preferStaticCredentialsToRoleArn = $preferStaticCredentials && isset($data[$profile]['aws_access_key_id']) && isset($data[$profile]['aws_secret_access_key']); + if (isset($data[$profile]['role_arn']) && !$preferStaticCredentialsToRoleArn) { + if ($disableAssumeRole) { + return self::reject("Role assumption profiles are disabled. " . "Failed to load profile " . $profile); + } + return self::loadRoleProfile($data, $profile, $filename, $stsClient, $config); + } + if (!isset($data[$profile]['aws_access_key_id']) || !isset($data[$profile]['aws_secret_access_key'])) { + return self::reject("No credentials present in INI profile " . "'{$profile}' ({$filename})"); + } + if (empty($data[$profile]['aws_session_token'])) { + $data[$profile]['aws_session_token'] = isset($data[$profile]['aws_security_token']) ? $data[$profile]['aws_security_token'] : null; + } + return Promise\Create::promiseFor(new Credentials($data[$profile]['aws_access_key_id'], $data[$profile]['aws_secret_access_key'], $data[$profile]['aws_session_token'], null, $data[$profile]['aws_account_id'] ?? null, CredentialSources::PROFILE)); + }; + } + /** + * Credentials provider that creates credentials using a process configured in + * ini file stored in the current user's home directory. + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile in "~/.aws/credentials". + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the home directory. + * + * @return callable + */ + public static function process($profile = null, $filename = null) + { + $filename = self::getFileName($filename); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read process credentials from {$filename}"); + } + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true, \INI_SCANNER_RAW); + if ($data === \false) { + return self::reject("Invalid credentials file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in credentials file"); + } + if (!isset($data[$profile]['credential_process'])) { + return self::reject("No credential_process present in INI profile " . "'{$profile}' ({$filename})"); + } + $credentialProcess = $data[$profile]['credential_process']; + $json = \shell_exec($credentialProcess); + $processData = \json_decode($json, \true); + // Only support version 1 + if (isset($processData['Version'])) { + if ($processData['Version'] !== 1) { + return self::reject("credential_process does not return Version == 1"); + } + } + if (!isset($processData['AccessKeyId']) || !isset($processData['SecretAccessKey'])) { + return self::reject("credential_process does not return valid credentials"); + } + if (isset($processData['Expiration'])) { + try { + $expiration = new DateTimeResult($processData['Expiration']); + } catch (\Exception $e) { + return self::reject("credential_process returned invalid expiration"); + } + $now = new DateTimeResult(); + if ($expiration < $now) { + return self::reject("credential_process returned expired credentials"); + } + $expires = $expiration->getTimestamp(); + } else { + $expires = null; + } + if (empty($processData['SessionToken'])) { + $processData['SessionToken'] = null; + } + $accountId = null; + if (!empty($processData['AccountId'])) { + $accountId = $processData['AccountId']; + } elseif (!empty($data[$profile]['aws_account_id'])) { + $accountId = $data[$profile]['aws_account_id']; + } + return Promise\Create::promiseFor(new Credentials($processData['AccessKeyId'], $processData['SecretAccessKey'], $processData['SessionToken'], $expires, $accountId, CredentialSources::PROFILE_PROCESS)); + }; + } + /** + * Assumes role for profile that includes role_arn + * + * @return callable + */ + private static function loadRoleProfile($profiles, $profileName, $filename, $stsClient, $config = []) + { + $roleProfile = $profiles[$profileName]; + $roleArn = isset($roleProfile['role_arn']) ? $roleProfile['role_arn'] : ''; + $roleSessionName = isset($roleProfile['role_session_name']) ? $roleProfile['role_session_name'] : 'aws-sdk-php-' . \round(\microtime(\true) * 1000); + if (empty($roleProfile['source_profile']) == empty($roleProfile['credential_source'])) { + return self::reject("Either source_profile or credential_source must be set " . "using profile " . $profileName . ", but not both."); + } + $sourceProfileName = ""; + if (!empty($roleProfile['source_profile'])) { + $sourceProfileName = $roleProfile['source_profile']; + if (!isset($profiles[$sourceProfileName])) { + return self::reject("source_profile " . $sourceProfileName . " using profile " . $profileName . " does not exist"); + } + if (isset($config['visited_profiles']) && \in_array($roleProfile['source_profile'], $config['visited_profiles'])) { + return self::reject("Circular source_profile reference found."); + } + $config['visited_profiles'][] = $roleProfile['source_profile']; + } else { + if (empty($roleArn)) { + return self::reject("A role_arn must be provided with credential_source in " . "file {$filename} under profile {$profileName} "); + } + } + if (empty($stsClient)) { + $sourceRegion = isset($profiles[$sourceProfileName]['region']) ? $profiles[$sourceProfileName]['region'] : 'us-east-1'; + $config['preferStaticCredentials'] = \true; + $sourceCredentials = null; + if (!empty($roleProfile['source_profile'])) { + $sourceCredentials = \call_user_func(CredentialProvider::ini($sourceProfileName, $filename, $config))->wait(); + } else { + $sourceCredentials = self::getCredentialsFromSource($profileName, $filename); + } + $stsClient = new StsClient(['credentials' => $sourceCredentials, 'region' => $sourceRegion, 'version' => '2011-06-15']); + } + $result = $stsClient->assumeRole(['RoleArn' => $roleArn, 'RoleSessionName' => $roleSessionName]); + $credentials = $stsClient->createCredentials($result, CredentialSources::STS_ASSUME_ROLE); + return Promise\Create::promiseFor($credentials); + } + /** + * Gets the environment's HOME directory if available. + * + * @return null|string + */ + private static function getHomeDir() + { + // On Linux/Unix-like systems, use the HOME environment variable + if ($homeDir = \getenv('HOME')) { + return $homeDir; + } + // Get the HOMEDRIVE and HOMEPATH values for Windows hosts + $homeDrive = \getenv('HOMEDRIVE'); + $homePath = \getenv('HOMEPATH'); + return $homeDrive && $homePath ? $homeDrive . $homePath : null; + } + /** + * Gets profiles from specified $filename, or default ini files. + */ + private static function loadProfiles($filename) + { + $profileData = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true, \INI_SCANNER_RAW); + // If loading .aws/credentials, also load .aws/config when AWS_SDK_LOAD_NONDEFAULT_CONFIG is set + if ($filename === self::getHomeDir() . '/.aws/credentials' && \getenv('AWS_SDK_LOAD_NONDEFAULT_CONFIG')) { + $configFilename = self::getHomeDir() . '/.aws/config'; + $configProfileData = \WPMailSMTP\Vendor\Aws\parse_ini_file($configFilename, \true, \INI_SCANNER_RAW); + foreach ($configProfileData as $name => $profile) { + // standardize config profile names + $name = \str_replace('profile ', '', $name); + if (!isset($profileData[$name])) { + $profileData[$name] = $profile; + } + } + } + return $profileData; + } + /** + * Gets profiles from ~/.aws/credentials and ~/.aws/config ini files + */ + private static function loadDefaultProfiles() + { + $profiles = []; + $credFile = self::getHomeDir() . '/.aws/credentials'; + $configFile = self::getHomeDir() . '/.aws/config'; + if (\file_exists($credFile)) { + $profiles = \WPMailSMTP\Vendor\Aws\parse_ini_file($credFile, \true, \INI_SCANNER_RAW); + } + if (\file_exists($configFile)) { + $configProfileData = \WPMailSMTP\Vendor\Aws\parse_ini_file($configFile, \true, \INI_SCANNER_RAW); + foreach ($configProfileData as $name => $profile) { + // standardize config profile names + $name = \str_replace('profile ', '', $name); + if (!isset($profiles[$name])) { + $profiles[$name] = $profile; + } + } + } + return $profiles; + } + public static function getCredentialsFromSource($profileName = '', $filename = '', $config = []) + { + $data = self::loadProfiles($filename); + $credentialSource = !empty($data[$profileName]['credential_source']) ? $data[$profileName]['credential_source'] : null; + $credentialsPromise = null; + switch ($credentialSource) { + case 'Environment': + $credentialsPromise = self::env(); + break; + case 'Ec2InstanceMetadata': + $credentialsPromise = self::instanceProfile($config); + break; + case 'EcsContainer': + $credentialsPromise = self::ecsCredentials($config); + break; + default: + throw new CredentialsException("Invalid credential_source found in config file: {$credentialSource}. Valid inputs " . "include Environment, Ec2InstanceMetadata, and EcsContainer."); + } + $credentialsResult = null; + try { + $credentialsResult = $credentialsPromise()->wait(); + } catch (\Exception $reason) { + return self::reject("Unable to successfully retrieve credentials from the source specified in the" . " credentials file: {$credentialSource}; failure message was: " . $reason->getMessage()); + } + return function () use($credentialsResult) { + return Promise\Create::promiseFor($credentialsResult); + }; + } + private static function reject($msg) + { + return new Promise\RejectedPromise(new CredentialsException($msg)); + } + /** + * @param $filename + * @return string + */ + private static function getFileName($filename) + { + if (!isset($filename)) { + $filename = \getenv(self::ENV_SHARED_CREDENTIALS_FILE) ?: self::getHomeDir() . '/.aws/credentials'; + } + return $filename; + } + /** + * @return boolean + */ + public static function shouldUseEcs() + { + //Check for relative uri. if not, then full uri. + //fall back to server for each as getenv is not thread-safe. + return !empty(\getenv(EcsCredentialProvider::ENV_URI)) || !empty($_SERVER[EcsCredentialProvider::ENV_URI]) || !empty(\getenv(EcsCredentialProvider::ENV_FULL_URI)) || !empty($_SERVER[EcsCredentialProvider::ENV_FULL_URI]); + } + /** + * @param $profiles + * @param $ssoProfileName + * @param $filename + * @param $config + * @return Promise\PromiseInterface + */ + private static function getSsoCredentials($profiles, $ssoProfileName, $filename, $config) + { + if (empty($config['ssoOidcClient'])) { + $ssoProfile = $profiles[$ssoProfileName]; + $sessionName = $ssoProfile['sso_session']; + if (empty($profiles['sso-session ' . $sessionName])) { + return self::reject("Could not find sso-session {$sessionName} in {$filename}"); + } + $ssoSession = $profiles['sso-session ' . $ssoProfile['sso_session']]; + $ssoOidcClient = new Aws\SSOOIDC\SSOOIDCClient(['region' => $ssoSession['sso_region'], 'version' => '2019-06-10', 'credentials' => \false]); + } else { + $ssoOidcClient = $config['ssoClient']; + } + $tokenPromise = new Aws\Token\SsoTokenProvider($ssoProfileName, $filename, $ssoOidcClient); + $token = $tokenPromise()->wait(); + $ssoCredentials = CredentialProvider::getCredentialsFromSsoService($ssoProfile, $ssoSession['sso_region'], $token->getToken(), $config); + $expiration = $ssoCredentials['expiration']; + return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration, $ssoProfile['sso_account_id'], CredentialSources::PROFILE_SSO)); + } + /** + * @param $profiles + * @param $ssoProfileName + * @param $filename + * @param $config + * @return Promise\PromiseInterface + */ + private static function getSsoCredentialsLegacy($profiles, $ssoProfileName, $filename, $config) + { + $ssoProfile = $profiles[$ssoProfileName]; + if (empty($ssoProfile['sso_start_url']) || empty($ssoProfile['sso_region']) || empty($ssoProfile['sso_account_id']) || empty($ssoProfile['sso_role_name'])) { + return self::reject("Profile {$ssoProfileName} in {$filename} must contain the following keys: " . "sso_start_url, sso_region, sso_account_id, and sso_role_name."); + } + $tokenLocation = self::getHomeDir() . '/.aws/sso/cache/' . \sha1($ssoProfile['sso_start_url']) . ".json"; + if (!@\is_readable($tokenLocation)) { + return self::reject("Unable to read token file at {$tokenLocation}"); + } + $tokenData = \json_decode(\file_get_contents($tokenLocation), \true); + if (empty($tokenData['accessToken']) || empty($tokenData['expiresAt'])) { + return self::reject("Token file at {$tokenLocation} must contain an access token and an expiration"); + } + try { + $expiration = (new DateTimeResult($tokenData['expiresAt']))->getTimestamp(); + } catch (\Exception $e) { + return self::reject("Cached SSO credentials returned an invalid expiration"); + } + $now = \time(); + if ($expiration < $now) { + return self::reject("Cached SSO credentials returned expired credentials"); + } + $ssoCredentials = CredentialProvider::getCredentialsFromSsoService($ssoProfile, $ssoProfile['sso_region'], $tokenData['accessToken'], $config); + return Promise\Create::promiseFor(new Credentials($ssoCredentials['accessKeyId'], $ssoCredentials['secretAccessKey'], $ssoCredentials['sessionToken'], $expiration, $ssoProfile['sso_account_id'], CredentialSources::PROFILE_SSO_LEGACY)); + } + /** + * @param array $ssoProfile + * @param string $clientRegion + * @param string $accessToken + * @param array $config + * @return array|null + */ + private static function getCredentialsFromSsoService($ssoProfile, $clientRegion, $accessToken, $config) + { + if (empty($config['ssoClient'])) { + $ssoClient = new Aws\SSO\SSOClient(['region' => $clientRegion, 'version' => '2019-06-10', 'credentials' => \false]); + } else { + $ssoClient = $config['ssoClient']; + } + $ssoResponse = $ssoClient->getRoleCredentials(['accessToken' => $accessToken, 'accountId' => $ssoProfile['sso_account_id'], 'roleName' => $ssoProfile['sso_role_name']]); + $ssoCredentials = $ssoResponse['roleCredentials']; + return $ssoCredentials; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialSources.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialSources.php new file mode 100644 index 00000000..4f66b3a7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/CredentialSources.php @@ -0,0 +1,22 @@ +key = \trim((string) $key); + $this->secret = \trim((string) $secret); + $this->token = $token; + $this->expires = $expires; + $this->accountId = $accountId; + $this->source = $source ?? CredentialSources::STATIC; + } + public static function __set_state(array $state) + { + return new self($state['key'], $state['secret'], $state['token'], $state['expires'], $state['accountId'], $state['source'] ?? null); + } + public function getAccessKeyId() + { + return $this->key; + } + public function getSecretKey() + { + return $this->secret; + } + public function getSecurityToken() + { + return $this->token; + } + public function getExpiration() + { + return $this->expires; + } + public function isExpired() + { + return $this->expires !== null && \time() >= $this->expires; + } + public function getAccountId() + { + return $this->accountId; + } + public function getSource() + { + return $this->source; + } + public function toArray() + { + return ['key' => $this->key, 'secret' => $this->secret, 'token' => $this->token, 'expires' => $this->expires, 'accountId' => $this->accountId, 'source' => $this->source]; + } + public function serialize() + { + return \json_encode($this->__serialize()); + } + public function unserialize($serialized) + { + $data = \json_decode($serialized, \true); + $this->__unserialize($data); + } + public function __serialize() + { + return $this->toArray(); + } + public function __unserialize($data) + { + $this->key = $data['key']; + $this->secret = $data['secret']; + $this->token = $data['token']; + $this->expires = $data['expires']; + $this->accountId = $data['accountId'] ?? null; + $this->source = $data['source'] ?? null; + } + /** + * Internal-only. Used when IMDS is unreachable + * or returns expires credentials. + * + * @internal + */ + public function extendExpiration() + { + $extension = \mt_rand(5, 10); + $this->expires = \time() + $extension * 60; + $message = <<= $loopbackStart && $ipLong <= $loopbackEnd; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/EcsCredentialProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/EcsCredentialProvider.php new file mode 100644 index 00000000..b218618f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/EcsCredentialProvider.php @@ -0,0 +1,206 @@ +timeout = (float) isset($config['timeout']) ? $config['timeout'] : (\getenv(self::ENV_TIMEOUT) ?: self::DEFAULT_ENV_TIMEOUT); + $this->retries = (int) isset($config['retries']) ? $config['retries'] : ((int) \getenv(self::ENV_RETRIES) ?: self::DEFAULT_ENV_RETRIES); + $this->client = $config['client'] ?? \WPMailSMTP\Vendor\Aws\default_http_handler(); + } + /** + * Load container credentials. + * + * @return PromiseInterface + * @throws GuzzleException + */ + public function __invoke() + { + $this->attempts = 0; + $uri = $this->getEcsUri(); + if ($this->isCompatibleUri($uri)) { + return Promise\Coroutine::of(function () { + $client = $this->client; + $request = new Request('GET', $this->getEcsUri()); + $headers = $this->getHeadersForAuthToken(); + $credentials = null; + while ($credentials === null) { + $credentials = (yield $client($request, ['timeout' => $this->timeout, 'proxy' => '', 'headers' => $headers])->then(function (ResponseInterface $response) { + $result = $this->decodeResult((string) $response->getBody()); + if (!isset($result['AccountId']) && isset($result['RoleArn'])) { + try { + $parsedArn = new Arn($result['RoleArn']); + $result['AccountId'] = $parsedArn->getAccountId(); + } catch (\Exception $e) { + // AccountId will be null + } + } + return new Credentials($result['AccessKeyId'], $result['SecretAccessKey'], $result['Token'], \strtotime($result['Expiration']), $result['AccountId'] ?? null, CredentialSources::ECS); + })->otherwise(function ($reason) { + $reason = \is_array($reason) ? $reason['exception'] : $reason; + $isRetryable = $reason instanceof ConnectException; + if ($isRetryable && $this->attempts < $this->retries) { + \sleep((int) \pow(1.2, $this->attempts)); + } else { + $msg = $reason->getMessage(); + throw new CredentialsException(\sprintf('Error retrieving credentials from container metadata after attempt %d/%d (%s)', $this->attempts, $this->retries, $msg)); + } + })); + $this->attempts++; + } + (yield $credentials); + }); + } + throw new CredentialsException("Uri '{$uri}' contains an unsupported host."); + } + /** + * Returns the number of attempts that have been done. + * + * @return int + */ + public function getAttempts() : int + { + return $this->attempts; + } + /** + * Retrieves authorization token. + * + * @return array|false|string + */ + private function getEcsAuthToken() + { + if (!empty($path = \getenv(self::ENV_AUTH_TOKEN_FILE))) { + $token = @\file_get_contents($path); + if (\false === $token) { + \clearstatcache(\true, \dirname($path) . \DIRECTORY_SEPARATOR . @\readlink($path)); + \clearstatcache(\true, \dirname($path) . \DIRECTORY_SEPARATOR . \dirname(@\readlink($path))); + \clearstatcache(\true, $path); + } + if (!\is_readable($path)) { + throw new CredentialsException("Failed to read authorization token from '{$path}': no such file or directory."); + } + $token = @\file_get_contents($path); + if (empty($token)) { + throw new CredentialsException("Invalid authorization token read from `{$path}`. Token file is empty!"); + } + return $token; + } + return \getenv(self::ENV_AUTH_TOKEN); + } + /** + * Provides headers for credential metadata request. + * + * @return array|array[]|string[] + */ + private function getHeadersForAuthToken() + { + $authToken = self::getEcsAuthToken(); + $headers = []; + if (!empty($authToken)) { + $headers = ['Authorization' => $authToken]; + } + return $headers; + } + /** @deprecated */ + public function setHeaderForAuthToken() + { + $authToken = self::getEcsAuthToken(); + $headers = []; + if (!empty($authToken)) { + $headers = ['Authorization' => $authToken]; + } + return $headers; + } + /** + * Fetch container metadata URI from container environment variable. + * + * @return string Returns container metadata URI + */ + private function getEcsUri() + { + $credsUri = \getenv(self::ENV_URI); + if ($credsUri === \false) { + $credsUri = $_SERVER[self::ENV_URI] ?? ''; + } + if (empty($credsUri)) { + $credFullUri = \getenv(self::ENV_FULL_URI); + if ($credFullUri === \false) { + $credFullUri = $_SERVER[self::ENV_FULL_URI] ?? ''; + } + if (!empty($credFullUri)) { + return $credFullUri; + } + } + return self::SERVER_URI . $credsUri; + } + private function decodeResult($response) + { + $result = \json_decode($response, \true); + if (!isset($result['AccessKeyId'])) { + throw new CredentialsException('Unexpected container metadata credentials value'); + } + return $result; + } + /** + * Determines whether or not a given request URI is a valid + * container credential request URI. + * + * @param $uri + * + * @return bool + */ + private function isCompatibleUri($uri) + { + $parsed = \parse_url($uri); + if ($parsed['scheme'] !== 'https') { + $host = \trim($parsed['host'], '[]'); + $ecsHost = \parse_url(self::SERVER_URI)['host']; + $eksHost = self::EKS_SERVER_HOST_IPV4; + if ($host !== $ecsHost && $host !== $eksHost && $host !== self::EKS_SERVER_HOST_IPV6 && !CredentialsUtils::isLoopBackAddress(\gethostbyname($host))) { + return \false; + } + } + return \true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php new file mode 100644 index 00000000..302159e1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php @@ -0,0 +1,324 @@ +timeout = (float) \getenv(self::ENV_TIMEOUT) ?: $config['timeout'] ?? self::DEFAULT_TIMEOUT; + $this->profile = $config['profile'] ?? null; + $this->retries = (int) \getenv(self::ENV_RETRIES) ?: $config['retries'] ?? self::DEFAULT_RETRIES; + $this->client = $config['client'] ?? \WPMailSMTP\Vendor\Aws\default_http_handler(); + $this->ec2MetadataV1Disabled = $config[self::CFG_EC2_METADATA_V1_DISABLED] ?? null; + $this->endpoint = $config[self::CFG_EC2_METADATA_SERVICE_ENDPOINT] ?? null; + if (!empty($this->endpoint) && !$this->isValidEndpoint($this->endpoint)) { + throw new \InvalidArgumentException('The provided URI "' . $this->endpoint . '" is invalid, or contains an unsupported host'); + } + $this->endpointMode = $config[self::CFG_EC2_METADATA_SERVICE_ENDPOINT_MODE] ?? null; + $this->config = $config; + } + /** + * Loads instance profile credentials. + * + * @return PromiseInterface + */ + public function __invoke($previousCredentials = null) + { + $this->attempts = 0; + return Promise\Coroutine::of(function () use($previousCredentials) { + // Retrieve token or switch out of secure mode + $token = null; + while ($this->secureMode && \is_null($token)) { + try { + $token = (yield $this->request(self::TOKEN_PATH, 'PUT', ['x-aws-ec2-metadata-token-ttl-seconds' => self::DEFAULT_TOKEN_TTL_SECONDS])); + } catch (TransferException $e) { + if ($this->getExceptionStatusCode($e) === 500 && $previousCredentials instanceof Credentials) { + goto generateCredentials; + } elseif ($this->shouldFallbackToIMDSv1() && (!\method_exists($e, 'getResponse') || empty($e->getResponse()) || !\in_array($e->getResponse()->getStatusCode(), [400, 500, 502, 503, 504]))) { + $this->secureMode = \false; + } else { + $this->handleRetryableException($e, [], $this->createErrorMessage('Error retrieving metadata token')); + } + } + $this->attempts++; + } + // Set token header only for secure mode + $headers = []; + if ($this->secureMode) { + $headers = ['x-aws-ec2-metadata-token' => $token]; + } + // Retrieve profile + while (!$this->profile) { + try { + $this->profile = (yield $this->request(self::CRED_PATH, 'GET', $headers)); + } catch (TransferException $e) { + // 401 indicates insecure flow not supported, switch to + // attempting secure mode for subsequent calls + if (!empty($this->getExceptionStatusCode($e)) && $this->getExceptionStatusCode($e) === 401) { + $this->secureMode = \true; + } + $this->handleRetryableException($e, ['blacklist' => [401, 403]], $this->createErrorMessage($e->getMessage())); + } + $this->attempts++; + } + // Retrieve credentials + $result = null; + while ($result == null) { + try { + $json = (yield $this->request(self::CRED_PATH . $this->profile, 'GET', $headers)); + $result = $this->decodeResult($json); + } catch (InvalidJsonException $e) { + $this->handleRetryableException($e, ['blacklist' => [401, 403]], $this->createErrorMessage('Invalid JSON response, retries exhausted')); + } catch (TransferException $e) { + // 401 indicates insecure flow not supported, switch to + // attempting secure mode for subsequent calls + if (($this->getExceptionStatusCode($e) === 500 || \strpos($e->getMessage(), "cURL error 28") !== \false) && $previousCredentials instanceof Credentials) { + goto generateCredentials; + } elseif (!empty($this->getExceptionStatusCode($e)) && $this->getExceptionStatusCode($e) === 401) { + $this->secureMode = \true; + } + $this->handleRetryableException($e, ['blacklist' => [401, 403]], $this->createErrorMessage($e->getMessage())); + } + $this->attempts++; + } + generateCredentials: + if (!isset($result)) { + $credentials = $previousCredentials; + } else { + $credentials = new Credentials($result['AccessKeyId'], $result['SecretAccessKey'], $result['Token'], \strtotime($result['Expiration']), $result['AccountId'] ?? null, CredentialSources::IMDS); + } + if ($credentials->isExpired()) { + $credentials->extendExpiration(); + } + (yield $credentials); + }); + } + /** + * @param string $url + * @param string $method + * @param array $headers + * @return PromiseInterface Returns a promise that is fulfilled with the + * body of the response as a string. + */ + private function request($url, $method = 'GET', $headers = []) + { + $disabled = \getenv(self::ENV_DISABLE) ?: \false; + if (\strcasecmp($disabled, 'true') === 0) { + throw new CredentialsException($this->createErrorMessage('EC2 metadata service access disabled')); + } + $fn = $this->client; + $request = new Request($method, $this->resolveEndpoint() . $url); + $userAgent = 'aws-sdk-php/' . Sdk::VERSION; + if (\defined('WPMailSMTP\\Vendor\\HHVM_VERSION')) { + $userAgent .= ' HHVM/' . HHVM_VERSION; + } + $userAgent .= ' ' . \WPMailSMTP\Vendor\Aws\default_user_agent(); + $request = $request->withHeader('User-Agent', $userAgent); + foreach ($headers as $key => $value) { + $request = $request->withHeader($key, $value); + } + return $fn($request, ['timeout' => $this->timeout])->then(function (ResponseInterface $response) { + return (string) $response->getBody(); + })->otherwise(function (array $reason) { + $reason = $reason['exception']; + if ($reason instanceof TransferException) { + throw $reason; + } + $msg = $reason->getMessage(); + throw new CredentialsException($this->createErrorMessage($msg)); + }); + } + private function handleRetryableException(\Exception $e, $retryOptions, $message) + { + $isRetryable = \true; + if (!empty($status = $this->getExceptionStatusCode($e)) && isset($retryOptions['blacklist']) && \in_array($status, $retryOptions['blacklist'])) { + $isRetryable = \false; + } + if ($isRetryable && $this->attempts < $this->retries) { + \sleep((int) \pow(1.2, $this->attempts)); + } else { + throw new CredentialsException($message); + } + } + private function getExceptionStatusCode(\Exception $e) + { + if (\method_exists($e, 'getResponse') && !empty($e->getResponse())) { + return $e->getResponse()->getStatusCode(); + } + return null; + } + private function createErrorMessage($previous) + { + return "Error retrieving credentials from the instance profile " . "metadata service. ({$previous})"; + } + private function decodeResult($response) + { + $result = \json_decode($response, \true); + if (\json_last_error() > 0) { + throw new InvalidJsonException(); + } + if ($result['Code'] !== 'Success') { + throw new CredentialsException('Unexpected instance profile ' . 'response code: ' . $result['Code']); + } + return $result; + } + /** + * This functions checks for whether we should fall back to IMDSv1 or not. + * If $ec2MetadataV1Disabled is null then we will try to resolve this value from + * the following sources: + * - From environment: "AWS_EC2_METADATA_V1_DISABLED". + * - From config file: aws_ec2_metadata_v1_disabled + * - Defaulted to false + * + * @return bool + */ + private function shouldFallbackToIMDSv1() : bool + { + $isImdsV1Disabled = \WPMailSMTP\Vendor\Aws\boolean_value($this->ec2MetadataV1Disabled) ?? \WPMailSMTP\Vendor\Aws\boolean_value(ConfigurationResolver::resolve(self::CFG_EC2_METADATA_V1_DISABLED, self::DEFAULT_AWS_EC2_METADATA_V1_DISABLED, 'bool', $this->config)) ?? self::DEFAULT_AWS_EC2_METADATA_V1_DISABLED; + return !$isImdsV1Disabled; + } + /** + * Resolves the metadata service endpoint. If the endpoint is not provided + * or configured then, the default endpoint, based on the endpoint mode resolved, + * will be used. + * Example: if endpoint_mode is resolved to be IPv4 and the endpoint is not provided + * then, the endpoint to be used will be http://169.254.169.254. + * + * @return string + */ + private function resolveEndpoint() : string + { + $endpoint = $this->endpoint; + if (\is_null($endpoint)) { + $endpoint = ConfigurationResolver::resolve(self::CFG_EC2_METADATA_SERVICE_ENDPOINT, $this->getDefaultEndpoint(), 'string', $this->config); + } + if (!$this->isValidEndpoint($endpoint)) { + throw new CredentialsException('The provided URI "' . $endpoint . '" is invalid, or contains an unsupported host'); + } + if (\substr($endpoint, \strlen($endpoint) - 1) !== '/') { + $endpoint = $endpoint . '/'; + } + return $endpoint . 'latest/'; + } + /** + * Resolves the default metadata service endpoint. + * If endpoint_mode is resolved as IPv4 then: + * - endpoint = http://169.254.169.254 + * If endpoint_mode is resolved as IPv6 then: + * - endpoint = http://[fd00:ec2::254] + * + * @return string + */ + private function getDefaultEndpoint() : string + { + $endpointMode = $this->resolveEndpointMode(); + switch ($endpointMode) { + case self::ENDPOINT_MODE_IPv4: + return self::DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT; + case self::ENDPOINT_MODE_IPv6: + return self::DEFAULT_METADATA_SERVICE_IPv6_ENDPOINT; + } + throw new CredentialsException("Invalid endpoint mode '{$endpointMode}' resolved"); + } + /** + * Resolves the endpoint mode to be considered when resolving the default + * metadata service endpoint. + * + * @return string + */ + private function resolveEndpointMode() : string + { + $endpointMode = $this->endpointMode; + if (\is_null($endpointMode)) { + $endpointMode = ConfigurationResolver::resolve(self::CFG_EC2_METADATA_SERVICE_ENDPOINT_MODE, self::ENDPOINT_MODE_IPv4, 'string', $this->config); + } + return $endpointMode; + } + /** + * This method checks for whether a provide URI is valid. + * @param string $uri this parameter is the uri to do the validation against to. + * + * @return string|null + */ + private function isValidEndpoint($uri) : bool + { + // We make sure first the provided uri is a valid URL + $isValidURL = \filter_var($uri, \FILTER_VALIDATE_URL) !== \false; + if (!$isValidURL) { + return \false; + } + // We make sure that if is a no secure host then it must be a loop back address. + $parsedUri = \parse_url($uri); + if ($parsedUri['scheme'] !== 'https') { + $host = \trim($parsedUri['host'], '[]'); + return CredentialsUtils::isLoopBackAddress(\gethostbyname($host)) || \in_array($uri, [self::DEFAULT_METADATA_SERVICE_IPv4_ENDPOINT, self::DEFAULT_METADATA_SERVICE_IPv6_ENDPOINT]); + } + return \true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AbstractCryptoClient.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AbstractCryptoClient.php new file mode 100644 index 00000000..2a6c130e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AbstractCryptoClient.php @@ -0,0 +1,102 @@ +stream = $cipherText; + $this->key = $key; + $this->cipherMethod = clone $cipherMethod; + } + public function getOpenSslName() + { + return $this->cipherMethod->getOpenSslName(); + } + public function getAesName() + { + return $this->cipherMethod->getAesName(); + } + public function getCurrentIv() + { + return $this->cipherMethod->getCurrentIv(); + } + public function getSize() : ?int + { + $plainTextSize = $this->stream->getSize(); + if ($this->cipherMethod->requiresPadding()) { + // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be + // added to the plaintext to make it an even number of blocks. The + // plaintext is between strlen($cipherText) - self::BLOCK_SIZE and + // strlen($cipherText) - 1 + return null; + } + return $plainTextSize; + } + public function isWritable() : bool + { + return \false; + } + public function read($length) : string + { + if ($length > \strlen($this->buffer)) { + $this->buffer .= $this->decryptBlock((int) (self::BLOCK_SIZE * \ceil(($length - \strlen($this->buffer)) / self::BLOCK_SIZE))); + } + $data = \substr($this->buffer, 0, $length); + $this->buffer = \substr($this->buffer, $length); + return $data ? $data : ''; + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if ($offset === 0 && $whence === \SEEK_SET) { + $this->buffer = ''; + $this->cipherMethod->seek(0, \SEEK_SET); + $this->stream->seek(0, \SEEK_SET); + } else { + throw new LogicException('AES encryption streams only support being' . ' rewound, not arbitrary seeking.'); + } + } + private function decryptBlock($length) + { + if ($this->stream->eof()) { + return ''; + } + $cipherText = ''; + do { + $cipherText .= $this->stream->read((int) ($length - \strlen($cipherText))); + } while (\strlen($cipherText) < $length && !$this->stream->eof()); + $options = \OPENSSL_RAW_DATA; + if (!$this->stream->eof() && $this->stream->getSize() !== $this->stream->tell()) { + $options |= \OPENSSL_ZERO_PADDING; + } + $plaintext = \openssl_decrypt($cipherText, $this->cipherMethod->getOpenSslName(), $this->key, $options, $this->cipherMethod->getCurrentIv()); + $this->cipherMethod->update($cipherText); + return $plaintext; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesEncryptingStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesEncryptingStream.php new file mode 100644 index 00000000..cb3222a8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesEncryptingStream.php @@ -0,0 +1,113 @@ +stream = $plainText; + $this->key = $key; + $this->cipherMethod = clone $cipherMethod; + } + public function getOpenSslName() + { + return $this->cipherMethod->getOpenSslName(); + } + public function getAesName() + { + return $this->cipherMethod->getAesName(); + } + public function getCurrentIv() + { + return $this->cipherMethod->getCurrentIv(); + } + public function getSize() : ?int + { + $plainTextSize = $this->stream->getSize(); + if ($this->cipherMethod->requiresPadding() && $plainTextSize !== null) { + // PKCS7 padding requires that between 1 and self::BLOCK_SIZE be + // added to the plaintext to make it an even number of blocks. + $padding = self::BLOCK_SIZE - $plainTextSize % self::BLOCK_SIZE; + return $plainTextSize + $padding; + } + return $plainTextSize; + } + public function isWritable() : bool + { + return \false; + } + public function read($length) : string + { + if ($length > \strlen($this->buffer)) { + $this->buffer .= $this->encryptBlock((int) self::BLOCK_SIZE * \ceil(($length - \strlen($this->buffer)) / self::BLOCK_SIZE)); + } + $data = \substr($this->buffer, 0, $length); + $this->buffer = \substr($this->buffer, $length); + return $data ? $data : ''; + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if ($whence === \SEEK_CUR) { + $offset = $this->tell() + $offset; + $whence = \SEEK_SET; + } + if ($whence === \SEEK_SET) { + $this->buffer = ''; + $wholeBlockOffset = (int) ($offset / self::BLOCK_SIZE) * self::BLOCK_SIZE; + $this->stream->seek($wholeBlockOffset); + $this->cipherMethod->seek($wholeBlockOffset); + $this->read($offset - $wholeBlockOffset); + } else { + throw new LogicException('Unrecognized whence.'); + } + } + private function encryptBlock($length) + { + if ($this->stream->eof()) { + return ''; + } + $plainText = ''; + do { + $plainText .= $this->stream->read((int) ($length - \strlen($plainText))); + } while (\strlen($plainText) < $length && !$this->stream->eof()); + $options = \OPENSSL_RAW_DATA; + if (!$this->stream->eof() || $this->stream->getSize() !== $this->stream->tell()) { + $options |= \OPENSSL_ZERO_PADDING; + } + $cipherText = \openssl_encrypt($plainText, $this->cipherMethod->getOpenSslName(), $this->key, $options, $this->cipherMethod->getCurrentIv()); + $this->cipherMethod->update($cipherText); + return $cipherText; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmDecryptingStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmDecryptingStream.php new file mode 100644 index 00000000..8246b6f9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmDecryptingStream.php @@ -0,0 +1,78 @@ +cipherText = $cipherText; + $this->key = $key; + $this->initializationVector = $initializationVector; + $this->tag = $tag; + $this->aad = $aad; + $this->tagLength = $tagLength; + $this->keySize = $keySize; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + public function getOpenSslName() + { + return "aes-{$this->keySize}-gcm"; + } + public function getAesName() + { + return 'AES/GCM/NoPadding'; + } + public function getCurrentIv() + { + return $this->initializationVector; + } + public function createStream() + { + if (\version_compare(\PHP_VERSION, '7.1', '<')) { + return Psr7\Utils::streamFor(AesGcm::decrypt((string) $this->cipherText, $this->initializationVector, new Key($this->key), $this->aad, $this->tag, $this->keySize)); + } else { + $result = \openssl_decrypt((string) $this->cipherText, $this->getOpenSslName(), $this->key, \OPENSSL_RAW_DATA, $this->initializationVector, $this->tag, $this->aad); + if ($result === \false) { + throw new CryptoException('The requested object could not be' . ' decrypted due to an invalid authentication tag.'); + } + return Psr7\Utils::streamFor($result); + } + } + public function isWritable() : bool + { + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmEncryptingStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmEncryptingStream.php new file mode 100644 index 00000000..4dda04ef --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesGcmEncryptingStream.php @@ -0,0 +1,94 @@ +plaintext = $plaintext; + $this->key = $key; + $this->initializationVector = $initializationVector; + $this->aad = $aad; + $this->tagLength = $tagLength; + $this->keySize = $keySize; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + public function getOpenSslName() + { + return "aes-{$this->keySize}-gcm"; + } + /** + * Same as static method and retained for backwards compatibility + * + * @return string + */ + public function getAesName() + { + return self::getStaticAesName(); + } + public function getCurrentIv() + { + return $this->initializationVector; + } + public function createStream() + { + if (\version_compare(\PHP_VERSION, '7.1', '<')) { + return Psr7\Utils::streamFor(AesGcm::encrypt((string) $this->plaintext, $this->initializationVector, new Key($this->key), $this->aad, $this->tag, $this->keySize)); + } else { + return Psr7\Utils::streamFor(\openssl_encrypt((string) $this->plaintext, $this->getOpenSslName(), $this->key, \OPENSSL_RAW_DATA, $this->initializationVector, $this->tag, $this->aad, $this->tagLength)); + } + } + /** + * @return string + */ + public function getTag() + { + return $this->tag; + } + public function isWritable() : bool + { + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesStreamInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesStreamInterface.php new file mode 100644 index 00000000..45f62256 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/AesStreamInterface.php @@ -0,0 +1,28 @@ +baseIv = $this->iv = $iv; + $this->keySize = $keySize; + if (\strlen($iv) !== \openssl_cipher_iv_length($this->getOpenSslName())) { + throw new InvalidArgumentException('Invalid initialization vector'); + } + } + public function getOpenSslName() + { + return "aes-{$this->keySize}-cbc"; + } + public function getAesName() + { + return 'AES/CBC/PKCS5Padding'; + } + public function getCurrentIv() + { + return $this->iv; + } + public function requiresPadding() + { + return \true; + } + public function seek($offset, $whence = \SEEK_SET) + { + if ($offset === 0 && $whence === \SEEK_SET) { + $this->iv = $this->baseIv; + } else { + throw new LogicException('CBC initialization only support being' . ' rewound, not arbitrary seeking.'); + } + } + public function update($cipherTextBlock) + { + $this->iv = \substr($cipherTextBlock, self::BLOCK_SIZE * -1); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Cipher/CipherBuilderTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Cipher/CipherBuilderTrait.php new file mode 100644 index 00000000..1553a9db --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Cipher/CipherBuilderTrait.php @@ -0,0 +1,66 @@ +decryptCek(\base64_decode($envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER]), \json_decode($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], \true)); + $cipherOptions['KeySize'] = \strlen($cek) * 8; + $cipherOptions['Cipher'] = $this->getCipherFromAesName($envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]); + $decryptionStream = $this->getDecryptingStream($cipherText, $cek, $cipherOptions); + unset($cek); + return $decryptionStream; + } + private function getTagFromCiphertextStream(StreamInterface $cipherText, $tagLength) + { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' . ' size.'); + } + return (string) new LimitStream($cipherText, $tagLength, $cipherTextSize - $tagLength); + } + private function getStrippedCiphertextStream(StreamInterface $cipherText, $tagLength) + { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' . ' size.'); + } + return new LimitStream($cipherText, $cipherTextSize - $tagLength, 0); + } + /** + * Generates a stream that wraps the cipher text with the proper cipher and + * uses the content encryption key (CEK) to decrypt the data when read. + * + * @param string $cipherText Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return AesStreamInterface + * + * @internal + */ + protected function getDecryptingStream($cipherText, $cek, $cipherOptions) + { + $cipherTextStream = Psr7\Utils::streamFor($cipherText); + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['Tag'] = $this->getTagFromCiphertextStream($cipherTextStream, $cipherOptions['TagLength']); + return new AesGcmDecryptingStream($this->getStrippedCiphertextStream($cipherTextStream, $cipherOptions['TagLength']), $cek, $cipherOptions['Iv'], $cipherOptions['Tag'], $cipherOptions['Aad'] = isset($cipherOptions['Aad']) ? $cipherOptions['Aad'] : '', $cipherOptions['TagLength'] ?: null, $cipherOptions['KeySize']); + default: + $cipherMethod = $this->buildCipherMethod($cipherOptions['Cipher'], $cipherOptions['Iv'], $cipherOptions['KeySize']); + return new AesDecryptingStream($cipherTextStream, $cek, $cipherMethod); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/DecryptionTraitV2.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/DecryptionTraitV2.php new file mode 100644 index 00000000..fb26e89b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/DecryptionTraitV2.php @@ -0,0 +1,138 @@ +decryptCek(\base64_decode($envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER]), \json_decode($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], \true), $options); + $options['@CipherOptions']['KeySize'] = \strlen($cek) * 8; + $options['@CipherOptions']['Cipher'] = $this->getCipherFromAesName($envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]); + $this->validateOptionsAndEnvelope($options, $envelope); + $decryptionStream = $this->getDecryptingStream($cipherText, $cek, $options['@CipherOptions']); + unset($cek); + return $decryptionStream; + } + private function getTagFromCiphertextStream(StreamInterface $cipherText, $tagLength) + { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' . ' size.'); + } + return (string) new LimitStream($cipherText, $tagLength, $cipherTextSize - $tagLength); + } + private function getStrippedCiphertextStream(StreamInterface $cipherText, $tagLength) + { + $cipherTextSize = $cipherText->getSize(); + if ($cipherTextSize == null || $cipherTextSize <= 0) { + throw new \RuntimeException('Cannot decrypt a stream of unknown' . ' size.'); + } + return new LimitStream($cipherText, $cipherTextSize - $tagLength, 0); + } + private function validateOptionsAndEnvelope($options, $envelope) + { + $allowedCiphers = AbstractCryptoClientV2::$supportedCiphers; + $allowedKeywraps = AbstractCryptoClientV2::$supportedKeyWraps; + if ($options['@SecurityProfile'] == 'V2_AND_LEGACY') { + $allowedCiphers = \array_unique(\array_merge($allowedCiphers, AbstractCryptoClient::$supportedCiphers)); + $allowedKeywraps = \array_unique(\array_merge($allowedKeywraps, AbstractCryptoClient::$supportedKeyWraps)); + } + $v1SchemaException = new CryptoException("The requested object is encrypted" . " with V1 encryption schemas that have been disabled by" . " client configuration @SecurityProfile=V2. Retry with" . " V2_AND_LEGACY enabled or reencrypt the object."); + if (!\in_array($options['@CipherOptions']['Cipher'], $allowedCiphers)) { + if (\in_array($options['@CipherOptions']['Cipher'], AbstractCryptoClient::$supportedCiphers)) { + throw $v1SchemaException; + } + throw new CryptoException("The requested object is encrypted with" . " the cipher '{$options['@CipherOptions']['Cipher']}', which is not" . " supported for decryption with the selected security profile." . " This profile allows decryption with: " . \implode(", ", $allowedCiphers)); + } + if (!\in_array($envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], $allowedKeywraps)) { + if (\in_array($envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER], AbstractCryptoClient::$supportedKeyWraps)) { + throw $v1SchemaException; + } + throw new CryptoException("The requested object is encrypted with" . " the keywrap schema '{$envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER]}'," . " which is not supported for decryption with the current security" . " profile."); + } + $matdesc = \json_decode($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], \true); + if (isset($matdesc['aws:x-amz-cek-alg']) && $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] !== $matdesc['aws:x-amz-cek-alg']) { + throw new CryptoException("There is a mismatch in specified content" . " encryption algrithm between the materials description value" . " and the metadata envelope value: {$matdesc['aws:x-amz-cek-alg']}" . " vs. {$envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER]}."); + } + } + /** + * Generates a stream that wraps the cipher text with the proper cipher and + * uses the content encryption key (CEK) to decrypt the data when read. + * + * @param string $cipherText Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return AesStreamInterface + * + * @internal + */ + protected function getDecryptingStream($cipherText, $cek, $cipherOptions) + { + $cipherTextStream = Psr7\Utils::streamFor($cipherText); + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['Tag'] = $this->getTagFromCiphertextStream($cipherTextStream, $cipherOptions['TagLength']); + return new AesGcmDecryptingStream($this->getStrippedCiphertextStream($cipherTextStream, $cipherOptions['TagLength']), $cek, $cipherOptions['Iv'], $cipherOptions['Tag'], $cipherOptions['Aad'] = isset($cipherOptions['Aad']) ? $cipherOptions['Aad'] : '', $cipherOptions['TagLength'] ?: null, $cipherOptions['KeySize']); + default: + $cipherMethod = $this->buildCipherMethod($cipherOptions['Cipher'], $cipherOptions['Iv'], $cipherOptions['KeySize']); + return new AesDecryptingStream($cipherTextStream, $cek, $cipherMethod); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTrait.php new file mode 100644 index 00000000..e50e28fb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTrait.php @@ -0,0 +1,114 @@ + \true, 'KeySize' => \true, 'Aad' => \true]; + /** + * Dependency to generate a CipherMethod from a set of inputs for loading + * in to an AesEncryptingStream. + * + * @param string $cipherName Name of the cipher to generate for encrypting. + * @param string $iv Base Initialization Vector for the cipher. + * @param int $keySize Size of the encryption key, in bits, that will be + * used. + * + * @return Cipher\CipherMethod + * + * @internal + */ + protected abstract function buildCipherMethod($cipherName, $iv, $keySize); + /** + * Builds an AesStreamInterface and populates encryption metadata into the + * supplied envelope. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * @param MaterialsProvider $provider A provider to supply and encrypt + * materials used in encryption. + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be added to. + * + * @return AesStreamInterface + * + * @throws \InvalidArgumentException Thrown when a value in $cipherOptions + * is not valid. + * + * @internal + */ + public function encrypt(Stream $plaintext, array $cipherOptions, MaterialsProvider $provider, MetadataEnvelope $envelope) + { + $materialsDescription = $provider->getMaterialsDescription(); + $cipherOptions = \array_intersect_key($cipherOptions, self::$allowedOptions); + if (empty($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('An encryption cipher must be' . ' specified in the "cipher_options".'); + } + if (!self::isSupportedCipher($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('The cipher requested is not' . ' supported by the SDK.'); + } + if (empty($cipherOptions['KeySize'])) { + $cipherOptions['KeySize'] = 256; + } + if (!\is_int($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" must be' . ' an integer.'); + } + if (!MaterialsProvider::isSupportedKeySize($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" requested' . ' is not supported by AES (128, 192, or 256).'); + } + $cipherOptions['Iv'] = $provider->generateIv($this->getCipherOpenSslName($cipherOptions['Cipher'], $cipherOptions['KeySize'])); + $cek = $provider->generateCek($cipherOptions['KeySize']); + list($encryptingStream, $aesName) = $this->getEncryptingStream($plaintext, $cek, $cipherOptions); + // Populate envelope data + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $provider->encryptCek($cek, $materialsDescription); + unset($cek); + $envelope[MetadataEnvelope::IV_HEADER] = \base64_encode($cipherOptions['Iv']); + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = $provider->getWrapAlgorithmName(); + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; + $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = \strlen($plaintext); + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = \json_encode($materialsDescription); + if (!empty($cipherOptions['Tag'])) { + $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = \strlen($cipherOptions['Tag']) * 8; + } + return $encryptingStream; + } + /** + * Generates a stream that wraps the plaintext with the proper cipher and + * uses the content encryption key (CEK) to encrypt the data when read. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return array returns an array with two elements as follows: [string, AesStreamInterface] + * + * @internal + */ + protected function getEncryptingStream(Stream $plaintext, $cek, &$cipherOptions) + { + switch ($cipherOptions['Cipher']) { + case 'gcm': + $cipherOptions['TagLength'] = 16; + $cipherTextStream = new AesGcmEncryptingStream($plaintext, $cek, $cipherOptions['Iv'], $cipherOptions['Aad'] = isset($cipherOptions['Aad']) ? $cipherOptions['Aad'] : '', $cipherOptions['TagLength'], $cipherOptions['KeySize']); + if (!empty($cipherOptions['Aad'])) { + \trigger_error("'Aad' has been supplied for content encryption" . " with " . $cipherTextStream->getAesName() . ". The" . " PHP SDK encryption client can decrypt an object" . " encrypted in this way, but other AWS SDKs may not be" . " able to.", \E_USER_WARNING); + } + $appendStream = new AppendStream([$cipherTextStream->createStream()]); + $cipherOptions['Tag'] = $cipherTextStream->getTag(); + $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); + return [$appendStream, $cipherTextStream->getAesName()]; + default: + $cipherMethod = $this->buildCipherMethod($cipherOptions['Cipher'], $cipherOptions['Iv'], $cipherOptions['KeySize']); + $cipherTextStream = new AesEncryptingStream($plaintext, $cek, $cipherMethod); + return [$cipherTextStream, $cipherTextStream->getAesName()]; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTraitV2.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTraitV2.php new file mode 100644 index 00000000..c1d4766b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/EncryptionTraitV2.php @@ -0,0 +1,122 @@ + \true, 'KeySize' => \true, 'Aad' => \true]; + private static $encryptClasses = ['gcm' => AesGcmEncryptingStream::class]; + /** + * Dependency to generate a CipherMethod from a set of inputs for loading + * in to an AesEncryptingStream. + * + * @param string $cipherName Name of the cipher to generate for encrypting. + * @param string $iv Base Initialization Vector for the cipher. + * @param int $keySize Size of the encryption key, in bits, that will be + * used. + * + * @return Cipher\CipherMethod + * + * @internal + */ + protected abstract function buildCipherMethod($cipherName, $iv, $keySize); + /** + * Builds an AesStreamInterface and populates encryption metadata into the + * supplied envelope. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param array $options Options for use in encryption, including cipher + * options, and encryption context. + * @param MaterialsProviderV2 $provider A provider to supply and encrypt + * materials used in encryption. + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be added to. + * + * @return StreamInterface + * + * @throws \InvalidArgumentException Thrown when a value in $options['@CipherOptions'] + * is not valid. + *s + * @internal + */ + public function encrypt(Stream $plaintext, array $options, MaterialsProviderV2 $provider, MetadataEnvelope $envelope) + { + $options = \array_change_key_case($options); + $cipherOptions = \array_intersect_key($options['@cipheroptions'], self::$allowedOptions); + if (empty($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('An encryption cipher must be' . ' specified in @CipherOptions["Cipher"].'); + } + $cipherOptions['Cipher'] = \strtolower($cipherOptions['Cipher']); + if (!self::isSupportedCipher($cipherOptions['Cipher'])) { + throw new \InvalidArgumentException('The cipher requested is not' . ' supported by the SDK.'); + } + if (empty($cipherOptions['KeySize'])) { + $cipherOptions['KeySize'] = 256; + } + if (!\is_int($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" must be' . ' an integer.'); + } + if (!MaterialsProviderV2::isSupportedKeySize($cipherOptions['KeySize'])) { + throw new \InvalidArgumentException('The cipher "KeySize" requested' . ' is not supported by AES (128 or 256).'); + } + $cipherOptions['Iv'] = $provider->generateIv($this->getCipherOpenSslName($cipherOptions['Cipher'], $cipherOptions['KeySize'])); + $encryptClass = self::$encryptClasses[$cipherOptions['Cipher']]; + $aesName = $encryptClass::getStaticAesName(); + $materialsDescription = ['aws:x-amz-cek-alg' => $aesName]; + $keys = $provider->generateCek($cipherOptions['KeySize'], $materialsDescription, $options); + // Some providers modify materials description based on options + if (isset($keys['UpdatedContext'])) { + $materialsDescription = $keys['UpdatedContext']; + } + $encryptingStream = $this->getEncryptingStream($plaintext, $keys['Plaintext'], $cipherOptions); + // Populate envelope data + $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = $keys['Ciphertext']; + unset($keys); + $envelope[MetadataEnvelope::IV_HEADER] = \base64_encode($cipherOptions['Iv']); + $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = $provider->getWrapAlgorithmName(); + $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; + $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = \strlen($plaintext); + $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = \json_encode($materialsDescription); + if (!empty($cipherOptions['Tag'])) { + $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = \strlen($cipherOptions['Tag']) * 8; + } + return $encryptingStream; + } + /** + * Generates a stream that wraps the plaintext with the proper cipher and + * uses the content encryption key (CEK) to encrypt the data when read. + * + * @param Stream $plaintext Plain-text data to be encrypted using the + * materials, algorithm, and data provided. + * @param string $cek A content encryption key for use by the stream for + * encrypting the plaintext data. + * @param array $cipherOptions Options for use in determining the cipher to + * be used for encrypting data. + * + * @return array returns an array with two elements as follows: [string, AesStreamInterface] + * + * @internal + */ + protected function getEncryptingStream(Stream $plaintext, $cek, &$cipherOptions) + { + switch ($cipherOptions['Cipher']) { + // Only 'gcm' is supported for encryption currently + case 'gcm': + $cipherOptions['TagLength'] = 16; + $encryptClass = self::$encryptClasses['gcm']; + $cipherTextStream = new $encryptClass($plaintext, $cek, $cipherOptions['Iv'], $cipherOptions['Aad'] = isset($cipherOptions['Aad']) ? $cipherOptions['Aad'] : '', $cipherOptions['TagLength'], $cipherOptions['KeySize']); + if (!empty($cipherOptions['Aad'])) { + \trigger_error("'Aad' has been supplied for content encryption" . " with " . $cipherTextStream->getAesName() . ". The" . " PHP SDK encryption client can decrypt an object" . " encrypted in this way, but other AWS SDKs may not be" . " able to.", \E_USER_WARNING); + } + $appendStream = new AppendStream([$cipherTextStream->createStream()]); + $cipherOptions['Tag'] = $cipherTextStream->getTag(); + $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); + return $appendStream; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProvider.php new file mode 100644 index 00000000..8273ac7a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProvider.php @@ -0,0 +1,90 @@ +kmsClient = $kmsClient; + $this->kmsKeyId = $kmsKeyId; + } + public function fromDecryptionEnvelope(MetadataEnvelope $envelope) + { + if (empty($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER])) { + throw new \RuntimeException('Not able to detect the materials description.'); + } + $materialsDescription = \json_decode($envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER], \true); + if (empty($materialsDescription['kms_cmk_id']) && empty($materialsDescription['aws:x-amz-cek-alg'])) { + throw new \RuntimeException('Not able to detect kms_cmk_id (legacy' . ' implementation) or aws:x-amz-cek-alg (current implementation)' . ' from kms materials description.'); + } + return new self($this->kmsClient, isset($materialsDescription['kms_cmk_id']) ? $materialsDescription['kms_cmk_id'] : null); + } + /** + * The KMS key id for use in matching this Provider to its keys, + * consistently with other SDKs as 'kms_cmk_id'. + * + * @return array + */ + public function getMaterialsDescription() + { + return ['kms_cmk_id' => $this->kmsKeyId]; + } + public function getWrapAlgorithmName() + { + return self::WRAP_ALGORITHM_NAME; + } + /** + * Takes a content encryption key (CEK) and description to return an encrypted + * key by using KMS' Encrypt API. + * + * @param string $unencryptedCek Key for use in encrypting other data + * that itself needs to be encrypted by the + * Provider. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public function encryptCek($unencryptedCek, $materialDescription) + { + $encryptedDataKey = $this->kmsClient->encrypt(['Plaintext' => $unencryptedCek, 'KeyId' => $this->kmsKeyId, 'EncryptionContext' => $materialDescription]); + return \base64_encode($encryptedDataKey['CiphertextBlob']); + } + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key by using KMS' Decrypt API. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public function decryptCek($encryptedCek, $materialDescription) + { + $result = $this->kmsClient->decrypt(['CiphertextBlob' => $encryptedCek, 'EncryptionContext' => $materialDescription]); + return $result['Plaintext']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProviderV2.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProviderV2.php new file mode 100644 index 00000000..4cb6147e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/KmsMaterialsProviderV2.php @@ -0,0 +1,69 @@ +kmsClient = $kmsClient; + $this->kmsKeyId = $kmsKeyId; + } + /** + * @inheritDoc + */ + public function getWrapAlgorithmName() + { + return self::WRAP_ALGORITHM_NAME; + } + /** + * @inheritDoc + */ + public function decryptCek($encryptedCek, $materialDescription, $options) + { + $params = ['CiphertextBlob' => $encryptedCek, 'EncryptionContext' => $materialDescription]; + if (empty($options['@KmsAllowDecryptWithAnyCmk'])) { + if (empty($this->kmsKeyId)) { + throw new CryptoException('KMS CMK ID was not specified and the' . ' operation is not opted-in to attempting to use any valid' . ' CMK it discovers. Please specify a CMK ID, or explicitly' . ' enable attempts to use any valid KMS CMK with the' . ' @KmsAllowDecryptWithAnyCmk option.'); + } + $params['KeyId'] = $this->kmsKeyId; + } + $result = $this->kmsClient->decrypt($params); + return $result['Plaintext']; + } + /** + * @inheritDoc + */ + public function generateCek($keySize, $context, $options) + { + if (empty($this->kmsKeyId)) { + throw new CryptoException('A KMS key id is required for encryption' . ' with KMS keywrap. Use a KmsMaterialsProviderV2 that has been' . ' instantiated with a KMS key id.'); + } + $options = \array_change_key_case($options); + if (!isset($options['@kmsencryptioncontext']) || !\is_array($options['@kmsencryptioncontext'])) { + throw new CryptoException("'@KmsEncryptionContext' is a" . " required argument when using KmsMaterialsProviderV2, and" . " must be an associative array (or empty array)."); + } + if (isset($options['@kmsencryptioncontext']['aws:x-amz-cek-alg'])) { + throw new CryptoException("Conflict in reserved @KmsEncryptionContext" . " key aws:x-amz-cek-alg. This value is reserved for the S3" . " Encryption Client and cannot be set by the user."); + } + $context = \array_merge($options['@kmsencryptioncontext'], $context); + $result = $this->kmsClient->generateDataKey(['KeyId' => $this->kmsKeyId, 'KeySpec' => "AES_{$keySize}", 'EncryptionContext' => $context]); + return ['Plaintext' => $result['Plaintext'], 'Ciphertext' => \base64_encode($result['CiphertextBlob']), 'UpdatedContext' => $context]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProvider.php new file mode 100644 index 00000000..ba1e88de --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProvider.php @@ -0,0 +1,92 @@ + \true, 192 => \true, 256 => \true]; + /** + * Returns if the requested size is supported by AES. + * + * @param int $keySize Size of the requested key in bits. + * + * @return bool + */ + public static function isSupportedKeySize($keySize) + { + return isset(self::$supportedKeySizes[$keySize]); + } + /** + * Performs further initialization of the MaterialsProvider based on the + * data inside the MetadataEnvelope. + * + * @param MetadataEnvelope $envelope A storage envelope for encryption + * metadata to be read from. + * + * @return MaterialsProvider + * + * @throws \RuntimeException Thrown when there is an empty or improperly + * formed materials description in the envelope. + * + * @internal + */ + public abstract function fromDecryptionEnvelope(MetadataEnvelope $envelope); + /** + * Returns the material description for this Provider so it can be verified + * by encryption mechanisms. + * + * @return string + */ + public abstract function getMaterialsDescription(); + /** + * Returns the wrap algorithm name for this Provider. + * + * @return string + */ + public abstract function getWrapAlgorithmName(); + /** + * Takes a content encryption key (CEK) and description to return an + * encrypted key according to the Provider's specifications. + * + * @param string $unencryptedCek Key for use in encrypting other data + * that itself needs to be encrypted by the + * Provider. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public abstract function encryptCek($unencryptedCek, $materialDescription); + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key according to the Provider's specifications. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * encrypting the $cek. + * + * @return string + */ + public abstract function decryptCek($encryptedCek, $materialDescription); + /** + * @param string $keySize Length of a cipher key in bits for generating a + * random content encryption key (CEK). + * + * @return string + */ + public function generateCek($keySize) + { + return \openssl_random_pseudo_bytes($keySize / 8); + } + /** + * @param string $openSslName Cipher OpenSSL name to use for generating + * an initialization vector. + * + * @return string + */ + public function generateIv($openSslName) + { + return \openssl_random_pseudo_bytes(\openssl_cipher_iv_length($openSslName)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProviderInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProviderInterface.php new file mode 100644 index 00000000..4b11044c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MaterialsProviderInterface.php @@ -0,0 +1,57 @@ + \true, 256 => \true]; + /** + * Returns if the requested size is supported by AES. + * + * @param int $keySize Size of the requested key in bits. + * + * @return bool + */ + public static function isSupportedKeySize($keySize) + { + return isset(self::$supportedKeySizes[$keySize]); + } + /** + * Returns the wrap algorithm name for this Provider. + * + * @return string + */ + public abstract function getWrapAlgorithmName(); + /** + * Takes an encrypted content encryption key (CEK) and material description + * for use decrypting the key according to the Provider's specifications. + * + * @param string $encryptedCek Encrypted key to be decrypted by the Provider + * for use decrypting other data. + * @param string $materialDescription Material Description for use in + * decrypting the CEK. + * @param string $options Options for use in decrypting the CEK. + * + * @return string + */ + public abstract function decryptCek($encryptedCek, $materialDescription, $options); + /** + * @param string $keySize Length of a cipher key in bits for generating a + * random content encryption key (CEK). + * @param array $context Context map needed for key encryption + * @param array $options Additional options to be used in CEK generation + * + * @return array + */ + public abstract function generateCek($keySize, $context, $options); + /** + * @param string $openSslName Cipher OpenSSL name to use for generating + * an initialization vector. + * + * @return string + */ + public function generateIv($openSslName) + { + return \openssl_random_pseudo_bytes(\openssl_cipher_iv_length($openSslName)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataEnvelope.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataEnvelope.php new file mode 100644 index 00000000..b484a21c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataEnvelope.php @@ -0,0 +1,53 @@ +getConstants()) as $constant) { + self::$constants[$constant] = \true; + } + } + return \array_keys(self::$constants); + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($name, $value) + { + $constants = self::getConstantValues(); + if (\is_null($name) || !\in_array($name, $constants)) { + throw new InvalidArgumentException('MetadataEnvelope fields must' . ' must match a predefined offset; use the header constants.'); + } + $this->data[$name] = $value; + } + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataStrategyInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataStrategyInterface.php new file mode 100644 index 00000000..58232e93 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/MetadataStrategyInterface.php @@ -0,0 +1,30 @@ + 0 && $blockSize <= \PHP_INT_MAX, 'Block size must be a positive integer.', RangeException::class); + self::needs($aesKey->length() << 3 === $keySize, 'Incorrect key size; expected ' . $keySize . ' bits, got ' . ($aesKey->length() << 3) . ' bits.'); + $this->aesKey = $aesKey; + $this->keySize = $keySize; + } + /** + * Encryption interface for AES-GCM + * + * @param string $plaintext Message to be encrypted + * @param string $nonce Number to be used ONCE + * @param Key $key AES Key + * @param string $aad Additional authenticated data + * @param string &$tag Reference to variable to hold tag + * @param int $keySize Key size (bits) + * @param int $blockSize Block size (bytes) -- How much memory to buffer + * @return string + * @throws InvalidArgumentException + */ + public static function encrypt($plaintext, $nonce, Key $key, $aad, &$tag, $keySize = 256, $blockSize = 8192) + { + self::needs(self::strlen($nonce) === 12, 'Nonce must be exactly 12 bytes', InvalidArgumentException::class); + $encryptor = new AesGcm($key, $keySize, $blockSize); + list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad); + $ciphertext = \openssl_encrypt($plaintext, "aes-{$encryptor->keySize}-ctr", $key->get(), \OPENSSL_NO_PADDING | \OPENSSL_RAW_DATA, $nonce . "\x00\x00\x00\x02"); + /* Calculate auth tag in a streaming fashion to minimize memory usage: */ + $ciphertextLength = self::strlen($ciphertext); + for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) { + $cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize)); + $gmac->update($cBlock); + } + $tag = $gmac->finish($aadLength, $ciphertextLength)->toString(); + return $ciphertext; + } + /** + * Decryption interface for AES-GCM + * + * @param string $ciphertext Ciphertext to decrypt + * @param string $nonce Number to be used ONCE + * @param Key $key AES key + * @param string $aad Additional authenticated data + * @param string $tag Authentication tag + * @param int $keySize Key size (bits) + * @param int $blockSize Block size (bytes) -- How much memory to buffer + * @return string Plaintext + * + * @throws CryptoPolyfillException + * @throws InvalidArgumentException + */ + public static function decrypt($ciphertext, $nonce, Key $key, $aad, &$tag, $keySize = 256, $blockSize = 8192) + { + /* Precondition: */ + self::needs(self::strlen($nonce) === 12, 'Nonce must be exactly 12 bytes', InvalidArgumentException::class); + $encryptor = new AesGcm($key, $keySize, $blockSize); + list($aadLength, $gmac) = $encryptor->gmacInit($nonce, $aad); + /* Calculate auth tag in a streaming fashion to minimize memory usage: */ + $ciphertextLength = self::strlen($ciphertext); + for ($i = 0; $i < $ciphertextLength; $i += $encryptor->blockSize) { + $cBlock = new ByteArray(self::substr($ciphertext, $i, $encryptor->blockSize)); + $gmac->update($cBlock); + } + /* Validate auth tag in constant-time: */ + $calc = $gmac->finish($aadLength, $ciphertextLength); + $expected = new ByteArray($tag); + self::needs($calc->equals($expected), 'Invalid authentication tag'); + /* Return plaintext if auth tag check succeeded: */ + return \openssl_decrypt($ciphertext, "aes-{$encryptor->keySize}-ctr", $key->get(), \OPENSSL_NO_PADDING | \OPENSSL_RAW_DATA, $nonce . "\x00\x00\x00\x02"); + } + /** + * Initialize a Gmac object with the nonce and this object's key. + * + * @param string $nonce Must be exactly 12 bytes long. + * @param string|null $aad + * @return array + */ + protected function gmacInit($nonce, $aad = null) + { + $gmac = new Gmac($this->aesKey, $nonce . "\x00\x00\x00\x01", $this->keySize); + $aadBlock = new ByteArray($aad); + $aadLength = $aadBlock->count(); + $gmac->update($aadBlock); + $gmac->flush(); + return [$aadLength, $gmac]; + } + /** + * Calculate the length of a string. + * + * Uses the appropriate PHP function without being brittle to + * mbstring.func_overload. + * + * @param string $string + * @return int + */ + protected static function strlen($string) + { + if (\is_callable('\\mb_strlen')) { + return (int) \mb_strlen($string, '8bit'); + } + return (int) \strlen($string); + } + /** + * Return a substring of the provided string. + * + * Uses the appropriate PHP function without being brittle to + * mbstring.func_overload. + * + * @param string $string + * @param int $offset + * @param int|null $length + * @return string + */ + protected static function substr($string, $offset = 0, $length = null) + { + if (\is_callable('\\mb_substr')) { + return \mb_substr($string, $offset, $length, '8bit'); + } elseif (!\is_null($length)) { + return \substr($string, $offset, $length); + } + return \substr($string, $offset); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/ByteArray.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/ByteArray.php new file mode 100644 index 00000000..4166d817 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/ByteArray.php @@ -0,0 +1,234 @@ +toArray(); + $size = $size->count(); + } elseif (!\is_int($size)) { + throw new \InvalidArgumentException('Argument must be an integer, string, or array of integers.'); + } + parent::__construct($size); + if (!empty($arr)) { + // Populate this object with values from constructor argument + foreach ($arr as $i => $v) { + $this->offsetSet($i, $v); + } + } else { + // Initialize to zero. + for ($i = 0; $i < $size; ++$i) { + $this->offsetSet($i, 0); + } + } + } + /** + * Encode an integer into a byte array. 32-bit (unsigned), big endian byte order. + * + * @param int $num + * @return self + */ + public static function enc32be($num) + { + return new ByteArray(\pack('N', $num)); + } + /** + * @param ByteArray $other + * @return bool + */ + public function equals(ByteArray $other) + { + if ($this->count() !== $other->count()) { + return \false; + } + $d = 0; + for ($i = $this->count() - 1; $i >= 0; --$i) { + $d |= $this[$i] ^ $other[$i]; + } + return $d === 0; + } + /** + * @param ByteArray $array + * @return ByteArray + */ + public function exclusiveOr(ByteArray $array) + { + self::needs($this->count() === $array->count(), 'Both ByteArrays must be equal size for exclusiveOr()'); + $out = clone $this; + for ($i = 0; $i < $this->count(); ++$i) { + $out[$i] = $array[$i] ^ $out[$i]; + } + return $out; + } + /** + * Returns a new ByteArray incremented by 1 (big endian byte order). + * + * @param int $increase + * @return self + */ + public function getIncremented($increase = 1) + { + $clone = clone $this; + $index = $clone->count(); + while ($index > 0) { + --$index; + $tmp = $clone[$index] + $increase & \PHP_INT_MAX; + $clone[$index] = $tmp & 0xff; + $increase = $tmp >> 8; + } + return $clone; + } + /** + * Sets a value. See SplFixedArray for more. + * + * @param int $index + * @param int $newval + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($index, $newval) + { + parent::offsetSet($index, $newval & 0xff); + } + /** + * Return a copy of this ByteArray, bitshifted to the right by 1. + * Used in Gmac. + * + * @return self + */ + public function rshift() + { + $out = clone $this; + for ($j = $this->count() - 1; $j > 0; --$j) { + $out[$j] = ($out[$j - 1] & 1) << 7 | $out[$j] >> 1; + } + $out[0] >>= 1; + return $out; + } + /** + * Constant-time conditional select. This is meant to read like a ternary operator. + * + * $z = ByteArray::select(1, $x, $y); // $z is equal to $x + * $z = ByteArray::select(0, $x, $y); // $z is equal to $y + * + * @param int $select + * @param ByteArray $left + * @param ByteArray $right + * @return ByteArray + */ + public static function select($select, ByteArray $left, ByteArray $right) + { + self::needs($left->count() === $right->count(), 'Both ByteArrays must be equal size for select()'); + $rightLength = $right->count(); + $out = clone $right; + $mask = -($select & 1) & 0xff; + for ($i = 0; $i < $rightLength; $i++) { + $out[$i] = $out[$i] ^ ($left[$i] ^ $right[$i]) & $mask; + } + return $out; + } + /** + * Overwrite values of this ByteArray based on a separate ByteArray, with + * a given starting offset and length. + * + * See JavaScript's Uint8Array.set() for more information. + * + * @param ByteArray $input + * @param int $offset + * @param int|null $length + * @return self + */ + public function set(ByteArray $input, $offset = 0, $length = null) + { + self::needs(\is_int($offset) && $offset >= 0, 'Offset must be a positive integer or zero'); + if (\is_null($length)) { + $length = $input->count(); + } + $i = 0; + $j = $offset; + while ($i < $length && $j < $this->count()) { + $this[$j] = $input[$i]; + ++$i; + ++$j; + } + return $this; + } + /** + * Returns a slice of this ByteArray. + * + * @param int $start + * @param null $length + * @return self + */ + public function slice($start = 0, $length = null) + { + return new ByteArray(\array_slice($this->toArray(), $start, $length)); + } + /** + * Mutates the current state and sets all values to zero. + * + * @return void + */ + public function zeroize() + { + for ($i = $this->count() - 1; $i >= 0; --$i) { + $this->offsetSet($i, 0); + } + } + /** + * Converts the ByteArray to a raw binary string. + * + * @return string + */ + public function toString() + { + $count = $this->count(); + if ($count === 0) { + return ''; + } + $args = $this->toArray(); + \array_unshift($args, \str_repeat('C', $count)); + // constant-time, PHP <5.6 equivalent to pack('C*', ...$args); + return \call_user_func_array('\\pack', $args); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Gmac.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Gmac.php new file mode 100644 index 00000000..3a6c866a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Gmac.php @@ -0,0 +1,173 @@ +buf = new ByteArray(16); + $this->h = new ByteArray(\openssl_encrypt(\str_repeat("\x00", 16), "aes-{$keySize}-ecb", $aesKey->get(), \OPENSSL_RAW_DATA | \OPENSSL_NO_PADDING)); + $this->key = $aesKey; + $this->x = new ByteArray(16); + $this->hf = new ByteArray(\openssl_encrypt($nonce, "aes-{$keySize}-ecb", $aesKey->get(), \OPENSSL_RAW_DATA | \OPENSSL_NO_PADDING)); + } + /** + * Update the object with some data. + * + * This method mutates this Gmac object. + * + * @param ByteArray $blocks + * @return self + */ + public function update(ByteArray $blocks) + { + if ($blocks->count() + $this->bufLength < self::BLOCK_SIZE) { + // Write to internal buffer until we reach enough to write. + $this->buf->set($blocks, $this->bufLength); + $this->bufLength += $blocks->count(); + return $this; + } + // Process internal buffer first. + if ($this->bufLength > 0) { + // 0 <= state.buf_len < BLOCK_SIZE is an invariant + $tmp = new ByteArray(self::BLOCK_SIZE); + $tmp->set($this->buf->slice(0, $this->bufLength)); + $remainingBlockLength = self::BLOCK_SIZE - $this->bufLength; + $tmp->set($blocks->slice(0, $remainingBlockLength), $this->bufLength); + $blocks = $blocks->slice($remainingBlockLength); + $this->bufLength = 0; + $this->x = $this->blockMultiply($this->x->exclusiveOr($tmp), $this->h); + } + // Process full blocks. + $numBlocks = $blocks->count() >> 4; + for ($i = 0; $i < $numBlocks; ++$i) { + $tmp = $blocks->slice($i << 4, self::BLOCK_SIZE); + $this->x = $this->blockMultiply($this->x->exclusiveOr($tmp), $this->h); + } + $last = $numBlocks << 4; + // Zero-fill buffer + for ($i = 0; $i < 16; ++$i) { + $this->buf[$i] = 0; + } + // Feed leftover into buffer. + if ($last < $blocks->count()) { + $tmp = $blocks->slice($last); + $this->buf->set($tmp); + $this->bufLength += $blocks->count() - $last; + } + return $this; + } + /** + * Finish processing the authentication tag. + * + * This method mutates this Gmac object (effectively resetting it). + * + * @param int $aadLength + * @param int $ciphertextLength + * @return ByteArray + */ + public function finish($aadLength, $ciphertextLength) + { + $lengthBlock = new ByteArray(16); + $state = $this->flush(); + // AES-GCM expects bit lengths, not byte lengths. + $lengthBlock->set(ByteArray::enc32be($aadLength >> 29), 0); + $lengthBlock->set(ByteArray::enc32be($aadLength << 3), 4); + $lengthBlock->set(ByteArray::enc32be($ciphertextLength >> 29), 8); + $lengthBlock->set(ByteArray::enc32be($ciphertextLength << 3), 12); + $state->update($lengthBlock); + $output = $state->x->exclusiveOr($state->hf); + // Zeroize the internal values as a best-effort. + $state->buf->zeroize(); + $state->x->zeroize(); + $state->h->zeroize(); + $state->hf->zeroize(); + return $output; + } + /** + * Get a specific bit from the provided array, at the given index. + * + * [01234567], 8+[01234567], 16+[01234567], ... + * + * @param ByteArray $x + * @param int $i + * @return int + */ + protected function bit(ByteArray $x, $i) + { + $byte = $i >> 3; + return $x[$byte] >> (7 - $i & 7) & 1; + } + /** + * Galois Field Multiplication + * + * This function is the critical path that must be constant-time in order to + * avoid timing side-channels against AES-GCM. + * + * The contents of each are always calculated, regardless of the branching + * condition, to prevent another kind of timing leak. + * + * @param ByteArray $x + * @param ByteArray $y + * @return ByteArray + */ + protected function blockMultiply(ByteArray $x, ByteArray $y) + { + static $fieldPolynomial = null; + if (!$fieldPolynomial) { + $fieldPolynomial = new ByteArray([0xe1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + self::needs($x->count() === 16, 'Argument 1 must be a ByteArray of exactly 16 bytes'); + self::needs($y->count() === 16, 'Argument 2 must be a ByteArray of exactly 16 bytes'); + $v = clone $y; + $z = new ByteArray(16); + for ($i = 0; $i < 128; ++$i) { + // if ($b) $z = $z->exclusiveOr($v); + $b = $this->bit($x, $i); + $z = ByteArray::select($b, $z->exclusiveOr($v), $z); + // if ($b) $v = $v->exclusiveOr($fieldPolynomial); + $b = $v[15] & 1; + $v = $v->rshift(); + $v = ByteArray::select($b, $v->exclusiveOr($fieldPolynomial), $v); + } + return $z; + } + /** + * Finish processing any leftover bytes in the internal buffer. + * + * @return self + */ + public function flush() + { + if ($this->bufLength !== 0) { + $this->x = $this->blockMultiply($this->x->exclusiveOr($this->buf), $this->h); + $this->bufLength = 0; + } + return $this; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Key.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Key.php new file mode 100644 index 00000000..b0578f6d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/Key.php @@ -0,0 +1,71 @@ +internalString = $str; + } + /** + * Defense in depth: + * + * PHP 7.2 includes the Sodium cryptography library, which (among other things) + * exposes a function called sodium_memzero() that we can use to zero-fill strings + * to minimize the risk of sensitive cryptographic materials persisting in memory. + * + * If this function is not available, we XOR the string in-place with itself as a + * best-effort attempt. + */ + public function __destruct() + { + if (\extension_loaded('sodium') && \function_exists('sodium_memzero')) { + try { + \sodium_memzero($this->internalString); + } catch (\SodiumException $ex) { + // This is a best effort, but does not provide the same guarantees as sodium_memzero(): + $this->internalString ^= $this->internalString; + } + } + } + /** + * @return string + */ + public function get() + { + return $this->internalString; + } + /** + * @return int + */ + public function length() + { + if (\is_callable('\\mb_strlen')) { + return (int) \mb_strlen($this->internalString, '8bit'); + } + return (int) \strlen($this->internalString); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/NeedsTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/NeedsTrait.php new file mode 100644 index 00000000..970eaafd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Crypto/Polyfill/NeedsTrait.php @@ -0,0 +1,37 @@ +validModes)) { + throw new \InvalidArgumentException("'{$mode}' is not a valid mode." . " The mode has to be 'legacy', 'standard', 'cross-region', 'in-region'," . " 'mobile', or 'auto'."); + } + $this->mode = $mode; + if ($this->mode == 'legacy') { + return; + } + $data = \WPMailSMTP\Vendor\Aws\load_compiled_json(__DIR__ . '/../data/sdk-default-configuration.json'); + $this->retryMode = $data['base']['retryMode']; + $this->stsRegionalEndpoints = $data['base']['stsRegionalEndpoints']; + $this->s3UsEast1RegionalEndpoints = $data['base']['s3UsEast1RegionalEndpoints']; + $this->connectTimeoutInMillis = $data['base']['connectTimeoutInMillis']; + if (isset($data['modes'][$mode])) { + $modeData = $data['modes'][$mode]; + foreach ($modeData as $settingName => $settingValue) { + if (isset($this->{$settingName})) { + if (isset($settingValue['override'])) { + $this->{$settingName} = $settingValue['override']; + } else { + if (isset($settingValue['multiply'])) { + $this->{$settingName} *= $settingValue['multiply']; + } else { + if (isset($settingValue['add'])) { + $this->{$settingName} += $settingValue['add']; + } + } + } + } else { + if (isset($settingValue['override'])) { + if (\property_exists($this, $settingName)) { + $this->{$settingName} = $settingValue['override']; + } + } + } + } + } + } + /** + * {@inheritdoc} + */ + public function getMode() + { + return $this->mode; + } + /** + * {@inheritdoc} + */ + public function getRetryMode() + { + return $this->retryMode; + } + /** + * {@inheritdoc} + */ + public function getStsRegionalEndpoints() + { + return $this->stsRegionalEndpoints; + } + /** + * {@inheritdoc} + */ + public function getS3UsEast1RegionalEndpoints() + { + return $this->s3UsEast1RegionalEndpoints; + } + /** + * {@inheritdoc} + */ + public function getConnectTimeoutInMillis() + { + return $this->connectTimeoutInMillis; + } + /** + * {@inheritdoc} + */ + public function getHttpRequestTimeoutInMillis() + { + return $this->httpRequestTimeoutInMillis; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['mode' => $this->getMode(), 'retry_mode' => $this->getRetryMode(), 'sts_regional_endpoints' => $this->getStsRegionalEndpoints(), 's3_us_east_1_regional_endpoint' => $this->getS3UsEast1RegionalEndpoints(), 'connect_timeout_in_milliseconds' => $this->getConnectTimeoutInMillis(), 'http_request_timeout_in_milliseconds' => $this->getHttpRequestTimeoutInMillis()]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/ConfigurationInterface.php new file mode 100644 index 00000000..116a9ccf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/ConfigurationInterface.php @@ -0,0 +1,47 @@ + + * use Aws\Sts\RegionalEndpoints\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see \Aws\DefaultsMode\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const DEFAULT_MODE = 'legacy'; + const ENV_MODE = 'AWS_DEFAULTS_MODE'; + const ENV_PROFILE = 'AWS_PROFILE'; + const INI_MODE = 'defaults_mode'; + public static $cacheKey = 'aws_defaults_mode'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $configProviders = [self::env()]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini(); + } + $configProviders[] = self::fallback(); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['defaultsMode']) && $config['defaultsMode'] instanceof CacheInterface) { + return self::cache($memo, $config['defaultsMode'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates config from environment variables. + * + * @return callable + */ + public static function env() + { + return function () { + // Use config from environment variables, if available + $mode = \getenv(self::ENV_MODE); + if (!empty($mode)) { + return Promise\Create::promiseFor(new Configuration($mode)); + } + return self::reject('Could not find environment variable config' . ' in ' . self::ENV_MODE); + }; + } + /** + * Fallback config options when other sources are not set. + * + * @return callable + */ + public static function fallback() + { + return function () { + return Promise\Create::promiseFor(new Configuration(self::DEFAULT_MODE)); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * + * @return callable + */ + public static function ini($profile = null, $filename = null) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename) { + if (!\is_readable($filename)) { + return self::reject("Cannot read configuration from {$filename}"); + } + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile][self::INI_MODE])) { + return self::reject("Required defaults mode config values\n not present in INI profile '{$profile}' ({$filename})"); + } + return Promise\Create::promiseFor(new Configuration($data[$profile][self::INI_MODE])); + }; + } + /** + * Unwraps a configuration object in whatever valid form it is in, + * always returning a ConfigurationInterface object. + * + * @param mixed $config + * @return ConfigurationInterface + * @throws \InvalidArgumentException + */ + public static function unwrap($config) + { + if (\is_callable($config)) { + $config = $config(); + } + if ($config instanceof PromiseInterface) { + $config = $config->wait(); + } + if ($config instanceof ConfigurationInterface) { + return $config; + } + if (\is_string($config)) { + return new Configuration($config); + } + throw new \InvalidArgumentException('Not a valid defaults mode configuration' . ' argument.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/Exception/ConfigurationException.php new file mode 100644 index 00000000..2052854b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/DefaultsMode/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +cache = $cache; + } + public function get($key) + { + return $this->cache->fetch($key); + } + /** + * @return mixed + */ + public function fetch($key) + { + return $this->get($key); + } + public function set($key, $value, $ttl = 0) + { + return $this->cache->save($key, $value, $ttl); + } + /** + * @return bool + */ + public function save($key, $value, $ttl = 0) + { + return $this->set($key, $value, $ttl); + } + public function remove($key) + { + return $this->cache->delete($key); + } + /** + * @return bool + */ + public function delete($key) + { + return $this->remove($key); + } + /** + * @return bool + */ + public function contains($key) + { + return $this->cache->contains($key); + } + /** + * @return mixed[]|null + */ + public function getStats() + { + return $this->cache->getStats(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/EndpointProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/EndpointProvider.php new file mode 100644 index 00000000..549297ed --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/EndpointProvider.php @@ -0,0 +1,89 @@ + 'ec2', 'region' => 'us-west-2']); + * // Returns an endpoint array or throws. + * $endpoint = EndpointProvider::resolve($provider, [ + * 'service' => 'ec2', + * 'region' => 'us-west-2' + * ]); + * + * You can compose multiple providers into a single provider using + * {@see Aws\or_chain}. This function accepts providers as arguments and + * returns a new function that will invoke each provider until a non-null value + * is returned. + * + * $a = function (array $args) { + * if ($args['region'] === 'my-test-region') { + * return ['endpoint' => 'http://localhost:123/api']; + * } + * }; + * $b = EndpointProvider::defaultProvider(); + * $c = \Aws\or_chain($a, $b); + * $config = ['service' => 'ec2', 'region' => 'my-test-region']; + * $res = $c($config); // $a handles this. + * $config['region'] = 'us-west-2'; + * $res = $c($config); // $b handles this. + */ +class EndpointProvider +{ + /** + * Resolves and endpoint provider and ensures a non-null return value. + * + * @param callable $provider Provider function to invoke. + * @param array $args Endpoint arguments to pass to the provider. + * + * @return array + * @throws UnresolvedEndpointException + */ + public static function resolve(callable $provider, array $args = []) + { + $result = $provider($args); + if (\is_array($result)) { + return $result; + } + throw new UnresolvedEndpointException('Unable to resolve an endpoint using the provider arguments: ' . \json_encode($args) . '. Note: you can provide an "endpoint" ' . 'option to a client constructor to bypass invoking an endpoint ' . 'provider.'); + } + /** + * Creates and returns the default SDK endpoint provider. + * + * @deprecated Use an instance of \Aws\Endpoint\Partition instead. + * + * @return callable + */ + public static function defaultProvider() + { + return PartitionEndpointProvider::defaultProvider(); + } + /** + * Creates and returns an endpoint provider that uses patterns from an + * array. + * + * @param array $patterns Endpoint patterns + * + * @return callable + */ + public static function patterns(array $patterns) + { + return new PatternEndpointProvider($patterns); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/Partition.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/Partition.php new file mode 100644 index 00000000..fffd4751 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/Partition.php @@ -0,0 +1,210 @@ +data = $definition; + } + public function getName() + { + return $this->data['partition']; + } + /** + * @internal + * @return mixed + */ + public function getDnsSuffix() + { + return $this->data['dnsSuffix']; + } + public function isRegionMatch($region, $service) + { + if (isset($this->data['regions'][$region]) || isset($this->data['services'][$service]['endpoints'][$region])) { + return \true; + } + if (isset($this->data['regionRegex'])) { + return (bool) \preg_match("@{$this->data['regionRegex']}@", $region); + } + return \false; + } + public function getAvailableEndpoints($service, $allowNonRegionalEndpoints = \false) + { + if ($this->isServicePartitionGlobal($service)) { + return [$this->getPartitionEndpoint($service)]; + } + if (isset($this->data['services'][$service]['endpoints'])) { + $serviceRegions = \array_keys($this->data['services'][$service]['endpoints']); + return $allowNonRegionalEndpoints ? $serviceRegions : \array_intersect($serviceRegions, \array_keys($this->data['regions'])); + } + return []; + } + public function __invoke(array $args = []) + { + $service = isset($args['service']) ? $args['service'] : ''; + $region = isset($args['region']) ? $args['region'] : ''; + $scheme = isset($args['scheme']) ? $args['scheme'] : 'https'; + $options = isset($args['options']) ? $args['options'] : []; + $data = $this->getEndpointData($service, $region, $options); + $variant = $this->getVariant($options, $data); + if (isset($variant['hostname'])) { + $template = $variant['hostname']; + } else { + $template = isset($data['hostname']) ? $data['hostname'] : ''; + } + $dnsSuffix = isset($variant['dnsSuffix']) ? $variant['dnsSuffix'] : $this->data['dnsSuffix']; + return ['endpoint' => "{$scheme}://" . $this->formatEndpoint($template, $service, $region, $dnsSuffix), 'signatureVersion' => $this->getSignatureVersion($data), 'signingRegion' => isset($data['credentialScope']['region']) ? $data['credentialScope']['region'] : $region, 'signingName' => isset($data['credentialScope']['service']) ? $data['credentialScope']['service'] : $service]; + } + private function getEndpointData($service, $region, $options) + { + $defaultRegion = $this->resolveRegion($service, $region, $options); + $data = isset($this->data['services'][$service]['endpoints'][$defaultRegion]) ? $this->data['services'][$service]['endpoints'][$defaultRegion] : []; + $data += isset($this->data['services'][$service]['defaults']) ? $this->data['services'][$service]['defaults'] : []; + $data += isset($this->data['defaults']) ? $this->data['defaults'] : []; + return $data; + } + private function getSignatureVersion(array $data) + { + static $supportedBySdk = ['s3v4', 'v4', 'anonymous']; + $possibilities = \array_intersect($supportedBySdk, isset($data['signatureVersions']) ? $data['signatureVersions'] : ['v4']); + return \array_shift($possibilities); + } + private function resolveRegion($service, $region, $options) + { + if (isset($this->data['services'][$service]['endpoints'][$region]) && $this->isFipsEndpointUsed($region)) { + return $region; + } + if ($this->isServicePartitionGlobal($service) || $this->isStsLegacyEndpointUsed($service, $region, $options) || $this->isS3LegacyEndpointUsed($service, $region, $options)) { + return $this->getPartitionEndpoint($service); + } + return $region; + } + private function isServicePartitionGlobal($service) + { + return isset($this->data['services'][$service]['isRegionalized']) && \false === $this->data['services'][$service]['isRegionalized'] && isset($this->data['services'][$service]['partitionEndpoint']); + } + /** + * STS legacy endpoints used for valid regions unless option is explicitly + * set to 'regional' + * + * @param string $service + * @param string $region + * @param array $options + * @return bool + */ + private function isStsLegacyEndpointUsed($service, $region, $options) + { + return $service === 'sts' && \in_array($region, $this->stsLegacyGlobalRegions) && (empty($options['sts_regional_endpoints']) || ConfigurationProvider::unwrap($options['sts_regional_endpoints'])->getEndpointsType() !== 'regional'); + } + /** + * S3 legacy us-east-1 endpoint used for valid regions unless option is explicitly + * set to 'regional' + * + * @param string $service + * @param string $region + * @param array $options + * @return bool + */ + private function isS3LegacyEndpointUsed($service, $region, $options) + { + return $service === 's3' && $region === 'us-east-1' && (empty($options['s3_us_east_1_regional_endpoint']) || S3ConfigurationProvider::unwrap($options['s3_us_east_1_regional_endpoint'])->getEndpointsType() !== 'regional'); + } + private function getPartitionEndpoint($service) + { + return $this->data['services'][$service]['partitionEndpoint']; + } + private function formatEndpoint($template, $service, $region, $dnsSuffix) + { + return \strtr($template, ['{service}' => $service, '{region}' => $region, '{dnsSuffix}' => $dnsSuffix]); + } + /** + * @param $region + * @return bool + */ + private function isFipsEndpointUsed($region) + { + return \strpos($region, "fips") !== \false; + } + /** + * @param array $options + * @param array $data + * @return array + */ + private function getVariant(array $options, array $data) + { + $variantTags = []; + if (isset($options['use_fips_endpoint'])) { + $useFips = $options['use_fips_endpoint']; + if (\is_bool($useFips)) { + $useFips && ($variantTags[] = 'fips'); + } elseif ($useFips->isUseFipsEndpoint()) { + $variantTags[] = 'fips'; + } + } + if (isset($options['use_dual_stack_endpoint'])) { + $useDualStack = $options['use_dual_stack_endpoint']; + if (\is_bool($useDualStack)) { + $useDualStack && ($variantTags[] = 'dualstack'); + } elseif ($useDualStack->isUseDualStackEndpoint()) { + $variantTags[] = 'dualstack'; + } + } + if (!empty($variantTags)) { + if (isset($data['variants'])) { + foreach ($data['variants'] as $variant) { + if (\array_count_values($variant['tags']) == \array_count_values($variantTags)) { + return $variant; + } + } + } + if (isset($this->data['defaults']['variants'])) { + foreach ($this->data['defaults']['variants'] as $variant) { + if (\array_count_values($variant['tags']) == \array_count_values($variantTags)) { + return $variant; + } + } + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionEndpointProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionEndpointProvider.php new file mode 100644 index 00000000..a080ccba --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionEndpointProvider.php @@ -0,0 +1,113 @@ +partitions = \array_map(function (array $definition) { + return new Partition($definition); + }, \array_values($partitions)); + $this->defaultPartition = $defaultPartition; + $this->options = $options; + } + public function __invoke(array $args = []) + { + $partition = $this->getPartition(isset($args['region']) ? $args['region'] : '', isset($args['service']) ? $args['service'] : ''); + $args['options'] = $this->options; + return $partition($args); + } + /** + * Returns the partition containing the provided region or the default + * partition if no match is found. + * + * @param string $region + * @param string $service + * + * @return Partition + */ + public function getPartition($region, $service) + { + foreach ($this->partitions as $partition) { + if ($partition->isRegionMatch($region, $service)) { + return $partition; + } + } + return $this->getPartitionByName($this->defaultPartition); + } + /** + * Returns the partition with the provided name or null if no partition with + * the provided name can be found. + * + * @param string $name + * + * @return Partition|null + */ + public function getPartitionByName($name) + { + foreach ($this->partitions as $partition) { + if ($name === $partition->getName()) { + return $partition; + } + } + } + /** + * Creates and returns the default SDK partition provider. + * + * @param array $options + * @return PartitionEndpointProvider + */ + public static function defaultProvider($options = []) + { + $data = \WPMailSMTP\Vendor\Aws\load_compiled_json(__DIR__ . '/../data/endpoints.json'); + $prefixData = \WPMailSMTP\Vendor\Aws\load_compiled_json(__DIR__ . '/../data/endpoints_prefix_history.json'); + $mergedData = self::mergePrefixData($data, $prefixData); + return new self($mergedData['partitions'], 'aws', $options); + } + /** + * Copy endpoint data for other prefixes used by a given service + * + * @param $data + * @param $prefixData + * @return array + */ + public static function mergePrefixData($data, $prefixData) + { + $prefixGroups = $prefixData['prefix-groups']; + foreach ($data["partitions"] as $index => $partition) { + foreach ($prefixGroups as $current => $old) { + $serviceData = Env::search("services.\"{$current}\"", $partition); + if (!empty($serviceData)) { + foreach ($old as $prefix) { + if (empty(Env::search("services.\"{$prefix}\"", $partition))) { + $data["partitions"][$index]["services"][$prefix] = $serviceData; + } + } + } + } + } + return $data; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionInterface.php new file mode 100644 index 00000000..e7acb5d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/PartitionInterface.php @@ -0,0 +1,51 @@ +patterns = $patterns; + } + public function __invoke(array $args = []) + { + $service = isset($args['service']) ? $args['service'] : ''; + $region = isset($args['region']) ? $args['region'] : ''; + $keys = ["{$region}/{$service}", "{$region}/*", "*/{$service}", "*/*"]; + foreach ($keys as $key) { + if (isset($this->patterns[$key])) { + return $this->expand($this->patterns[$key], isset($args['scheme']) ? $args['scheme'] : 'https', $service, $region); + } + } + return null; + } + private function expand(array $config, $scheme, $service, $region) + { + $config['endpoint'] = $scheme . '://' . \strtr($config['endpoint'], ['{service}' => $service, '{region}' => $region]); + return $config; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Configuration.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Configuration.php new file mode 100644 index 00000000..0bda601e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Configuration.php @@ -0,0 +1,34 @@ +useDualstackEndpoint = Aws\boolean_value($useDualstackEndpoint); + if (\is_null($this->useDualstackEndpoint)) { + throw new ConfigurationException("'use_dual_stack_endpoint' config option" . " must be a boolean value."); + } + if ($this->useDualstackEndpoint == \true && (\strpos($region, "iso-") !== \false || \strpos($region, "-iso") !== \false)) { + throw new ConfigurationException("Dual-stack is not supported in ISO regions"); + } + } + /** + * {@inheritdoc} + */ + public function isUseDualstackEndpoint() + { + return $this->useDualstackEndpoint; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['use_dual_stack_endpoint' => $this->isUseDualstackEndpoint()]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/ConfigurationInterface.php new file mode 100644 index 00000000..ae5a461e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/ConfigurationInterface.php @@ -0,0 +1,19 @@ + + * use Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const ENV_USE_DUAL_STACK_ENDPOINT = 'AWS_USE_DUALSTACK_ENDPOINT'; + const INI_USE_DUAL_STACK_ENDPOINT = 'use_dualstack_endpoint'; + public static $cacheKey = 'aws_cached_use_dualstack_endpoint_config'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $region = $config['region']; + $configProviders = [self::env($region)]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini($region); + } + $configProviders[] = self::fallback($region); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['use_dual_stack_endpoint']) && $config['use_dual_stack_endpoint'] instanceof CacheInterface) { + return self::cache($memo, $config['use_dual_stack_endpoint'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates config from environment variables. + * + * @return callable + */ + public static function env($region) + { + return function () use($region) { + // Use config from environment variables, if available + $useDualstackEndpoint = \getenv(self::ENV_USE_DUAL_STACK_ENDPOINT); + if (!empty($useDualstackEndpoint)) { + return Promise\Create::promiseFor(new Configuration($useDualstackEndpoint, $region)); + } + return self::reject('Could not find environment variable config' . ' in ' . self::ENV_USE_DUAL_STACK_ENDPOINT); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * + * @return callable + */ + public static function ini($region, $profile = null, $filename = null) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($region, $profile, $filename) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read configuration from {$filename}"); + } + // Use INI_SCANNER_NORMAL instead of INI_SCANNER_TYPED for PHP 5.5 compatibility + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true, \INI_SCANNER_NORMAL); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT])) { + return self::reject("Required use dualstack endpoint config values\n not present in INI profile '{$profile}' ({$filename})"); + } + // INI_SCANNER_NORMAL parses false-y values as an empty string + if ($data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT] === "") { + $data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT] = \false; + } + return Promise\Create::promiseFor(new Configuration($data[$profile][self::INI_USE_DUAL_STACK_ENDPOINT], $region)); + }; + } + /** + * Fallback config options when other sources are not set. + * + * @return callable + */ + public static function fallback($region) + { + return function () use($region) { + return Promise\Create::promiseFor(new Configuration(\false, $region)); + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Exception/ConfigurationException.php new file mode 100644 index 00000000..a4e58818 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseDualstackEndpoint/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +useFipsEndpoint = Aws\boolean_value($useFipsEndpoint); + if (\is_null($this->useFipsEndpoint)) { + throw new ConfigurationException("'use_fips_endpoint' config option" . " must be a boolean value."); + } + } + /** + * {@inheritdoc} + */ + public function isUseFipsEndpoint() + { + return $this->useFipsEndpoint; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['use_fips_endpoint' => $this->isUseFipsEndpoint()]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/ConfigurationInterface.php new file mode 100644 index 00000000..d3ec1ba1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/ConfigurationInterface.php @@ -0,0 +1,19 @@ + + * use Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const ENV_USE_FIPS_ENDPOINT = 'AWS_USE_FIPS_ENDPOINT'; + const INI_USE_FIPS_ENDPOINT = 'use_fips_endpoint'; + public static $cacheKey = 'aws_cached_use_fips_endpoint_config'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $configProviders = [self::env()]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini(); + } + $configProviders[] = self::fallback($config['region']); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['use_fips_endpoint']) && $config['use_fips_endpoint'] instanceof CacheInterface) { + return self::cache($memo, $config['use_fips_endpoint'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates config from environment variables. + * + * @return callable + */ + public static function env() + { + return function () { + // Use config from environment variables, if available + $useFipsEndpoint = \getenv(self::ENV_USE_FIPS_ENDPOINT); + if (!empty($useFipsEndpoint)) { + return Promise\Create::promiseFor(new Configuration($useFipsEndpoint)); + } + return self::reject('Could not find environment variable config' . ' in ' . self::ENV_USE_FIPS_ENDPOINT); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * + * @return callable + */ + public static function ini($profile = null, $filename = null) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read configuration from {$filename}"); + } + // Use INI_SCANNER_NORMAL instead of INI_SCANNER_TYPED for PHP 5.5 compatibility + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true, \INI_SCANNER_NORMAL); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile][self::INI_USE_FIPS_ENDPOINT])) { + return self::reject("Required use fips endpoint config values\n not present in INI profile '{$profile}' ({$filename})"); + } + // INI_SCANNER_NORMAL parses false-y values as an empty string + if ($data[$profile][self::INI_USE_FIPS_ENDPOINT] === "") { + $data[$profile][self::INI_USE_FIPS_ENDPOINT] = \false; + } + return Promise\Create::promiseFor(new Configuration($data[$profile][self::INI_USE_FIPS_ENDPOINT])); + }; + } + /** + * Fallback config options when other sources are not set. + * + * @return callable + */ + public static function fallback($region) + { + return function () use($region) { + $isFipsPseudoRegion = \strpos($region, 'fips-') !== \false || \strpos($region, '-fips') !== \false; + if ($isFipsPseudoRegion) { + $configuration = new Configuration(\true); + } else { + $configuration = new Configuration(\false); + } + return Promise\Create::promiseFor($configuration); + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/Exception/ConfigurationException.php new file mode 100644 index 00000000..58e737d2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Endpoint/UseFipsEndpoint/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +cacheLimit = \filter_var($cacheLimit, \FILTER_VALIDATE_INT); + if ($this->cacheLimit == \false || $this->cacheLimit < 1) { + throw new \InvalidArgumentException("'cache_limit' value must be a positive integer."); + } + // Unparsable $enabled flag errs on the side of disabling endpoint discovery + $this->enabled = \filter_var($enabled, \FILTER_VALIDATE_BOOLEAN); + } + /** + * {@inheritdoc} + */ + public function isEnabled() + { + return $this->enabled; + } + /** + * {@inheritdoc} + */ + public function getCacheLimit() + { + return $this->cacheLimit; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['enabled' => $this->isEnabled(), 'cache_limit' => $this->getCacheLimit()]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/ConfigurationInterface.php new file mode 100644 index 00000000..ce086891 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/ConfigurationInterface.php @@ -0,0 +1,29 @@ + + * use Aws\EndpointDiscovery\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see Aws\EndpointDiscovery\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const DEFAULT_ENABLED = \false; + const DEFAULT_CACHE_LIMIT = 1000; + const ENV_ENABLED = 'AWS_ENDPOINT_DISCOVERY_ENABLED'; + const ENV_ENABLED_ALT = 'AWS_ENABLE_ENDPOINT_DISCOVERY'; + const ENV_PROFILE = 'AWS_PROFILE'; + public static $cacheKey = 'aws_cached_endpoint_discovery_config'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $configProviders = [self::env()]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini(); + } + $configProviders[] = self::fallback($config); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['endpoint_discovery']) && $config['endpoint_discovery'] instanceof CacheInterface) { + return self::cache($memo, $config['endpoint_discovery'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates config from environment variables. + * + * @param $cacheLimit + * @return callable + */ + public static function env($cacheLimit = self::DEFAULT_CACHE_LIMIT) + { + return function () use($cacheLimit) { + // Use config from environment variables, if available + $enabled = \getenv(self::ENV_ENABLED); + if ($enabled === \false || $enabled === '') { + $enabled = \getenv(self::ENV_ENABLED_ALT); + } + if ($enabled !== \false && $enabled !== '') { + return Promise\Create::promiseFor(new Configuration($enabled, $cacheLimit)); + } + return self::reject('Could not find environment variable config' . ' in ' . self::ENV_ENABLED); + }; + } + /** + * Fallback config options when other sources are not set. Will check the + * service model for any endpoint discovery required operations, and enable + * endpoint discovery in that case. If no required operations found, will use + * the class default values. + * + * @param array $config + * @return callable + */ + public static function fallback($config = []) + { + $enabled = self::DEFAULT_ENABLED; + if (!empty($config['api_provider']) && !empty($config['service']) && !empty($config['version'])) { + $provider = $config['api_provider']; + $apiData = $provider('api', $config['service'], $config['version']); + if (!empty($apiData['operations'])) { + foreach ($apiData['operations'] as $operation) { + if (!empty($operation['endpointdiscovery']['required'])) { + $enabled = \true; + } + } + } + } + return function () use($enabled) { + return Promise\Create::promiseFor(new Configuration($enabled, self::DEFAULT_CACHE_LIMIT)); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * @param int $cacheLimit + * + * @return callable + */ + public static function ini($profile = null, $filename = null, $cacheLimit = self::DEFAULT_CACHE_LIMIT) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename, $cacheLimit) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read configuration from {$filename}"); + } + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile]['endpoint_discovery_enabled'])) { + return self::reject("Required endpoint discovery config values\n not present in INI profile '{$profile}' ({$filename})"); + } + return Promise\Create::promiseFor(new Configuration($data[$profile]['endpoint_discovery_enabled'], $cacheLimit)); + }; + } + /** + * Unwraps a configuration object in whatever valid form it is in, + * always returning a ConfigurationInterface object. + * + * @param mixed $config + * @return ConfigurationInterface + * @throws \InvalidArgumentException + */ + public static function unwrap($config) + { + if (\is_callable($config)) { + $config = $config(); + } + if ($config instanceof PromiseInterface) { + $config = $config->wait(); + } + if ($config instanceof ConfigurationInterface) { + return $config; + } elseif (\is_array($config) && isset($config['enabled'])) { + if (isset($config['cache_limit'])) { + return new Configuration($config['enabled'], $config['cache_limit']); + } + return new Configuration($config['enabled'], self::DEFAULT_CACHE_LIMIT); + } + throw new \InvalidArgumentException('Not a valid endpoint_discovery ' . 'configuration argument.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointDiscoveryMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointDiscoveryMiddleware.php new file mode 100644 index 00000000..9afacc9b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointDiscoveryMiddleware.php @@ -0,0 +1,246 @@ +nextHandler = $handler; + $this->client = $client; + $this->args = $args; + $this->service = $client->getApi(); + $this->config = $config; + } + public function __invoke(CommandInterface $cmd, RequestInterface $request) + { + $nextHandler = $this->nextHandler; + $op = $this->service->getOperation($cmd->getName())->toArray(); + // Continue only if endpointdiscovery trait is set + if (isset($op['endpointdiscovery'])) { + $config = ConfigurationProvider::unwrap($this->config); + $isRequired = !empty($op['endpointdiscovery']['required']); + if ($isRequired && !$config->isEnabled()) { + throw new UnresolvedEndpointException('This operation ' . 'requires the use of endpoint discovery, but this has ' . 'been disabled in the configuration. Enable endpoint ' . 'discovery or use a different operation.'); + } + // Continue only if enabled by config + if ($config->isEnabled()) { + if (isset($op['endpointoperation'])) { + throw new UnresolvedEndpointException('This operation is ' . 'contradictorily marked both as using endpoint discovery ' . 'and being the endpoint discovery operation. Please ' . 'verify the accuracy of your model files.'); + } + // Original endpoint may be used if discovery optional + $originalUri = $request->getUri(); + $identifiers = $this->getIdentifiers($op); + $cacheKey = $this->getCacheKey($this->client->getCredentials()->wait(), $cmd, $identifiers); + // Check/create cache + if (!isset(self::$cache)) { + self::$cache = new LruArrayCache($config->getCacheLimit()); + } + if (empty($endpointList = self::$cache->get($cacheKey))) { + $endpointList = new EndpointList([]); + } + $endpoint = $endpointList->getActive(); + // Retrieve endpoints if there is no active endpoint + if (empty($endpoint)) { + try { + $endpoint = $this->discoverEndpoint($cacheKey, $cmd, $identifiers); + } catch (\Exception $e) { + // Use cached endpoint, expired or active, if any remain + $endpoint = $endpointList->getEndpoint(); + if (empty($endpoint)) { + return $this->handleDiscoveryException($isRequired, $originalUri, $e, $cmd, $request); + } + } + } + $request = $this->modifyRequest($request, $endpoint); + $g = function ($value) use($cacheKey, $cmd, $identifiers, $isRequired, $originalUri, $request, &$endpoint, &$g) { + if ($value instanceof AwsException && ($value->getAwsErrorCode() == 'InvalidEndpointException' || $value->getStatusCode() == 421)) { + return $this->handleInvalidEndpoint($cacheKey, $cmd, $identifiers, $isRequired, $originalUri, $request, $value, $endpoint, $g); + } + return $value; + }; + return $nextHandler($cmd, $request)->otherwise($g); + } + } + return $nextHandler($cmd, $request); + } + private function discoverEndpoint($cacheKey, CommandInterface $cmd, array $identifiers) + { + $discCmd = $this->getDiscoveryCommand($cmd, $identifiers); + $this->discoveryTimes[$cacheKey] = \time(); + $result = $this->client->execute($discCmd); + if (isset($result['Endpoints'])) { + $endpointData = []; + foreach ($result['Endpoints'] as $datum) { + $endpointData[$datum['Address']] = \time() + $datum['CachePeriodInMinutes'] * 60; + } + $endpointList = new EndpointList($endpointData); + self::$cache->set($cacheKey, $endpointList); + return $endpointList->getEndpoint(); + } + throw new UnresolvedEndpointException('The endpoint discovery operation ' . 'yielded a response that did not contain properly formatted ' . 'endpoint data.'); + } + private function getCacheKey(CredentialsInterface $creds, CommandInterface $cmd, array $identifiers) + { + $key = $this->service->getServiceName() . '_' . $creds->getAccessKeyId(); + if (!empty($identifiers)) { + $key .= '_' . $cmd->getName(); + foreach ($identifiers as $identifier) { + $key .= "_{$cmd[$identifier]}"; + } + } + return $key; + } + private function getDiscoveryCommand(CommandInterface $cmd, array $identifiers) + { + foreach ($this->service->getOperations() as $op) { + if (isset($op['endpointoperation'])) { + $endpointOperation = $op->toArray()['name']; + break; + } + } + if (!isset($endpointOperation)) { + throw new UnresolvedEndpointException('This command is set to use ' . 'endpoint discovery, but no endpoint discovery operation was ' . 'found. Please verify the accuracy of your model files.'); + } + $params = []; + if (!empty($identifiers)) { + $params['Operation'] = $cmd->getName(); + $params['Identifiers'] = []; + foreach ($identifiers as $identifier) { + $params['Identifiers'][$identifier] = $cmd[$identifier]; + } + } + $command = $this->client->getCommand($endpointOperation, $params); + $command->getHandlerList()->appendBuild(Middleware::mapRequest(function (RequestInterface $r) { + return $r->withHeader('x-amz-api-version', $this->service->getApiVersion()); + }), 'x-amz-api-version-header'); + return $command; + } + private function getIdentifiers(array $operation) + { + $inputShape = $this->service->getShapeMap()->resolve($operation['input'])->toArray(); + $identifiers = []; + foreach ($inputShape['members'] as $key => $member) { + if (!empty($member['endpointdiscoveryid'])) { + $identifiers[] = $key; + } + } + return $identifiers; + } + private function handleDiscoveryException($isRequired, $originalUri, \Exception $e, CommandInterface $cmd, RequestInterface $request) + { + // If no cached endpoints and discovery required, + // throw exception + if ($isRequired) { + $message = 'The endpoint required for this service is currently ' . 'unable to be retrieved, and your request can not be fulfilled ' . 'unless you manually specify an endpoint.'; + throw new AwsException($message, $cmd, ['code' => 'EndpointDiscoveryException', 'message' => $message], $e); + } + // If discovery isn't required, use original endpoint + return $this->useOriginalUri($originalUri, $cmd, $request); + } + private function handleInvalidEndpoint($cacheKey, $cmd, $identifiers, $isRequired, $originalUri, $request, $value, &$endpoint, &$g) + { + $nextHandler = $this->nextHandler; + $endpointList = self::$cache->get($cacheKey); + if ($endpointList instanceof EndpointList) { + // Remove invalid endpoint from cached list + $endpointList->remove($endpoint); + // If possible, get another cached endpoint + $newEndpoint = $endpointList->getEndpoint(); + } + if (empty($newEndpoint)) { + // If no more cached endpoints, make discovery call + // if none made within cooldown for given key + if (\time() - $this->discoveryTimes[$cacheKey] < self::$discoveryCooldown) { + // If no more cached endpoints and it's required, + // fail with original exception + if ($isRequired) { + return $value; + } + // Use original endpoint if not required + return $this->useOriginalUri($originalUri, $cmd, $request); + } + $newEndpoint = $this->discoverEndpoint($cacheKey, $cmd, $identifiers); + } + $endpoint = $newEndpoint; + $request = $this->modifyRequest($request, $endpoint); + return $nextHandler($cmd, $request)->otherwise($g); + } + private function modifyRequest(RequestInterface $request, $endpoint) + { + $parsed = $this->parseEndpoint($endpoint); + if (!empty($request->getHeader('User-Agent'))) { + $userAgent = $request->getHeader('User-Agent')[0]; + if (\strpos($userAgent, 'endpoint-discovery') === \false) { + $userAgent = $userAgent . ' endpoint-discovery'; + } + } else { + $userAgent = 'endpoint-discovery'; + } + return $request->withUri($request->getUri()->withHost($parsed['host'])->withPath($parsed['path']))->withHeader('User-Agent', $userAgent); + } + /** + * Parses an endpoint returned from the discovery API into an array with + * 'host' and 'path' keys. + * + * @param $endpoint + * @return array + */ + private function parseEndpoint($endpoint) + { + $parsed = \parse_url($endpoint); + // parse_url() will correctly parse full URIs with schemes + if (isset($parsed['host'])) { + return $parsed; + } + // parse_url() will put host & path in 'path' if scheme is not provided + if (isset($parsed['path'])) { + $split = \explode('/', $parsed['path'], 2); + $parsed['host'] = $split[0]; + if (isset($split[1])) { + if (\substr($split[1], 0, 1) !== '/') { + $split[1] = '/' . $split[1]; + } + $parsed['path'] = $split[1]; + } else { + $parsed['path'] = ''; + } + return $parsed; + } + throw new UnresolvedEndpointException("The supplied endpoint '" . "{$endpoint}' is invalid."); + } + private function useOriginalUri(UriInterface $uri, CommandInterface $cmd, RequestInterface $request) + { + $nextHandler = $this->nextHandler; + $endpoint = $uri->getHost() . $uri->getPath(); + $request = $this->modifyRequest($request, $endpoint); + return $nextHandler($cmd, $request); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointList.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointList.php new file mode 100644 index 00000000..09516993 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/EndpointList.php @@ -0,0 +1,80 @@ +active = $endpoints; + \reset($this->active); + } + /** + * Gets an active (unexpired) endpoint. Returns null if none found. + * + * @return null|string + */ + public function getActive() + { + if (\count($this->active) < 1) { + return null; + } + while (\time() > \current($this->active)) { + $key = \key($this->active); + $this->expired[$key] = \current($this->active); + $this->increment($this->active); + unset($this->active[$key]); + if (\count($this->active) < 1) { + return null; + } + } + $active = \key($this->active); + $this->increment($this->active); + return $active; + } + /** + * Gets an active endpoint if possible, then an expired endpoint if possible. + * Returns null if no endpoints found. + * + * @return null|string + */ + public function getEndpoint() + { + if (!empty($active = $this->getActive())) { + return $active; + } + return $this->getExpired(); + } + /** + * Removes an endpoint from both lists. + * + * @param string $key + */ + public function remove($key) + { + unset($this->active[$key]); + unset($this->expired[$key]); + } + /** + * Get an expired endpoint. Returns null if none found. + * + * @return null|string + */ + private function getExpired() + { + if (\count($this->expired) < 1) { + return null; + } + $expired = \key($this->expired); + $this->increment($this->expired); + return $expired; + } + private function increment(&$array) + { + if (\next($array) === \false) { + \reset($array); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/Exception/ConfigurationException.php new file mode 100644 index 00000000..2646d853 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointDiscovery/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +nextHandler = $nextHandler; + $this->service = $service; + } + public function __invoke(CommandInterface $command, RequestInterface $request) + { + $nextHandler = $this->nextHandler; + $operation = $this->service->getOperation($command->getName()); + if (!empty($operation['endpoint']['hostPrefix'])) { + $prefix = $operation['endpoint']['hostPrefix']; + // Captures endpoint parameters stored in the modeled host. + // These are denoted by enclosure in braces, i.e. '{param}' + \preg_match_all("/\\{([a-zA-Z0-9]+)}/", $prefix, $parameters); + if (!empty($parameters[1])) { + // Captured parameters without braces stored in $parameters[1], + // which should correspond to members in the Command object + foreach ($parameters[1] as $index => $parameter) { + if (empty($command[$parameter])) { + throw new \InvalidArgumentException("The parameter '{$parameter}' must be set and not empty."); + } + // Captured parameters with braces stored in $parameters[0], + // which are replaced by their corresponding Command value + $prefix = \str_replace($parameters[0][$index], $command[$parameter], $prefix); + } + } + $uri = $request->getUri(); + $host = $prefix . $uri->getHost(); + if (!\WPMailSMTP\Vendor\Aws\is_valid_hostname($host)) { + throw new \InvalidArgumentException("The supplied parameters result in an invalid hostname: '{$host}'."); + } + $request = $request->withUri($uri->withHost($host)); + } + return $nextHandler($command, $request); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointDefinitionProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointDefinitionProvider.php new file mode 100644 index 00000000..89351833 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointDefinitionProvider.php @@ -0,0 +1,57 @@ +ruleset = new Ruleset($ruleset, $partitions); + $this->cache = new LruArrayCache(100); + } + /** + * @return Ruleset + */ + public function getRuleset() + { + return $this->ruleset; + } + /** + * Given a Ruleset and input parameters, determines the correct endpoint + * or an error to be thrown for a given request. + * + * @return RulesetEndpoint + * @throws UnresolvedEndpointException + */ + public function resolveEndpoint(array $inputParameters) + { + $hashedParams = $this->hashInputParameters($inputParameters); + $match = $this->cache->get($hashedParams); + if (!\is_null($match)) { + return $match; + } + $endpoint = $this->ruleset->evaluate($inputParameters); + if ($endpoint === \false) { + throw new UnresolvedEndpointException('Unable to resolve an endpoint using the provider arguments: ' . \json_encode($inputParameters)); + } + $this->cache->set($hashedParams, $endpoint); + return $endpoint; + } + private function hashInputParameters($inputParameters) + { + return \md5(\serialize($inputParameters)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2Middleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2Middleware.php new file mode 100644 index 00000000..c8d32b7b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2Middleware.php @@ -0,0 +1,287 @@ + 'v4', 'sigv4a' => 'v4a', 'none' => 'anonymous', 'bearer' => 'bearer', 'sigv4-s3express' => 'v4-s3express']; + /** @var callable */ + private $nextHandler; + /** @var EndpointProviderV2 */ + private $endpointProvider; + /** @var Service */ + private $api; + /** @var array */ + private $clientArgs; + /** @var Closure */ + private $credentialProvider; + /** + * Create a middleware wrapper function + * + * @param EndpointProviderV2 $endpointProvider + * @param Service $api + * @param array $args + * @param callable $credentialProvider + * + * @return Closure + */ + public static function wrap(EndpointProviderV2 $endpointProvider, Service $api, array $args, callable $credentialProvider) : Closure + { + return function (callable $handler) use($endpointProvider, $api, $args, $credentialProvider) { + return new self($handler, $endpointProvider, $api, $args, $credentialProvider); + }; + } + /** + * @param callable $nextHandler + * @param EndpointProviderV2 $endpointProvider + * @param Service $api + * @param array $args + */ + public function __construct(callable $nextHandler, EndpointProviderV2 $endpointProvider, Service $api, array $args, ?callable $credentialProvider = null) + { + $this->nextHandler = $nextHandler; + $this->endpointProvider = $endpointProvider; + $this->api = $api; + $this->clientArgs = $args; + $this->credentialProvider = $credentialProvider; + } + /** + * @param CommandInterface $command + * + * @return Promise + */ + public function __invoke(CommandInterface $command) + { + $nextHandler = $this->nextHandler; + $operation = $this->api->getOperation($command->getName()); + $commandArgs = $command->toArray(); + $providerArgs = $this->resolveArgs($commandArgs, $operation); + if (!empty($providerArgs[self::ACCOUNT_ID_PARAM])) { + $command->getMetricsBuilder()->append(MetricsBuilder::RESOLVED_ACCOUNT_ID); + } + $endpoint = $this->endpointProvider->resolveEndpoint($providerArgs); + if (!empty($authSchemes = $endpoint->getProperty('authSchemes'))) { + $this->applyAuthScheme($authSchemes, $command); + } + return $nextHandler($command, $endpoint); + } + /** + * Resolves client, context params, static context params and endpoint provider + * arguments provided at the command level. + * + * @param array $commandArgs + * @param Operation $operation + * + * @return array + */ + private function resolveArgs(array $commandArgs, Operation $operation) : array + { + $rulesetParams = $this->endpointProvider->getRuleset()->getParameters(); + if (isset($rulesetParams[self::ACCOUNT_ID_PARAM]) && isset($rulesetParams[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM])) { + $this->clientArgs[self::ACCOUNT_ID_PARAM] = $this->resolveAccountId(); + } + $endpointCommandArgs = $this->filterEndpointCommandArgs($rulesetParams, $commandArgs); + $staticContextParams = $this->bindStaticContextParams($operation->getStaticContextParams()); + $contextParams = $this->bindContextParams($commandArgs, $operation->getContextParams()); + $operationContextParams = $this->bindOperationContextParams($commandArgs, $operation->getOperationContextParams()); + return \array_merge($this->clientArgs, $operationContextParams, $contextParams, $staticContextParams, $endpointCommandArgs); + } + /** + * Compares Ruleset parameters against Command arguments + * to create a mapping of arguments to pass into the + * endpoint provider for endpoint resolution. + * + * @param array $rulesetParams + * @param array $commandArgs + * @return array + */ + private function filterEndpointCommandArgs(array $rulesetParams, array $commandArgs) : array + { + $endpointMiddlewareOpts = ['@use_dual_stack_endpoint' => 'UseDualStack', '@use_accelerate_endpoint' => 'Accelerate', '@use_path_style_endpoint' => 'ForcePathStyle']; + $filteredArgs = []; + foreach ($rulesetParams as $name => $value) { + if (isset($commandArgs[$name])) { + if (!empty($value->getBuiltIn())) { + continue; + } + $filteredArgs[$name] = $commandArgs[$name]; + } + } + if ($this->api->getServiceName() === 's3') { + foreach ($endpointMiddlewareOpts as $optionName => $newValue) { + if (isset($commandArgs[$optionName])) { + $filteredArgs[$newValue] = $commandArgs[$optionName]; + } + } + } + return $filteredArgs; + } + /** + * Binds static context params to their corresponding values. + * + * @param $staticContextParams + * + * @return array + */ + private function bindStaticContextParams($staticContextParams) : array + { + $scopedParams = []; + foreach ($staticContextParams as $paramName => $paramValue) { + $scopedParams[$paramName] = $paramValue['value']; + } + return $scopedParams; + } + /** + * Binds context params to their corresponding values found in + * command arguments. + * + * @param array $commandArgs + * @param array $contextParams + * + * @return array + */ + private function bindContextParams(array $commandArgs, array $contextParams) : array + { + $scopedParams = []; + foreach ($contextParams as $name => $spec) { + if (isset($commandArgs[$spec['shape']])) { + $scopedParams[$name] = $commandArgs[$spec['shape']]; + } + } + return $scopedParams; + } + /** + * Binds context params to their corresponding values found in + * command arguments. + * + * @param array $commandArgs + * @param array $contextParams + * + * @return array + */ + private function bindOperationContextParams(array $commandArgs, array $operationContextParams) : array + { + $scopedParams = []; + foreach ($operationContextParams as $name => $spec) { + $scopedValue = search($spec['path'], $commandArgs); + if ($scopedValue) { + $scopedParams[$name] = $scopedValue; + } + } + return $scopedParams; + } + /** + * Applies resolved auth schemes to the command object. + * + * @param $authSchemes + * @param $command + * + * @return void + */ + private function applyAuthScheme(array $authSchemes, CommandInterface $command) : void + { + $authScheme = $this->resolveAuthScheme($authSchemes); + $command['@context']['signature_version'] = $authScheme['version']; + if (isset($authScheme['name'])) { + $command['@context']['signing_service'] = $authScheme['name']; + } + if (isset($authScheme['region'])) { + $command['@context']['signing_region'] = $authScheme['region']; + } elseif (isset($authScheme['signingRegionSet'])) { + $command['@context']['signing_region_set'] = $authScheme['signingRegionSet']; + } + } + /** + * Returns the first compatible auth scheme in an endpoint object's + * auth schemes. + * + * @param array $authSchemes + * + * @return array + */ + private function resolveAuthScheme(array $authSchemes) : array + { + $invalidAuthSchemes = []; + foreach ($authSchemes as $authScheme) { + if ($this->isValidAuthScheme($authScheme['name'])) { + return $this->normalizeAuthScheme($authScheme); + } + $invalidAuthSchemes[$authScheme['name']] = \false; + } + $invalidAuthSchemesString = '`' . \implode('`, `', \array_keys($invalidAuthSchemes)) . '`'; + $validAuthSchemesString = '`' . \implode('`, `', \array_keys(\array_diff_key(self::$validAuthSchemes, $invalidAuthSchemes))) . '`'; + throw new UnresolvedAuthSchemeException("This operation requests {$invalidAuthSchemesString}" . " auth schemes, but the client currently supports {$validAuthSchemesString}."); + } + /** + * Normalizes an auth scheme's name, signing region or signing region set + * to the auth keys recognized by the SDK. + * + * @param array $authScheme + * @return array + */ + private function normalizeAuthScheme(array $authScheme) : array + { + /* + sigv4a will contain a regionSet property. which is guaranteed to be `*` + for now. The SigV4 class handles this automatically for now. It seems + complexity will be added here in the future. + */ + $normalizedAuthScheme = []; + if (isset($authScheme['disableDoubleEncoding']) && $authScheme['disableDoubleEncoding'] === \true && $authScheme['name'] !== 'sigv4a' && $authScheme['name'] !== 'sigv4-s3express') { + $normalizedAuthScheme['version'] = 's3v4'; + } else { + $normalizedAuthScheme['version'] = self::$validAuthSchemes[$authScheme['name']]; + } + $normalizedAuthScheme['name'] = $authScheme['signingName'] ?? null; + $normalizedAuthScheme['region'] = $authScheme['signingRegion'] ?? null; + $normalizedAuthScheme['signingRegionSet'] = $authScheme['signingRegionSet'] ?? null; + return $normalizedAuthScheme; + } + private function isValidAuthScheme($signatureVersion) : bool + { + if (isset(self::$validAuthSchemes[$signatureVersion])) { + if ($signatureVersion === 'sigv4a') { + return \extension_loaded('awscrt'); + } + return \true; + } + return \false; + } + /** + * This method tries to resolve an `AccountId` parameter from a resolved identity. + * We will just perform this operation if the parameter `AccountId` is part of the ruleset parameters and + * `AccountIdEndpointMode` is not disabled, otherwise, we will ignore it. + * + * @return null|string + */ + private function resolveAccountId() : ?string + { + if (isset($this->clientArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM]) && $this->clientArgs[self::ACCOUNT_ID_ENDPOINT_MODE_PARAM] === 'disabled') { + return null; + } + if (\is_null($this->credentialProvider)) { + return null; + } + $identityProviderFn = $this->credentialProvider; + $identity = $identityProviderFn()->wait(); + return $identity->getAccountId(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2SerializerTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2SerializerTrait.php new file mode 100644 index 00000000..d2ca4fd8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/EndpointV2SerializerTrait.php @@ -0,0 +1,59 @@ +applyHeaders($endpoint, $headers); + $resolvedUrl = $endpoint->getUrl(); + $this->applyScheme($resolvedUrl); + $this->endpoint = $this instanceof RestSerializer ? new Uri($resolvedUrl) : $resolvedUrl; + } + /** + * Combines modeled headers and headers resolved from an endpoint object. + * + * @param $endpoint + * @param $headers + * @return void + */ + private function applyHeaders(RulesetEndpoint $endpoint, array &$headers) : void + { + if (!\is_null($endpoint->getHeaders())) { + $headers = \array_merge($headers, $endpoint->getHeaders()); + } + } + /** + * Applies custom HTTP schemes provided in client configuration. + * + * @param $resolvedUrl + * @return void + */ + private function applyScheme(&$resolvedUrl) : void + { + $resolvedEndpointScheme = \parse_url($resolvedUrl, \PHP_URL_SCHEME); + $scheme = $this->endpoint instanceof Uri ? $this->endpoint->getScheme() : \parse_url($this->endpoint, \PHP_URL_SCHEME); + if (!empty($scheme) && $scheme !== $resolvedEndpointScheme) { + $resolvedUrl = \str_replace($resolvedEndpointScheme, $scheme, $resolvedUrl); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/AbstractRule.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/AbstractRule.php new file mode 100644 index 00000000..513fa734 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/AbstractRule.php @@ -0,0 +1,49 @@ +conditions = $definition['conditions']; + $this->documentation = isset($definition['documentation']) ? $definition['documentation'] : null; + } + /** + * @return array + */ + public function getConditions() + { + return $this->conditions; + } + /** + * @return mixed + */ + public function getDocumentation() + { + return $this->documentation; + } + /** + * Determines if all conditions for a given rule are met. + * + * @return boolean + */ + protected function evaluateConditions(array &$inputParameters, RulesetStandardLibrary $standardLibrary) + { + foreach ($this->getConditions() as $condition) { + $result = $standardLibrary->callFunction($condition, $inputParameters); + if (\is_null($result) || $result === \false) { + return \false; + } + } + return \true; + } + public abstract function evaluate(array $inputParameters, RulesetStandardLibrary $standardLibrary); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/EndpointRule.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/EndpointRule.php new file mode 100644 index 00000000..bd1e4879 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/EndpointRule.php @@ -0,0 +1,90 @@ +endpoint = $definition['endpoint']; + } + /** + * @return array + */ + public function getEndpoint() + { + return $this->endpoint; + } + /** + * If all the rule's conditions are met, return the resolved + * endpoint object. + * + * @return RulesetEndpoint | null + */ + public function evaluate(array $inputParameters, RulesetStandardLibrary $standardLibrary) + { + if ($this->evaluateConditions($inputParameters, $standardLibrary)) { + return $this->resolve($inputParameters, $standardLibrary); + } + return \false; + } + /** + * Given input parameters, resolve an endpoint in its entirety. + * + * @return RulesetEndpoint + */ + private function resolve(array $inputParameters, RulesetStandardLibrary $standardLibrary) + { + $uri = $standardLibrary->resolveValue($this->endpoint['url'], $inputParameters); + $properties = isset($this->endpoint['properties']) ? $this->resolveProperties($this->endpoint['properties'], $inputParameters, $standardLibrary) : null; + $headers = $this->resolveHeaders($inputParameters, $standardLibrary); + return new RulesetEndpoint($uri, $properties, $headers); + } + /** + * Recurse through an endpoint's `properties` attribute, resolving template + * strings when found. Return the fully resolved attribute. + * + * @return array + */ + private function resolveProperties($properties, array $inputParameters, RulesetStandardLibrary $standardLibrary) + { + if (\is_array($properties)) { + $propertiesArr = []; + foreach ($properties as $key => $val) { + $propertiesArr[$key] = $this->resolveProperties($val, $inputParameters, $standardLibrary); + } + return $propertiesArr; + } elseif ($standardLibrary->isTemplate($properties)) { + return $standardLibrary->resolveTemplateString($properties, $inputParameters); + } + return $properties; + } + /** + * If present, iterate through an endpoint's headers attribute resolving + * values along the way. Return the fully resolved attribute. + * + * @return array + */ + private function resolveHeaders(array $inputParameters, RulesetStandardLibrary $standardLibrary) + { + $headers = isset($this->endpoint['headers']) ? $this->endpoint['headers'] : null; + if (\is_null($headers)) { + return null; + } + $resolvedHeaders = []; + foreach ($headers as $headerName => $headerValues) { + $resolvedValues = []; + foreach ($headerValues as $value) { + $resolvedValue = $standardLibrary->resolveValue($value, $inputParameters, $standardLibrary); + $resolvedValues[] = $resolvedValue; + } + $resolvedHeaders[$headerName] = $resolvedValues; + } + return $resolvedHeaders; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/ErrorRule.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/ErrorRule.php new file mode 100644 index 00000000..6069b702 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/ErrorRule.php @@ -0,0 +1,38 @@ +error = $definition['error']; + } + /** + * @return array + */ + public function getError() + { + return $this->error; + } + /** + * If an error rule's conditions are met, raise an + * UnresolvedEndpointError containing the fully resolved error string. + * + * @return null + * @throws UnresolvedEndpointException + */ + public function evaluate(array $inputParameters, RulesetStandardLibrary $standardLibrary) + { + if ($this->evaluateConditions($inputParameters, $standardLibrary)) { + $message = $standardLibrary->resolveValue($this->error, $inputParameters); + throw new UnresolvedEndpointException($message); + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/RuleCreator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/RuleCreator.php new file mode 100644 index 00000000..998dfc2e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Rule/RuleCreator.php @@ -0,0 +1,21 @@ +rules = $this->createRules($definition['rules']); + } + /** + * @return array + */ + public function getRules() + { + return $this->rules; + } + /** + * If a tree rule's conditions evaluate successfully, iterate over its + * subordinate rules and return a result if there is one. If any of the + * subsequent rules are trees, the function will recurse until it reaches + * an error or an endpoint rule + * + * @return mixed + */ + public function evaluate(array $inputParameters, RulesetStandardLibrary $standardLibrary) + { + if ($this->evaluateConditions($inputParameters, $standardLibrary)) { + foreach ($this->rules as $rule) { + $inputParametersCopy = $inputParameters; + $evaluation = $rule->evaluate($inputParametersCopy, $standardLibrary); + if ($evaluation !== \false) { + return $evaluation; + } + } + } + return \false; + } + private function createRules(array $rules) + { + $rulesList = []; + foreach ($rules as $rule) { + $ruleType = RuleCreator::create($rule['type'], $rule); + $rulesList[] = $ruleType; + } + return $rulesList; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/Ruleset.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/Ruleset.php new file mode 100644 index 00000000..71fb3863 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/Ruleset.php @@ -0,0 +1,99 @@ +version = $ruleset['version']; + $this->parameters = $this->createParameters($ruleset['parameters']); + $this->rules = $this->createRules($ruleset['rules']); + $this->standardLibrary = new RulesetStandardLibrary($partitions); + } + /** + * @return mixed + */ + public function getVersion() + { + return $this->version; + } + /** + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + /** + * @return array + */ + public function getRules() + { + return $this->rules; + } + /** + * Evaluate the ruleset against the input parameters. + * Return the first rule the parameters match against. + * + * @return mixed + */ + public function evaluate(array $inputParameters) + { + $this->validateInputParameters($inputParameters); + foreach ($this->rules as $rule) { + $evaluation = $rule->evaluate($inputParameters, $this->standardLibrary); + if ($evaluation !== \false) { + return $evaluation; + } + } + return \false; + } + /** + * Ensures all corresponding client-provided parameters match + * the Ruleset parameter's specified type. + * + * @return void + */ + private function validateInputParameters(array &$inputParameters) + { + foreach ($this->parameters as $paramName => $param) { + $inputParam = isset($inputParameters[$paramName]) ? $inputParameters[$paramName] : null; + if (\is_null($inputParam) && !\is_null($param->getDefault())) { + $inputParameters[$paramName] = $param->getDefault(); + } elseif (!\is_null($inputParam)) { + $param->validateInputParam($inputParam); + } + } + } + private function createParameters(array $parameters) + { + $parameterList = []; + foreach ($parameters as $name => $definition) { + $parameterList[$name] = new RulesetParameter($name, $definition); + } + return $parameterList; + } + private function createRules(array $rules) + { + $rulesList = []; + foreach ($rules as $rule) { + $ruleObj = RuleCreator::create($rule['type'], $rule); + $rulesList[] = $ruleObj; + } + return $rulesList; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetEndpoint.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetEndpoint.php new file mode 100644 index 00000000..525c339a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetEndpoint.php @@ -0,0 +1,55 @@ +url = $url; + $this->properties = $properties; + $this->headers = $headers; + } + /** + * @return mixed + */ + public function getUrl() + { + return $this->url; + } + /** + * @param $property + * @return mixed + */ + public function getProperty($property) + { + if (isset($this->properties[$property])) { + return $this->properties[$property]; + } + return null; + } + /** + * @return mixed + */ + public function getProperties() + { + return $this->properties; + } + /** + * @return mixed + */ + public function getHeaders() + { + return $this->headers; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetParameter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetParameter.php new file mode 100644 index 00000000..fc54d627 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetParameter.php @@ -0,0 +1,143 @@ + */ + private static $typeMap = ['String' => 'is_string', 'Boolean' => 'is_bool', 'StringArray' => 'isStringArray']; + public function __construct($name, array $definition) + { + $type = \ucfirst($definition['type']); + if ($this->isValidType($type)) { + $this->type = $type; + } else { + throw new UnresolvedEndpointException('Unknown parameter type ' . "`{$type}`" . '. Parameters must be of type `String`, `Boolean` or `StringArray.'); + } + $this->name = $name; + $this->builtIn = $definition['builtIn'] ?? null; + $this->default = $definition['default'] ?? null; + $this->required = $definition['required'] ?? \false; + $this->documentation = $definition['documentation'] ?? null; + $this->deprecated = $definition['deprecated'] ?? \false; + } + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + /** + * @return mixed + */ + public function getBuiltIn() + { + return $this->builtIn; + } + /** + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + /** + * @return boolean + */ + public function getRequired() + { + return $this->required; + } + /** + * @return string + */ + public function getDocumentation() + { + return $this->documentation; + } + /** + * @return boolean + */ + public function getDeprecated() + { + return $this->deprecated; + } + /** + * Validates that an input parameter matches the type provided in its definition. + * + * @return void + * @throws InvalidArgumentException + */ + public function validateInputParam($inputParam) + { + if (!$this->isValidInput($inputParam)) { + throw new UnresolvedEndpointException("Input parameter `{$this->name}` is the wrong type. Must be a {$this->type}."); + } + if ($this->deprecated) { + $deprecated = $this->deprecated; + $deprecationString = "{$this->name} has been deprecated "; + $msg = $deprecated['message'] ?? null; + $since = $deprecated['since'] ?? null; + if (!\is_null($since)) { + $deprecationString .= 'since ' . $since . '. '; + } + if (!\is_null($msg)) { + $deprecationString .= $msg; + } + \trigger_error($deprecationString, \E_USER_WARNING); + } + } + private function isValidType($type) + { + return isset(self::$typeMap[$type]); + } + private function isValidInput($inputParam) : bool + { + $method = self::$typeMap[$this->type]; + if (\is_callable($method)) { + return $method($inputParam); + } elseif (\method_exists($this, $method)) { + return $this->{$method}($inputParam); + } + return \false; + } + private function isStringArray(array $array) : bool + { + if (is_associative($array)) { + return \false; + } + foreach ($array as $value) { + if (!\is_string($value)) { + return \false; + } + } + return \true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetStandardLibrary.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetStandardLibrary.php new file mode 100644 index 00000000..fe20ca43 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/EndpointV2/Ruleset/RulesetStandardLibrary.php @@ -0,0 +1,342 @@ +[^\\{\\}]+)|(?R))*\\}#x'; + const HOST_LABEL_RE = '/^(?!-)[a-zA-Z\\d-]{1,63}(?partitions = $partitions; + } + /** + * Determines if a value is set. + * + * @return boolean + */ + public function is_set($value) + { + return isset($value); + } + /** + * Function implementation of logical operator `not` + * + * @return boolean + */ + public function not($value) + { + return !$value; + } + /** + * Find an attribute within a value given a path string. + * + * @return mixed + */ + public function getAttr($from, $path) + { + // Handles the case where "[= $stop or \strlen($input) < $stop) { + return null; + } + if (!$reverse) { + return \substr($input, $start, $stop - $start); + } else { + $offset = \strlen($input) - $stop; + $length = $stop - $start; + return \substr($input, $offset, $length); + } + } + /** + * Evaluates two strings for equality. + * + * @return boolean + */ + public function stringEquals($string1, $string2) + { + if (!\is_string($string1) || !\is_string($string2)) { + throw new UnresolvedEndpointException('Values passed to StringEquals must be `string`.'); + } + return $string1 === $string2; + } + /** + * Evaluates two booleans for equality. + * + * @return boolean + */ + public function booleanEquals($boolean1, $boolean2) + { + return \filter_var($boolean1, \FILTER_VALIDATE_BOOLEAN) === \filter_var($boolean2, \FILTER_VALIDATE_BOOLEAN); + } + /** + * Percent-encodes an input string. + * + * @return mixed + */ + public function uriEncode($input) + { + if (\is_null($input)) { + return null; + } + return \str_replace('%7E', '~', \rawurlencode($input)); + } + /** + * Parses URL string into components. + * + * @return mixed + */ + public function parseUrl($url) + { + if (\is_null($url)) { + return null; + } + $parsed = \parse_url($url); + if ($parsed === \false || !empty($parsed['query'])) { + return null; + } elseif (!isset($parsed['scheme'])) { + return null; + } + if ($parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https') { + return null; + } + $urlInfo = []; + $urlInfo['scheme'] = $parsed['scheme']; + $urlInfo['authority'] = isset($parsed['host']) ? $parsed['host'] : ''; + if (isset($parsed['port'])) { + $urlInfo['authority'] = $urlInfo['authority'] . ":" . $parsed['port']; + } + $urlInfo['path'] = isset($parsed['path']) ? $parsed['path'] : ''; + $urlInfo['normalizedPath'] = !empty($parsed['path']) ? \rtrim($urlInfo['path'] ?: '', '/' . "/") . '/' : '/'; + $urlInfo['isIp'] = !isset($parsed['host']) ? 'false' : $this->isValidIp($parsed['host']); + return $urlInfo; + } + /** + * Evaluates whether a value is a valid host label per + * RFC 1123. If allow_subdomains is true, split on `.` and validate + * each subdomain separately. + * + * @return boolean + */ + public function isValidHostLabel($hostLabel, $allowSubDomains) + { + if (!isset($hostLabel) || !$allowSubDomains && \strpos($hostLabel, '.') != \false) { + return \false; + } + if ($allowSubDomains) { + foreach (\explode('.', $hostLabel) as $subdomain) { + if (!$this->validateHostLabel($subdomain)) { + return \false; + } + } + return \true; + } else { + return $this->validateHostLabel($hostLabel); + } + } + /** + * Parse and validate string for ARN components. + * + * @return array|null + */ + public function parseArn($arnString) + { + if (\is_null($arnString) || \substr($arnString, 0, 3) !== "arn") { + return null; + } + $arn = []; + $parts = \explode(':', $arnString, 6); + if (\sizeof($parts) < 6) { + return null; + } + $arn['partition'] = isset($parts[1]) ? $parts[1] : null; + $arn['service'] = isset($parts[2]) ? $parts[2] : null; + $arn['region'] = isset($parts[3]) ? $parts[3] : null; + $arn['accountId'] = isset($parts[4]) ? $parts[4] : null; + $arn['resourceId'] = isset($parts[5]) ? $parts[5] : null; + if (empty($arn['partition']) || empty($arn['service']) || empty($arn['resourceId'])) { + return null; + } + $resource = $arn['resourceId']; + $arn['resourceId'] = \preg_split("/[:\\/]/", $resource); + return $arn; + } + /** + * Matches a region string to an AWS partition. + * + * @return mixed + */ + public function partition($region) + { + if (!\is_string($region)) { + throw new UnresolvedEndpointException('Value passed to `partition` must be `string`.'); + } + $partitions = $this->partitions; + foreach ($partitions['partitions'] as $partition) { + if (\array_key_exists($region, $partition['regions']) || \preg_match("/{$partition['regionRegex']}/", $region)) { + return $partition['outputs']; + } + } + //return `aws` partition if no match is found. + return $partitions['partitions'][0]['outputs']; + } + /** + * Evaluates whether a value is a valid bucket name for virtual host + * style bucket URLs. + * + * @return boolean + */ + public function isVirtualHostableS3Bucket($bucketName, $allowSubdomains) + { + if (\is_null($bucketName) || (\strlen($bucketName) < 3 || \strlen($bucketName) > 63) || \preg_match(self::IPV4_RE, $bucketName) || \strtolower($bucketName) !== $bucketName) { + return \false; + } + if ($allowSubdomains) { + $labels = \explode('.', $bucketName); + $results = []; + foreach ($labels as $label) { + $results[] = $this->isVirtualHostableS3Bucket($label, \false); + } + return !\in_array(\false, $results); + } + return $this->isValidHostLabel($bucketName, \false); + } + public function callFunction($funcCondition, &$inputParameters) + { + $funcArgs = []; + foreach ($funcCondition['argv'] as $arg) { + $funcArgs[] = $this->resolveValue($arg, $inputParameters); + } + $funcName = \str_replace('aws.', '', $funcCondition['fn']); + if ($funcName === 'isSet') { + $funcName = 'is_set'; + } + $result = \call_user_func_array([RulesetStandardLibrary::class, $funcName], $funcArgs); + if (isset($funcCondition['assign'])) { + $assign = $funcCondition['assign']; + if (isset($inputParameters[$assign])) { + throw new UnresolvedEndpointException("Assignment `{$assign}` already exists in input parameters" . " or has already been assigned by an endpoint rule and cannot be overwritten."); + } + $inputParameters[$assign] = $result; + } + return $result; + } + public function resolveValue($value, $inputParameters) + { + //Given a value, check if it's a function, reference or template. + //returns resolved value + if ($this->isFunc($value)) { + return $this->callFunction($value, $inputParameters); + } elseif ($this->isRef($value)) { + return isset($inputParameters[$value['ref']]) ? $inputParameters[$value['ref']] : null; + } elseif ($this->isTemplate($value)) { + return $this->resolveTemplateString($value, $inputParameters); + } + return $value; + } + public function isFunc($arg) + { + return \is_array($arg) && isset($arg['fn']); + } + public function isRef($arg) + { + return \is_array($arg) && isset($arg['ref']); + } + public function isTemplate($arg) + { + return \is_string($arg) && !empty(\preg_match(self::TEMPLATE_SEARCH_RE, $arg)); + } + public function resolveTemplateString($value, $inputParameters) + { + return \preg_replace_callback(self::TEMPLATE_PARSE_RE, function ($match) use($inputParameters) { + if (\preg_match(self::TEMPLATE_ESCAPE_RE, $match[0])) { + return $match[1]; + } + $notFoundMessage = 'Resolved value was null. Please check rules and ' . 'input parameters and try again.'; + $parts = \explode("#", $match[1]); + if (\count($parts) > 1) { + $resolvedValue = $inputParameters; + foreach ($parts as $part) { + if (!isset($resolvedValue[$part])) { + throw new UnresolvedEndpointException($notFoundMessage); + } + $resolvedValue = $resolvedValue[$part]; + } + return $resolvedValue; + } else { + if (!isset($inputParameters[$parts[0]])) { + throw new UnresolvedEndpointException($notFoundMessage); + } + return $inputParameters[$parts[0]]; + } + }, $value); + } + private function validateHostLabel($hostLabel) + { + if (empty($hostLabel) || \strlen($hostLabel) > 63) { + return \false; + } + if (\preg_match(self::HOST_LABEL_RE, $hostLabel)) { + return \true; + } + return \false; + } + private function isValidIp($hostName) + { + $isWrapped = \strpos($hostName, '[') === 0 && \strrpos($hostName, ']') === \strlen($hostName) - 1; + return \preg_match(self::IPV4_RE, $hostName) || $isWrapped && \preg_match(self::IPV6_RE, $hostName) ? 'true' : 'false'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/AwsException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/AwsException.php new file mode 100644 index 00000000..d35a5aee --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/AwsException.php @@ -0,0 +1,226 @@ +data = isset($context['body']) ? $context['body'] : []; + $this->command = $command; + $this->response = isset($context['response']) ? $context['response'] : null; + $this->request = isset($context['request']) ? $context['request'] : null; + $this->requestId = isset($context['request_id']) ? $context['request_id'] : null; + $this->errorType = isset($context['type']) ? $context['type'] : null; + $this->errorCode = isset($context['code']) ? $context['code'] : null; + $this->errorShape = isset($context['error_shape']) ? $context['error_shape'] : null; + $this->connectionError = !empty($context['connection_error']); + $this->result = isset($context['result']) ? $context['result'] : null; + $this->transferInfo = isset($context['transfer_stats']) ? $context['transfer_stats'] : []; + $this->errorMessage = isset($context['message']) ? $context['message'] : null; + $this->monitoringEvents = []; + $this->maxRetriesExceeded = \false; + parent::__construct($message, 0, $previous); + } + public function __toString() + { + if (!$this->getPrevious()) { + return parent::__toString(); + } + // PHP strangely shows the innermost exception first before the outer + // exception message. It also has a default character limit for + // exception message strings such that the "next" exception (this one) + // might not even get shown, causing developers to attempt to catch + // the inner exception instead of the actual exception because they + // can't see the outer exception's __toString output. + return \sprintf("exception '%s' with message '%s'\n\n%s", \get_class($this), $this->getMessage(), parent::__toString()); + } + /** + * Get the command that was executed. + * + * @return CommandInterface + */ + public function getCommand() + { + return $this->command; + } + /** + * Get the concise error message if any. + * + * @return string|null + */ + public function getAwsErrorMessage() + { + return $this->errorMessage; + } + /** + * Get the sent HTTP request if any. + * + * @return RequestInterface|null + */ + public function getRequest() + { + return $this->request; + } + /** + * Get the received HTTP response if any. + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + /** + * Get the result of the exception if available + * + * @return ResultInterface|null + */ + public function getResult() + { + return $this->result; + } + /** + * Returns true if this is a connection error. + * + * @return bool + */ + public function isConnectionError() + { + return $this->connectionError; + } + /** + * If available, gets the HTTP status code of the corresponding response + * + * @return int|null + */ + public function getStatusCode() + { + return $this->response ? $this->response->getStatusCode() : null; + } + /** + * Get the request ID of the error. This value is only present if a + * response was received and is not present in the event of a networking + * error. + * + * @return string|null Returns null if no response was received + */ + public function getAwsRequestId() + { + return $this->requestId; + } + /** + * Get the AWS error type. + * + * @return string|null Returns null if no response was received + */ + public function getAwsErrorType() + { + return $this->errorType; + } + /** + * Get the AWS error code. + * + * @return string|null Returns null if no response was received + */ + public function getAwsErrorCode() + { + return $this->errorCode; + } + /** + * Get the AWS error shape. + * + * @return Shape|null Returns null if no response was received + */ + public function getAwsErrorShape() + { + return $this->errorShape; + } + /** + * Get all transfer information as an associative array if no $name + * argument is supplied, or gets a specific transfer statistic if + * a $name attribute is supplied (e.g., 'retries_attempted'). + * + * @param string $name Name of the transfer stat to retrieve + * + * @return mixed|null|array + */ + public function getTransferInfo($name = null) + { + if (!$name) { + return $this->transferInfo; + } + return isset($this->transferInfo[$name]) ? $this->transferInfo[$name] : null; + } + /** + * Replace the transfer information associated with an exception. + * + * @param array $info + */ + public function setTransferInfo(array $info) + { + $this->transferInfo = $info; + } + /** + * Returns whether the max number of retries is exceeded. + * + * @return bool + */ + public function isMaxRetriesExceeded() + { + return $this->maxRetriesExceeded; + } + /** + * Sets the flag for max number of retries exceeded. + */ + public function setMaxRetriesExceeded() + { + $this->maxRetriesExceeded = \true; + } + public function hasKey($name) + { + return isset($this->data[$name]); + } + public function get($key) + { + return $this[$key]; + } + public function search($expression) + { + return JmesPath::search($expression, $this->toArray()); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CommonRuntimeException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CommonRuntimeException.php new file mode 100644 index 00000000..ed42734e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/CommonRuntimeException.php @@ -0,0 +1,7 @@ +errorCode = $code; + $this->errorMessage = $message; + parent::__construct($message); + } + /** + * Get the AWS error code. + * + * @return string|null Returns null if no response was received + */ + public function getAwsErrorCode() + { + return $this->errorCode; + } + /** + * Get the concise error message if any. + * + * @return string|null + */ + public function getAwsErrorMessage() + { + return $this->errorMessage; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/IncalculablePayloadException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/IncalculablePayloadException.php new file mode 100644 index 00000000..4b92d04d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/IncalculablePayloadException.php @@ -0,0 +1,10 @@ + 'uploading parts to']); + $msg .= ". The following parts had errors:\n"; + /** @var $error AwsException */ + foreach ($prev as $part => $error) { + $msg .= "- Part {$part}: " . $error->getMessage() . "\n"; + } + } elseif ($prev instanceof AwsException) { + switch ($prev->getCommand()->getName()) { + case 'CreateMultipartUpload': + case 'InitiateMultipartUpload': + $action = 'initiating'; + break; + case 'CompleteMultipartUpload': + $action = 'completing'; + break; + } + if (isset($action)) { + $msg = \strtr($msg, ['performing' => $action]); + } + $msg .= ": {$prev->getMessage()}"; + } + if (!$prev instanceof \Exception) { + $prev = null; + } + parent::__construct($msg, 0, $prev); + $this->state = $state; + } + /** + * Get the state of the transfer + * + * @return UploadState + */ + public function getState() + { + return $this->state; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/TokenException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/TokenException.php new file mode 100644 index 00000000..26bc93d8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Exception/TokenException.php @@ -0,0 +1,10 @@ + \true, 'expect' => \true, 'cert' => \true, 'verify' => \true, 'timeout' => \true, 'debug' => \true, 'connect_timeout' => \true, 'stream' => \true, 'delay' => \true, 'sink' => \true]; + /** @var ClientInterface */ + private $client; + /** + * @param ClientInterface $client + */ + public function __construct(?ClientInterface $client = null) + { + $this->client = $client ?: new Client(); + } + /** + * @param Psr7Request $request + * @param array $options + * @return Promise\Promise|Promise\PromiseInterface + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function __invoke(Psr7Request $request, array $options = []) + { + // Create and send a Guzzle 5 request + $guzzlePromise = $this->client->send($this->createGuzzleRequest($request, $options)); + $promise = new Promise\Promise(function () use($guzzlePromise) { + try { + $guzzlePromise->wait(); + } catch (\Exception $e) { + // The promise is already delivered when the exception is + // thrown, so don't rethrow it. + } + }, [$guzzlePromise, 'cancel']); + $guzzlePromise->then([$promise, 'resolve'], [$promise, 'reject']); + return $promise->then(function (GuzzleResponse $response) { + // Adapt the Guzzle 5 Future to a Guzzle 6 ResponsePromise. + return $this->createPsr7Response($response); + }, function (Exception $exception) use($options) { + // If we got a 'sink' that's a path, set the response body to + // the contents of the file. This will build the resulting + // exception with more information. + if ($exception instanceof RequestException) { + if (isset($options['sink'])) { + if (!$options['sink'] instanceof Psr7StreamInterface) { + $exception->getResponse()->setBody(Stream::factory(\file_get_contents($options['sink']))); + } + } + } + // Reject with information about the error. + return new Promise\RejectedPromise($this->prepareErrorData($exception)); + }); + } + private function createGuzzleRequest(Psr7Request $psrRequest, array $options) + { + $ringConfig = []; + $statsCallback = isset($options['http_stats_receiver']) ? $options['http_stats_receiver'] : null; + unset($options['http_stats_receiver']); + // Remove unsupported options. + foreach (\array_keys($options) as $key) { + if (!isset(self::$validOptions[$key])) { + unset($options[$key]); + } + } + // Handle delay option. + if (isset($options['delay'])) { + $ringConfig['delay'] = $options['delay']; + unset($options['delay']); + } + // Prepare sink option. + if (isset($options['sink'])) { + $ringConfig['save_to'] = $options['sink'] instanceof Psr7StreamInterface ? new GuzzleStream($options['sink']) : $options['sink']; + unset($options['sink']); + } + // Ensure that all requests are async and lazy like Guzzle 6. + $options['future'] = 'lazy'; + // Create the Guzzle 5 request from the provided PSR7 request. + $request = $this->client->createRequest($psrRequest->getMethod(), $psrRequest->getUri(), $options); + if (\is_callable($statsCallback)) { + $request->getEmitter()->on('end', function (EndEvent $event) use($statsCallback) { + $statsCallback($event->getTransferInfo()); + }); + } + // For the request body, adapt the PSR stream to a Guzzle stream. + $body = $psrRequest->getBody(); + if ($body->getSize() === 0) { + $request->setBody(null); + } else { + $request->setBody(new GuzzleStream($body)); + } + $request->setHeaders($psrRequest->getHeaders()); + $request->setHeader('User-Agent', $request->getHeader('User-Agent') . ' ' . Client::getDefaultUserAgent()); + // Make sure the delay is configured, if provided. + if ($ringConfig) { + foreach ($ringConfig as $k => $v) { + $request->getConfig()->set($k, $v); + } + } + return $request; + } + private function createPsr7Response(GuzzleResponse $response) + { + if ($body = $response->getBody()) { + $body = new PsrStream($body); + } + return new Psr7Response($response->getStatusCode(), $response->getHeaders(), $body, $response->getReasonPhrase()); + } + private function prepareErrorData(Exception $e) + { + $error = ['exception' => $e, 'connection_error' => \false, 'response' => null]; + if ($e instanceof ConnectException) { + $error['connection_error'] = \true; + } + if ($e instanceof RequestException && $e->getResponse()) { + $error['response'] = $this->createPsr7Response($e->getResponse()); + } + return $error; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleStream.php new file mode 100644 index 00000000..b4e83460 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleStream.php @@ -0,0 +1,22 @@ +stream = $stream; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/PsrStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/PsrStream.php new file mode 100644 index 00000000..9710d0e0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV5/PsrStream.php @@ -0,0 +1,30 @@ +stream = $stream; + } + public function rewind() + { + $this->stream->seek(0); + } + public function getContents() + { + return $this->stream->getContents(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV6/GuzzleHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV6/GuzzleHandler.php new file mode 100644 index 00000000..57b4ba2b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Handler/GuzzleV6/GuzzleHandler.php @@ -0,0 +1,61 @@ +client = $client ?: new Client(); + } + /** + * @param Psr7Request $request + * @param array $options + * + * @return Promise\Promise + */ + public function __invoke(Psr7Request $request, array $options = []) + { + $request = $request->withHeader('User-Agent', $request->getHeaderLine('User-Agent') . ' ' . \WPMailSMTP\Vendor\GuzzleHttp\default_user_agent()); + return $this->client->sendAsync($request, $this->parseOptions($options))->otherwise(static function ($e) { + $error = ['exception' => $e, 'connection_error' => $e instanceof ConnectException, 'response' => null]; + if ($e instanceof RequestException && $e->getResponse()) { + $error['response'] = $e->getResponse(); + } + return new Promise\RejectedPromise($error); + }); + } + private function parseOptions(array $options) + { + if (isset($options['http_stats_receiver'])) { + $fn = $options['http_stats_receiver']; + unset($options['http_stats_receiver']); + $prev = isset($options['on_stats']) ? $options['on_stats'] : null; + $options['on_stats'] = static function (TransferStats $stats) use($fn, $prev) { + if (\is_callable($prev)) { + $prev($stats); + } + $transferStats = ['total_time' => $stats->getTransferTime()]; + $transferStats += $stats->getHandlerStats(); + $fn($transferStats); + }; + } + return $options; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HandlerList.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HandlerList.php new file mode 100644 index 00000000..9527eedc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HandlerList.php @@ -0,0 +1,391 @@ + [], self::SIGN => [], self::BUILD => [], self::VALIDATE => [], self::INIT => []]; + /** + * @param callable $handler HTTP handler. + */ + public function __construct(?callable $handler = null) + { + $this->handler = $handler; + } + /** + * Dumps a string representation of the list. + * + * @return string + */ + public function __toString() + { + $str = ''; + $i = 0; + foreach (\array_reverse($this->steps) as $k => $step) { + foreach (\array_reverse($step) as $j => $tuple) { + $str .= "{$i}) Step: {$k}, "; + if ($tuple[1]) { + $str .= "Name: {$tuple[1]}, "; + } + $str .= "Function: " . $this->debugCallable($tuple[0]) . "\n"; + $i++; + } + } + if ($this->handler) { + $str .= "{$i}) Handler: " . $this->debugCallable($this->handler) . "\n"; + } + return $str; + } + /** + * Set the HTTP handler that actually returns a response. + * + * @param callable $handler Function that accepts a request and array of + * options and returns a Promise. + */ + public function setHandler(callable $handler) + { + $this->handler = $handler; + } + /** + * Returns true if the builder has a handler. + * + * @return bool + */ + public function hasHandler() + { + return (bool) $this->handler; + } + /** + * Append a middleware to the init step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function appendInit(callable $middleware, $name = null) + { + $this->add(self::INIT, $name, $middleware); + } + /** + * Prepend a middleware to the init step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function prependInit(callable $middleware, $name = null) + { + $this->add(self::INIT, $name, $middleware, \true); + } + /** + * Append a middleware to the validate step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function appendValidate(callable $middleware, $name = null) + { + $this->add(self::VALIDATE, $name, $middleware); + } + /** + * Prepend a middleware to the validate step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function prependValidate(callable $middleware, $name = null) + { + $this->add(self::VALIDATE, $name, $middleware, \true); + } + /** + * Append a middleware to the build step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function appendBuild(callable $middleware, $name = null) + { + $this->add(self::BUILD, $name, $middleware); + } + /** + * Prepend a middleware to the build step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function prependBuild(callable $middleware, $name = null) + { + $this->add(self::BUILD, $name, $middleware, \true); + } + /** + * Append a middleware to the sign step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function appendSign(callable $middleware, $name = null) + { + $this->add(self::SIGN, $name, $middleware); + } + /** + * Prepend a middleware to the sign step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function prependSign(callable $middleware, $name = null) + { + $this->add(self::SIGN, $name, $middleware, \true); + } + /** + * Append a middleware to the attempt step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function appendAttempt(callable $middleware, $name = null) + { + $this->add(self::ATTEMPT, $name, $middleware); + } + /** + * Prepend a middleware to the attempt step. + * + * @param callable $middleware Middleware function to add. + * @param string $name Name of the middleware. + */ + public function prependAttempt(callable $middleware, $name = null) + { + $this->add(self::ATTEMPT, $name, $middleware, \true); + } + /** + * Add a middleware before the given middleware by name. + * + * @param string|callable $findName Add before this + * @param string $withName Optional name to give the middleware + * @param callable $middleware Middleware to add. + */ + public function before($findName, $withName, callable $middleware) + { + $this->splice($findName, $withName, $middleware, \true); + } + /** + * Add a middleware after the given middleware by name. + * + * @param string|callable $findName Add after this + * @param string $withName Optional name to give the middleware + * @param callable $middleware Middleware to add. + */ + public function after($findName, $withName, callable $middleware) + { + $this->splice($findName, $withName, $middleware, \false); + } + /** + * Remove a middleware by name or by instance from the list. + * + * @param string|callable $nameOrInstance Middleware to remove. + */ + public function remove($nameOrInstance) + { + if (\is_callable($nameOrInstance)) { + $this->removeByInstance($nameOrInstance); + } elseif (\is_string($nameOrInstance)) { + $this->removeByName($nameOrInstance); + } + } + /** + * Interpose a function between each middleware (e.g., allowing for a trace + * through the middleware layers). + * + * The interpose function is a function that accepts a "step" argument as a + * string and a "name" argument string. This function must then return a + * function that accepts the next handler in the list. This function must + * then return a function that accepts a CommandInterface and optional + * RequestInterface and returns a promise that is fulfilled with an + * Aws\ResultInterface or rejected with an Aws\Exception\AwsException + * object. + * + * @param callable|null $fn Pass null to remove any previously set function + */ + public function interpose(?callable $fn = null) + { + $this->sorted = null; + $this->interposeFn = $fn; + } + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable + */ + public function resolve() + { + if (!($prev = $this->handler)) { + throw new \LogicException('No handler has been specified'); + } + if ($this->sorted === null) { + $this->sortMiddleware(); + } + foreach ($this->sorted as $fn) { + $prev = $fn($prev); + } + return $prev; + } + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->steps[self::INIT]) + \count($this->steps[self::VALIDATE]) + \count($this->steps[self::BUILD]) + \count($this->steps[self::SIGN]) + \count($this->steps[self::ATTEMPT]); + } + /** + * Splices a function into the middleware list at a specific position. + * + * @param $findName + * @param $withName + * @param callable $middleware + * @param $before + */ + private function splice($findName, $withName, callable $middleware, $before) + { + if (!isset($this->named[$findName])) { + throw new \InvalidArgumentException("{$findName} not found"); + } + $idx = $this->sorted = null; + $step = $this->named[$findName]; + if ($withName) { + $this->named[$withName] = $step; + } + foreach ($this->steps[$step] as $i => $tuple) { + if ($tuple[1] === $findName) { + $idx = $i; + break; + } + } + $replacement = $before ? [$this->steps[$step][$idx], [$middleware, $withName]] : [[$middleware, $withName], $this->steps[$step][$idx]]; + \array_splice($this->steps[$step], $idx, 1, $replacement); + } + /** + * Provides a debug string for a given callable. + * + * @param array|callable $fn Function to write as a string. + * + * @return string + */ + private function debugCallable($fn) + { + if (\is_string($fn)) { + return "callable({$fn})"; + } + if (\is_array($fn)) { + $ele = \is_string($fn[0]) ? $fn[0] : \get_class($fn[0]); + return "callable(['{$ele}', '{$fn[1]}'])"; + } + return 'callable(' . \spl_object_hash($fn) . ')'; + } + /** + * Sort the middleware, and interpose if needed in the sorted list. + */ + private function sortMiddleware() + { + $this->sorted = []; + if (!$this->interposeFn) { + foreach ($this->steps as $step) { + foreach ($step as $fn) { + $this->sorted[] = $fn[0]; + } + } + return; + } + $ifn = $this->interposeFn; + // Interpose the interposeFn into the handler stack. + foreach ($this->steps as $stepName => $step) { + foreach ($step as $fn) { + $this->sorted[] = $ifn($stepName, $fn[1]); + $this->sorted[] = $fn[0]; + } + } + } + private function removeByName($name) + { + if (!isset($this->named[$name])) { + return; + } + $this->sorted = null; + $step = $this->named[$name]; + $this->steps[$step] = \array_values(\array_filter($this->steps[$step], function ($tuple) use($name) { + return $tuple[1] !== $name; + })); + } + private function removeByInstance(callable $fn) + { + foreach ($this->steps as $k => $step) { + foreach ($step as $j => $tuple) { + if ($tuple[0] === $fn) { + $this->sorted = null; + unset($this->named[$this->steps[$k][$j][1]]); + unset($this->steps[$k][$j]); + } + } + } + } + /** + * Add a middleware to a step. + * + * @param string $step Middleware step. + * @param string $name Middleware name. + * @param callable $middleware Middleware function to add. + * @param bool $prepend Prepend instead of append. + */ + private function add($step, $name, callable $middleware, $prepend = \false) + { + $this->sorted = null; + if ($prepend) { + $this->steps[$step][] = [$middleware, $name]; + } else { + \array_unshift($this->steps[$step], [$middleware, $name]); + } + if ($name) { + $this->named[$name] = $step; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasDataTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasDataTrait.php new file mode 100644 index 00000000..210b7921 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasDataTrait.php @@ -0,0 +1,74 @@ +data); + } + /** + * This method returns a reference to the variable to allow for indirect + * array modification (e.g., $foo['bar']['baz'] = 'qux'). + * + * @param $offset + * + * @return mixed|null + */ + #[\ReturnTypeWillChange] + public function &offsetGet($offset) + { + if (isset($this->data[$offset])) { + return $this->data[$offset]; + } + $value = null; + return $value; + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + public function toArray() + { + return $this->data; + } + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->data); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasMonitoringEventsTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasMonitoringEventsTrait.php new file mode 100644 index 00000000..7ef5e314 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HasMonitoringEventsTrait.php @@ -0,0 +1,36 @@ +monitoringEvents; + } + /** + * Prepend a client-side monitoring event to this object's event list + * + * @param array $event + */ + public function prependMonitoringEvent(array $event) + { + \array_unshift($this->monitoringEvents, $event); + } + /** + * Append a client-side monitoring event to this object's event list + * + * @param array $event + */ + public function appendMonitoringEvent(array $event) + { + $this->monitoringEvents[] = $event; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HashInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HashInterface.php new file mode 100644 index 00000000..8a462fdd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/HashInterface.php @@ -0,0 +1,26 @@ +stream = $stream; + $this->hash = $hash; + $this->callback = $onComplete; + } + public function read($length) : string + { + $data = $this->stream->read($length); + $this->hash->update($data); + if ($this->eof()) { + $result = $this->hash->complete(); + if ($this->callback) { + \call_user_func($this->callback, $result); + } + } + return $data; + } + public function seek($offset, $whence = \SEEK_SET) : void + { + // Seeking arbitrarily is not supported. + if ($offset !== 0) { + return; + } + $this->hash->reset(); + $this->stream->seek($offset); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/History.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/History.php new file mode 100644 index 00000000..41ebafb1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/History.php @@ -0,0 +1,134 @@ +maxEntries = $maxEntries; + } + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->entries); + } + #[\ReturnTypeWillChange] + public function getIterator() + { + return new \ArrayIterator(\array_values($this->entries)); + } + /** + * Get the last finished command seen by the history container. + * + * @return CommandInterface + * @throws \LogicException if no commands have been seen. + */ + public function getLastCommand() + { + if (!$this->entries) { + throw new \LogicException('No commands received'); + } + return \end($this->entries)['command']; + } + /** + * Get the last finished request seen by the history container. + * + * @return RequestInterface + * @throws \LogicException if no requests have been seen. + */ + public function getLastRequest() + { + if (!$this->entries) { + throw new \LogicException('No requests received'); + } + return \end($this->entries)['request']; + } + /** + * Get the last received result or exception. + * + * @return ResultInterface|AwsException + * @throws \LogicException if no return values have been received. + */ + public function getLastReturn() + { + if (!$this->entries) { + throw new \LogicException('No entries'); + } + $last = \end($this->entries); + if (isset($last['result'])) { + return $last['result']; + } + if (isset($last['exception'])) { + return $last['exception']; + } + throw new \LogicException('No return value for last entry.'); + } + /** + * Initiate an entry being added to the history. + * + * @param CommandInterface $cmd Command be executed. + * @param RequestInterface $req Request being sent. + * + * @return string Returns the ticket used to finish the entry. + */ + public function start(CommandInterface $cmd, RequestInterface $req) + { + $ticket = \uniqid(); + $this->entries[$ticket] = ['command' => $cmd, 'request' => $req, 'result' => null, 'exception' => null]; + return $ticket; + } + /** + * Finish adding an entry to the history container. + * + * @param string $ticket Ticket returned from the start call. + * @param mixed $result The result (an exception or AwsResult). + */ + public function finish($ticket, $result) + { + if (!isset($this->entries[$ticket])) { + throw new \InvalidArgumentException('Invalid history ticket'); + } + if (isset($this->entries[$ticket]['result']) || isset($this->entries[$ticket]['exception'])) { + throw new \LogicException('History entry is already finished'); + } + if ($result instanceof \Exception) { + $this->entries[$ticket]['exception'] = $result; + } else { + $this->entries[$ticket]['result'] = $result; + } + if (\count($this->entries) >= $this->maxEntries) { + $this->entries = \array_slice($this->entries, -$this->maxEntries, null, \true); + } + } + /** + * Flush the history + */ + public function clear() + { + $this->entries = []; + } + /** + * Converts the history to an array. + * + * @return array + */ + public function toArray() + { + return \array_values($this->entries); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/IdempotencyTokenMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/IdempotencyTokenMiddleware.php new file mode 100644 index 00000000..6f9be9b0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/IdempotencyTokenMiddleware.php @@ -0,0 +1,102 @@ +bytesGenerator = $bytesGenerator ?: $this->findCompatibleRandomSource(); + $this->service = $service; + $this->nextHandler = $nextHandler; + } + public function __invoke(CommandInterface $command, ?RequestInterface $request = null) + { + $handler = $this->nextHandler; + if ($this->bytesGenerator) { + $operation = $this->service->getOperation($command->getName()); + $members = $operation->getInput()->getMembers(); + foreach ($members as $member => $value) { + if ($value['idempotencyToken']) { + $bytes = \call_user_func($this->bytesGenerator, 16); + // populating UUIDv4 only when the parameter is not set + $command[$member] = $command[$member] ?: $this->getUuidV4($bytes); + // only one member could have the trait enabled + break; + } + } + } + return $handler($command, $request); + } + /** + * This function generates a random UUID v4 string, + * which is used as auto filled token value. + * + * @param string $bytes 16 bytes of pseudo-random bytes + * @return string + * More information about UUID v4, see: + * https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29 + * https://tools.ietf.org/html/rfc4122#page-14 + */ + private static function getUuidV4($bytes) + { + // set version to 0100 + $bytes[6] = \chr(\ord($bytes[6]) & 0xf | 0x40); + // set bits 6-7 to 10 + $bytes[8] = \chr(\ord($bytes[8]) & 0x3f | 0x80); + return \vsprintf('%s%s-%s-%s-%s-%s%s%s', \str_split(\bin2hex($bytes), 4)); + } + /** + * This function decides the PHP function used in generating random bytes. + * + * @return callable|null + */ + private function findCompatibleRandomSource() + { + if (\function_exists('random_bytes')) { + return 'random_bytes'; + } + if (\function_exists('openssl_random_pseudo_bytes')) { + return 'openssl_random_pseudo_bytes'; + } + if (\function_exists('mcrypt_create_iv')) { + return 'mcrypt_create_iv'; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/AwsCredentialIdentity.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/AwsCredentialIdentity.php new file mode 100644 index 00000000..8af24681 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Identity/AwsCredentialIdentity.php @@ -0,0 +1,19 @@ +cache = new LruArrayCache(100); + $this->region = $clientRegion; + $this->config = $config; + } + public function __invoke($command) + { + $s3Client = $this->getS3Client(); + $bucket = $command['Bucket']; + if ($identity = $this->cache->get($bucket)) { + if (!$identity->isExpired()) { + return Promise\Create::promiseFor($identity); + } + } + $response = $s3Client->createSession(['Bucket' => $bucket]); + $identity = new Aws\Identity\S3\S3ExpressIdentity($response['Credentials']['AccessKeyId'], $response['Credentials']['SecretAccessKey'], $response['Credentials']['SessionToken'], $response['Credentials']['Expiration']->getTimestamp()); + $this->cache->set($bucket, $identity); + return Promise\Create::promiseFor($identity); + } + private function getS3Client() + { + if (\is_null($this->s3Client)) { + $this->s3Client = $this->config['client'] ?? new Aws\S3\S3Client(['region' => $this->region, 'disable_express_session_auth' => \true]); + } + return $this->s3Client; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/InputValidationMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/InputValidationMiddleware.php new file mode 100644 index 00000000..d5575117 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/InputValidationMiddleware.php @@ -0,0 +1,62 @@ +service = $service; + $this->nextHandler = $nextHandler; + $this->mandatoryAttributeList = $mandatoryAttributeList; + } + public function __invoke(CommandInterface $cmd) + { + $nextHandler = $this->nextHandler; + $op = $this->service->getOperation($cmd->getName())->toArray(); + if (!empty($op['input']['shape'])) { + $service = $this->service->toArray(); + if (!empty($input = $service['shapes'][$op['input']['shape']])) { + if (!empty($input['required'])) { + foreach ($input['required'] as $key => $member) { + if (\in_array($member, $this->mandatoryAttributeList)) { + $argument = \is_string($cmd[$member]) ? \trim($cmd[$member]) : $cmd[$member]; + if ($argument === '' || $argument === null) { + $commandName = $cmd->getName(); + throw new \InvalidArgumentException("The {$commandName} operation requires non-empty parameter: {$member}"); + } + } + } + } + } + } + return $nextHandler($cmd); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/JsonCompiler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/JsonCompiler.php new file mode 100644 index 00000000..e4136dea --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/JsonCompiler.php @@ -0,0 +1,25 @@ +maxItems = $maxItems; + } + public function get($key) + { + if (!isset($this->items[$key])) { + return null; + } + $entry = $this->items[$key]; + // Ensure the item is not expired. + if (!$entry[1] || \time() < $entry[1]) { + // LRU: remove the item and push it to the end of the array. + unset($this->items[$key]); + $this->items[$key] = $entry; + return $entry[0]; + } + unset($this->items[$key]); + return null; + } + public function set($key, $value, $ttl = 0) + { + // Only call time() if the TTL is not 0/false/null + $ttl = $ttl ? \time() + $ttl : 0; + $this->items[$key] = [$value, $ttl]; + // Determine if there are more items in the cache than allowed. + $diff = \count($this->items) - $this->maxItems; + // Clear out least recently used items. + if ($diff > 0) { + // Reset to the beginning of the array and begin unsetting. + \reset($this->items); + for ($i = 0; $i < $diff; $i++) { + unset($this->items[\key($this->items)]); + \next($this->items); + } + } + } + public function remove($key) + { + unset($this->items[$key]); + } + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->items); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MetricsBuilder.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MetricsBuilder.php new file mode 100644 index 00000000..ccd86354 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MetricsBuilder.php @@ -0,0 +1,245 @@ +metrics = []; + // The first metrics does not include the separator + // therefore it is reduced by default. + $this->metricsSize = -\strlen(self::$METRIC_SEPARATOR); + } + /** + * Build the metrics string value. + * + * @return string + */ + public function build() : string + { + if (empty($this->metrics)) { + return ""; + } + return $this->encode(); + } + /** + * Encodes the metrics by separating each metric + * with a comma. Example: for the metrics[A,B,C] then + * the output would be "A,B,C". + * + * @return string + */ + private function encode() : string + { + return \implode(self::$METRIC_SEPARATOR, \array_keys($this->metrics)); + } + /** + * Appends a metric to the internal metrics holder after validating it. + * Increases the current metrics size by the length of the new metric + * plus the length of the encoding separator. + * Example: $currentSize = $currentSize + len($newMetric) + len($separator) + * + * @param string $metric The metric to append. + * + * @return void + */ + public function append(string $metric) : void + { + if (!$this->canMetricBeAppended($metric)) { + return; + } + $this->metrics[$metric] = \true; + $this->metricsSize += \strlen($metric) + \strlen(self::$METRIC_SEPARATOR); + } + /** + * Receives a feature group and a value to identify which one is the metric. + * For example, a group could be `signature` and a value could be `v4a`, + * then the metric will be `SIGV4A_SIGNING`. + * + * @param string $featureGroup the feature group such as `signature`. + * @param mixed $value the value for identifying the metric. + * + * @return void + */ + public function identifyMetricByValueAndAppend(string $featureGroup, $value) : void + { + if (empty($value)) { + return; + } + static $appendMetricFns = ['signature' => 'appendSignatureMetric', 'request_compression' => 'appendRequestCompressionMetric', 'request_checksum' => 'appendRequestChecksumMetric', 'credentials' => 'appendCredentialsMetric']; + $fn = $appendMetricFns[$featureGroup]; + $this->{$fn}($value); + } + /** + * Appends the signature metric based on the signature value. + * + * @param string $signature + * + * @return void + */ + private function appendSignatureMetric(string $signature) : void + { + if ($signature === 'v4-s3express') { + $this->append(MetricsBuilder::S3_EXPRESS_BUCKET); + } elseif ($signature === 'v4a') { + $this->append(MetricsBuilder::SIGV4A_SIGNING); + } + } + /** + * Appends the request compression metric based on the format resolved. + * + * @param string $format + * + * @return void + */ + private function appendRequestCompressionMetric(string $format) : void + { + if ($format === 'gzip') { + $this->append(MetricsBuilder::GZIP_REQUEST_COMPRESSION); + } + } + /** + * Appends the request checksum metric based on the algorithm. + * + * @param string $algorithm + * + * @return void + */ + private function appendRequestChecksumMetric(string $algorithm) : void + { + if ($algorithm === 'crc32') { + $this->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_CRC32); + } elseif ($algorithm === 'crc32c') { + $this->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_CRC32C); + } elseif ($algorithm === 'crc64') { + $this->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_CRC64); + } elseif ($algorithm === 'sha1') { + $this->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_SHA1); + } elseif ($algorithm === 'sha256') { + $this->append(MetricsBuilder::FLEXIBLE_CHECKSUMS_REQ_SHA256); + } + } + /** + * Appends the credentials metric based on the type of credentials + * resolved. + * + * @param CredentialsInterface $credentials + * + * @return void + */ + private function appendCredentialsMetric(CredentialsInterface $credentials) : void + { + $source = $credentials->toArray()['source'] ?? null; + if (empty($source)) { + return; + } + static $credentialsMetricMapping = [CredentialSources::STATIC => MetricsBuilder::CREDENTIALS_CODE, CredentialSources::ENVIRONMENT => MetricsBuilder::CREDENTIALS_ENV_VARS, CredentialSources::ENVIRONMENT_STS_WEB_ID_TOKEN => MetricsBuilder::CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN, CredentialSources::STS_ASSUME_ROLE => MetricsBuilder::CREDENTIALS_STS_ASSUME_ROLE, CredentialSources::STS_WEB_ID_TOKEN => MetricsBuilder::CREDENTIALS_STS_ASSUME_ROLE_WEB_ID, CredentialSources::PROFILE => MetricsBuilder::CREDENTIALS_PROFILE, CredentialSources::IMDS => MetricsBuilder::CREDENTIALS_IMDS, CredentialSources::ECS => MetricsBuilder::CREDENTIALS_HTTP, CredentialSources::PROFILE_STS_WEB_ID_TOKEN => MetricsBuilder::CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN, CredentialSources::PROFILE_PROCESS => MetricsBuilder::CREDENTIALS_PROFILE_PROCESS, CredentialSources::PROFILE_SSO => MetricsBuilder::CREDENTIALS_PROFILE_SSO, CredentialSources::PROFILE_SSO_LEGACY => MetricsBuilder::CREDENTIALS_PROFILE_SSO_LEGACY]; + if (isset($credentialsMetricMapping[$source])) { + $this->append($credentialsMetricMapping[$source]); + } + } + /** + * Validates if a metric can be appended by ensuring the total size, + * including the new metric and separator, does not exceed the limit. + * Also checks that the metric does not already exist. + * Example: Appendable if: + * $currentSize + len($newMetric) + len($separator) <= MAX_SIZE + * and: + * $newMetric not in $existingMetrics + * + * @param string $newMetric The metric to validate. + * + * @return bool True if the metric can be appended, false otherwise. + */ + private function canMetricBeAppended(string $newMetric) : bool + { + if ($newMetric === "") { + return \false; + } + if ($this->metricsSize + (\strlen($newMetric) + \strlen(self::$METRIC_SEPARATOR)) > self::$MAX_METRICS_SIZE) { + return \false; + } + if (isset($this->metrics[$newMetric])) { + return \false; + } + return \true; + } + /** + * Returns the metrics builder from the property @context of a command. + * + * @param Command $command + * + * @return MetricsBuilder + */ + public static function fromCommand(CommandInterface $command) : MetricsBuilder + { + return $command->getMetricsBuilder(); + } + /** + * Helper method for appending a metrics capture middleware into a + * handler stack given. The middleware appended here is on top of the + * build step. + * + * @param HandlerList $handlerList + * @param $metric + * + * @return void + */ + public static function appendMetricsCaptureMiddleware(HandlerList $handlerList, $metric) : void + { + $handlerList->appendBuild(Middleware::tap(function (CommandInterface $command) use($metric) { + self::fromCommand($command)->append($metric); + }), 'metrics-capture-' . $metric); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Middleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Middleware.php new file mode 100644 index 00000000..744c5e94 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Middleware.php @@ -0,0 +1,327 @@ +getOperation($command->getName()); + $source = $command[$sourceParameter]; + if ($source !== null && $operation->getInput()->hasMember($bodyParameter)) { + $command[$bodyParameter] = new LazyOpenStream($source, 'r'); + unset($command[$sourceParameter]); + } + return $handler($command, $request); + }; + }; + } + /** + * Adds a middleware that uses client-side validation. + * + * @param Service $api API being accessed. + * + * @return callable + */ + public static function validation(Service $api, ?Validator $validator = null) + { + $validator = $validator ?: new Validator(); + return function (callable $handler) use($api, $validator) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($api, $validator, $handler) { + if ($api->isModifiedModel()) { + $api = new Service($api->getDefinition(), $api->getProvider()); + } + $operation = $api->getOperation($command->getName()); + $validator->validate($command->getName(), $operation->getInput(), $command->toArray()); + return $handler($command, $request); + }; + }; + } + /** + * Builds an HTTP request for a command. + * + * @param callable $serializer Function used to serialize a request for a + * command. + * @param EndpointProviderV2 | null $endpointProvider + * @param array $providerArgs + * @return callable + */ + public static function requestBuilder($serializer) + { + return function (callable $handler) use($serializer) { + return function (CommandInterface $command, $endpoint = null) use($serializer, $handler) { + return $handler($command, $serializer($command, $endpoint)); + }; + }; + } + /** + * Creates a middleware that signs requests for a command. + * + * @param callable $credProvider Credentials provider function that + * returns a promise that is resolved + * with a CredentialsInterface object. + * @param callable $signatureFunction Function that accepts a Command + * object and returns a + * SignatureInterface. + * + * @return callable + */ + public static function signer(callable $credProvider, callable $signatureFunction, $tokenProvider = null, $config = []) + { + return function (callable $handler) use($signatureFunction, $credProvider, $tokenProvider, $config) { + return function (CommandInterface $command, RequestInterface $request) use($handler, $signatureFunction, $credProvider, $tokenProvider, $config) { + $signer = $signatureFunction($command); + if ($signer instanceof TokenAuthorization) { + return $tokenProvider()->then(function (TokenInterface $token) use($handler, $command, $signer, $request) { + return $handler($command, $signer->authorizeRequest($request, $token)); + }); + } + if ($signer instanceof S3ExpressSignature) { + $credentialPromise = $config['s3_express_identity_provider']($command); + } else { + $credentialPromise = $credProvider(); + } + return $credentialPromise->then(function (CredentialsInterface $creds) use($handler, $command, $signer, $request) { + // Capture credentials metric + $command->getMetricsBuilder()->identifyMetricByValueAndAppend('credentials', $creds); + return $handler($command, $signer->signRequest($request, $creds)); + }); + }; + }; + } + /** + * Creates a middleware that invokes a callback at a given step. + * + * The tap callback accepts a CommandInterface and RequestInterface as + * arguments but is not expected to return a new value or proxy to + * downstream middleware. It's simply a way to "tap" into the handler chain + * to debug or get an intermediate value. + * + * @param callable $fn Tap function + * + * @return callable + */ + public static function tap(callable $fn) + { + return function (callable $handler) use($fn) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, $fn) { + $fn($command, $request); + return $handler($command, $request); + }; + }; + } + /** + * Middleware wrapper function that retries requests based on the boolean + * result of invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [result], and [exception] and + * returns true if the command is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * @param bool $stats Whether to collect statistics on retries and the + * associated delay. + * + * @return callable + */ + public static function retry(?callable $decider = null, ?callable $delay = null, $stats = \false) + { + $decider = $decider ?: RetryMiddleware::createDefaultDecider(); + $delay = $delay ?: [RetryMiddleware::class, 'exponentialDelay']; + return function (callable $handler) use($decider, $delay, $stats) { + return new RetryMiddleware($decider, $delay, $handler, $stats); + }; + } + /** + * Middleware wrapper function that adds an invocation id header to + * requests, which is only applied after the build step. + * + * This is a uniquely generated UUID to identify initial and subsequent + * retries as part of a complete request lifecycle. + * + * @return callable + */ + public static function invocationId() + { + return function (callable $handler) { + return function (CommandInterface $command, RequestInterface $request) use($handler) { + return $handler($command, $request->withHeader('aws-sdk-invocation-id', \md5(\uniqid(\gethostname(), \true)))); + }; + }; + } + /** + * Middleware wrapper function that adds a Content-Type header to requests. + * This is only done when the Content-Type has not already been set, and the + * request body's URI is available. It then checks the file extension of the + * URI to determine the mime-type. + * + * @param array $operations Operations that Content-Type should be added to. + * + * @return callable + */ + public static function contentType(array $operations) + { + return function (callable $handler) use($operations) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, $operations) { + if (!$request->hasHeader('Content-Type') && \in_array($command->getName(), $operations, \true) && ($uri = $request->getBody()->getMetadata('uri'))) { + $request = $request->withHeader('Content-Type', Psr7\MimeType::fromFilename($uri) ?: 'application/octet-stream'); + } + return $handler($command, $request); + }; + }; + } + /** + * Middleware wrapper function that adds a trace id header to requests + * from clients instantiated in supported Lambda runtime environments. + * + * The purpose for this header is to track and stop Lambda functions + * from being recursively invoked due to misconfigured resources. + * + * @return callable + */ + public static function recursionDetection() + { + return function (callable $handler) { + return function (CommandInterface $command, RequestInterface $request) use($handler) { + $isLambda = \getenv('AWS_LAMBDA_FUNCTION_NAME'); + $traceId = \str_replace('\\e', '\\x1b', \getenv('_X_AMZN_TRACE_ID')); + if ($isLambda && $traceId) { + if (!$request->hasHeader('X-Amzn-Trace-Id')) { + $ignoreChars = ['=', ';', ':', '+', '&', '[', ']', '{', '}', '"', '\'', ',']; + $traceIdEncoded = \rawurlencode(\stripcslashes($traceId)); + foreach ($ignoreChars as $char) { + $encodedChar = \rawurlencode($char); + $traceIdEncoded = \str_replace($encodedChar, $char, $traceIdEncoded); + } + return $handler($command, $request->withHeader('X-Amzn-Trace-Id', $traceIdEncoded)); + } + } + return $handler($command, $request); + }; + }; + } + /** + * Tracks command and request history using a history container. + * + * This is useful for testing. + * + * @param History $history History container to store entries. + * + * @return callable + */ + public static function history(History $history) + { + return function (callable $handler) use($history) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, $history) { + $ticket = $history->start($command, $request); + return $handler($command, $request)->then(function ($result) use($history, $ticket) { + $history->finish($ticket, $result); + return $result; + }, function ($reason) use($history, $ticket) { + $history->finish($ticket, $reason); + return Promise\Create::rejectionFor($reason); + }); + }; + }; + } + /** + * Creates a middleware that applies a map function to requests as they + * pass through the middleware. + * + * @param callable $f Map function that accepts a RequestInterface and + * returns a RequestInterface. + * + * @return callable + */ + public static function mapRequest(callable $f) + { + return function (callable $handler) use($f) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, $f) { + return $handler($command, $f($request)); + }; + }; + } + /** + * Creates a middleware that applies a map function to commands as they + * pass through the middleware. + * + * @param callable $f Map function that accepts a command and returns a + * command. + * + * @return callable + */ + public static function mapCommand(callable $f) + { + return function (callable $handler) use($f) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, $f) { + return $handler($f($command), $request); + }; + }; + } + /** + * Creates a middleware that applies a map function to results. + * + * @param callable $f Map function that accepts an Aws\ResultInterface and + * returns an Aws\ResultInterface. + * + * @return callable + */ + public static function mapResult(callable $f) + { + return function (callable $handler) use($f) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, $f) { + return $handler($command, $request)->then($f); + }; + }; + } + public static function timer() + { + return function (callable $handler) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler) { + $start = \microtime(\true); + return $handler($command, $request)->then(function (ResultInterface $res) use($start) { + if (!isset($res['@metadata'])) { + $res['@metadata'] = []; + } + if (!isset($res['@metadata']['transferStats'])) { + $res['@metadata']['transferStats'] = []; + } + $res['@metadata']['transferStats']['total_time'] = \microtime(\true) - $start; + return $res; + }, function ($err) use($start) { + if ($err instanceof AwsException) { + $err->setTransferInfo(['total_time' => \microtime(\true) - $start] + $err->getTransferInfo()); + } + return Promise\Create::rejectionFor($err); + }); + }; + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MockHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MockHandler.php new file mode 100644 index 00000000..ffd77b5e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MockHandler.php @@ -0,0 +1,123 @@ +queue = []; + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + if ($resultOrQueue) { + \call_user_func_array([$this, 'append'], \array_values($resultOrQueue)); + } + } + /** + * Adds one or more variadic ResultInterface or AwsException objects to the + * queue. + */ + public function append() + { + foreach (\func_get_args() as $value) { + if ($value instanceof ResultInterface || $value instanceof Exception || \is_callable($value)) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected an Aws\\ResultInterface or Exception.'); + } + } + } + /** + * Adds one or more \Exception or \Throwable to the queue + */ + public function appendException() + { + foreach (\func_get_args() as $value) { + if ($value instanceof \Exception || $value instanceof \Throwable) { + $this->queue[] = $value; + } else { + throw new \InvalidArgumentException('Expected an \\Exception or \\Throwable.'); + } + } + } + public function __invoke(CommandInterface $command, RequestInterface $request) + { + if (!$this->queue) { + $last = $this->lastCommand ? ' The last command sent was ' . $this->lastCommand->getName() . '.' : ''; + throw new \RuntimeException('Mock queue is empty. Trying to send a ' . $command->getName() . ' command failed.' . $last); + } + $this->lastCommand = $command; + $this->lastRequest = $request; + $result = \array_shift($this->queue); + if (\is_callable($result)) { + $result = $result($command, $request); + } + if ($result instanceof \Exception) { + $result = new RejectedPromise($result); + } else { + // Add an effective URI and statusCode if not present. + $meta = $result['@metadata']; + if (!isset($meta['effectiveUri'])) { + $meta['effectiveUri'] = (string) $request->getUri(); + } + if (!isset($meta['statusCode'])) { + $meta['statusCode'] = 200; + } + $result['@metadata'] = $meta; + $result = Promise\Create::promiseFor($result); + } + $result->then($this->onFulfilled, $this->onRejected); + return $result; + } + /** + * Get the last received request. + * + * @return RequestInterface + */ + public function getLastRequest() + { + return $this->lastRequest; + } + /** + * Get the last received command. + * + * @return CommandInterface + */ + public function getLastCommand() + { + return $this->lastCommand; + } + /** + * Returns the number of remaining items in the queue. + * + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->queue); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MonitoringEventsInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MonitoringEventsInterface.php new file mode 100644 index 00000000..1492d9af --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/MonitoringEventsInterface.php @@ -0,0 +1,29 @@ + \true, 'region' => \true]); + $args['region']['required'] = \false; + unset($args['region']['fn']); + unset($args['region']['default']); + return $args + ['client_factory' => ['type' => 'config', 'valid' => ['callable'], 'doc' => 'A callable that takes an array of client' . ' configuration arguments and returns a regionalized' . ' client.', 'required' => \true, 'internal' => \true, 'default' => function (array $args) { + $namespace = manifest($args['service'])['namespace']; + $klass = "WPMailSMTP\\Vendor\\Aws\\{$namespace}\\{$namespace}Client"; + $region = isset($args['region']) ? $args['region'] : null; + return function (array $args) use($klass, $region) { + if ($region && empty($args['region'])) { + $args['region'] = $region; + } + return new $klass($args); + }; + }], 'partition' => ['type' => 'config', 'valid' => ['string', PartitionInterface::class], 'doc' => 'AWS partition to connect to. Valid partitions' . ' include "aws," "aws-cn," and "aws-us-gov." Used to' . ' restrict the scope of the mapRegions method.', 'default' => function (array $args) { + $region = isset($args['region']) ? $args['region'] : ''; + return PartitionEndpointProvider::defaultProvider()->getPartition($region, $args['service']); + }, 'fn' => function ($value, array &$args) { + if (\is_string($value)) { + $value = PartitionEndpointProvider::defaultProvider()->getPartitionByName($value); + } + if (!$value instanceof PartitionInterface) { + throw new \InvalidArgumentException('No valid partition' . ' was provided. Provide a concrete partition or' . ' the name of a partition (e.g., "aws," "aws-cn,"' . ' or "aws-us-gov").'); + } + $ruleset = EndpointDefinitionProvider::getEndpointRuleset($args['service'], isset($args['version']) ? $args['version'] : 'latest'); + $partitions = EndpointDefinitionProvider::getPartitions(); + $args['endpoint_provider'] = new EndpointProviderV2($ruleset, $partitions); + }]]; + } + /** + * The multi-region client constructor accepts the following options: + * + * - client_factory: (callable) An optional callable that takes an array of + * client configuration arguments and returns a regionalized client. + * - partition: (Aws\Endpoint\Partition|string) AWS partition to connect to. + * Valid partitions include "aws," "aws-cn," and "aws-us-gov." Used to + * restrict the scope of the mapRegions method. + * - region: (string) Region to connect to when no override is provided. + * Used to create the default client factory and determine the appropriate + * AWS partition when present. + * + * @param array $args Client configuration arguments. + */ + public function __construct(array $args = []) + { + if (!isset($args['service'])) { + $args['service'] = $this->parseClass(); + } + $this->handlerList = new HandlerList(function (CommandInterface $command) { + list($region, $args) = $this->getRegionFromArgs($command->toArray()); + $command = $this->getClientFromPool($region)->getCommand($command->getName(), $args); + if ($this->isUseCustomHandler()) { + $command->getHandlerList()->setHandler($this->customHandler); + } + return $this->executeAsync($command); + }); + $argDefinitions = static::getArguments(); + $resolver = new ClientResolver($argDefinitions); + $args = $resolver->resolve($args, $this->handlerList); + $this->config = $args['config']; + $this->factory = $args['client_factory']; + $this->partition = $args['partition']; + $this->args = \array_diff_key($args, $args['config']); + } + /** + * Get the region to which the client is configured to send requests by + * default. + * + * @return string + */ + public function getRegion() + { + return $this->getClientFromPool()->getRegion(); + } + /** + * Create a command for an operation name. + * + * Special keys may be set on the command to control how it behaves, + * including: + * + * - @http: Associative array of transfer specific options to apply to the + * request that is serialized for this command. Available keys include + * "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and + * "headers". + * - @region: The region to which the command should be sent. + * + * @param string $name Name of the operation to use in the command + * @param array $args Arguments to pass to the command + * + * @return CommandInterface + * @throws \InvalidArgumentException if no command can be found by name + */ + public function getCommand($name, array $args = []) + { + return new Command($name, $args, clone $this->getHandlerList()); + } + public function getConfig($option = null) + { + if (null === $option) { + return $this->config; + } + if (isset($this->config[$option])) { + return $this->config[$option]; + } + return $this->getClientFromPool()->getConfig($option); + } + public function getCredentials() + { + return $this->getClientFromPool()->getCredentials(); + } + public function getHandlerList() + { + return $this->handlerList; + } + public function getApi() + { + return $this->getClientFromPool()->getApi(); + } + public function getEndpoint() + { + return $this->getClientFromPool()->getEndpoint(); + } + public function useCustomHandler(callable $handler) + { + $this->customHandler = $handler; + } + private function isUseCustomHandler() + { + return isset($this->customHandler); + } + /** + * @param string $region Omit this argument or pass in an empty string to + * allow the configured client factory to apply the + * region. + * + * @return AwsClientInterface + */ + protected function getClientFromPool($region = '') + { + if (empty($this->clientPool[$region])) { + $factory = $this->factory; + $this->clientPool[$region] = $factory(\array_replace($this->args, \array_filter(['region' => $region]))); + } + return $this->clientPool[$region]; + } + /** + * Parse the class name and return the "service" name of the client. + * + * @return string + */ + private function parseClass() + { + $klass = \get_class($this); + if ($klass === __CLASS__) { + return ''; + } + return \strtolower(\substr($klass, \strrpos($klass, '\\') + 1, -17)); + } + private function getRegionFromArgs(array $args) + { + $region = isset($args['@region']) ? $args['@region'] : $this->getRegion(); + unset($args['@region']); + return [$region, $args]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php new file mode 100644 index 00000000..43d0b5cc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php @@ -0,0 +1,252 @@ + null, 'state' => null, 'concurrency' => self::DEFAULT_CONCURRENCY, 'prepare_data_source' => null, 'before_initiate' => null, 'before_upload' => null, 'before_complete' => null, 'exception_class' => MultipartUploadException::class]; + /** @var Client Client used for the upload. */ + protected $client; + /** @var array Configuration used to perform the upload. */ + protected $config; + /** @var array Service-specific information about the upload workflow. */ + protected $info; + /** @var PromiseInterface Promise that represents the multipart upload. */ + protected $promise; + /** @var UploadState State used to manage the upload. */ + protected $state; + /** + * @param Client $client + * @param array $config + */ + public function __construct(Client $client, array $config = []) + { + $this->client = $client; + $this->info = $this->loadUploadWorkflowInfo(); + $this->config = $config + self::$defaultConfig; + $this->state = $this->determineState(); + } + /** + * Returns the current state of the upload + * + * @return UploadState + */ + public function getState() + { + return $this->state; + } + /** + * Upload the source using multipart upload operations. + * + * @return Result The result of the CompleteMultipartUpload operation. + * @throws \LogicException if the upload is already complete or aborted. + * @throws MultipartUploadException if an upload operation fails. + */ + public function upload() + { + return $this->promise()->wait(); + } + /** + * Upload the source asynchronously using multipart upload operations. + * + * @return PromiseInterface + */ + public function promise() : PromiseInterface + { + if ($this->promise) { + return $this->promise; + } + return $this->promise = Promise\Coroutine::of(function () { + // Initiate the upload. + if ($this->state->isCompleted()) { + throw new \LogicException('This multipart upload has already ' . 'been completed or aborted.'); + } + if (!$this->state->isInitiated()) { + // Execute the prepare callback. + if (\is_callable($this->config["prepare_data_source"])) { + $this->config["prepare_data_source"](); + } + $result = (yield $this->execCommand('initiate', $this->getInitiateParams())); + $this->state->setUploadId($this->info['id']['upload_id'], $result[$this->info['id']['upload_id']]); + $this->state->setStatus(UploadState::INITIATED); + } + // Create a command pool from a generator that yields UploadPart + // commands for each upload part. + $resultHandler = $this->getResultHandler($errors); + $commands = new CommandPool($this->client, $this->getUploadCommands($resultHandler), ['concurrency' => $this->config['concurrency'], 'before' => $this->config['before_upload']]); + // Execute the pool of commands concurrently, and process errors. + (yield $commands->promise()); + if ($errors) { + throw new $this->config['exception_class']($this->state, $errors); + } + // Complete the multipart upload. + (yield $this->execCommand('complete', $this->getCompleteParams())); + $this->state->setStatus(UploadState::COMPLETED); + })->otherwise($this->buildFailureCatch()); + } + private function transformException($e) + { + // Throw errors from the operations as a specific Multipart error. + if ($e instanceof AwsException) { + $e = new $this->config['exception_class']($this->state, $e); + } + throw $e; + } + private function buildFailureCatch() + { + if (\interface_exists("Throwable")) { + return function (\Throwable $e) { + return $this->transformException($e); + }; + } else { + return function (\Exception $e) { + return $this->transformException($e); + }; + } + } + protected function getConfig() + { + return $this->config; + } + /** + * Provides service-specific information about the multipart upload + * workflow. + * + * This array of data should include the keys: 'command', 'id', and 'part_num'. + * + * @return array + */ + protected abstract function loadUploadWorkflowInfo(); + /** + * Determines the part size to use for upload parts. + * + * Examines the provided partSize value and the source to determine the + * best possible part size. + * + * @throws \InvalidArgumentException if the part size is invalid. + * + * @return int + */ + protected abstract function determinePartSize(); + /** + * Uses information from the Command and Result to determine which part was + * uploaded and mark it as uploaded in the upload's state. + * + * @param CommandInterface $command + * @param ResultInterface $result + */ + protected abstract function handleResult(CommandInterface $command, ResultInterface $result); + /** + * Gets the service-specific parameters used to initiate the upload. + * + * @return array + */ + protected abstract function getInitiateParams(); + /** + * Gets the service-specific parameters used to complete the upload. + * + * @return array + */ + protected abstract function getCompleteParams(); + /** + * Based on the config and service-specific workflow info, creates a + * `Promise` for an `UploadState` object. + */ + private function determineState() : UploadState + { + // If the state was provided via config, then just use it. + if ($this->config['state'] instanceof UploadState) { + return $this->config['state']; + } + // Otherwise, construct a new state from the provided identifiers. + $required = $this->info['id']; + $id = [$required['upload_id'] => null]; + unset($required['upload_id']); + foreach ($required as $key => $param) { + if (!$this->config[$key]) { + throw new IAE('You must provide a value for "' . $key . '" in ' . 'your config for the MultipartUploader for ' . $this->client->getApi()->getServiceFullName() . '.'); + } + $id[$param] = $this->config[$key]; + } + $state = new UploadState($id); + $state->setPartSize($this->determinePartSize()); + return $state; + } + /** + * Executes a MUP command with all of the parameters for the operation. + * + * @param string $operation Name of the operation. + * @param array $params Service-specific params for the operation. + * + * @return PromiseInterface + */ + protected function execCommand($operation, array $params) + { + // Create the command. + $command = $this->client->getCommand($this->info['command'][$operation], $params + $this->state->getId()); + // Execute the before callback. + if (\is_callable($this->config["before_{$operation}"])) { + $this->config["before_{$operation}"]($command); + } + // Execute the command asynchronously and return the promise. + return $this->client->executeAsync($command); + } + /** + * Returns a middleware for processing responses of part upload operations. + * + * - Adds an onFulfilled callback that calls the service-specific + * handleResult method on the Result of the operation. + * - Adds an onRejected callback that adds the error to an array of errors. + * - Has a passedByRef $errors arg that the exceptions get added to. The + * caller should use that &$errors array to do error handling. + * + * @param array $errors Errors from upload operations are added to this. + * + * @return callable + */ + protected function getResultHandler(&$errors = []) + { + return function (callable $handler) use(&$errors) { + return function (CommandInterface $command, ?RequestInterface $request = null) use($handler, &$errors) { + return $handler($command, $request)->then(function (ResultInterface $result) use($command) { + $this->handleResult($command, $result); + return $result; + }, function (AwsException $e) use(&$errors) { + $errors[$e->getCommand()[$this->info['part_num']]] = $e; + return new Result(); + }); + }; + }; + } + /** + * Creates a generator that yields part data for the upload's source. + * + * Yields associative arrays of parameters that are ultimately merged in + * with others to form the complete parameters of a command. This can + * include the Body parameter, which is a limited stream (i.e., a Stream + * object, decorated with a LimitStream). + * + * @param callable $resultHandler + * + * @return \Generator + */ + protected abstract function getUploadCommands(callable $resultHandler); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploader.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploader.php new file mode 100644 index 00000000..29d8db4b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/AbstractUploader.php @@ -0,0 +1,118 @@ +source = $this->determineSource($source); + parent::__construct($client, $config); + } + /** + * Create a stream for a part that starts at the current position and + * has a length of the upload part size (or less with the final part). + * + * @param Stream $stream + * + * @return Psr7\LimitStream + */ + protected function limitPartStream(Stream $stream) + { + // Limit what is read from the stream to the part size. + return new Psr7\LimitStream($stream, $this->state->getPartSize(), $this->source->tell()); + } + protected function getUploadCommands(callable $resultHandler) + { + // Determine if the source can be seeked. + $seekable = $this->source->isSeekable() && $this->source->getMetadata('wrapper_type') === 'plainfile'; + for ($partNumber = 1; $this->isEof($seekable); $partNumber++) { + // If we haven't already uploaded this part, yield a new part. + if (!$this->state->hasPartBeenUploaded($partNumber)) { + $partStartPos = $this->source->tell(); + if (!($data = $this->createPart($seekable, $partNumber))) { + break; + } + $command = $this->client->getCommand($this->info['command']['upload'], $data + $this->state->getId()); + $command->getHandlerList()->appendSign($resultHandler, 'mup'); + $numberOfParts = $this->getNumberOfParts($this->state->getPartSize()); + if (isset($numberOfParts) && $partNumber > $numberOfParts) { + throw new $this->config['exception_class']($this->state, new AwsException("Maximum part number for this job exceeded, file has likely been corrupted." . " Please restart this upload.", $command)); + } + (yield $command); + if ($this->source->tell() > $partStartPos) { + continue; + } + } + // Advance the source's offset if not already advanced. + if ($seekable) { + $this->source->seek(\min($this->source->tell() + $this->state->getPartSize(), $this->source->getSize())); + } else { + $this->source->read($this->state->getPartSize()); + } + } + } + /** + * Generates the parameters for an upload part by analyzing a range of the + * source starting from the current offset up to the part size. + * + * @param bool $seekable + * @param int $number + * + * @return array|null + */ + protected abstract function createPart($seekable, $number); + /** + * Checks if the source is at EOF. + * + * @param bool $seekable + * + * @return bool + */ + private function isEof($seekable) + { + return $seekable ? $this->source->tell() < $this->source->getSize() : !$this->source->eof(); + } + /** + * Turns the provided source into a stream and stores it. + * + * If a string is provided, it is assumed to be a filename, otherwise, it + * passes the value directly to `Psr7\Utils::streamFor()`. + * + * @param mixed $source + * + * @return Stream + */ + private function determineSource($source) + { + // Use the contents of a file as the data source. + if (\is_string($source)) { + $source = Psr7\Utils::tryFopen($source, 'r'); + } + // Create a source stream. + $stream = Psr7\Utils::streamFor($source); + if (!$stream->isReadable()) { + throw new IAE('Source stream must be readable.'); + } + return $stream; + } + protected function getNumberOfParts($partSize) + { + if ($sourceSize = $this->source->getSize()) { + return \ceil($sourceSize / $partSize); + } + return null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/UploadState.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/UploadState.php new file mode 100644 index 00000000..24afbfec --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Multipart/UploadState.php @@ -0,0 +1,130 @@ +id = $id; + } + /** + * Get the upload's ID, which is a tuple of parameters that can uniquely + * identify the upload. + * + * @return array + */ + public function getId() + { + return $this->id; + } + /** + * Set's the "upload_id", or 3rd part of the upload's ID. This typically + * only needs to be done after initiating an upload. + * + * @param string $key The param key of the upload_id. + * @param string $value The param value of the upload_id. + */ + public function setUploadId($key, $value) + { + $this->id[$key] = $value; + } + /** + * Get the part size. + * + * @return int + */ + public function getPartSize() + { + return $this->partSize; + } + /** + * Set the part size. + * + * @param $partSize int Size of upload parts. + */ + public function setPartSize($partSize) + { + $this->partSize = $partSize; + } + /** + * Marks a part as being uploaded. + * + * @param int $partNumber The part number. + * @param array $partData Data from the upload operation that needs to be + * recalled during the complete operation. + */ + public function markPartAsUploaded($partNumber, array $partData = []) + { + $this->uploadedParts[$partNumber] = $partData; + } + /** + * Returns whether a part has been uploaded. + * + * @param int $partNumber The part number. + * + * @return bool + */ + public function hasPartBeenUploaded($partNumber) + { + return isset($this->uploadedParts[$partNumber]); + } + /** + * Returns a sorted list of all the uploaded parts. + * + * @return array + */ + public function getUploadedParts() + { + \ksort($this->uploadedParts); + return $this->uploadedParts; + } + /** + * Set the status of the upload. + * + * @param int $status Status is an integer code defined by the constants + * CREATED, INITIATED, and COMPLETED on this class. + */ + public function setStatus($status) + { + $this->status = $status; + } + /** + * Determines whether the upload state is in the INITIATED status. + * + * @return bool + */ + public function isInitiated() + { + return $this->status === self::INITIATED; + } + /** + * Determines whether the upload state is in the COMPLETED status. + * + * @return bool + */ + public function isCompleted() + { + return $this->status === self::COMPLETED; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PhpHash.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PhpHash.php new file mode 100644 index 00000000..dfef1182 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PhpHash.php @@ -0,0 +1,65 @@ +algo = $algo; + $this->options = $options; + } + public function update($data) + { + if ($this->hash !== null) { + $this->reset(); + } + \hash_update($this->getContext(), $data); + } + public function complete() + { + if ($this->hash) { + return $this->hash; + } + $this->hash = \hash_final($this->getContext(), \true); + if (isset($this->options['base64']) && $this->options['base64']) { + $this->hash = \base64_encode($this->hash); + } + return $this->hash; + } + public function reset() + { + $this->context = $this->hash = null; + } + /** + * Get a hash context or create one if needed + * + * @return resource|\HashContext + */ + private function getContext() + { + if (!$this->context) { + $key = isset($this->options['key']) ? $this->options['key'] : ''; + $this->context = \hash_init($this->algo, $key ? \HASH_HMAC : 0, $key); + } + return $this->context; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PresignUrlMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PresignUrlMiddleware.php new file mode 100644 index 00000000..41c22874 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PresignUrlMiddleware.php @@ -0,0 +1,91 @@ +endpointProvider = $endpointProvider; + $this->client = $client; + $this->nextHandler = $nextHandler; + $this->commandPool = $options['operations']; + $this->serviceName = $options['service']; + $this->presignParam = !empty($options['presign_param']) ? $options['presign_param'] : 'PresignedUrl'; + $this->extraQueryParams = !empty($options['extra_query_params']) ? $options['extra_query_params'] : []; + $this->requireDifferentRegion = !empty($options['require_different_region']); + } + public static function wrap(AwsClientInterface $client, $endpointProvider, array $options = []) + { + return function (callable $handler) use($endpointProvider, $client, $options) { + $f = new PresignUrlMiddleware($options, $endpointProvider, $client, $handler); + return $f; + }; + } + public function __invoke(CommandInterface $cmd, ?RequestInterface $request = null) + { + if (\in_array($cmd->getName(), $this->commandPool) && !isset($cmd['__skip' . $cmd->getName()])) { + $cmd['DestinationRegion'] = $this->client->getRegion(); + if (!empty($cmd['SourceRegion']) && !empty($cmd[$this->presignParam])) { + goto nexthandler; + } + if (!$this->requireDifferentRegion || !empty($cmd['SourceRegion']) && $cmd['SourceRegion'] !== $cmd['DestinationRegion']) { + $cmd[$this->presignParam] = $this->createPresignedUrl($this->client, $cmd); + } + } + nexthandler: + $nextHandler = $this->nextHandler; + return $nextHandler($cmd, $request); + } + private function createPresignedUrl(AwsClientInterface $client, CommandInterface $cmd) + { + $cmdName = $cmd->getName(); + $newCmd = $client->getCommand($cmdName, $cmd->toArray()); + // Avoid infinite recursion by flagging the new command. + $newCmd['__skip' . $cmdName] = \true; + // Serialize a request for the operation. + $request = \WPMailSMTP\Vendor\Aws\serialize($newCmd); + // Create the new endpoint for the target endpoint. + if ($this->endpointProvider instanceof \WPMailSMTP\Vendor\Aws\EndpointV2\EndpointProviderV2) { + $providerArgs = \array_merge($this->client->getEndpointProviderArgs(), ['Region' => $cmd['SourceRegion']]); + $endpoint = $this->endpointProvider->resolveEndpoint($providerArgs)->getUrl(); + } else { + $endpoint = EndpointProvider::resolve($this->endpointProvider, ['region' => $cmd['SourceRegion'], 'service' => $this->serviceName])['endpoint']; + } + // Set the request to hit the target endpoint. + $uri = $request->getUri()->withHost((new Uri($endpoint))->getHost()); + $request = $request->withUri($uri); + // Create a presigned URL for our generated request. + $signer = new SignatureV4($this->serviceName, $cmd['SourceRegion']); + $currentQueryParams = (string) $request->getBody(); + $paramsToAdd = \false; + if (!empty($this->extraQueryParams[$cmdName])) { + foreach ($this->extraQueryParams[$cmdName] as $param) { + if (!\strpos($currentQueryParams, $param)) { + $paramsToAdd = "&{$param}={$cmd[$param]}"; + } + } + } + return (string) $signer->presign(SignatureV4::convertPostToGet($request, $paramsToAdd ?: ""), $client->getCredentials()->wait(), '+1 hour')->getUri(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Psr16CacheAdapter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Psr16CacheAdapter.php new file mode 100644 index 00000000..25e165f0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Psr16CacheAdapter.php @@ -0,0 +1,26 @@ +cache = $cache; + } + public function get($key) + { + return $this->cache->get($key); + } + public function set($key, $value, $ttl = 0) + { + $this->cache->set($key, $value, $ttl); + } + public function remove($key) + { + $this->cache->delete($key); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PsrCacheAdapter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PsrCacheAdapter.php new file mode 100644 index 00000000..4f2a962c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/PsrCacheAdapter.php @@ -0,0 +1,32 @@ +pool = $pool; + } + public function get($key) + { + $item = $this->pool->getItem($key); + return $item->isHit() ? $item->get() : null; + } + public function set($key, $value, $ttl = 0) + { + $item = $this->pool->getItem($key); + $item->set($value); + if ($ttl > 0) { + $item->expiresAfter($ttl); + } + $this->pool->save($item); + } + public function remove($key) + { + $this->pool->deleteItem($key); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/QueryCompatibleInputMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/QueryCompatibleInputMiddleware.php new file mode 100644 index 00000000..8715601a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/QueryCompatibleInputMiddleware.php @@ -0,0 +1,204 @@ +service = $service; + $this->nextHandler = $nextHandler; + } + public function __invoke(CommandInterface $cmd) + { + $this->command = $cmd; + $nextHandler = $this->nextHandler; + $op = $this->service->getOperation($cmd->getName()); + $inputMembers = $op->getInput()->getMembers(); + $input = $cmd->toArray(); + foreach ($input as $param => $value) { + if (isset($inputMembers[$param])) { + $shape = $inputMembers[$param]; + $this->processInput($value, $shape, [$param]); + } + } + return $nextHandler($this->command); + } + /** + * Recurses a given input shape. if a given scalar input does not match its + * modeled type, it is cast to its modeled type. + * + * @param $input + * @param $shape + * @param array $path + * + * @return void + */ + private function processInput($input, $shape, array $path) : void + { + switch ($shape->getType()) { + case 'structure': + $this->processStructure($input, $shape, $path); + break; + case 'list': + $this->processList($input, $shape, $path); + break; + case 'map': + $this->processMap($input, $shape, $path); + break; + default: + $this->processScalar($input, $shape, $path); + } + } + /** + * @param array $input + * @param StructureShape $shape + * @param array $path + * + * @return void + */ + private function processStructure(array $input, StructureShape $shape, array $path) : void + { + foreach ($input as $param => $value) { + if ($shape->hasMember($param)) { + $memberPath = \array_merge($path, [$param]); + $this->processInput($value, $shape->getMember($param), $memberPath); + } + } + } + /** + * @param array $input + * @param ListShape $shape + * @param array $path + * + * @return void + */ + private function processList(array $input, ListShape $shape, array $path) : void + { + foreach ($input as $param => $value) { + $memberPath = \array_merge($path, [$param]); + $this->processInput($value, $shape->getMember(), $memberPath); + } + } + /** + * @param array $input + * @param MapShape $shape + * @param array $path + * + * @return void + */ + private function processMap(array $input, MapShape $shape, array $path) : void + { + foreach ($input as $param => $value) { + $memberPath = \array_merge($path, [$param]); + $this->processInput($value, $shape->getValue(), $memberPath); + } + } + /** + * @param $input + * @param Shape $shape + * @param array $path + * + * @return void + */ + private function processScalar($input, Shape $shape, array $path) : void + { + $expectedType = $shape->getType(); + if (!$this->isModeledType($input, $expectedType)) { + \trigger_error("The provided type for `" . \implode(' -> ', $path) . "` value was `" . (\gettype($input) === 'double' ? 'float' : \gettype($input)) . "`." . " The modeled type is `{$expectedType}`.", \E_USER_WARNING); + $value = $this->castValue($input, $expectedType); + $this->changeValueAtPath($path, $value); + } + } + /** + * Modifies command in place + * + * @param array $path + * @param $newValue + * + * @return void + */ + private function changeValueAtPath(array $path, $newValue) : void + { + $commandRef =& $this->command; + foreach ($path as $segment) { + if (!isset($commandRef[$segment])) { + return; + } + $commandRef =& $commandRef[$segment]; + } + $commandRef = $newValue; + } + /** + * @param $value + * @param $type + * + * @return bool + */ + private function isModeledType($value, $type) : bool + { + switch ($type) { + case 'string': + return \is_string($value); + case 'integer': + case 'long': + return \is_int($value); + case 'float': + return \is_float($value); + default: + return \true; + } + } + /** + * @param $value + * @param $type + * + * @return float|int|mixed|string + */ + private function castValue($value, $type) + { + switch ($type) { + case 'integer': + return (int) $value; + case 'long': + return $value + 0; + case 'float': + return (float) $value; + case 'string': + return (string) $value; + default: + return $value; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RequestCompressionMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RequestCompressionMiddleware.php new file mode 100644 index 00000000..733ca5e9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RequestCompressionMiddleware.php @@ -0,0 +1,118 @@ + 'gzencode']; + /** + * Create a middleware wrapper function. + * + * @return callable + */ + public static function wrap(array $config) + { + return function (callable $handler) use($config) { + return new self($handler, $config); + }; + } + public function __construct(callable $nextHandler, $config) + { + $this->minimumCompressionSize = $this->determineMinimumCompressionSize($config); + $this->api = $config['api']; + $this->nextHandler = $nextHandler; + } + public function __invoke(CommandInterface $command, RequestInterface $request) + { + if (isset($command['@request_min_compression_size_bytes']) && \is_int($command['@request_min_compression_size_bytes']) && $this->isValidCompressionSize($command['@request_min_compression_size_bytes'])) { + $this->minimumCompressionSize = $command['@request_min_compression_size_bytes']; + } + $nextHandler = $this->nextHandler; + $operation = $this->api->getOperation($command->getName()); + $compressionInfo = isset($operation['requestcompression']) ? $operation['requestcompression'] : null; + if (!$this->shouldCompressRequestBody($compressionInfo, $command, $operation, $request)) { + return $nextHandler($command, $request); + } + $this->encodings = $compressionInfo['encodings']; + $request = $this->compressRequestBody($request); + // Capture request compression metric + $command->getMetricsBuilder()->identifyMetricByValueAndAppend('request_compression', $request->getHeaderLine('content-encoding')); + return $nextHandler($command, $request); + } + private function compressRequestBody(RequestInterface $request) + { + $fn = $this->determineEncoding(); + if (\is_null($fn)) { + return $request; + } + $body = $request->getBody()->getContents(); + $compressedBody = $fn($body); + return $request->withBody(Psr7\Utils::streamFor($compressedBody))->withHeader('content-encoding', $this->encoding); + } + private function determineEncoding() + { + foreach ($this->encodings as $encoding) { + if (isset($this->encodingMap[$encoding])) { + $this->encoding = $encoding; + return $this->encodingMap[$encoding]; + } + } + return null; + } + private function shouldCompressRequestBody($compressionInfo, $command, $operation, $request) + { + if ($compressionInfo) { + if (isset($command['@disable_request_compression']) && $command['@disable_request_compression'] === \true) { + return \false; + } elseif ($this->hasStreamingTraitWithoutRequiresLength($command, $operation)) { + return \true; + } + $requestBodySize = $request->hasHeader('content-length') ? (int) $request->getHeaderLine('content-length') : $request->getBody()->getSize(); + if ($requestBodySize >= $this->minimumCompressionSize) { + return \true; + } + } + return \false; + } + private function hasStreamingTraitWithoutRequiresLength($command, $operation) + { + foreach ($operation->getInput()->getMembers() as $name => $member) { + if (isset($command[$name]) && !empty($member['streaming']) && empty($member['requiresLength'])) { + return \true; + } + } + return \false; + } + private function determineMinimumCompressionSize($config) + { + if (\is_callable($config['request_min_compression_size_bytes'])) { + $minCompressionSz = $config['request_min_compression_size_bytes'](); + } else { + $minCompressionSz = $config['request_min_compression_size_bytes']; + } + if ($this->isValidCompressionSize($minCompressionSz)) { + return $minCompressionSz; + } + } + private function isValidCompressionSize($compressionSize) + { + if (\is_numeric($compressionSize) && ($compressionSize >= 0 && $compressionSize <= 10485760)) { + return \true; + } + throw new \InvalidArgumentException('The minimum request compression size must be a ' . 'non-negative integer value between 0 and 10485760 bytes, inclusive.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResponseContainerInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResponseContainerInterface.php new file mode 100644 index 00000000..82116cad --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResponseContainerInterface.php @@ -0,0 +1,14 @@ +data = $data; + } + public function hasKey($name) + { + return isset($this->data[$name]); + } + public function get($key) + { + return $this[$key]; + } + public function search($expression) + { + return JmesPath::search($expression, $this->toArray()); + } + public function __toString() + { + $jsonData = \json_encode($this->toArray(), \JSON_PRETTY_PRINT); + return <<get(\$key)`) or "accessing the result like an +associative array (e.g. `\$result['key']`). You can also execute JMESPath +expressions on the result data using the search() method. + +{$jsonData} + +EOT; + } + /** + * @deprecated + */ + public function getPath($path) + { + return $this->search(\str_replace('/', '.', $path)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultInterface.php new file mode 100644 index 00000000..b59f9b74 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultInterface.php @@ -0,0 +1,51 @@ +execute($command); + * $jpResult = $result->search('foo.*.bar[?baz > `10`]'); + * + * @param string $expression JMESPath expression to execute + * + * @return mixed Returns the result of the JMESPath expression. + * @link http://jmespath.readthedocs.org/en/latest/ JMESPath documentation + */ + public function search($expression); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultPaginator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultPaginator.php new file mode 100644 index 00000000..6f922bff --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/ResultPaginator.php @@ -0,0 +1,166 @@ +client = $client; + $this->operation = $operation; + $this->args = $args; + $this->config = $config; + MetricsBuilder::appendMetricsCaptureMiddleware($this->client->getHandlerList(), MetricsBuilder::PAGINATOR); + } + /** + * Runs a paginator asynchronously and uses a callback to handle results. + * + * The callback should have the signature: function (Aws\Result $result). + * A non-null return value from the callback will be yielded by the + * promise. This means that you can return promises from the callback that + * will need to be resolved before continuing iteration over the remaining + * items, essentially merging in other promises to the iteration. The last + * non-null value returned by the callback will be the result that fulfills + * the promise to any downstream promises. + * + * @param callable $handleResult Callback for handling each page of results. + * The callback accepts the result that was + * yielded as a single argument. If the + * callback returns a promise, the promise + * will be merged into the coroutine. + * + * @return Promise\Promise + */ + public function each(callable $handleResult) + { + return Promise\Coroutine::of(function () use($handleResult) { + $nextToken = null; + do { + $command = $this->createNextCommand($this->args, $nextToken); + $result = (yield $this->client->executeAsync($command)); + $nextToken = $this->determineNextToken($result); + $retVal = $handleResult($result); + if ($retVal !== null) { + (yield Promise\Create::promiseFor($retVal)); + } + } while ($nextToken); + }); + } + /** + * Returns an iterator that iterates over the values of applying a JMESPath + * search to each result yielded by the iterator as a flat sequence. + * + * @param string $expression JMESPath expression to apply to each result. + * + * @return \Iterator + */ + public function search($expression) + { + // Apply JMESPath expression on each result, but as a flat sequence. + return flatmap($this, function (Result $result) use($expression) { + return (array) $result->search($expression); + }); + } + /** + * @return Result + */ + #[\ReturnTypeWillChange] + public function current() + { + return $this->valid() ? $this->result : \false; + } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function key() + { + return $this->valid() ? $this->requestCount - 1 : null; + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function next() + { + $this->result = null; + } + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function valid() + { + if ($this->result) { + return \true; + } + if ($this->nextToken || !$this->requestCount) { + //Forward/backward paging can result in a case where the last page's nextforwardtoken + //is the same as the one that came before it. This can cause an infinite loop. + $hasBidirectionalPaging = $this->config['output_token'] === 'nextForwardToken'; + if ($hasBidirectionalPaging && $this->nextToken) { + $tokenKey = $this->config['input_token']; + $previousToken = $this->nextToken[$tokenKey]; + } + $this->result = $this->client->execute($this->createNextCommand($this->args, $this->nextToken)); + $this->nextToken = $this->determineNextToken($this->result); + if (isset($previousToken) && $previousToken === $this->nextToken[$tokenKey]) { + return \false; + } + $this->requestCount++; + return \true; + } + return \false; + } + /** + * @return void + */ + #[\ReturnTypeWillChange] + public function rewind() + { + $this->requestCount = 0; + $this->nextToken = null; + $this->result = null; + } + private function createNextCommand(array $args, ?array $nextToken = null) + { + return $this->client->getCommand($this->operation, \array_merge($args, $nextToken ?: [])); + } + private function determineNextToken(Result $result) + { + if (!$this->config['output_token']) { + return null; + } + if ($this->config['more_results'] && !$result->search($this->config['more_results'])) { + return null; + } + $nextToken = \is_scalar($this->config['output_token']) ? [$this->config['input_token'] => $this->config['output_token']] : \array_combine($this->config['input_token'], $this->config['output_token']); + return \array_filter(\array_map(function ($outputToken) use($result) { + return $result->search($outputToken); + }, $nextToken)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Configuration.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Configuration.php new file mode 100644 index 00000000..e0630b04 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Configuration.php @@ -0,0 +1,44 @@ +validModes)) { + throw new ConfigurationException("'{$mode}' is not a valid mode." . " The mode has to be 'legacy', 'standard', or 'adaptive'."); + } + if (!\is_numeric($maxAttempts) || \intval($maxAttempts) != $maxAttempts || $maxAttempts < 1) { + throw new ConfigurationException("The 'maxAttempts' parameter has" . " to be an integer >= 1."); + } + $this->mode = $mode; + $this->maxAttempts = \intval($maxAttempts); + } + /** + * {@inheritdoc} + */ + public function getMode() + { + return $this->mode; + } + /** + * {@inheritdoc} + */ + public function getMaxAttempts() + { + return $this->maxAttempts; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['mode' => $this->getMode(), 'max_attempts' => $this->getMaxAttempts()]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/ConfigurationInterface.php new file mode 100644 index 00000000..2e2e6407 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/ConfigurationInterface.php @@ -0,0 +1,29 @@ + + * use Aws\Sts\RegionalEndpoints\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see \Aws\Retry\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const DEFAULT_MAX_ATTEMPTS = 3; + const DEFAULT_MODE = 'legacy'; + const ENV_MAX_ATTEMPTS = 'AWS_MAX_ATTEMPTS'; + const ENV_MODE = 'AWS_RETRY_MODE'; + const ENV_PROFILE = 'AWS_PROFILE'; + const INI_MAX_ATTEMPTS = 'max_attempts'; + const INI_MODE = 'retry_mode'; + public static $cacheKey = 'aws_retries_config'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $configProviders = [self::env()]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini(); + } + $configProviders[] = self::fallback(); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['retries']) && $config['retries'] instanceof CacheInterface) { + return self::cache($memo, $config['retries'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates config from environment variables. + * + * @return callable + */ + public static function env() + { + return function () { + // Use config from environment variables, if available + $mode = \getenv(self::ENV_MODE); + $maxAttempts = \getenv(self::ENV_MAX_ATTEMPTS) ? \getenv(self::ENV_MAX_ATTEMPTS) : self::DEFAULT_MAX_ATTEMPTS; + if (!empty($mode)) { + return Promise\Create::promiseFor(new Configuration($mode, $maxAttempts)); + } + return self::reject('Could not find environment variable config' . ' in ' . self::ENV_MODE); + }; + } + /** + * Fallback config options when other sources are not set. + * + * @return callable + */ + public static function fallback() + { + return function () { + return Promise\Create::promiseFor(new Configuration(self::DEFAULT_MODE, self::DEFAULT_MAX_ATTEMPTS)); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * + * @return callable + */ + public static function ini($profile = null, $filename = null) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read configuration from {$filename}"); + } + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile][self::INI_MODE])) { + return self::reject("Required retry config values\n not present in INI profile '{$profile}' ({$filename})"); + } + $maxAttempts = isset($data[$profile][self::INI_MAX_ATTEMPTS]) ? $data[$profile][self::INI_MAX_ATTEMPTS] : self::DEFAULT_MAX_ATTEMPTS; + return Promise\Create::promiseFor(new Configuration($data[$profile][self::INI_MODE], $maxAttempts)); + }; + } + /** + * Unwraps a configuration object in whatever valid form it is in, + * always returning a ConfigurationInterface object. + * + * @param mixed $config + * @return ConfigurationInterface + * @throws \InvalidArgumentException + */ + public static function unwrap($config) + { + if (\is_callable($config)) { + $config = $config(); + } + if ($config instanceof PromiseInterface) { + $config = $config->wait(); + } + if ($config instanceof ConfigurationInterface) { + return $config; + } + // An integer value for this config indicates the legacy 'retries' + // config option, which is incremented to translate to max attempts + if (\is_int($config)) { + return new Configuration('legacy', $config + 1); + } + if (\is_array($config) && isset($config['mode'])) { + $maxAttempts = isset($config['max_attempts']) ? $config['max_attempts'] : self::DEFAULT_MAX_ATTEMPTS; + return new Configuration($config['mode'], $maxAttempts); + } + throw new \InvalidArgumentException('Not a valid retry configuration' . ' argument.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Exception/ConfigurationException.php new file mode 100644 index 00000000..3af3f2e8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +initialRetryTokens = isset($config['initial_retry_tokens']) ? $config['initial_retry_tokens'] : 500; + $this->noRetryIncrement = isset($config['no_retry_increment']) ? $config['no_retry_increment'] : 1; + $this->retryCost = isset($config['retry_cost']) ? $config['retry_cost'] : 5; + $this->timeoutRetryCost = isset($config['timeout_retry_cost']) ? $config['timeout_retry_cost'] : 10; + $this->maxCapacity = $this->initialRetryTokens; + $this->availableCapacity = $this->initialRetryTokens; + } + public function hasRetryQuota($result) + { + if ($result instanceof AwsException && $result->isConnectionError()) { + $this->capacityAmount = $this->timeoutRetryCost; + } else { + $this->capacityAmount = $this->retryCost; + } + if ($this->capacityAmount > $this->availableCapacity) { + return \false; + } + $this->availableCapacity -= $this->capacityAmount; + return \true; + } + public function releaseToQuota($result) + { + if ($result instanceof AwsException) { + $statusCode = (int) $result->getStatusCode(); + } elseif ($result instanceof ResultInterface) { + $statusCode = isset($result['@metadata']['statusCode']) ? (int) $result['@metadata']['statusCode'] : null; + } + if (!empty($statusCode) && $statusCode >= 200 && $statusCode < 300) { + if (isset($this->capacityAmount)) { + $amount = $this->capacityAmount; + $this->availableCapacity += $amount; + unset($this->capacityAmount); + } else { + $amount = $this->noRetryIncrement; + $this->availableCapacity += $amount; + } + $this->availableCapacity = \min($this->availableCapacity, $this->maxCapacity); + } + return isset($amount) ? $amount : 0; + } + public function getAvailableCapacity() + { + return $this->availableCapacity; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RateLimiter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RateLimiter.php new file mode 100644 index 00000000..e2b44ea5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RateLimiter.php @@ -0,0 +1,143 @@ +beta = isset($options['beta']) ? $options['beta'] : 0.7; + $this->minCapacity = isset($options['min_capacity']) ? $options['min_capacity'] : 1; + $this->minFillRate = isset($options['min_fill_rate']) ? $options['min_fill_rate'] : 0.5; + $this->scaleConstant = isset($options['scale_constant']) ? $options['scale_constant'] : 0.4; + $this->smooth = isset($options['smooth']) ? $options['smooth'] : 0.8; + $this->timeProvider = isset($options['time_provider']) ? $options['time_provider'] : null; + $this->lastTxRateBucket = \floor($this->time()); + $this->lastThrottleTime = $this->time(); + } + public function isEnabled() + { + return $this->enabled; + } + public function getSendToken() + { + $this->acquireToken(1); + } + public function updateSendingRate($isThrottled) + { + $this->updateMeasuredRate(); + if ($isThrottled) { + if (!$this->isEnabled()) { + $rateToUse = $this->measuredTxRate; + } else { + $rateToUse = \min($this->measuredTxRate, $this->fillRate); + } + $this->lastMaxRate = $rateToUse; + $this->calculateTimeWindow(); + $this->lastThrottleTime = $this->time(); + $calculatedRate = $this->cubicThrottle($rateToUse); + $this->enableTokenBucket(); + } else { + $this->calculateTimeWindow(); + $calculatedRate = $this->cubicSuccess($this->time()); + } + $newRate = \min($calculatedRate, 2 * $this->measuredTxRate); + $this->updateTokenBucketRate($newRate); + return $newRate; + } + private function acquireToken($amount) + { + if (!$this->enabled) { + return \true; + } + $this->refillTokenBucket(); + if ($amount > $this->currentCapacity) { + \usleep((int) (1000000 * ($amount - $this->currentCapacity) / $this->fillRate)); + } + $this->currentCapacity -= $amount; + return \true; + } + private function calculateTimeWindow() + { + $this->timeWindow = \pow($this->lastMaxRate * (1 - $this->beta) / $this->scaleConstant, 0.333); + } + private function cubicSuccess($timestamp) + { + $dt = $timestamp - $this->lastThrottleTime; + return $this->scaleConstant * \pow($dt - $this->timeWindow, 3) + $this->lastMaxRate; + } + private function cubicThrottle($rateToUse) + { + return $rateToUse * $this->beta; + } + private function enableTokenBucket() + { + $this->enabled = \true; + } + private function refillTokenBucket() + { + $timestamp = $this->time(); + if (!isset($this->lastTimestamp)) { + $this->lastTimestamp = $timestamp; + return; + } + $fillAmount = ($timestamp - $this->lastTimestamp) * $this->fillRate; + $this->currentCapacity = $this->currentCapacity + $fillAmount; + if (!\is_null($this->maxCapacity)) { + $this->currentCapacity = \min($this->maxCapacity, $this->currentCapacity); + } + $this->lastTimestamp = $timestamp; + } + private function time() + { + if (\is_callable($this->timeProvider)) { + $provider = $this->timeProvider; + $time = $provider(); + return $time; + } + return \microtime(\true); + } + private function updateMeasuredRate() + { + $timestamp = $this->time(); + $timeBucket = \floor(\round($timestamp, 3) * 2) / 2; + $this->requestCount++; + if ($timeBucket > $this->lastTxRateBucket) { + $currentRate = $this->requestCount / ($timeBucket - $this->lastTxRateBucket); + $this->measuredTxRate = $currentRate * $this->smooth + $this->measuredTxRate * (1 - $this->smooth); + $this->requestCount = 0; + $this->lastTxRateBucket = $timeBucket; + } + } + private function updateTokenBucketRate($newRps) + { + $this->refillTokenBucket(); + $this->fillRate = \max($newRps, $this->minFillRate); + $this->maxCapacity = \max($newRps, $this->minCapacity); + $this->currentCapacity = \min($this->currentCapacity, $this->maxCapacity); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RetryHelperTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RetryHelperTrait.php new file mode 100644 index 00000000..659f98d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Retry/RetryHelperTrait.php @@ -0,0 +1,46 @@ +withHeader('aws-sdk-retry', "{$retries}/{$delayBy}"); + } + private function updateStats($retries, $delay, array &$stats) + { + if (!isset($stats['total_retry_delay'])) { + $stats['total_retry_delay'] = 0; + } + $stats['total_retry_delay'] += $delay; + $stats['retries_attempted'] = $retries; + } + private function updateHttpStats($value, array &$stats) + { + if (empty($stats['http'])) { + $stats['http'] = []; + } + if ($value instanceof AwsException) { + $resultStats = $value->getTransferInfo(); + $stats['http'][] = $resultStats; + } elseif ($value instanceof ResultInterface) { + $resultStats = isset($value['@metadata']['transferStats']['http'][0]) ? $value['@metadata']['transferStats']['http'][0] : []; + $stats['http'][] = $resultStats; + } + } + private function bindStatsToReturn($return, array $stats) + { + if ($return instanceof ResultInterface) { + if (!isset($return['@metadata'])) { + $return['@metadata'] = []; + } + $return['@metadata']['transferStats'] = $stats; + } elseif ($return instanceof AwsException) { + $return->setTransferInfo($stats); + } + return $return; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddleware.php new file mode 100644 index 00000000..6218be57 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddleware.php @@ -0,0 +1,191 @@ + \true, 502 => \true, 503 => \true, 504 => \true]; + private static $retryCodes = [ + // Throttling error + 'RequestLimitExceeded' => \true, + 'Throttling' => \true, + 'ThrottlingException' => \true, + 'ThrottledException' => \true, + 'ProvisionedThroughputExceededException' => \true, + 'RequestThrottled' => \true, + 'BandwidthLimitExceeded' => \true, + 'RequestThrottledException' => \true, + 'TooManyRequestsException' => \true, + 'IDPCommunicationError' => \true, + 'EC2ThrottledException' => \true, + ]; + private $decider; + private $delay; + private $nextHandler; + private $collectStats; + public function __construct(callable $decider, callable $delay, callable $nextHandler, $collectStats = \false) + { + $this->decider = $decider; + $this->delay = $delay; + $this->nextHandler = $nextHandler; + $this->collectStats = (bool) $collectStats; + } + /** + * Creates a default AWS retry decider function. + * + * The optional $extraConfig parameter is an associative array + * that specifies additional retry conditions on top of the ones specified + * by default by the Aws\RetryMiddleware class, with the following keys: + * + * - errorCodes: (string[]) An indexed array of AWS exception codes to retry. + * Optional. + * - statusCodes: (int[]) An indexed array of HTTP status codes to retry. + * Optional. + * - curlErrors: (int[]) An indexed array of Curl error codes to retry. Note + * these should be valid Curl constants. Optional. + * + * @param int $maxRetries + * @param array $extraConfig + * @return callable + */ + public static function createDefaultDecider($maxRetries = 3, $extraConfig = []) + { + $retryCurlErrors = []; + if (\extension_loaded('curl')) { + $retryCurlErrors[\CURLE_RECV_ERROR] = \true; + } + return function ($retries, CommandInterface $command, RequestInterface $request, ?ResultInterface $result = null, $error = null) use($maxRetries, $retryCurlErrors, $extraConfig) { + // Allow command-level options to override this value + $maxRetries = null !== $command['@retries'] ? $command['@retries'] : $maxRetries; + $isRetryable = self::isRetryable($result, $error, $retryCurlErrors, $extraConfig); + if ($retries >= $maxRetries) { + if (!empty($error) && $error instanceof AwsException && $isRetryable) { + $error->setMaxRetriesExceeded(); + } + return \false; + } + return $isRetryable; + }; + } + private static function isRetryable($result, $error, $retryCurlErrors, $extraConfig = []) + { + $errorCodes = self::$retryCodes; + if (!empty($extraConfig['error_codes']) && \is_array($extraConfig['error_codes'])) { + foreach ($extraConfig['error_codes'] as $code) { + $errorCodes[$code] = \true; + } + } + $statusCodes = self::$retryStatusCodes; + if (!empty($extraConfig['status_codes']) && \is_array($extraConfig['status_codes'])) { + foreach ($extraConfig['status_codes'] as $code) { + $statusCodes[$code] = \true; + } + } + if (!empty($extraConfig['curl_errors']) && \is_array($extraConfig['curl_errors'])) { + foreach ($extraConfig['curl_errors'] as $code) { + $retryCurlErrors[$code] = \true; + } + } + if (!$error) { + if (!isset($result['@metadata']['statusCode'])) { + return \false; + } + return isset($statusCodes[$result['@metadata']['statusCode']]); + } + if (!$error instanceof AwsException) { + return \false; + } + if ($error->isConnectionError()) { + return \true; + } + if (isset($errorCodes[$error->getAwsErrorCode()])) { + return \true; + } + if (isset($statusCodes[$error->getStatusCode()])) { + return \true; + } + if (\count($retryCurlErrors) && ($previous = $error->getPrevious()) && $previous instanceof RequestException) { + if (\method_exists($previous, 'getHandlerContext')) { + $context = $previous->getHandlerContext(); + return !empty($context['errno']) && isset($retryCurlErrors[$context['errno']]); + } + $message = $previous->getMessage(); + foreach (\array_keys($retryCurlErrors) as $curlError) { + if (\strpos($message, 'cURL error ' . $curlError . ':') === 0) { + return \true; + } + } + } + return \false; + } + /** + * Delay function that calculates an exponential delay. + * + * Exponential backoff with jitter, 100ms base, 20 sec ceiling + * + * @param $retries - The number of retries that have already been attempted + * + * @return int + * + * @link https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ + */ + public static function exponentialDelay($retries) + { + return \mt_rand(0, (int) \min(20000, (int) \pow(2, $retries) * 100)); + } + /** + * @param CommandInterface $command + * @param RequestInterface $request + * + * @return PromiseInterface + */ + public function __invoke(CommandInterface $command, ?RequestInterface $request = null) + { + $retries = 0; + $requestStats = []; + $monitoringEvents = []; + $handler = $this->nextHandler; + $decider = $this->decider; + $delay = $this->delay; + $request = $this->addRetryHeader($request, 0, 0); + $g = function ($value) use($handler, $decider, $delay, $command, $request, &$retries, &$requestStats, &$monitoringEvents, &$g) { + $this->updateHttpStats($value, $requestStats); + if ($value instanceof MonitoringEventsInterface) { + $reversedEvents = \array_reverse($monitoringEvents); + $monitoringEvents = \array_merge($monitoringEvents, $value->getMonitoringEvents()); + foreach ($reversedEvents as $event) { + $value->prependMonitoringEvent($event); + } + } + if ($value instanceof \Exception || $value instanceof \Throwable) { + if (!$decider($retries, $command, $request, null, $value)) { + return Promise\Create::rejectionFor($this->bindStatsToReturn($value, $requestStats)); + } + } elseif ($value instanceof ResultInterface && !$decider($retries, $command, $request, $value, null)) { + return $this->bindStatsToReturn($value, $requestStats); + } + // Delay fn is called with 0, 1, ... so increment after the call. + $delayBy = $delay($retries++); + $command['@http']['delay'] = $delayBy; + if ($this->collectStats) { + $this->updateStats($retries, $delayBy, $requestStats); + } + // Update retry header with retry count and delayBy + $request = $this->addRetryHeader($request, $retries, $delayBy); + return $handler($command, $request)->then($g, $g); + }; + return $handler($command, $request)->then($g, $g); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddlewareV2.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddlewareV2.php new file mode 100644 index 00000000..be3c2eae --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/RetryMiddlewareV2.php @@ -0,0 +1,235 @@ + \true, 'ThrottlingException' => \true, 'ThrottledException' => \true, 'RequestThrottledException' => \true, 'TooManyRequestsException' => \true, 'ProvisionedThroughputExceededException' => \true, 'TransactionInProgressException' => \true, 'RequestLimitExceeded' => \true, 'BandwidthLimitExceeded' => \true, 'LimitExceededException' => \true, 'RequestThrottled' => \true, 'SlowDown' => \true, 'PriorRequestNotComplete' => \true, 'EC2ThrottledException' => \true]; + private static $standardTransientErrors = ['RequestTimeout' => \true, 'RequestTimeoutException' => \true]; + private static $standardTransientStatusCodes = [500 => \true, 502 => \true, 503 => \true, 504 => \true]; + private $collectStats; + private $decider; + private $delayer; + private $maxAttempts; + private $maxBackoff; + private $mode; + private $nextHandler; + private $options; + private $quotaManager; + private $rateLimiter; + public static function wrap($config, $options) + { + return function (callable $handler) use($config, $options) { + return new static($config, $handler, $options); + }; + } + public static function createDefaultDecider(QuotaManager $quotaManager, $maxAttempts = 3, $options = []) + { + $retryCurlErrors = []; + if (\extension_loaded('curl')) { + $retryCurlErrors[\CURLE_RECV_ERROR] = \true; + } + return function ($attempts, CommandInterface $command, $result) use($options, $quotaManager, $retryCurlErrors, $maxAttempts) { + // Release retry tokens back to quota on a successful result + $quotaManager->releaseToQuota($result); + // Allow command-level option to override this value + // # of attempts = # of retries + 1 + $maxAttempts = null !== $command['@retries'] ? $command['@retries'] + 1 : $maxAttempts; + $isRetryable = self::isRetryable($result, $retryCurlErrors, $options); + if ($isRetryable) { + // Retrieve retry tokens and check if quota has been exceeded + if (!$quotaManager->hasRetryQuota($result)) { + return \false; + } + if ($attempts >= $maxAttempts) { + if (!empty($result) && $result instanceof AwsException) { + $result->setMaxRetriesExceeded(); + } + return \false; + } + } + return $isRetryable; + }; + } + public function __construct(ConfigurationInterface $config, callable $handler, $options = []) + { + $this->options = $options; + $this->maxAttempts = $config->getMaxAttempts(); + $this->mode = $config->getMode(); + $this->nextHandler = $handler; + $this->quotaManager = new QuotaManager(); + $this->maxBackoff = isset($options['max_backoff']) ? $options['max_backoff'] : 20000; + $this->collectStats = isset($options['collect_stats']) ? (bool) $options['collect_stats'] : \false; + $this->decider = isset($options['decider']) ? $options['decider'] : self::createDefaultDecider($this->quotaManager, $this->maxAttempts, $options); + $this->delayer = isset($options['delayer']) ? $options['delayer'] : function ($attempts) { + return $this->exponentialDelayWithJitter($attempts); + }; + if ($this->mode === 'adaptive') { + $this->rateLimiter = isset($options['rate_limiter']) ? $options['rate_limiter'] : new RateLimiter(); + } + } + public function __invoke(CommandInterface $cmd, RequestInterface $req) + { + $decider = $this->decider; + $delayer = $this->delayer; + $handler = $this->nextHandler; + $attempts = 1; + $monitoringEvents = []; + $requestStats = []; + $req = $this->addRetryHeader($req, 0, 0); + $callback = function ($value) use($handler, $cmd, $req, $decider, $delayer, &$attempts, &$requestStats, &$monitoringEvents, &$callback) { + if ($this->mode === 'adaptive') { + $this->rateLimiter->updateSendingRate($this->isThrottlingError($value)); + } + $this->updateHttpStats($value, $requestStats); + if ($value instanceof MonitoringEventsInterface) { + $reversedEvents = \array_reverse($monitoringEvents); + $monitoringEvents = \array_merge($monitoringEvents, $value->getMonitoringEvents()); + foreach ($reversedEvents as $event) { + $value->prependMonitoringEvent($event); + } + } + if ($value instanceof \Exception || $value instanceof \Throwable) { + if (!$decider($attempts, $cmd, $value)) { + return Promise\Create::rejectionFor($this->bindStatsToReturn($value, $requestStats)); + } + } elseif ($value instanceof ResultInterface && !$decider($attempts, $cmd, $value)) { + return $this->bindStatsToReturn($value, $requestStats); + } + $delayBy = $delayer($attempts++); + $cmd['@http']['delay'] = $delayBy; + if ($this->collectStats) { + $this->updateStats($attempts - 1, $delayBy, $requestStats); + } + // Update retry header with retry count and delayBy + $req = $this->addRetryHeader($req, $attempts - 1, $delayBy); + // Get token from rate limiter, which will sleep if necessary + if ($this->mode === 'adaptive') { + $this->rateLimiter->getSendToken(); + } + return $handler($cmd, $req)->then($callback, $callback); + }; + // Get token from rate limiter, which will sleep if necessary + if ($this->mode === 'adaptive') { + $this->rateLimiter->getSendToken(); + } + return $handler($cmd, $req)->then($callback, $callback); + } + /** + * Amount of milliseconds to delay as a function of attempt number + * + * @param $attempts + * @return mixed + */ + public function exponentialDelayWithJitter($attempts) + { + $rand = \mt_rand() / \mt_getrandmax(); + return \min(1000 * $rand * \pow(2, $attempts), $this->maxBackoff); + } + private static function isRetryable($result, $retryCurlErrors, $options = []) + { + $errorCodes = self::$standardThrottlingErrors + self::$standardTransientErrors; + if (!empty($options['transient_error_codes']) && \is_array($options['transient_error_codes'])) { + foreach ($options['transient_error_codes'] as $code) { + $errorCodes[$code] = \true; + } + } + if (!empty($options['throttling_error_codes']) && \is_array($options['throttling_error_codes'])) { + foreach ($options['throttling_error_codes'] as $code) { + $errorCodes[$code] = \true; + } + } + $statusCodes = self::$standardTransientStatusCodes; + if (!empty($options['status_codes']) && \is_array($options['status_codes'])) { + foreach ($options['status_codes'] as $code) { + $statusCodes[$code] = \true; + } + } + if (!empty($options['curl_errors']) && \is_array($options['curl_errors'])) { + foreach ($options['curl_errors'] as $code) { + $retryCurlErrors[$code] = \true; + } + } + if ($result instanceof \Exception || $result instanceof \Throwable) { + $isError = \true; + } else { + $isError = \false; + } + if (!$isError) { + if (!isset($result['@metadata']['statusCode'])) { + return \false; + } + return isset($statusCodes[$result['@metadata']['statusCode']]); + } + if (!$result instanceof AwsException) { + return \false; + } + if ($result->isConnectionError()) { + return \true; + } + if (!empty($errorCodes[$result->getAwsErrorCode()])) { + return \true; + } + if (!empty($statusCodes[$result->getStatusCode()])) { + return \true; + } + if (\count($retryCurlErrors) && ($previous = $result->getPrevious()) && $previous instanceof RequestException) { + if (\method_exists($previous, 'getHandlerContext')) { + $context = $previous->getHandlerContext(); + return !empty($context['errno']) && isset($retryCurlErrors[$context['errno']]); + } + $message = $previous->getMessage(); + foreach (\array_keys($retryCurlErrors) as $curlError) { + if (\strpos($message, 'cURL error ' . $curlError . ':') === 0) { + return \true; + } + } + } + // Check error shape for the retryable trait + if (!empty($errorShape = $result->getAwsErrorShape())) { + $definition = $errorShape->toArray(); + if (!empty($definition['retryable'])) { + return \true; + } + } + return \false; + } + private function isThrottlingError($result) + { + if ($result instanceof AwsException) { + // Check pre-defined throttling errors + $throttlingErrors = self::$standardThrottlingErrors; + if (!empty($this->options['throttling_error_codes']) && \is_array($this->options['throttling_error_codes'])) { + foreach ($this->options['throttling_error_codes'] as $code) { + $throttlingErrors[$code] = \true; + } + } + if (!empty($result->getAwsErrorCode()) && !empty($throttlingErrors[$result->getAwsErrorCode()])) { + return \true; + } + // Check error shape for the throttling trait + if (!empty($errorShape = $result->getAwsErrorShape())) { + $definition = $errorShape->toArray(); + if (!empty($definition['retryable']['throttling'])) { + return \true; + } + } + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSO/Exception/SSOException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSO/Exception/SSOException.php new file mode 100644 index 00000000..23e0d427 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SSO/Exception/SSOException.php @@ -0,0 +1,11 @@ +isDevMode()) { + return; + } + $composer = $event->getComposer(); + $extra = $composer->getPackage()->getExtra(); + $listedServices = isset($extra['aws/aws-sdk-php']) ? $extra['aws/aws-sdk-php'] : []; + if ($listedServices) { + $serviceMapping = self::buildServiceMapping(); + self::verifyListedServices($serviceMapping, $listedServices); + $filesystem = $filesystem ?: new Filesystem(); + $vendorPath = $composer->getConfig()->get('vendor-dir'); + self::removeServiceDirs($event, $filesystem, $serviceMapping, $listedServices, $vendorPath); + } else { + throw new \InvalidArgumentException('There are no services listed. Did you intend to use this script?'); + } + } + public static function buildServiceMapping() + { + $serviceMapping = []; + $manifest = (require __DIR__ . '/../../data/manifest.json.php'); + foreach ($manifest as $service => $attributes) { + $serviceMapping[$attributes['namespace']] = $service; + } + return $serviceMapping; + } + private static function verifyListedServices($serviceMapping, $listedServices) + { + foreach ($listedServices as $serviceToKeep) { + if (!isset($serviceMapping[$serviceToKeep])) { + throw new \InvalidArgumentException("'{$serviceToKeep}' is not a valid AWS service namespace. Please check spelling and casing."); + } + } + } + private static function removeServiceDirs($event, $filesystem, $serviceMapping, $listedServices, $vendorPath) + { + $unsafeForDeletion = ['Kms', 'S3', 'SSO', 'SSOOIDC', 'Sts']; + if (\in_array('DynamoDbStreams', $listedServices)) { + $unsafeForDeletion[] = 'DynamoDb'; + } + $clientPath = $vendorPath . '/aws/aws-sdk-php/src/'; + $modelPath = $clientPath . 'data/'; + $deleteCount = 0; + foreach ($serviceMapping as $clientName => $modelName) { + if (!\in_array($clientName, $listedServices) && !\in_array($clientName, $unsafeForDeletion)) { + $clientDir = $clientPath . $clientName; + $modelDir = $modelPath . $modelName; + if ($filesystem->exists([$clientDir, $modelDir])) { + $attempts = 3; + $delay = 2; + while ($attempts) { + try { + $filesystem->remove([$clientDir, $modelDir]); + $deleteCount++; + break; + } catch (IOException $e) { + $attempts--; + if (!$attempts) { + throw new IOException("Removal failed after several attempts. Last error: " . $e->getMessage()); + } else { + \sleep($delay); + $event->getIO()->write("Error encountered: " . $e->getMessage() . ". Retrying..."); + $delay += 2; + } + } + } + } + } + } + $event->getIO()->write("Removed {$deleteCount} AWS service" . ($deleteCount === 1 ? '' : 's')); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sdk.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sdk.php new file mode 100644 index 00000000..def58ce9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sdk.php @@ -0,0 +1,911 @@ +args = $args; + if (!isset($args['handler']) && !isset($args['http_handler'])) { + $this->args['http_handler'] = default_http_handler(); + } + } + public function __call($name, array $args) + { + $args = isset($args[0]) ? $args[0] : []; + if (\strpos($name, 'createMultiRegion') === 0) { + return $this->createMultiRegionClient(\substr($name, 17), $args); + } + if (\strpos($name, 'create') === 0) { + return $this->createClient(\substr($name, 6), $args); + } + throw new \BadMethodCallException("Unknown method: {$name}."); + } + /** + * Get a client by name using an array of constructor options. + * + * @param string $name Service name or namespace (e.g., DynamoDb, s3). + * @param array $args Arguments to configure the client. + * + * @return AwsClientInterface + * @throws \InvalidArgumentException if any required options are missing or + * the service is not supported. + * @see Aws\AwsClient::__construct for a list of available options for args. + */ + public function createClient($name, array $args = []) + { + // Get information about the service from the manifest file. + $service = manifest($name); + $namespace = $service['namespace']; + // Instantiate the client class. + $client = "WPMailSMTP\\Vendor\\Aws\\{$namespace}\\{$namespace}Client"; + return new $client($this->mergeArgs($namespace, $service, $args)); + } + public function createMultiRegionClient($name, array $args = []) + { + // Get information about the service from the manifest file. + $service = manifest($name); + $namespace = $service['namespace']; + $klass = "WPMailSMTP\\Vendor\\Aws\\{$namespace}\\{$namespace}MultiRegionClient"; + $klass = \class_exists($klass) ? $klass : MultiRegionClient::class; + return new $klass($this->mergeArgs($namespace, $service, $args)); + } + /** + * Clone existing SDK instance with ability to pass an associative array + * of extra client settings. + * + * @param array $args + * + * @return self + */ + public function copy(array $args = []) + { + return new self($args + $this->args); + } + private function mergeArgs($namespace, array $manifest, array $args = []) + { + // Merge provided args with stored, service-specific args. + if (isset($this->args[$namespace])) { + $args += $this->args[$namespace]; + } + // Provide the endpoint prefix in the args. + if (!isset($args['service'])) { + $args['service'] = $manifest['endpoint']; + } + return $args + $this->args; + } + /** + * Determine the endpoint prefix from a client namespace. + * + * @param string $name Namespace name + * + * @return string + * @internal + * @deprecated Use the `\Aws\manifest()` function instead. + */ + public static function getEndpointPrefix($name) + { + return manifest($name)['endpoint']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Ses/Exception/SesException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Ses/Exception/SesException.php new file mode 100644 index 00000000..c31ed683 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Ses/Exception/SesException.php @@ -0,0 +1,11 @@ +getSecretKey(), \true); + return \base64_encode($version . $signature); + } + /** + * Create an SMTP password for a given IAM user's credentials. + * + * The SMTP username is the Access Key ID for the provided credentials. This + * utility method is not guaranteed to work indefinitely and is provided as + * a convenience to customers using the generateSmtpPassword method. It is + * not recommended for use in production + * + * @link https://docs.aws.amazon.com/ses/latest/DeveloperGuide/smtp-credentials.html#smtp-credentials-convert + * + * @param CredentialsInterface $creds + * @param string $region + * + * @return string + */ + public static function generateSmtpPasswordV4(CredentialsInterface $creds, $region) + { + $key = $creds->getSecretKey(); + $date = "11111111"; + $service = "ses"; + $terminal = "aws4_request"; + $message = "SendRawEmail"; + $version = 0x4; + $signature = self::sign($date, "AWS4" . $key); + $signature = self::sign($region, $signature); + $signature = self::sign($service, $signature); + $signature = self::sign($terminal, $signature); + $signature = self::sign($message, $signature); + $signatureAndVersion = \pack('c', $version) . $signature; + return \base64_encode($signatureAndVersion); + } + private static function sign($key, $message) + { + return \hash_hmac('sha256', $key, $message, \true); + } + /** + * @internal + * @codeCoverageIgnore + */ + public static function applyDocFilters(array $api, array $docs) + { + $b64 = '
    This value will be base64 encoded on your behalf.
    '; + $docs['shapes']['RawMessage']['append'] = $b64; + return [new Service($api, ApiProvider::defaultProvider()), new DocModel($docs)]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SesV2/Exception/SesV2Exception.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SesV2/Exception/SesV2Exception.php new file mode 100644 index 00000000..be94a562 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/SesV2/Exception/SesV2Exception.php @@ -0,0 +1,11 @@ +modifyTokenHeaders($request, $credentials); + $credentials = $this->getSigningCredentials($credentials); + return parent::signRequest($request, $credentials, $signingService); + } + public function presign(RequestInterface $request, CredentialsInterface $credentials, $expires, array $options = []) + { + $request = $this->modifyTokenHeaders($request, $credentials); + $credentials = $this->getSigningCredentials($credentials); + return parent::presign($request, $credentials, $expires, $options); + } + private function modifyTokenHeaders(RequestInterface $request, CredentialsInterface $credentials) + { + //The x-amz-security-token header is not supported by s3 express + $request = $request->withoutHeader('X-Amz-Security-Token'); + return $request->withHeader('x-amz-s3session-token', $credentials->getSecurityToken()); + } + private function getSigningCredentials(CredentialsInterface $credentials) + { + return new Credentials($credentials->getAccessKeyId(), $credentials->getSecretKey()); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/S3SignatureV4.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/S3SignatureV4.php new file mode 100644 index 00000000..21edd27a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/S3SignatureV4.php @@ -0,0 +1,88 @@ +hasHeader('x-amz-content-sha256')) { + $request = $request->withHeader('x-amz-content-sha256', $this->getPayload($request)); + } + $useCrt = \strpos($request->getUri()->getHost(), "accesspoint.s3-global") !== \false; + if (!$useCrt) { + if (\strpos($request->getUri()->getHost(), "s3-object-lambda")) { + return parent::signRequest($request, $credentials, "s3-object-lambda"); + } + return parent::signRequest($request, $credentials); + } + $signingService = $signingService ?: 's3'; + return $this->signWithV4a($credentials, $request, $signingService); + } + /** + * @param CredentialsInterface $credentials + * @param RequestInterface $request + * @param $signingService + * @param SigningConfigAWS|null $signingConfig + * @return RequestInterface + * + * Instantiates a separate sigv4a signing config. All services except S3 + * use double encoding. All services except S3 require path normalization. + */ + protected function signWithV4a(CredentialsInterface $credentials, RequestInterface $request, $signingService, ?SigningConfigAWS $signingConfig = null) + { + $this->verifyCRTLoaded(); + $credentials_provider = $this->createCRTStaticCredentialsProvider($credentials); + $signingConfig = new SigningConfigAWS(['algorithm' => SigningAlgorithm::SIGv4_ASYMMETRIC, 'signature_type' => SignatureType::HTTP_REQUEST_HEADERS, 'credentials_provider' => $credentials_provider, 'signed_body_value' => $this->getPayload($request), 'region' => $this->region, 'should_normalize_uri_path' => \false, 'use_double_uri_encode' => \false, 'service' => $signingService, 'date' => \time()]); + return parent::signWithV4a($credentials, $request, $signingService, $signingConfig); + } + /** + * Always add a x-amz-content-sha-256 for data integrity. + * + * {@inheritdoc} + */ + public function presign(RequestInterface $request, CredentialsInterface $credentials, $expires, array $options = []) + { + if (!$request->hasHeader('x-amz-content-sha256')) { + $request = $request->withHeader('X-Amz-Content-Sha256', $this->getPresignedPayload($request)); + } + if (\strpos($request->getUri()->getHost(), "accesspoint.s3-global")) { + $request = $request->withHeader("x-amz-region-set", "*"); + } + return parent::presign($request, $credentials, $expires, $options); + } + /** + * Override used to allow pre-signed URLs to be created for an + * in-determinate request payload. + */ + protected function getPresignedPayload(RequestInterface $request) + { + return SignatureV4::UNSIGNED_PAYLOAD; + } + /** + * Amazon S3 does not double-encode the path component in the canonical request + */ + protected function createCanonicalizedPath($path) + { + // Only remove one slash in case of keys that have a preceding slash + if (\substr($path, 0, 1) === '/') { + $path = \substr($path, 1); + } + return '/' . $path; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureInterface.php new file mode 100644 index 00000000..cbb27f29 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureInterface.php @@ -0,0 +1,36 @@ + \true, 's3control' => \true, 's3-outposts' => \true, 's3-object-lambda' => \true, 's3express' => \true]; + /** + * Resolves and signature provider and ensures a non-null return value. + * + * @param callable $provider Provider function to invoke. + * @param string $version Signature version. + * @param string $service Service name. + * @param string $region Region name. + * + * @return SignatureInterface + * @throws UnresolvedSignatureException + */ + public static function resolve(callable $provider, $version, $service, $region) + { + $result = $provider($version, $service, $region); + if ($result instanceof SignatureInterface || $result instanceof BearerTokenAuthorization) { + return $result; + } + throw new UnresolvedSignatureException("Unable to resolve a signature for {$version}/{$service}/{$region}.\n" . "Valid signature versions include v4 and anonymous."); + } + /** + * Default SDK signature provider. + * + * @return callable + */ + public static function defaultProvider() + { + return self::memoize(self::version()); + } + /** + * Creates a signature provider that caches previously created signature + * objects. The computed cache key is the concatenation of the version, + * service, and region. + * + * @param callable $provider Signature provider to wrap. + * + * @return callable + */ + public static function memoize(callable $provider) + { + $cache = []; + return function ($version, $service, $region) use(&$cache, $provider) { + $key = "({$version})({$service})({$region})"; + if (!isset($cache[$key])) { + $cache[$key] = $provider($version, $service, $region); + } + return $cache[$key]; + }; + } + /** + * Creates signature objects from known signature versions. + * + * This provider currently recognizes the following signature versions: + * + * - v4: Signature version 4. + * - anonymous: Does not sign requests. + * + * @return callable + */ + public static function version() + { + return function ($version, $service, $region) { + switch ($version) { + case 'v4-s3express': + return new S3ExpressSignature($service, $region); + case 's3v4': + case 'v4': + return !empty(self::$s3v4SignedServices[$service]) ? new S3SignatureV4($service, $region) : new SignatureV4($service, $region); + case 'v4a': + return !empty(self::$s3v4SignedServices[$service]) ? new S3SignatureV4($service, $region, ['use_v4a' => \true]) : new SignatureV4($service, $region, ['use_v4a' => \true]); + case 'v4-unsigned-body': + return !empty(self::$s3v4SignedServices[$service]) ? new S3SignatureV4($service, $region, ['unsigned-body' => 'true']) : new SignatureV4($service, $region, ['unsigned-body' => 'true']); + case 'bearer': + return new BearerTokenAuthorization(); + case 'anonymous': + return new AnonymousSignature(); + default: + return null; + } + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureTrait.php new file mode 100644 index 00000000..971e2308 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureTrait.php @@ -0,0 +1,34 @@ +cache[$k])) { + // Clear the cache when it reaches 50 entries + if (++$this->cacheSize > 50) { + $this->cache = []; + $this->cacheSize = 0; + } + $dateKey = \hash_hmac('sha256', $shortDate, "AWS4{$secretKey}", \true); + $regionKey = \hash_hmac('sha256', $region, $dateKey, \true); + $serviceKey = \hash_hmac('sha256', $service, $regionKey, \true); + $this->cache[$k] = \hash_hmac('sha256', 'aws4_request', $serviceKey, \true); + } + return $this->cache[$k]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureV4.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureV4.php new file mode 100644 index 00000000..9a8ad927 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Signature/SignatureV4.php @@ -0,0 +1,383 @@ + \true, 'content-type' => \true, 'content-length' => \true, 'expect' => \true, 'max-forwards' => \true, 'pragma' => \true, 'range' => \true, 'te' => \true, 'if-match' => \true, 'if-none-match' => \true, 'if-modified-since' => \true, 'if-unmodified-since' => \true, 'if-range' => \true, 'accept' => \true, 'authorization' => \true, 'proxy-authorization' => \true, 'from' => \true, 'referer' => \true, 'user-agent' => \true, 'X-Amz-User-Agent' => \true, 'x-amzn-trace-id' => \true, 'aws-sdk-invocation-id' => \true, 'aws-sdk-retry' => \true]; + } + /** + * @param string $service Service name to use when signing + * @param string $region Region name to use when signing + * @param array $options Array of configuration options used when signing + * - unsigned-body: Flag to make request have unsigned payload. + * Unsigned body is used primarily for streaming requests. + */ + public function __construct($service, $region, array $options = []) + { + $this->service = $service; + $this->region = $region; + $this->unsigned = isset($options['unsigned-body']) ? $options['unsigned-body'] : \false; + $this->useV4a = isset($options['use_v4a']) && $options['use_v4a'] === \true; + } + /** + * {@inheritdoc} + */ + public function signRequest(RequestInterface $request, CredentialsInterface $credentials, $signingService = null) + { + $ldt = \gmdate(self::ISO8601_BASIC); + $sdt = \substr($ldt, 0, 8); + $parsed = $this->parseRequest($request); + $parsed['headers']['X-Amz-Date'] = [$ldt]; + if ($token = $credentials->getSecurityToken()) { + $parsed['headers']['X-Amz-Security-Token'] = [$token]; + } + $service = isset($signingService) ? $signingService : $this->service; + if ($this->useV4a) { + return $this->signWithV4a($credentials, $request, $service); + } + $cs = $this->createScope($sdt, $this->region, $service); + $payload = $this->getPayload($request); + if ($payload == self::UNSIGNED_PAYLOAD) { + $parsed['headers'][self::AMZ_CONTENT_SHA256_HEADER] = [$payload]; + } + $context = $this->createContext($parsed, $payload); + $toSign = $this->createStringToSign($ldt, $cs, $context['creq']); + $signingKey = $this->getSigningKey($sdt, $this->region, $service, $credentials->getSecretKey()); + $signature = \hash_hmac('sha256', $toSign, $signingKey); + $parsed['headers']['Authorization'] = ["AWS4-HMAC-SHA256 " . "Credential={$credentials->getAccessKeyId()}/{$cs}, " . "SignedHeaders={$context['headers']}, Signature={$signature}"]; + return $this->buildRequest($parsed); + } + /** + * Get the headers that were used to pre-sign the request. + * Used for the X-Amz-SignedHeaders header. + * + * @param array $headers + * @return array + */ + private function getPresignHeaders(array $headers) + { + $presignHeaders = []; + $blacklist = $this->getHeaderBlacklist(); + foreach ($headers as $name => $value) { + $lName = \strtolower($name); + if (!isset($blacklist[$lName]) && $name !== self::AMZ_CONTENT_SHA256_HEADER) { + $presignHeaders[] = $lName; + } + } + return $presignHeaders; + } + /** + * {@inheritdoc} + */ + public function presign(RequestInterface $request, CredentialsInterface $credentials, $expires, array $options = []) + { + $startTimestamp = isset($options['start_time']) ? $this->convertToTimestamp($options['start_time'], null) : \time(); + $expiresTimestamp = $this->convertToTimestamp($expires, $startTimestamp); + if ($this->useV4a) { + return $this->presignWithV4a($request, $credentials, $this->convertExpires($expiresTimestamp, $startTimestamp)); + } + $parsed = $this->createPresignedRequest($request, $credentials); + $payload = $this->getPresignedPayload($request); + $httpDate = \gmdate(self::ISO8601_BASIC, $startTimestamp); + $shortDate = \substr($httpDate, 0, 8); + $scope = $this->createScope($shortDate, $this->region, $this->service); + $credential = $credentials->getAccessKeyId() . '/' . $scope; + if ($credentials->getSecurityToken()) { + unset($parsed['headers']['X-Amz-Security-Token']); + } + $parsed['query']['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256'; + $parsed['query']['X-Amz-Credential'] = $credential; + $parsed['query']['X-Amz-Date'] = \gmdate('Ymd\\THis\\Z', $startTimestamp); + $parsed['query']['X-Amz-SignedHeaders'] = \implode(';', $this->getPresignHeaders($parsed['headers'])); + $parsed['query']['X-Amz-Expires'] = $this->convertExpires($expiresTimestamp, $startTimestamp); + $context = $this->createContext($parsed, $payload); + $stringToSign = $this->createStringToSign($httpDate, $scope, $context['creq']); + $key = $this->getSigningKey($shortDate, $this->region, $this->service, $credentials->getSecretKey()); + $parsed['query']['X-Amz-Signature'] = \hash_hmac('sha256', $stringToSign, $key); + return $this->buildRequest($parsed); + } + /** + * Converts a POST request to a GET request by moving POST fields into the + * query string. + * + * Useful for pre-signing query protocol requests. + * + * @param RequestInterface $request Request to clone + * + * @return RequestInterface + * @throws \InvalidArgumentException if the method is not POST + */ + public static function convertPostToGet(RequestInterface $request, $additionalQueryParams = "") + { + if ($request->getMethod() !== 'POST') { + throw new \InvalidArgumentException('Expected a POST request but ' . 'received a ' . $request->getMethod() . ' request.'); + } + $sr = $request->withMethod('GET')->withBody(Psr7\Utils::streamFor(''))->withoutHeader('Content-Type')->withoutHeader('Content-Length'); + // Move POST fields to the query if they are present + if ($request->getHeaderLine('Content-Type') === 'application/x-www-form-urlencoded') { + $body = (string) $request->getBody() . $additionalQueryParams; + $sr = $sr->withUri($sr->getUri()->withQuery($body)); + } + return $sr; + } + protected function getPayload(RequestInterface $request) + { + if ($this->unsigned && $request->getUri()->getScheme() == 'https') { + return self::UNSIGNED_PAYLOAD; + } + // Calculate the request signature payload + if ($request->hasHeader(self::AMZ_CONTENT_SHA256_HEADER)) { + // Handle streaming operations (e.g. Glacier.UploadArchive) + return $request->getHeaderLine(self::AMZ_CONTENT_SHA256_HEADER); + } + if (!$request->getBody()->isSeekable()) { + throw new CouldNotCreateChecksumException('sha256'); + } + try { + return Psr7\Utils::hash($request->getBody(), 'sha256'); + } catch (\Exception $e) { + throw new CouldNotCreateChecksumException('sha256', $e); + } + } + protected function getPresignedPayload(RequestInterface $request) + { + return $this->getPayload($request); + } + protected function createCanonicalizedPath($path) + { + $doubleEncoded = \rawurlencode(\ltrim($path, '/')); + return '/' . \str_replace('%2F', '/', $doubleEncoded); + } + private function createStringToSign($longDate, $credentialScope, $creq) + { + $hash = \hash('sha256', $creq); + return "AWS4-HMAC-SHA256\n{$longDate}\n{$credentialScope}\n{$hash}"; + } + private function createPresignedRequest(RequestInterface $request, CredentialsInterface $credentials) + { + $parsedRequest = $this->parseRequest($request); + // Make sure to handle temporary credentials + if ($token = $credentials->getSecurityToken()) { + $parsedRequest['headers']['X-Amz-Security-Token'] = [$token]; + } + return $this->moveHeadersToQuery($parsedRequest); + } + /** + * @param array $parsedRequest + * @param string $payload Hash of the request payload + * @return array Returns an array of context information + */ + private function createContext(array $parsedRequest, $payload) + { + $blacklist = $this->getHeaderBlacklist(); + // Normalize the path as required by SigV4 + $canon = $parsedRequest['method'] . "\n" . $this->createCanonicalizedPath($parsedRequest['path']) . "\n" . $this->getCanonicalizedQuery($parsedRequest['query']) . "\n"; + // Case-insensitively aggregate all of the headers. + $aggregate = []; + foreach ($parsedRequest['headers'] as $key => $values) { + $key = \strtolower($key); + if (!isset($blacklist[$key])) { + foreach ($values as $v) { + $aggregate[$key][] = $v; + } + } + } + \ksort($aggregate); + $canonHeaders = []; + foreach ($aggregate as $k => $v) { + if (\count($v) > 0) { + \sort($v); + } + $canonHeaders[] = $k . ':' . \preg_replace('/\\s+/', ' ', \implode(',', $v)); + } + $signedHeadersString = \implode(';', \array_keys($aggregate)); + $canon .= \implode("\n", $canonHeaders) . "\n\n" . $signedHeadersString . "\n" . $payload; + return ['creq' => $canon, 'headers' => $signedHeadersString]; + } + private function getCanonicalizedQuery(array $query) + { + unset($query['X-Amz-Signature']); + if (!$query) { + return ''; + } + $qs = ''; + \ksort($query); + foreach ($query as $k => $v) { + if (!\is_array($v)) { + $qs .= \rawurlencode($k) . '=' . \rawurlencode($v !== null ? $v : '') . '&'; + } else { + \sort($v); + foreach ($v as $value) { + $qs .= \rawurlencode($k) . '=' . \rawurlencode($value !== null ? $value : '') . '&'; + } + } + } + return \substr($qs, 0, -1); + } + private function convertToTimestamp($dateValue, $relativeTimeBase = null) + { + if ($dateValue instanceof \DateTimeInterface) { + $timestamp = $dateValue->getTimestamp(); + } elseif (!\is_numeric($dateValue)) { + $timestamp = \strtotime($dateValue, $relativeTimeBase === null ? \time() : $relativeTimeBase); + } else { + $timestamp = $dateValue; + } + return $timestamp; + } + private function convertExpires($expiresTimestamp, $startTimestamp) + { + $duration = $expiresTimestamp - $startTimestamp; + // Ensure that the duration of the signature is not longer than a week + if ($duration > 604800) { + throw new \InvalidArgumentException('The expiration date of a ' . 'signature version 4 presigned URL must be less than one ' . 'week'); + } + return $duration; + } + private function moveHeadersToQuery(array $parsedRequest) + { + //x-amz-user-agent shouldn't be put in a query param + unset($parsedRequest['headers']['X-Amz-User-Agent']); + foreach ($parsedRequest['headers'] as $name => $header) { + $lname = \strtolower($name); + if (\substr($lname, 0, 5) == 'x-amz') { + $parsedRequest['query'][$name] = $header; + } + $blacklist = $this->getHeaderBlacklist(); + if (isset($blacklist[$lname]) || $lname === \strtolower(self::AMZ_CONTENT_SHA256_HEADER)) { + unset($parsedRequest['headers'][$name]); + } + } + return $parsedRequest; + } + private function parseRequest(RequestInterface $request) + { + // Clean up any previously set headers. + /** @var RequestInterface $request */ + $request = $request->withoutHeader('X-Amz-Date')->withoutHeader('Date')->withoutHeader('Authorization'); + $uri = $request->getUri(); + return ['method' => $request->getMethod(), 'path' => $uri->getPath(), 'query' => Psr7\Query::parse($uri->getQuery()), 'uri' => $uri, 'headers' => $request->getHeaders(), 'body' => $request->getBody(), 'version' => $request->getProtocolVersion()]; + } + private function buildRequest(array $req) + { + if ($req['query']) { + $req['uri'] = $req['uri']->withQuery(Psr7\Query::build($req['query'])); + } + return new Psr7\Request($req['method'], $req['uri'], $req['headers'], $req['body'], $req['version']); + } + protected function verifyCRTLoaded() + { + if (!\extension_loaded('awscrt')) { + throw new CommonRuntimeException("AWS Common Runtime for PHP is required to use Signature V4A" . ". Please install it using the instructions found at" . " https://github.com/aws/aws-sdk-php/blob/master/CRT_INSTRUCTIONS.md"); + } + } + protected function createCRTStaticCredentialsProvider($credentials) + { + return new StaticCredentialsProvider(['access_key_id' => $credentials->getAccessKeyId(), 'secret_access_key' => $credentials->getSecretKey(), 'session_token' => $credentials->getSecurityToken()]); + } + private function removeIllegalV4aHeaders(&$request) + { + $illegalV4aHeaders = [self::AMZ_CONTENT_SHA256_HEADER, "aws-sdk-invocation-id", "aws-sdk-retry", 'x-amz-region-set']; + $storedHeaders = []; + foreach ($illegalV4aHeaders as $header) { + if ($request->hasHeader($header)) { + $storedHeaders[$header] = $request->getHeader($header); + $request = $request->withoutHeader($header); + } + } + return $storedHeaders; + } + private function CRTRequestFromGuzzleRequest($request) + { + return new Request( + $request->getMethod(), + (string) $request->getUri(), + [], + //leave empty as the query is parsed from the uri object + \array_map(function ($header) { + return $header[0]; + }, $request->getHeaders()) + ); + } + /** + * @param CredentialsInterface $credentials + * @param RequestInterface $request + * @param $signingService + * @param SigningConfigAWS|null $signingConfig + * @return RequestInterface + */ + protected function signWithV4a(CredentialsInterface $credentials, RequestInterface $request, $signingService, ?SigningConfigAWS $signingConfig = null) + { + $this->verifyCRTLoaded(); + $signingConfig = $signingConfig ?? new SigningConfigAWS(['algorithm' => SigningAlgorithm::SIGv4_ASYMMETRIC, 'signature_type' => SignatureType::HTTP_REQUEST_HEADERS, 'credentials_provider' => $this->createCRTStaticCredentialsProvider($credentials), 'signed_body_value' => $this->getPayload($request), 'should_normalize_uri_path' => \true, 'use_double_uri_encode' => \true, 'region' => $this->region, 'service' => $signingService, 'date' => \time()]); + $removedIllegalHeaders = $this->removeIllegalV4aHeaders($request); + $http_request = $this->CRTRequestFromGuzzleRequest($request); + Signing::signRequestAws(Signable::fromHttpRequest($http_request), $signingConfig, function ($signing_result, $error_code) use(&$http_request) { + $signing_result->applyToHttpRequest($http_request); + }); + foreach ($removedIllegalHeaders as $header => $value) { + $request = $request->withHeader($header, $value); + } + $sigV4AHeaders = $http_request->headers(); + foreach ($sigV4AHeaders->toArray() as $h => $v) { + $request = $request->withHeader($h, $v); + } + return $request; + } + protected function presignWithV4a(RequestInterface $request, CredentialsInterface $credentials, $expires) + { + $this->verifyCRTLoaded(); + $credentials_provider = $this->createCRTStaticCredentialsProvider($credentials); + $signingConfig = new SigningConfigAWS(['algorithm' => SigningAlgorithm::SIGv4_ASYMMETRIC, 'signature_type' => SignatureType::HTTP_REQUEST_QUERY_PARAMS, 'credentials_provider' => $credentials_provider, 'signed_body_value' => $this->getPresignedPayload($request), 'region' => "*", 'service' => $this->service, 'date' => \time(), 'expiration_in_seconds' => $expires]); + $this->removeIllegalV4aHeaders($request); + foreach ($this->getHeaderBlacklist() as $headerName => $headerValue) { + if ($request->hasHeader($headerName)) { + $request = $request->withoutHeader($headerName); + } + } + $http_request = $this->CRTRequestFromGuzzleRequest($request); + Signing::signRequestAws(Signable::fromHttpRequest($http_request), $signingConfig, function ($signing_result, $error_code) use(&$http_request) { + $signing_result->applyToHttpRequest($http_request); + }); + return $request->withUri(new Psr7\Uri($http_request->pathAndQuery())); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/StreamRequestPayloadMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/StreamRequestPayloadMiddleware.php new file mode 100644 index 00000000..7ba6c131 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/StreamRequestPayloadMiddleware.php @@ -0,0 +1,67 @@ +nextHandler = $nextHandler; + $this->service = $service; + } + public function __invoke(CommandInterface $command, RequestInterface $request) + { + $nextHandler = $this->nextHandler; + $operation = $this->service->getOperation($command->getName()); + $contentLength = $request->getHeader('content-length'); + $hasStreaming = \false; + $requiresLength = \false; + // Check if any present input member is a stream and requires the + // content length + foreach ($operation->getInput()->getMembers() as $name => $member) { + if (!empty($member['streaming']) && isset($command[$name])) { + $hasStreaming = \true; + if (!empty($member['requiresLength'])) { + $requiresLength = \true; + } + } + } + if ($hasStreaming) { + // Add 'transfer-encoding' header if payload size not required to + // to be calculated and not already known + if (empty($requiresLength) && empty($contentLength) && isset($operation['authtype']) && $operation['authtype'] == 'v4-unsigned-body') { + $request = $request->withHeader('transfer-encoding', 'chunked'); + // Otherwise, make sure 'content-length' header is added + } else { + if (empty($contentLength)) { + $size = $request->getBody()->getSize(); + if (\is_null($size)) { + throw new IncalculablePayloadException('Payload' . ' content length is required and can not be' . ' calculated.'); + } + $request = $request->withHeader('content-length', $size); + } + } + } + return $nextHandler($command, $request); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/Exception/StsException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/Exception/StsException.php new file mode 100644 index 00000000..4ac64fd4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/Exception/StsException.php @@ -0,0 +1,11 @@ +endpointsType = \strtolower($endpointsType); + $this->isFallback = $isFallback; + if (!\in_array($this->endpointsType, ['legacy', 'regional'])) { + throw new \InvalidArgumentException("Configuration parameter must either be 'legacy' or 'regional'."); + } + } + /** + * {@inheritdoc} + */ + public function getEndpointsType() + { + return $this->endpointsType; + } + /** + * {@inheritdoc} + */ + public function toArray() + { + return ['endpoints_type' => $this->getEndpointsType()]; + } + public function isFallback() + { + return $this->isFallback; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/ConfigurationInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/ConfigurationInterface.php new file mode 100644 index 00000000..19ede016 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/ConfigurationInterface.php @@ -0,0 +1,22 @@ + + * use Aws\Sts\RegionalEndpoints\ConfigurationProvider; + * $provider = ConfigurationProvider::defaultProvider(); + * // Returns a ConfigurationInterface or throws. + * $config = $provider()->wait(); + * + * + * Configuration providers can be composed to create configuration using + * conditional logic that can create different configurations in different + * environments. You can compose multiple providers into a single provider using + * {@see \Aws\Sts\RegionalEndpoints\ConfigurationProvider::chain}. This function + * accepts providers as variadic arguments and returns a new function that will + * invoke each provider until a successful configuration is returned. + * + * + * // First try an INI file at this location. + * $a = ConfigurationProvider::ini(null, '/path/to/file.ini'); + * // Then try an INI file at this location. + * $b = ConfigurationProvider::ini(null, '/path/to/other-file.ini'); + * // Then try loading from environment variables. + * $c = ConfigurationProvider::env(); + * // Combine the three providers together. + * $composed = ConfigurationProvider::chain($a, $b, $c); + * // Returns a promise that is fulfilled with a configuration or throws. + * $promise = $composed(); + * // Wait on the configuration to resolve. + * $config = $promise->wait(); + * + */ +class ConfigurationProvider extends AbstractConfigurationProvider implements ConfigurationProviderInterface +{ + const DEFAULT_ENDPOINTS_TYPE = 'legacy'; + const ENV_ENDPOINTS_TYPE = 'AWS_STS_REGIONAL_ENDPOINTS'; + const ENV_PROFILE = 'AWS_PROFILE'; + const INI_ENDPOINTS_TYPE = 'sts_regional_endpoints'; + public static $cacheKey = 'aws_sts_regional_endpoints_config'; + protected static $interfaceClass = ConfigurationInterface::class; + protected static $exceptionClass = ConfigurationException::class; + /** + * Create a default config provider that first checks for environment + * variables, then checks for a specified profile in the environment-defined + * config file location (env variable is 'AWS_CONFIG_FILE', file location + * defaults to ~/.aws/config), then checks for the "default" profile in the + * environment-defined config file location, and failing those uses a default + * fallback set of configuration options. + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided config options. + * + * @param array $config + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $configProviders = [self::env()]; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] != \false) { + $configProviders[] = self::ini(); + } + $configProviders[] = self::fallback(); + $memo = self::memoize(\call_user_func_array([ConfigurationProvider::class, 'chain'], $configProviders)); + if (isset($config['sts_regional_endpoints']) && $config['sts_regional_endpoints'] instanceof CacheInterface) { + return self::cache($memo, $config['sts_regional_endpoints'], self::$cacheKey); + } + return $memo; + } + /** + * Provider that creates config from environment variables. + * + * @return callable + */ + public static function env() + { + return function () { + // Use config from environment variables, if available + $endpointsType = \getenv(self::ENV_ENDPOINTS_TYPE); + if (!empty($endpointsType)) { + return Promise\Create::promiseFor(new Configuration($endpointsType)); + } + return self::reject('Could not find environment variable config' . ' in ' . self::ENV_ENDPOINTS_TYPE); + }; + } + /** + * Fallback config options when other sources are not set. + * + * @return callable + */ + public static function fallback() + { + return function () { + return Promise\Create::promiseFor(new Configuration(self::DEFAULT_ENDPOINTS_TYPE, \true)); + }; + } + /** + * Config provider that creates config using a config file whose location + * is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to + * ~/.aws/config if not specified + * + * @param string|null $profile Profile to use. If not specified will use + * the "default" profile. + * @param string|null $filename If provided, uses a custom filename rather + * than looking in the default directory. + * + * @return callable + */ + public static function ini($profile = null, $filename = null) + { + $filename = $filename ?: self::getDefaultConfigFilename(); + $profile = $profile ?: (\getenv(self::ENV_PROFILE) ?: 'default'); + return function () use($profile, $filename) { + if (!@\is_readable($filename)) { + return self::reject("Cannot read configuration from {$filename}"); + } + $data = \WPMailSMTP\Vendor\Aws\parse_ini_file($filename, \true); + if ($data === \false) { + return self::reject("Invalid config file: {$filename}"); + } + if (!isset($data[$profile])) { + return self::reject("'{$profile}' not found in config file"); + } + if (!isset($data[$profile][self::INI_ENDPOINTS_TYPE])) { + return self::reject("Required STS regional endpoints config values\n not present in INI profile '{$profile}' ({$filename})"); + } + return Promise\Create::promiseFor(new Configuration($data[$profile][self::INI_ENDPOINTS_TYPE])); + }; + } + /** + * Unwraps a configuration object in whatever valid form it is in, + * always returning a ConfigurationInterface object. + * + * @param mixed $config + * @return ConfigurationInterface + * @throws \InvalidArgumentException + */ + public static function unwrap($config) + { + if (\is_callable($config)) { + $config = $config(); + } + if ($config instanceof PromiseInterface) { + $config = $config->wait(); + } + if ($config instanceof ConfigurationInterface) { + return $config; + } + if (\is_string($config)) { + return new Configuration($config); + } + if (\is_array($config) && isset($config['endpoints_type'])) { + return new Configuration($config['endpoints_type']); + } + throw new \InvalidArgumentException('Not a valid STS regional endpoints ' . 'configuration argument.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/Exception/ConfigurationException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/Exception/ConfigurationException.php new file mode 100644 index 00000000..1bfe5482 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Sts/RegionalEndpoints/Exception/ConfigurationException.php @@ -0,0 +1,13 @@ +addBuiltIns($args); + parent::__construct($args); + } + /** + * Creates credentials from the result of an STS operations + * + * @param Result $result Result of an STS operation + * + * @return Credentials + * @throws \InvalidArgumentException if the result contains no credentials + */ + public function createCredentials(Result $result, $source = null) + { + if (!$result->hasKey('Credentials')) { + throw new \InvalidArgumentException('Result contains no credentials'); + } + $accountId = null; + if ($result->hasKey('AssumedRoleUser')) { + $parsedArn = ArnParser::parse($result->get('AssumedRoleUser')['Arn']); + $accountId = $parsedArn->getAccountId(); + } elseif ($result->hasKey('FederatedUser')) { + $parsedArn = ArnParser::parse($result->get('FederatedUser')['Arn']); + $accountId = $parsedArn->getAccountId(); + } + $credentials = $result['Credentials']; + $expiration = isset($credentials['Expiration']) && $credentials['Expiration'] instanceof \DateTimeInterface ? (int) $credentials['Expiration']->format('U') : null; + return new Credentials($credentials['AccessKeyId'], $credentials['SecretAccessKey'], isset($credentials['SessionToken']) ? $credentials['SessionToken'] : null, $expiration, $accountId, $source); + } + /** + * Adds service-specific client built-in value + * + * @return void + */ + private function addBuiltIns($args) + { + $key = 'AWS::STS::UseGlobalEndpoint'; + $result = $args['sts_regional_endpoints'] instanceof \Closure ? $args['sts_regional_endpoints']()->wait() : $args['sts_regional_endpoints']; + if (\is_string($result)) { + if ($result === 'regional') { + $value = \false; + } else { + if ($result === 'legacy') { + $value = \true; + } else { + return; + } + } + } else { + if ($result->getEndpointsType() === 'regional') { + $value = \false; + } else { + $value = \true; + } + } + $this->clientBuiltIns[$key] = $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/BearerTokenAuthorization.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/BearerTokenAuthorization.php new file mode 100644 index 00000000..4646eddb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/BearerTokenAuthorization.php @@ -0,0 +1,29 @@ +getToken())) { + throw new InvalidArgumentException("Cannot authorize a request with an empty token"); + } + $accessToken = $token->getToken(); + return $request->withHeader('Authorization', "Bearer {$accessToken}"); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/ParsesIniTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/ParsesIniTrait.php new file mode 100644 index 00000000..d7cec2ab --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/ParsesIniTrait.php @@ -0,0 +1,41 @@ + $profile) { + // standardize config profile names + $name = \str_replace('profile ', '', $name); + $profileData[$name] = $profile; + } + return $profileData; + } + /** + * Gets the environment's HOME directory if available. + * + * @return null|string + */ + private static function getHomeDir() + { + // On Linux/Unix-like systems, use the HOME environment variable + if ($homeDir = \getenv('HOME')) { + return $homeDir; + } + // Get the HOMEDRIVE and HOMEPATH values for Windows hosts + $homeDrive = \getenv('HOMEDRIVE'); + $homePath = \getenv('HOMEPATH'); + return $homeDrive && $homePath ? $homeDrive . $homePath : null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/RefreshableTokenProviderInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/RefreshableTokenProviderInterface.php new file mode 100644 index 00000000..0ab3ad4c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/RefreshableTokenProviderInterface.php @@ -0,0 +1,23 @@ +refreshToken = $refreshToken; + $this->clientId = $clientId; + $this->clientSecret = $clientSecret; + $this->registrationExpiresAt = $registrationExpiresAt; + $this->region = $region; + $this->startUrl = $startUrl; + } + /** + * @return bool + */ + public function isExpired() + { + if (isset($this->registrationExpiresAt) && \time() >= $this->registrationExpiresAt) { + return \false; + } + return $this->expires !== null && \time() >= $this->expires; + } + /** + * @return string|null + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + /** + * @return string|null + */ + public function getClientId() + { + return $this->clientId; + } + /** + * @return string|null + */ + public function getClientSecret() + { + return $this->clientSecret; + } + /** + * @return int|null + */ + public function getRegistrationExpiresAt() + { + return $this->registrationExpiresAt; + } + /** + * @return string|null + */ + public function getRegion() + { + return $this->region; + } + /** + * @return string|null + */ + public function getStartUrl() + { + return $this->startUrl; + } + /** + * Creates an instance of SsoToken from a token data. + * + * @param $tokenData + * + * @return SsoToken + */ + public static function fromTokenData($tokenData) : SsoToken + { + return new SsoToken($tokenData['accessToken'], \strtotime($tokenData['expiresAt']), isset($tokenData['refreshToken']) ? $tokenData['refreshToken'] : null, isset($tokenData['clientId']) ? $tokenData['clientId'] : null, isset($tokenData['clientSecret']) ? $tokenData['clientSecret'] : null, isset($tokenData['registrationExpiresAt']) ? $tokenData['registrationExpiresAt'] : null, isset($tokenData['region']) ? $tokenData['region'] : null, isset($tokenData['startUrl']) ? $tokenData['startUrl'] : null); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/SsoTokenProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/SsoTokenProvider.php new file mode 100644 index 00000000..884c174e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/SsoTokenProvider.php @@ -0,0 +1,225 @@ +profileName = $this->resolveProfileName($profileName); + $this->configFilePath = $this->resolveConfigFile($configFilePath); + $this->ssoOidcClient = $ssoOidcClient; + } + /** + * This method resolves the profile name to be used. The + * profile provided as instantiation argument takes precedence, + * followed by AWS_PROFILE env variable, otherwise `default` is + * used. + * + * @param string|null $argProfileName The profile provided as argument. + * + * @return string + */ + private function resolveProfileName($argProfileName) : string + { + if (empty($argProfileName)) { + return \getenv(self::ENV_PROFILE) ?: 'default'; + } else { + return $argProfileName; + } + } + /** + * This method resolves the config file from where the profiles + * are going to be loaded from. If $argFileName is not empty then, + * it takes precedence over the default config file location. + * + * @param string|null $argConfigFilePath The config path provided as argument. + * + * @return string + */ + private function resolveConfigFile($argConfigFilePath) : string + { + if (empty($argConfigFilePath)) { + return self::getHomeDir() . '/.aws/config'; + } else { + return $argConfigFilePath; + } + } + /** + * Loads cached sso credentials. + * + * @return Promise\PromiseInterface + */ + public function __invoke() + { + return Promise\Coroutine::of(function () { + if (empty($this->configFilePath) || !\is_readable($this->configFilePath)) { + throw new TokenException("Cannot read profiles from {$this->configFilePath}"); + } + $profiles = self::loadProfiles($this->configFilePath); + if (!isset($profiles[$this->profileName])) { + throw new TokenException("Profile `{$this->profileName}` does not exist in {$this->configFilePath}."); + } + $profile = $profiles[$this->profileName]; + if (empty($profile['sso_session'])) { + throw new TokenException("Profile `{$this->profileName}` in {$this->configFilePath} must contain an sso_session."); + } + $ssoSessionName = $profile['sso_session']; + $this->ssoSessionName = $ssoSessionName; + $profileSsoSession = 'sso-session ' . $ssoSessionName; + if (empty($profiles[$profileSsoSession])) { + throw new TokenException("Sso session `{$ssoSessionName}` does not exist in {$this->configFilePath}"); + } + $sessionProfileData = $profiles[$profileSsoSession]; + foreach (['sso_start_url', 'sso_region'] as $requiredProp) { + if (empty($sessionProfileData[$requiredProp])) { + throw new TokenException("Sso session `{$ssoSessionName}` in {$this->configFilePath} is missing the required property `{$requiredProp}`"); + } + } + $tokenData = $this->refresh(); + $tokenLocation = self::getTokenLocation($ssoSessionName); + $this->validateTokenData($tokenLocation, $tokenData); + $ssoToken = SsoToken::fromTokenData($tokenData); + // To make sure the token is not expired + if ($ssoToken->isExpired()) { + throw new TokenException("Cached SSO token returned an expired token."); + } + (yield $ssoToken); + }); + } + /** + * This method attempt to refresh when possible. + * If a refresh is not possible then it just returns + * the current token data as it is. + * + * @return array + * @throws TokenException + */ + public function refresh() : array + { + $tokenLocation = self::getTokenLocation($this->ssoSessionName); + $tokenData = $this->getTokenData($tokenLocation); + if (!$this->shouldAttemptRefresh()) { + return $tokenData; + } + if (null === $this->ssoOidcClient) { + throw new TokenException("Cannot refresh this token without an 'ssooidcClient' "); + } + foreach (['clientId', 'clientSecret', 'refreshToken'] as $requiredProp) { + if (empty($tokenData[$requiredProp])) { + throw new TokenException("Cannot refresh this token without `{$requiredProp}` being set"); + } + } + $response = $this->ssoOidcClient->createToken([ + 'clientId' => $tokenData['clientId'], + 'clientSecret' => $tokenData['clientSecret'], + 'grantType' => 'refresh_token', + // REQUIRED + 'refreshToken' => $tokenData['refreshToken'], + ]); + if ($response['@metadata']['statusCode'] !== 200) { + throw new TokenException('Unable to create a new sso token'); + } + $tokenData['accessToken'] = $response['accessToken']; + $tokenData['expiresAt'] = \time() + $response['expiresIn']; + $tokenData['refreshToken'] = $response['refreshToken']; + return $this->writeNewTokenDataToDisk($tokenData, $tokenLocation); + } + /** + * This method checks for whether a token refresh should happen. + * It will return true just if more than 30 seconds has happened + * since last refresh, and if the expiration is within a 5-minutes + * window from the current time. + * + * @return bool + */ + public function shouldAttemptRefresh() : bool + { + $tokenLocation = self::getTokenLocation($this->ssoSessionName); + $tokenData = $this->getTokenData($tokenLocation); + if (empty($tokenData['expiresAt'])) { + throw new TokenException("Token file at {$tokenLocation} must contain an expiration date"); + } + $tokenExpiresAt = \strtotime($tokenData['expiresAt']); + $lastRefreshAt = \filemtime($tokenLocation); + $now = \time(); + // If last refresh happened after 30 seconds + // and if the token expiration is in the 5 minutes window + return $now - $lastRefreshAt > self::REFRESH_ATTEMPT_WINDOW_IN_SECS && $tokenExpiresAt - $now < self::REFRESH_WINDOW_IN_SECS; + } + /** + * @param $sso_session + * @return string + */ + public static function getTokenLocation($sso_session) : string + { + return self::getHomeDir() . '/.aws/sso/cache/' . \mb_convert_encoding(\sha1($sso_session), "UTF-8") . ".json"; + } + /** + * @param $tokenLocation + * @return array + */ + function getTokenData($tokenLocation) : array + { + if (empty($tokenLocation) || !\is_readable($tokenLocation)) { + throw new TokenException("Unable to read token file at {$tokenLocation}"); + } + return \json_decode(\file_get_contents($tokenLocation), \true); + } + /** + * @param $tokenData + * @param $tokenLocation + * @return mixed + */ + private function validateTokenData($tokenLocation, $tokenData) + { + foreach (['accessToken', 'expiresAt'] as $requiredProp) { + if (empty($tokenData[$requiredProp])) { + throw new TokenException("Token file at {$tokenLocation} must contain the required property `{$requiredProp}`"); + } + } + $expiration = \strtotime($tokenData['expiresAt']); + if ($expiration === \false) { + throw new TokenException("Cached SSO token returned an invalid expiration"); + } elseif ($expiration < \time()) { + throw new TokenException("Cached SSO token returned an expired token"); + } + return $tokenData; + } + /** + * @param array $tokenData + * @param string $tokenLocation + * + * @return array + */ + private function writeNewTokenDataToDisk(array $tokenData, $tokenLocation) : array + { + $tokenData['expiresAt'] = \gmdate('Y-m-d\\TH:i:s\\Z', $tokenData['expiresAt']); + \file_put_contents($tokenLocation, \json_encode(\array_filter($tokenData))); + return $tokenData; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/Token.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/Token.php new file mode 100644 index 00000000..3d6ea4e6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/Token.php @@ -0,0 +1,94 @@ +token = $token; + $this->expires = $expires; + } + /** + * Sets the state of a token object + * + * @param array $state array containing 'token' and 'expires' + */ + public static function __set_state(array $state) + { + return new self($state['token'], $state['expires']); + } + /** + * @return string + */ + public function getToken() + { + return $this->token; + } + /** + * @return int + */ + public function getExpiration() + { + return $this->expires; + } + /** + * @return bool + */ + public function isExpired() + { + return $this->expires !== null && \time() >= $this->expires; + } + /** + * @return array + */ + public function toArray() + { + return ['token' => $this->token, 'expires' => $this->expires]; + } + /** + * @return string + */ + public function serialize() + { + return \json_encode($this->__serialize()); + } + /** + * Sets the state of the object from serialized json data + */ + public function unserialize($serialized) + { + $data = \json_decode($serialized, \true); + $this->__unserialize($data); + } + /** + * @return array + */ + public function __serialize() + { + return $this->toArray(); + } + /** + * Sets the state of this object from an array + */ + public function __unserialize($data) + { + $this->token = $data['token']; + $this->expires = $data['expires']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/TokenAuthorization.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/TokenAuthorization.php new file mode 100644 index 00000000..adb5c773 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Token/TokenAuthorization.php @@ -0,0 +1,21 @@ + + * use Aws\Token\TokenProvider; + * $provider = TokenProvider::defaultProvider(); + * // Returns a TokenInterface or throws. + * $token = $provider()->wait(); + * + * + * Token providers can be composed to create a token using conditional + * logic that can create different tokens in different environments. You + * can compose multiple providers into a single provider using + * {@see Aws\Token\TokenProvider::chain}. This function accepts + * providers as variadic arguments and returns a new function that will invoke + * each provider until a token is successfully returned. + */ +class TokenProvider +{ + use ParsesIniTrait; + const ENV_PROFILE = 'AWS_PROFILE'; + /** + * Create a default token provider tha checks for cached a SSO token from + * the CLI + * + * This provider is automatically wrapped in a memoize function that caches + * previously provided tokens. + * + * @param array $config Optional array of token provider options. + * + * @return callable + */ + public static function defaultProvider(array $config = []) + { + $cacheable = ['sso']; + $defaultChain = []; + if (!isset($config['use_aws_shared_config_files']) || $config['use_aws_shared_config_files'] !== \false) { + $profileName = \getenv(self::ENV_PROFILE) ?: 'default'; + $defaultChain['sso'] = self::sso($profileName, self::getHomeDir() . '/.aws/config', $config); + } + if (isset($config['token']) && $config['token'] instanceof CacheInterface) { + foreach ($cacheable as $provider) { + if (isset($defaultChain[$provider])) { + $defaultChain[$provider] = self::cache($defaultChain[$provider], $config['token'], 'aws_cached_' . $provider . '_token'); + } + } + } + return self::memoize(\call_user_func_array([TokenProvider::class, 'chain'], \array_values($defaultChain))); + } + /** + * Create a token provider function from a static token. + * + * @param TokenInterface $token + * + * @return callable + */ + public static function fromToken(TokenInterface $token) + { + $promise = Promise\Create::promiseFor($token); + return function () use($promise) { + return $promise; + }; + } + /** + * Creates an aggregate token provider that invokes the provided + * variadic providers one after the other until a provider returns + * a token. + * + * @return callable + */ + public static function chain() + { + $links = \func_get_args(); + //Common use case for when aws_shared_config_files is false + if (empty($links)) { + return function () { + return Promise\Create::promiseFor(\false); + }; + } + return function () use($links) { + /** @var callable $parent */ + $parent = \array_shift($links); + $promise = $parent(); + while ($next = \array_shift($links)) { + $promise = $promise->otherwise($next); + } + return $promise; + }; + } + /** + * Wraps a token provider and caches a previously provided token. + * Ensures that cached tokens are refreshed when they expire. + * + * @param callable $provider Token provider function to wrap. + * @return callable + */ + public static function memoize(callable $provider) + { + return function () use($provider) { + static $result; + static $isConstant; + // Constant tokens will be returned constantly. + if ($isConstant) { + return $result; + } + // Create the initial promise that will be used as the cached value + // until it expires. + if (null === $result) { + $result = $provider(); + } + // Return a token that could expire and refresh when needed. + return $result->then(function (TokenInterface $token) use($provider, &$isConstant, &$result) { + // Determine if the token is constant. + if (!$token->getExpiration()) { + $isConstant = \true; + return $token; + } + if (!$token->isExpired()) { + return $token; + } + return $result = $provider(); + })->otherwise(function ($reason) use(&$result) { + // Cleanup rejected promise. + $result = null; + return Promise\Create::promiseFor(null); + }); + }; + } + /** + * Wraps a token provider and saves provided token in an + * instance of Aws\CacheInterface. Forwards calls when no token found + * in cache and updates cache with the results. + * + * @param callable $provider Token provider function to wrap + * @param CacheInterface $cache Cache to store the token + * @param string|null $cacheKey (optional) Cache key to use + * + * @return callable + */ + public static function cache(callable $provider, CacheInterface $cache, $cacheKey = null) + { + $cacheKey = $cacheKey ?: 'aws_cached_token'; + return function () use($provider, $cache, $cacheKey) { + $found = $cache->get($cacheKey); + if (\is_array($found) && isset($found['token'])) { + $foundToken = $found['token']; + if ($foundToken instanceof TokenInterface) { + if (!$foundToken->isExpired()) { + return Promise\Create::promiseFor($foundToken); + } + if (isset($found['refreshMethod']) && \is_callable($found['refreshMethod'])) { + return Promise\Create::promiseFor($found['refreshMethod']()); + } + } + } + return $provider()->then(function (TokenInterface $token) use($cache, $cacheKey) { + $cache->set($cacheKey, $token, null === $token->getExpiration() ? 0 : $token->getExpiration() - \time()); + return $token; + }); + }; + } + /** + * Gets profiles from the ~/.aws/config ini file + */ + private static function loadDefaultProfiles() + { + $profiles = []; + $configFile = self::getHomeDir() . '/.aws/config'; + if (\file_exists($configFile)) { + $configProfileData = \WPMailSMTP\Vendor\Aws\parse_ini_file($configFile, \true, \INI_SCANNER_RAW); + foreach ($configProfileData as $name => $profile) { + // standardize config profile names + $name = \str_replace('profile ', '', $name); + if (!isset($profiles[$name])) { + $profiles[$name] = $profile; + } + } + } + return $profiles; + } + private static function reject($msg) + { + return new Promise\RejectedPromise(new TokenException($msg)); + } + /** + * Token provider that creates a token from cached sso credentials + * + * @param string $profileName the name of the ini profile name + * @param string $filename the location of the ini file + * @param array $config configuration options + * + * @return SsoTokenProvider + * @see Aws\Token\SsoTokenProvider for $config details. + */ + public static function sso($profileName, $filename, $config = []) + { + $ssoClient = isset($config['ssoClient']) ? $config['ssoClient'] : null; + return new SsoTokenProvider($profileName, $filename, $ssoClient); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/TraceMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/TraceMiddleware.php new file mode 100644 index 00000000..a6a0c430 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/TraceMiddleware.php @@ -0,0 +1,243 @@ + '[TOKEN]']; + private static $authStrings = [ + // S3Signature + '/AWSAccessKeyId=[A-Z0-9]{20}&/i' => 'AWSAccessKeyId=[KEY]&', + // SignatureV4 Signature and S3Signature + '/Signature=.+/i' => 'Signature=[SIGNATURE]', + // SignatureV4 access key ID + '/Credential=[A-Z0-9]{20}\\//i' => 'Credential=[KEY]/', + // S3 signatures + '/AWS [A-Z0-9]{20}:.+/' => 'AWS AKI[KEY]:[SIGNATURE]', + // STS Presigned URLs + '/X-Amz-Security-Token=[^&]+/i' => 'X-Amz-Security-Token=[TOKEN]', + // Crypto *Stream Keys + '/\\["key.{27,36}Stream.{9}\\]=>\\s+.{7}\\d{2}\\) "\\X{16,64}"/U' => '["key":[CONTENT KEY]]', + ]; + /** + * Configuration array can contain the following key value pairs. + * + * - logfn: (callable) Function that is invoked with log messages. By + * default, PHP's "echo" function will be utilized. + * - stream_size: (int) When the size of a stream is greater than this + * number, the stream data will not be logged. Set to "0" to not log any + * stream data. + * - scrub_auth: (bool) Set to false to disable the scrubbing of auth data + * from the logged messages. + * - http: (bool) Set to false to disable the "debug" feature of lower + * level HTTP adapters (e.g., verbose curl output). + * - auth_strings: (array) A mapping of authentication string regular + * expressions to scrubbed strings. These mappings are passed directly to + * preg_replace (e.g., preg_replace($key, $value, $debugOutput) if + * "scrub_auth" is set to true. + * - auth_headers: (array) A mapping of header names known to contain + * sensitive data to what the scrubbed value should be. The value of any + * headers contained in this array will be replaced with the if + * "scrub_auth" is set to true. + */ + public function __construct(array $config = [], ?Service $service = null) + { + $this->config = $config + ['logfn' => function ($value) { + echo $value; + }, 'stream_size' => 524288, 'scrub_auth' => \true, 'http' => \true, 'auth_strings' => [], 'auth_headers' => []]; + $this->config['auth_strings'] += self::$authStrings; + $this->config['auth_headers'] += self::$authHeaders; + $this->service = $service; + } + public function __invoke($step, $name) + { + $this->prevOutput = $this->prevInput = []; + return function (callable $next) use($step, $name) { + return function (CommandInterface $command, $request = null) use($next, $step, $name) { + $this->createHttpDebug($command); + $start = \microtime(\true); + $this->stepInput(['step' => $step, 'name' => $name, 'request' => $this->requestArray($request), 'command' => $this->commandArray($command)]); + return $next($command, $request)->then(function ($value) use($step, $name, $command, $start) { + $this->flushHttpDebug($command); + $this->stepOutput($start, ['step' => $step, 'name' => $name, 'result' => $this->resultArray($value), 'error' => null]); + return $value; + }, function ($reason) use($step, $name, $start, $command) { + $this->flushHttpDebug($command); + $this->stepOutput($start, ['step' => $step, 'name' => $name, 'result' => null, 'error' => $this->exceptionArray($reason)]); + return new RejectedPromise($reason); + }); + }; + }; + } + private function stepInput($entry) + { + static $keys = ['command', 'request']; + $this->compareStep($this->prevInput, $entry, '-> Entering', $keys); + $this->write("\n"); + $this->prevInput = $entry; + } + private function stepOutput($start, $entry) + { + static $keys = ['result', 'error']; + $this->compareStep($this->prevOutput, $entry, '<- Leaving', $keys); + $totalTime = \microtime(\true) - $start; + $this->write(" Inclusive step time: " . $totalTime . "\n\n"); + $this->prevOutput = $entry; + } + private function compareStep(array $a, array $b, $title, array $keys) + { + $changes = []; + foreach ($keys as $key) { + $av = isset($a[$key]) ? $a[$key] : null; + $bv = isset($b[$key]) ? $b[$key] : null; + $this->compareArray($av, $bv, $key, $changes); + } + $str = "\n{$title} step {$b['step']}, name '{$b['name']}'"; + $str .= "\n" . \str_repeat('-', \strlen($str) - 1) . "\n\n "; + $str .= $changes ? \implode("\n ", \str_replace("\n", "\n ", $changes)) : 'no changes'; + $this->write($str . "\n"); + } + private function commandArray(CommandInterface $cmd) + { + return ['instance' => \spl_object_hash($cmd), 'name' => $cmd->getName(), 'params' => $this->getRedactedArray($cmd)]; + } + private function requestArray($request = null) + { + return !$request instanceof RequestInterface ? [] : \array_filter(['instance' => \spl_object_hash($request), 'method' => $request->getMethod(), 'headers' => $this->redactHeaders($request->getHeaders()), 'body' => $this->streamStr($request->getBody()), 'scheme' => $request->getUri()->getScheme(), 'port' => $request->getUri()->getPort(), 'path' => $request->getUri()->getPath(), 'query' => $request->getUri()->getQuery()]); + } + private function responseArray(?ResponseInterface $response = null) + { + return !$response ? [] : ['instance' => \spl_object_hash($response), 'statusCode' => $response->getStatusCode(), 'headers' => $this->redactHeaders($response->getHeaders()), 'body' => $this->streamStr($response->getBody())]; + } + private function resultArray($value) + { + return $value instanceof ResultInterface ? ['instance' => \spl_object_hash($value), 'data' => $value->toArray()] : $value; + } + private function exceptionArray($e) + { + if (!$e instanceof \Exception) { + return $e; + } + $result = ['instance' => \spl_object_hash($e), 'class' => \get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString()]; + if ($e instanceof AwsException) { + $result += ['type' => $e->getAwsErrorType(), 'code' => $e->getAwsErrorCode(), 'requestId' => $e->getAwsRequestId(), 'statusCode' => $e->getStatusCode(), 'result' => $this->resultArray($e->getResult()), 'request' => $this->requestArray($e->getRequest()), 'response' => $this->responseArray($e->getResponse())]; + } + return $result; + } + private function compareArray($a, $b, $path, array &$diff) + { + if ($a === $b) { + return; + } + if (\is_array($a)) { + $b = (array) $b; + $keys = \array_unique(\array_merge(\array_keys($a), \array_keys($b))); + foreach ($keys as $k) { + if (!\array_key_exists($k, $a)) { + $this->compareArray(null, $b[$k], "{$path}.{$k}", $diff); + } elseif (!\array_key_exists($k, $b)) { + $this->compareArray($a[$k], null, "{$path}.{$k}", $diff); + } else { + $this->compareArray($a[$k], $b[$k], "{$path}.{$k}", $diff); + } + } + } elseif ($a !== null && $b === null) { + $diff[] = "{$path} was unset"; + } elseif ($a === null && $b !== null) { + $diff[] = \sprintf("%s was set to %s", $path, $this->str($b)); + } else { + $diff[] = \sprintf("%s changed from %s to %s", $path, $this->str($a), $this->str($b)); + } + } + private function str($value) + { + if (\is_scalar($value)) { + return (string) $value; + } + if ($value instanceof \Exception) { + $value = $this->exceptionArray($value); + } + \ob_start(); + \var_dump($value); + return \ob_get_clean(); + } + private function streamStr(StreamInterface $body) + { + return $body->getSize() < $this->config['stream_size'] ? (string) $body : 'stream(size=' . $body->getSize() . ')'; + } + private function createHttpDebug(CommandInterface $command) + { + if ($this->config['http'] && !isset($command['@http']['debug'])) { + $command['@http']['debug'] = \fopen('php://temp', 'w+'); + } + } + private function flushHttpDebug(CommandInterface $command) + { + if ($res = $command['@http']['debug']) { + if (\is_resource($res)) { + \rewind($res); + $this->write(\stream_get_contents($res)); + \fclose($res); + } + $command['@http']['debug'] = null; + } + } + private function write($value) + { + if ($this->config['scrub_auth']) { + foreach ($this->config['auth_strings'] as $pattern => $replacement) { + $value = \preg_replace_callback($pattern, function ($matches) use($replacement) { + return $replacement; + }, $value); + } + } + \call_user_func($this->config['logfn'], $value); + } + private function redactHeaders(array $headers) + { + if ($this->config['scrub_auth']) { + $headers = $this->config['auth_headers'] + $headers; + } + return $headers; + } + /** + * @param CommandInterface $cmd + * @return array + */ + private function getRedactedArray(CommandInterface $cmd) + { + if (!isset($this->service["shapes"])) { + return $cmd->toArray(); + } + $shapes = $this->service["shapes"]; + $cmdArray = $cmd->toArray(); + $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($cmdArray), RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $parameter => $value) { + if (isset($shapes[$parameter]['sensitive']) && $shapes[$parameter]['sensitive'] === \true) { + $redactedValue = \is_string($value) ? "[{$parameter}]" : ["[{$parameter}]"]; + $currentDepth = $iterator->getDepth(); + for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) { + $subIterator = $iterator->getSubIterator($subDepth); + $subIterator->offsetSet($subIterator->key(), $subDepth === $currentDepth ? $redactedValue : $iterator->getSubIterator($subDepth + 1)->getArrayCopy()); + } + } + } + return $iterator->getArrayCopy(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/UserAgentMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/UserAgentMiddleware.php new file mode 100644 index 00000000..dba189d7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/UserAgentMiddleware.php @@ -0,0 +1,263 @@ +nextHandler = $nextHandler; + $this->args = $args; + } + /** + * When invoked, its injects the user agent header into the + * request headers. + * + * @param CommandInterface $command + * @param RequestInterface $request + * + * @return mixed + */ + public function __invoke(CommandInterface $command, RequestInterface $request) + { + $handler = $this->nextHandler; + $this->metricsBuilder = MetricsBuilder::fromCommand($command); + $request = $this->requestWithUserAgentHeader($request); + return $handler($command, $request); + } + /** + * Builds the user agent header value, and injects it into the request + * headers. Then, it returns the mutated request. + * + * @param RequestInterface $request + * + * @return RequestInterface + */ + private function requestWithUserAgentHeader(RequestInterface $request) : RequestInterface + { + $uaAppend = $this->args['ua_append'] ?? []; + $userAgentValue = \array_merge($this->buildUserAgentValue(), $uaAppend); + // It includes the user agent values just for the User-Agent header. + // The reason is that the SEP does not mention appending the + // metrics into the X-Amz-User-Agent header. + return $request->withHeader('User-Agent', \implode(' ', \array_merge($userAgentValue, $request->getHeader('User-Agent')))); + } + /** + * Builds the different user agent values. + * + * @return array + */ + private function buildUserAgentValue() : array + { + $userAgentValue = []; + foreach (self::$userAgentFnList as $fn) { + $val = $this->{$fn}(); + if (!empty($val)) { + $userAgentValue[] = $val; + } + } + return $userAgentValue; + } + /** + * Returns the user agent value for SDK version. + * + * @return string + */ + private function getSdkVersion() : string + { + return 'aws-sdk-php/' . Sdk::VERSION; + } + /** + * Returns the user agent value for the agent version. + * + * @return string + */ + private function getUserAgentVersion() : string + { + return 'ua/' . self::AGENT_VERSION; + } + /** + * Returns the user agent value for the hhvm version, but just + * when it is defined. + * + * @return string + */ + private function getHhvmVersion() : string + { + if (\defined('WPMailSMTP\\Vendor\\HHVM_VERSION')) { + return 'HHVM/' . HHVM_VERSION; + } + return ""; + } + /** + * Returns the user agent value for the os version. + * + * @return string + */ + private function getOsName() : string + { + $disabledFunctions = \explode(',', \ini_get('disable_functions')); + if (\function_exists('php_uname') && !\in_array('php_uname', $disabledFunctions, \true)) { + $osName = "OS/" . \php_uname('s') . '#' . \php_uname('r'); + if (!empty($osName)) { + return $osName; + } + } + return ""; + } + /** + * Returns the user agent value for the php language used. + * + * @return string + */ + private function getLangVersion() : string + { + return 'lang/php#' . \phpversion(); + } + /** + * Returns the user agent value for the execution env. + * + * @return string + */ + private function getExecEnv() : string + { + if ($executionEnvironment = \getenv('AWS_EXECUTION_ENV')) { + return $executionEnvironment; + } + return ""; + } + /** + * Returns the user agent value for endpoint discovery as cfg. + * This feature is deprecated. + * + * @return string + */ + private function getEndpointDiscovery() : string + { + $args = $this->args; + if (isset($args['endpoint_discovery'])) { + if ($args['endpoint_discovery'] instanceof Configuration && $args['endpoint_discovery']->isEnabled()) { + return 'cfg/endpoint-discovery'; + } elseif (\is_array($args['endpoint_discovery']) && isset($args['endpoint_discovery']['enabled']) && $args['endpoint_discovery']['enabled']) { + return 'cfg/endpoint-discovery'; + } + } + return ""; + } + /** + * Returns the user agent value for app id, but just when an + * app id was provided as a client argument. + * + * @return string + */ + private function getAppId() : string + { + if (empty($this->args['app_id'])) { + return ""; + } + return 'app/' . $this->args['app_id']; + } + /** + * Returns the user agent value for metrics. + * + * @return string + */ + private function getMetrics() : string + { + foreach (self::$metricsFnList as $fn) { + $this->{$fn}(); + } + $metricsEncoded = $this->metricsBuilder->build(); + if (empty($metricsEncoded)) { + return ""; + } + return "m/" . $metricsEncoded; + } + /** + * Appends the endpoint metric into the metrics builder, + * just if a custom endpoint was provided at client construction. + */ + private function appendEndpointMetric() : void + { + if (!empty($this->args['endpoint_override'])) { + $this->metricsBuilder->append(MetricsBuilder::ENDPOINT_OVERRIDE); + } + } + /** + * Appends the account id endpoint mode metric into the metrics builder, + * based on the account id endpoint mode provide as client argument. + */ + private function appendAccountIdModeMetric() : void + { + $accountIdMode = $this->args['account_id_endpoint_mode'] ?? null; + if ($accountIdMode === null) { + return; + } + if ($accountIdMode === 'preferred') { + $this->metricsBuilder->append(MetricsBuilder::ACCOUNT_ID_MODE_PREFERRED); + } elseif ($accountIdMode === 'disabled') { + $this->metricsBuilder->append(MetricsBuilder::ACCOUNT_ID_MODE_DISABLED); + } elseif ($accountIdMode === 'required') { + $this->metricsBuilder->append(MetricsBuilder::ACCOUNT_ID_MODE_REQUIRED); + } + } + /** + * Appends the retry mode metric into the metrics builder, + * based on the resolved retry config mode. + */ + private function appendRetryConfigMetric() : void + { + $retries = $this->args['retries'] ?? null; + if ($retries === null) { + return; + } + $retryMode = ''; + if ($retries instanceof \WPMailSMTP\Vendor\Aws\Retry\Configuration) { + $retryMode = $retries->getMode(); + } elseif (\is_array($retries) && isset($retries["mode"])) { + $retryMode = $retries["mode"]; + } + if ($retryMode === 'legacy') { + $this->metricsBuilder->append(MetricsBuilder::RETRY_MODE_LEGACY); + } elseif ($retryMode === 'standard') { + $this->metricsBuilder->append(MetricsBuilder::RETRY_MODE_STANDARD); + } elseif ($retryMode === 'adaptive') { + $this->metricsBuilder->append(MetricsBuilder::RETRY_MODE_ADAPTIVE); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Waiter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Waiter.php new file mode 100644 index 00000000..c24c99fc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/Waiter.php @@ -0,0 +1,222 @@ + 0, 'before' => null]; + /** @var array Required configuration options. */ + private static $required = ['acceptors', 'delay', 'maxAttempts', 'operation']; + /** + * The array of configuration options include: + * + * - acceptors: (array) Array of acceptor options + * - delay: (int) Number of seconds to delay between attempts + * - maxAttempts: (int) Maximum number of attempts before failing + * - operation: (string) Name of the API operation to use for polling + * - before: (callable) Invoked before attempts. Accepts command and tries. + * + * @param AwsClientInterface $client Client used to execute commands. + * @param string $name Waiter name. + * @param array $args Command arguments. + * @param array $config Waiter config that overrides defaults. + * + * @throws \InvalidArgumentException if the configuration is incomplete. + */ + public function __construct(AwsClientInterface $client, $name, array $args = [], array $config = []) + { + $this->client = $client; + $this->name = $name; + $this->args = $args; + // Prepare and validate config. + $this->config = $config + self::$defaults; + foreach (self::$required as $key) { + if (!isset($this->config[$key])) { + throw new \InvalidArgumentException('The provided waiter configuration was incomplete.'); + } + } + if ($this->config['before'] && !\is_callable($this->config['before'])) { + throw new \InvalidArgumentException('The provided "before" callback is not callable.'); + } + MetricsBuilder::appendMetricsCaptureMiddleware($this->client->getHandlerList(), MetricsBuilder::WAITER); + } + /** + * @return Coroutine + */ + public function promise() : PromiseInterface + { + return Coroutine::of(function () { + $name = $this->config['operation']; + for ($state = 'retry', $attempt = 1; $state === 'retry'; $attempt++) { + // Execute the operation. + $args = $this->getArgsForAttempt($attempt); + $command = $this->client->getCommand($name, $args); + try { + if ($this->config['before']) { + $this->config['before']($command, $attempt); + } + $result = (yield $this->client->executeAsync($command)); + } catch (AwsException $e) { + $result = $e; + } + // Determine the waiter's state and what to do next. + $state = $this->determineState($result); + if ($state === 'success') { + (yield $command); + } elseif ($state === 'failed') { + $msg = "The {$this->name} waiter entered a failure state."; + if ($result instanceof \Exception) { + $msg .= ' Reason: ' . $result->getMessage(); + } + (yield new RejectedPromise(new \RuntimeException($msg))); + } elseif ($state === 'retry' && $attempt >= $this->config['maxAttempts']) { + $state = 'failed'; + (yield new RejectedPromise(new \RuntimeException("The {$this->name} waiter failed after attempt #{$attempt}."))); + } + } + }); + } + /** + * Gets the operation arguments for the attempt, including the delay. + * + * @param $attempt Number of the current attempt. + * + * @return mixed integer + */ + private function getArgsForAttempt($attempt) + { + $args = $this->args; + // Determine the delay. + $delay = $attempt === 1 ? $this->config['initDelay'] : $this->config['delay']; + if (\is_callable($delay)) { + $delay = $delay($attempt); + } + // Set the delay. (Note: handlers except delay in milliseconds.) + if (!isset($args['@http'])) { + $args['@http'] = []; + } + $args['@http']['delay'] = $delay * 1000; + return $args; + } + /** + * Determines the state of the waiter attempt, based on the result of + * polling the resource. A waiter can have the state of "success", "failed", + * or "retry". + * + * @param mixed $result + * + * @return string Will be "success", "failed", or "retry" + */ + private function determineState($result) + { + foreach ($this->config['acceptors'] as $acceptor) { + $matcher = 'matches' . \ucfirst($acceptor['matcher']); + if ($this->{$matcher}($result, $acceptor)) { + return $acceptor['state']; + } + } + return $result instanceof \Exception ? 'failed' : 'retry'; + } + /** + * @param Result $result Result or exception. + * @param array $acceptor Acceptor configuration being checked. + * + * @return bool + */ + private function matchesPath($result, array $acceptor) + { + return !$result instanceof ResultInterface ? \false : $acceptor['expected'] == $result->search($acceptor['argument']); + } + /** + * @param Result $result Result or exception. + * @param array $acceptor Acceptor configuration being checked. + * + * @return bool + */ + private function matchesPathAll($result, array $acceptor) + { + if (!$result instanceof ResultInterface) { + return \false; + } + $actuals = $result->search($acceptor['argument']) ?: []; + foreach ($actuals as $actual) { + if ($actual != $acceptor['expected']) { + return \false; + } + } + return \true; + } + /** + * @param Result $result Result or exception. + * @param array $acceptor Acceptor configuration being checked. + * + * @return bool + */ + private function matchesPathAny($result, array $acceptor) + { + if (!$result instanceof ResultInterface) { + return \false; + } + $actuals = $result->search($acceptor['argument']) ?: []; + return \in_array($acceptor['expected'], $actuals); + } + /** + * @param Result $result Result or exception. + * @param array $acceptor Acceptor configuration being checked. + * + * @return bool + */ + private function matchesStatus($result, array $acceptor) + { + if ($result instanceof ResultInterface) { + return $acceptor['expected'] == $result['@metadata']['statusCode']; + } + if ($result instanceof AwsException && ($response = $result->getResponse())) { + return $acceptor['expected'] == $response->getStatusCode(); + } + return \false; + } + /** + * @param Result $result Result or exception. + * @param array $acceptor Acceptor configuration being checked. + * + * @return bool + */ + private function matchesError($result, array $acceptor) + { + // If expected is true then the $result should be an instance of + // AwsException, otherwise it should not. + if (isset($acceptor['expected']) && \is_bool($acceptor['expected'])) { + return $acceptor['expected'] === $result instanceof AwsException; + } + if ($result instanceof AwsException) { + return $result->isConnectionError() || $result->getAwsErrorCode() == $acceptor['expected']; + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/WrappedHttpHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/WrappedHttpHandler.php new file mode 100644 index 00000000..21e29590 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/WrappedHttpHandler.php @@ -0,0 +1,142 @@ +httpHandler = $httpHandler; + $this->parser = $parser; + $this->errorParser = $errorParser; + $this->exceptionClass = $exceptionClass; + $this->collectStats = $collectStats; + } + /** + * Calls the simpler HTTP specific handler and wraps the returned promise + * with AWS specific values (e.g., a result object or AWS exception). + * + * @param CommandInterface $command Command being executed. + * @param RequestInterface $request Request to send. + * + * @return Promise\PromiseInterface + */ + public function __invoke(CommandInterface $command, RequestInterface $request) + { + $fn = $this->httpHandler; + $options = $command['@http'] ?: []; + $stats = []; + if ($this->collectStats || !empty($options['collect_stats'])) { + $options['http_stats_receiver'] = static function (array $transferStats) use(&$stats) { + $stats = $transferStats; + }; + } elseif (isset($options['http_stats_receiver'])) { + throw new \InvalidArgumentException('Providing a custom HTTP stats' . ' receiver to Aws\\WrappedHttpHandler is not supported.'); + } + return Promise\Create::promiseFor($fn($request, $options))->then(function (ResponseInterface $res) use($command, $request, &$stats) { + return $this->parseResponse($command, $request, $res, $stats); + }, function ($err) use($request, $command, &$stats) { + if (\is_array($err)) { + $err = $this->parseError($err, $request, $command, $stats); + } + return new Promise\RejectedPromise($err); + }); + } + /** + * @param CommandInterface $command + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $stats + * + * @return ResultInterface + */ + private function parseResponse(CommandInterface $command, RequestInterface $request, ResponseInterface $response, array $stats) + { + $parser = $this->parser; + $status = $response->getStatusCode(); + $result = $status < 300 ? $parser($command, $response) : new Result(); + $metadata = ['statusCode' => $status, 'effectiveUri' => (string) $request->getUri(), 'headers' => [], 'transferStats' => []]; + if (!empty($stats)) { + $metadata['transferStats']['http'] = [$stats]; + } + // Bring headers into the metadata array. + foreach ($response->getHeaders() as $name => $values) { + $metadata['headers'][\strtolower($name)] = $values[0]; + } + $result['@metadata'] = $metadata; + return $result; + } + /** + * Parses a rejection into an AWS error. + * + * @param array $err Rejection error array. + * @param RequestInterface $request Request that was sent. + * @param CommandInterface $command Command being sent. + * @param array $stats Transfer statistics + * + * @return \Exception + */ + private function parseError(array $err, RequestInterface $request, CommandInterface $command, array $stats) + { + if (!isset($err['exception'])) { + throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.'); + } + $serviceError = "AWS HTTP error: " . $err['exception']->getMessage(); + if (!isset($err['response'])) { + $parts = ['response' => null]; + } else { + try { + $parts = \call_user_func($this->errorParser, $err['response'], $command); + $serviceError .= " {$parts['code']} ({$parts['type']}): " . "{$parts['message']} - " . $err['response']->getBody(); + } catch (ParserException $e) { + $parts = []; + $serviceError .= ' Unable to parse error information from ' . "response - {$e->getMessage()}"; + } + $parts['response'] = $err['response']; + } + $parts['exception'] = $err['exception']; + $parts['request'] = $request; + $parts['connection_error'] = !empty($err['connection_error']); + $parts['transfer_stats'] = $stats; + return new $this->exceptionClass(\sprintf('Error executing "%s" on "%s"; %s', $command->getName(), $request->getUri(), $serviceError), $command, $parts, $err['exception']); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/functions.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/functions.php new file mode 100644 index 00000000..6553d213 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/aws/aws-sdk-php/src/functions.php @@ -0,0 +1,539 @@ + \true, '..' => \true]; + $pathLen = \strlen($path) + 1; + $iterator = dir_iterator($path, $context); + $queue = []; + do { + while ($iterator->valid()) { + $file = $iterator->current(); + $iterator->next(); + if (isset($invalid[\basename($file)])) { + continue; + } + $fullPath = "{$path}/{$file}"; + (yield $fullPath); + if (\is_dir($fullPath)) { + $queue[] = $iterator; + $iterator = map(dir_iterator($fullPath, $context), function ($file) use($fullPath, $pathLen) { + return \substr("{$fullPath}/{$file}", $pathLen); + }); + continue; + } + } + $iterator = \array_pop($queue); + } while ($iterator); +} +//----------------------------------------------------------------------------- +// Misc. functions. +//----------------------------------------------------------------------------- +/** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ +function describe_type($input) +{ + switch (\gettype($input)) { + case 'object': + return 'object(' . \get_class($input) . ')'; + case 'array': + return 'array(' . \count($input) . ')'; + default: + \ob_start(); + \var_dump($input); + // normalize float vs double + return \str_replace('double(', 'float(', \rtrim(\ob_get_clean())); + } +} +/** + * Creates a default HTTP handler based on the available clients. + * + * @return callable + */ +function default_http_handler() +{ + $version = guzzle_major_version(); + // If Guzzle 6 or 7 installed + if ($version === 6 || $version === 7) { + return new \WPMailSMTP\Vendor\Aws\Handler\GuzzleV6\GuzzleHandler(); + } + // If Guzzle 5 installed + if ($version === 5) { + return new \WPMailSMTP\Vendor\Aws\Handler\GuzzleV5\GuzzleHandler(); + } + throw new \RuntimeException('Unknown Guzzle version: ' . $version); +} +/** + * Gets the default user agent string depending on the Guzzle version + * + * @return string + */ +function default_user_agent() +{ + $version = guzzle_major_version(); + // If Guzzle 6 or 7 installed + if ($version === 6 || $version === 7) { + return \WPMailSMTP\Vendor\GuzzleHttp\default_user_agent(); + } + // If Guzzle 5 installed + if ($version === 5) { + return \WPMailSMTP\Vendor\GuzzleHttp\Client::getDefaultUserAgent(); + } + throw new \RuntimeException('Unknown Guzzle version: ' . $version); +} +/** + * Get the major version of guzzle that is installed. + * + * @internal This function is internal and should not be used outside aws/aws-sdk-php. + * @return int + * @throws \RuntimeException + */ +function guzzle_major_version() +{ + static $cache = null; + if (null !== $cache) { + return $cache; + } + if (\defined('\\WPMailSMTP\\Vendor\\GuzzleHttp\\ClientInterface::VERSION')) { + $version = (string) ClientInterface::VERSION; + if ($version[0] === '6') { + return $cache = 6; + } + if ($version[0] === '5') { + return $cache = 5; + } + } elseif (\defined('\\WPMailSMTP\\Vendor\\GuzzleHttp\\ClientInterface::MAJOR_VERSION')) { + return $cache = ClientInterface::MAJOR_VERSION; + } + throw new \RuntimeException('Unable to determine what Guzzle version is installed.'); +} +/** + * Serialize a request for a command but do not send it. + * + * Returns a promise that is fulfilled with the serialized request. + * + * @param CommandInterface $command Command to serialize. + * + * @return RequestInterface + * @throws \RuntimeException + */ +function serialize(CommandInterface $command) +{ + $request = null; + $handlerList = $command->getHandlerList(); + // Return a mock result. + $handlerList->setHandler(function (CommandInterface $_, RequestInterface $r) use(&$request) { + $request = $r; + return new FulfilledPromise(new Result([])); + }); + \call_user_func($handlerList->resolve(), $command)->wait(); + if (!$request instanceof RequestInterface) { + throw new \RuntimeException('Calling handler did not serialize request'); + } + return $request; +} +/** + * Retrieves data for a service from the SDK's service manifest file. + * + * Manifest data is stored statically, so it does not need to be loaded more + * than once per process. The JSON data is also cached in opcache. + * + * @param string $service Case-insensitive namespace or endpoint prefix of the + * service for which you are retrieving manifest data. + * + * @return array + * @throws \InvalidArgumentException if the service is not supported. + */ +function manifest($service = null) +{ + // Load the manifest and create aliases for lowercased namespaces + static $manifest = []; + static $aliases = []; + if (empty($manifest)) { + $manifest = load_compiled_json(__DIR__ . '/data/manifest.json'); + foreach ($manifest as $endpoint => $info) { + $alias = \strtolower($info['namespace']); + if ($alias !== $endpoint) { + $aliases[$alias] = $endpoint; + } + } + } + // If no service specified, then return the whole manifest. + if ($service === null) { + return $manifest; + } + // Look up the service's info in the manifest data. + $service = \strtolower($service); + if (isset($manifest[$service])) { + return $manifest[$service] + ['endpoint' => $service]; + } + if (isset($aliases[$service])) { + return manifest($aliases[$service]); + } + throw new \InvalidArgumentException("The service \"{$service}\" is not provided by the AWS SDK for PHP."); +} +/** + * Checks if supplied parameter is a valid hostname + * + * @param string $hostname + * @return bool + */ +function is_valid_hostname($hostname) +{ + return \preg_match("/^([a-z\\d](-*[a-z\\d])*)(\\.([a-z\\d](-*[a-z\\d])*))*\\.?\$/i", $hostname) && \preg_match("/^.{1,253}\$/", $hostname) && \preg_match("/^[^\\.]{1,63}(\\.[^\\.]{0,63})*\$/", $hostname); +} +/** + * Checks if supplied parameter is a valid host label + * + * @param $label + * @return bool + */ +function is_valid_hostlabel($label) +{ + return \preg_match("/^(?!-)[a-zA-Z0-9-]{1,63}(? + * The Gmail API lets you view and manage Gmail mailbox data like threads, + * messages, and labels.

    + * + *

    + * For more information about this service, see the API + * Documentation + *

    + * + * @author Google, Inc. + */ +class Gmail extends \WPMailSMTP\Vendor\Google\Service +{ + /** Read, compose, send, and permanently delete all your email from Gmail. */ + const MAIL_GOOGLE_COM = "https://mail.google.com/"; + /** Manage drafts and send emails when you interact with the add-on. */ + const GMAIL_ADDONS_CURRENT_ACTION_COMPOSE = "https://www.googleapis.com/auth/gmail.addons.current.action.compose"; + /** View your email messages when you interact with the add-on. */ + const GMAIL_ADDONS_CURRENT_MESSAGE_ACTION = "https://www.googleapis.com/auth/gmail.addons.current.message.action"; + /** View your email message metadata when the add-on is running. */ + const GMAIL_ADDONS_CURRENT_MESSAGE_METADATA = "https://www.googleapis.com/auth/gmail.addons.current.message.metadata"; + /** View your email messages when the add-on is running. */ + const GMAIL_ADDONS_CURRENT_MESSAGE_READONLY = "https://www.googleapis.com/auth/gmail.addons.current.message.readonly"; + /** Manage drafts and send emails. */ + const GMAIL_COMPOSE = "https://www.googleapis.com/auth/gmail.compose"; + /** Add emails into your Gmail mailbox. */ + const GMAIL_INSERT = "https://www.googleapis.com/auth/gmail.insert"; + /** See and edit your email labels. */ + const GMAIL_LABELS = "https://www.googleapis.com/auth/gmail.labels"; + /** View your email message metadata such as labels and headers, but not the email body. */ + const GMAIL_METADATA = "https://www.googleapis.com/auth/gmail.metadata"; + /** Read, compose, and send emails from your Gmail account. */ + const GMAIL_MODIFY = "https://www.googleapis.com/auth/gmail.modify"; + /** View your email messages and settings. */ + const GMAIL_READONLY = "https://www.googleapis.com/auth/gmail.readonly"; + /** Send email on your behalf. */ + const GMAIL_SEND = "https://www.googleapis.com/auth/gmail.send"; + /** See, edit, create, or change your email settings and filters in Gmail. */ + const GMAIL_SETTINGS_BASIC = "https://www.googleapis.com/auth/gmail.settings.basic"; + /** Manage your sensitive mail settings, including who can manage your mail. */ + const GMAIL_SETTINGS_SHARING = "https://www.googleapis.com/auth/gmail.settings.sharing"; + public $users; + public $users_drafts; + public $users_history; + public $users_labels; + public $users_messages; + public $users_messages_attachments; + public $users_settings; + public $users_settings_cse_identities; + public $users_settings_cse_keypairs; + public $users_settings_delegates; + public $users_settings_filters; + public $users_settings_forwardingAddresses; + public $users_settings_sendAs; + public $users_settings_sendAs_smimeInfo; + public $users_threads; + public $rootUrlTemplate; + /** + * Constructs the internal representation of the Gmail service. + * + * @param Client|array $clientOrConfig The client used to deliver requests, or a + * config array to pass to a new Client instance. + * @param string $rootUrl The root URL used for requests to the service. + */ + public function __construct($clientOrConfig = [], $rootUrl = null) + { + parent::__construct($clientOrConfig); + $this->rootUrl = $rootUrl ?: 'https://gmail.googleapis.com/'; + $this->rootUrlTemplate = $rootUrl ?: 'https://gmail.UNIVERSE_DOMAIN/'; + $this->servicePath = ''; + $this->batchPath = 'batch'; + $this->version = 'v1'; + $this->serviceName = 'gmail'; + $this->users = new Gmail\Resource\Users($this, $this->serviceName, 'users', ['methods' => ['getProfile' => ['path' => 'gmail/v1/users/{userId}/profile', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'temporaryEeccBypass' => ['location' => 'query', 'type' => 'boolean']]], 'stop' => ['path' => 'gmail/v1/users/{userId}/stop', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'watch' => ['path' => 'gmail/v1/users/{userId}/watch', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_drafts = new Gmail\Resource\UsersDrafts($this, $this->serviceName, 'drafts', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/drafts', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/drafts/{id}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/drafts/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'format' => ['location' => 'query', 'type' => 'string']]], 'list' => ['path' => 'gmail/v1/users/{userId}/drafts', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'includeSpamTrash' => ['location' => 'query', 'type' => 'boolean'], 'maxResults' => ['location' => 'query', 'type' => 'integer'], 'pageToken' => ['location' => 'query', 'type' => 'string'], 'q' => ['location' => 'query', 'type' => 'string']]], 'send' => ['path' => 'gmail/v1/users/{userId}/drafts/send', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'update' => ['path' => 'gmail/v1/users/{userId}/drafts/{id}', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_history = new Gmail\Resource\UsersHistory($this, $this->serviceName, 'history', ['methods' => ['list' => ['path' => 'gmail/v1/users/{userId}/history', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'historyTypes' => ['location' => 'query', 'type' => 'string', 'repeated' => \true], 'labelId' => ['location' => 'query', 'type' => 'string'], 'maxResults' => ['location' => 'query', 'type' => 'integer'], 'pageToken' => ['location' => 'query', 'type' => 'string'], 'startHistoryId' => ['location' => 'query', 'type' => 'string']]]]]); + $this->users_labels = new Gmail\Resource\UsersLabels($this, $this->serviceName, 'labels', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/labels', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/labels/{id}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/labels/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/labels', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'patch' => ['path' => 'gmail/v1/users/{userId}/labels/{id}', 'httpMethod' => 'PATCH', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'update' => ['path' => 'gmail/v1/users/{userId}/labels/{id}', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_messages = new Gmail\Resource\UsersMessages($this, $this->serviceName, 'messages', ['methods' => ['batchDelete' => ['path' => 'gmail/v1/users/{userId}/messages/batchDelete', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'batchModify' => ['path' => 'gmail/v1/users/{userId}/messages/batchModify', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/messages/{id}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/messages/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'format' => ['location' => 'query', 'type' => 'string'], 'metadataHeaders' => ['location' => 'query', 'type' => 'string', 'repeated' => \true], 'temporaryEeccBypass' => ['location' => 'query', 'type' => 'boolean']]], 'import' => ['path' => 'gmail/v1/users/{userId}/messages/import', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'deleted' => ['location' => 'query', 'type' => 'boolean'], 'internalDateSource' => ['location' => 'query', 'type' => 'string'], 'neverMarkSpam' => ['location' => 'query', 'type' => 'boolean'], 'processForCalendar' => ['location' => 'query', 'type' => 'boolean']]], 'insert' => ['path' => 'gmail/v1/users/{userId}/messages', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'deleted' => ['location' => 'query', 'type' => 'boolean'], 'internalDateSource' => ['location' => 'query', 'type' => 'string']]], 'list' => ['path' => 'gmail/v1/users/{userId}/messages', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'includeSpamTrash' => ['location' => 'query', 'type' => 'boolean'], 'labelIds' => ['location' => 'query', 'type' => 'string', 'repeated' => \true], 'maxResults' => ['location' => 'query', 'type' => 'integer'], 'pageToken' => ['location' => 'query', 'type' => 'string'], 'q' => ['location' => 'query', 'type' => 'string'], 'temporaryEeccBypass' => ['location' => 'query', 'type' => 'boolean']]], 'modify' => ['path' => 'gmail/v1/users/{userId}/messages/{id}/modify', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'send' => ['path' => 'gmail/v1/users/{userId}/messages/send', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'trash' => ['path' => 'gmail/v1/users/{userId}/messages/{id}/trash', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'untrash' => ['path' => 'gmail/v1/users/{userId}/messages/{id}/untrash', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_messages_attachments = new Gmail\Resource\UsersMessagesAttachments($this, $this->serviceName, 'attachments', ['methods' => ['get' => ['path' => 'gmail/v1/users/{userId}/messages/{messageId}/attachments/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'messageId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'temporaryEeccBypass' => ['location' => 'query', 'type' => 'boolean']]]]]); + $this->users_settings = new Gmail\Resource\UsersSettings($this, $this->serviceName, 'settings', ['methods' => ['getAutoForwarding' => ['path' => 'gmail/v1/users/{userId}/settings/autoForwarding', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'getImap' => ['path' => 'gmail/v1/users/{userId}/settings/imap', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'getLanguage' => ['path' => 'gmail/v1/users/{userId}/settings/language', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'getPop' => ['path' => 'gmail/v1/users/{userId}/settings/pop', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'getVacation' => ['path' => 'gmail/v1/users/{userId}/settings/vacation', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'updateAutoForwarding' => ['path' => 'gmail/v1/users/{userId}/settings/autoForwarding', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'updateImap' => ['path' => 'gmail/v1/users/{userId}/settings/imap', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'updateLanguage' => ['path' => 'gmail/v1/users/{userId}/settings/language', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'updatePop' => ['path' => 'gmail/v1/users/{userId}/settings/pop', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'updateVacation' => ['path' => 'gmail/v1/users/{userId}/settings/vacation', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_cse_identities = new Gmail\Resource\UsersSettingsCseIdentities($this, $this->serviceName, 'identities', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/settings/cse/identities', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/settings/cse/identities/{cseEmailAddress}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'cseEmailAddress' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/cse/identities/{cseEmailAddress}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'cseEmailAddress' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/cse/identities', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'pageSize' => ['location' => 'query', 'type' => 'integer'], 'pageToken' => ['location' => 'query', 'type' => 'string']]], 'patch' => ['path' => 'gmail/v1/users/{userId}/settings/cse/identities/{emailAddress}', 'httpMethod' => 'PATCH', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'emailAddress' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_cse_keypairs = new Gmail\Resource\UsersSettingsCseKeypairs($this, $this->serviceName, 'keypairs', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/settings/cse/keypairs', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'disable' => ['path' => 'gmail/v1/users/{userId}/settings/cse/keypairs/{keyPairId}:disable', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'keyPairId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'enable' => ['path' => 'gmail/v1/users/{userId}/settings/cse/keypairs/{keyPairId}:enable', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'keyPairId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/cse/keypairs/{keyPairId}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'keyPairId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/cse/keypairs', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'pageSize' => ['location' => 'query', 'type' => 'integer'], 'pageToken' => ['location' => 'query', 'type' => 'string']]], 'obliterate' => ['path' => 'gmail/v1/users/{userId}/settings/cse/keypairs/{keyPairId}:obliterate', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'keyPairId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_delegates = new Gmail\Resource\UsersSettingsDelegates($this, $this->serviceName, 'delegates', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/settings/delegates', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/settings/delegates/{delegateEmail}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'delegateEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/delegates/{delegateEmail}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'delegateEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/delegates', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_filters = new Gmail\Resource\UsersSettingsFilters($this, $this->serviceName, 'filters', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/settings/filters', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/settings/filters/{id}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/filters/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/filters', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_forwardingAddresses = new Gmail\Resource\UsersSettingsForwardingAddresses($this, $this->serviceName, 'forwardingAddresses', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/settings/forwardingAddresses', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/settings/forwardingAddresses/{forwardingEmail}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'forwardingEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/forwardingAddresses/{forwardingEmail}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'forwardingEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/forwardingAddresses', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_sendAs = new Gmail\Resource\UsersSettingsSendAs($this, $this->serviceName, 'sendAs', ['methods' => ['create' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'delete' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'patch' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}', 'httpMethod' => 'PATCH', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'update' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}', 'httpMethod' => 'PUT', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'verify' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}/verify', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_settings_sendAs_smimeInfo = new Gmail\Resource\UsersSettingsSendAsSmimeInfo($this, $this->serviceName, 'smimeInfo', ['methods' => ['delete' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}/smimeInfo/{id}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}/smimeInfo/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'insert' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}/smimeInfo', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'list' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}/smimeInfo', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'setDefault' => ['path' => 'gmail/v1/users/{userId}/settings/sendAs/{sendAsEmail}/smimeInfo/{id}/setDefault', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'sendAsEmail' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + $this->users_threads = new Gmail\Resource\UsersThreads($this, $this->serviceName, 'threads', ['methods' => ['delete' => ['path' => 'gmail/v1/users/{userId}/threads/{id}', 'httpMethod' => 'DELETE', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'get' => ['path' => 'gmail/v1/users/{userId}/threads/{id}', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'format' => ['location' => 'query', 'type' => 'string'], 'metadataHeaders' => ['location' => 'query', 'type' => 'string', 'repeated' => \true], 'temporaryEeccBypass' => ['location' => 'query', 'type' => 'boolean']]], 'list' => ['path' => 'gmail/v1/users/{userId}/threads', 'httpMethod' => 'GET', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'includeSpamTrash' => ['location' => 'query', 'type' => 'boolean'], 'labelIds' => ['location' => 'query', 'type' => 'string', 'repeated' => \true], 'maxResults' => ['location' => 'query', 'type' => 'integer'], 'pageToken' => ['location' => 'query', 'type' => 'string'], 'q' => ['location' => 'query', 'type' => 'string'], 'temporaryEeccBypass' => ['location' => 'query', 'type' => 'boolean']]], 'modify' => ['path' => 'gmail/v1/users/{userId}/threads/{id}/modify', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'trash' => ['path' => 'gmail/v1/users/{userId}/threads/{id}/trash', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]], 'untrash' => ['path' => 'gmail/v1/users/{userId}/threads/{id}/untrash', 'httpMethod' => 'POST', 'parameters' => ['userId' => ['location' => 'path', 'type' => 'string', 'required' => \true], 'id' => ['location' => 'path', 'type' => 'string', 'required' => \true]]]]]); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Gmail::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/AutoForwarding.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/AutoForwarding.php new file mode 100644 index 00000000..0a8284a7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/AutoForwarding.php @@ -0,0 +1,78 @@ +disposition = $disposition; + } + /** + * @return string + */ + public function getDisposition() + { + return $this->disposition; + } + /** + * @param string + */ + public function setEmailAddress($emailAddress) + { + $this->emailAddress = $emailAddress; + } + /** + * @return string + */ + public function getEmailAddress() + { + return $this->emailAddress; + } + /** + * @param bool + */ + public function setEnabled($enabled) + { + $this->enabled = $enabled; + } + /** + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(AutoForwarding::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_AutoForwarding'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchDeleteMessagesRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchDeleteMessagesRequest.php new file mode 100644 index 00000000..73496099 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchDeleteMessagesRequest.php @@ -0,0 +1,43 @@ +ids = $ids; + } + /** + * @return string[] + */ + public function getIds() + { + return $this->ids; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(BatchDeleteMessagesRequest::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_BatchDeleteMessagesRequest'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchModifyMessagesRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchModifyMessagesRequest.php new file mode 100644 index 00000000..25ab10b1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/BatchModifyMessagesRequest.php @@ -0,0 +1,79 @@ +addLabelIds = $addLabelIds; + } + /** + * @return string[] + */ + public function getAddLabelIds() + { + return $this->addLabelIds; + } + /** + * @param string[] + */ + public function setIds($ids) + { + $this->ids = $ids; + } + /** + * @return string[] + */ + public function getIds() + { + return $this->ids; + } + /** + * @param string[] + */ + public function setRemoveLabelIds($removeLabelIds) + { + $this->removeLabelIds = $removeLabelIds; + } + /** + * @return string[] + */ + public function getRemoveLabelIds() + { + return $this->removeLabelIds; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(BatchModifyMessagesRequest::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_BatchModifyMessagesRequest'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseIdentity.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseIdentity.php new file mode 100644 index 00000000..959fbd3f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseIdentity.php @@ -0,0 +1,76 @@ +emailAddress = $emailAddress; + } + /** + * @return string + */ + public function getEmailAddress() + { + return $this->emailAddress; + } + /** + * @param string + */ + public function setPrimaryKeyPairId($primaryKeyPairId) + { + $this->primaryKeyPairId = $primaryKeyPairId; + } + /** + * @return string + */ + public function getPrimaryKeyPairId() + { + return $this->primaryKeyPairId; + } + /** + * @param SignAndEncryptKeyPairs + */ + public function setSignAndEncryptKeyPairs(SignAndEncryptKeyPairs $signAndEncryptKeyPairs) + { + $this->signAndEncryptKeyPairs = $signAndEncryptKeyPairs; + } + /** + * @return SignAndEncryptKeyPairs + */ + public function getSignAndEncryptKeyPairs() + { + return $this->signAndEncryptKeyPairs; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(CseIdentity::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_CseIdentity'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseKeyPair.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseKeyPair.php new file mode 100644 index 00000000..97f53655 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CseKeyPair.php @@ -0,0 +1,149 @@ +disableTime = $disableTime; + } + /** + * @return string + */ + public function getDisableTime() + { + return $this->disableTime; + } + /** + * @param string + */ + public function setEnablementState($enablementState) + { + $this->enablementState = $enablementState; + } + /** + * @return string + */ + public function getEnablementState() + { + return $this->enablementState; + } + /** + * @param string + */ + public function setKeyPairId($keyPairId) + { + $this->keyPairId = $keyPairId; + } + /** + * @return string + */ + public function getKeyPairId() + { + return $this->keyPairId; + } + /** + * @param string + */ + public function setPem($pem) + { + $this->pem = $pem; + } + /** + * @return string + */ + public function getPem() + { + return $this->pem; + } + /** + * @param string + */ + public function setPkcs7($pkcs7) + { + $this->pkcs7 = $pkcs7; + } + /** + * @return string + */ + public function getPkcs7() + { + return $this->pkcs7; + } + /** + * @param CsePrivateKeyMetadata[] + */ + public function setPrivateKeyMetadata($privateKeyMetadata) + { + $this->privateKeyMetadata = $privateKeyMetadata; + } + /** + * @return CsePrivateKeyMetadata[] + */ + public function getPrivateKeyMetadata() + { + return $this->privateKeyMetadata; + } + /** + * @param string[] + */ + public function setSubjectEmailAddresses($subjectEmailAddresses) + { + $this->subjectEmailAddresses = $subjectEmailAddresses; + } + /** + * @return string[] + */ + public function getSubjectEmailAddresses() + { + return $this->subjectEmailAddresses; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(CseKeyPair::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_CseKeyPair'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CsePrivateKeyMetadata.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CsePrivateKeyMetadata.php new file mode 100644 index 00000000..84a965ca --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/CsePrivateKeyMetadata.php @@ -0,0 +1,74 @@ +hardwareKeyMetadata = $hardwareKeyMetadata; + } + /** + * @return HardwareKeyMetadata + */ + public function getHardwareKeyMetadata() + { + return $this->hardwareKeyMetadata; + } + /** + * @param KaclsKeyMetadata + */ + public function setKaclsKeyMetadata(KaclsKeyMetadata $kaclsKeyMetadata) + { + $this->kaclsKeyMetadata = $kaclsKeyMetadata; + } + /** + * @return KaclsKeyMetadata + */ + public function getKaclsKeyMetadata() + { + return $this->kaclsKeyMetadata; + } + /** + * @param string + */ + public function setPrivateKeyMetadataId($privateKeyMetadataId) + { + $this->privateKeyMetadataId = $privateKeyMetadataId; + } + /** + * @return string + */ + public function getPrivateKeyMetadataId() + { + return $this->privateKeyMetadataId; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(CsePrivateKeyMetadata::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_CsePrivateKeyMetadata'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Delegate.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Delegate.php new file mode 100644 index 00000000..f369e61f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Delegate.php @@ -0,0 +1,60 @@ +delegateEmail = $delegateEmail; + } + /** + * @return string + */ + public function getDelegateEmail() + { + return $this->delegateEmail; + } + /** + * @param string + */ + public function setVerificationStatus($verificationStatus) + { + $this->verificationStatus = $verificationStatus; + } + /** + * @return string + */ + public function getVerificationStatus() + { + return $this->verificationStatus; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Delegate::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Delegate'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/DisableCseKeyPairRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/DisableCseKeyPairRequest.php new file mode 100644 index 00000000..ca144734 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/DisableCseKeyPairRequest.php @@ -0,0 +1,24 @@ +id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } + /** + * @param Message + */ + public function setMessage(Message $message) + { + $this->message = $message; + } + /** + * @return Message + */ + public function getMessage() + { + return $this->message; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Draft::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Draft'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/EnableCseKeyPairRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/EnableCseKeyPairRequest.php new file mode 100644 index 00000000..3bb99c45 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/EnableCseKeyPairRequest.php @@ -0,0 +1,24 @@ +action = $action; + } + /** + * @return FilterAction + */ + public function getAction() + { + return $this->action; + } + /** + * @param FilterCriteria + */ + public function setCriteria(FilterCriteria $criteria) + { + $this->criteria = $criteria; + } + /** + * @return FilterCriteria + */ + public function getCriteria() + { + return $this->criteria; + } + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Filter::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Filter'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterAction.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterAction.php new file mode 100644 index 00000000..3020373b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterAction.php @@ -0,0 +1,79 @@ +addLabelIds = $addLabelIds; + } + /** + * @return string[] + */ + public function getAddLabelIds() + { + return $this->addLabelIds; + } + /** + * @param string + */ + public function setForward($forward) + { + $this->forward = $forward; + } + /** + * @return string + */ + public function getForward() + { + return $this->forward; + } + /** + * @param string[] + */ + public function setRemoveLabelIds($removeLabelIds) + { + $this->removeLabelIds = $removeLabelIds; + } + /** + * @return string[] + */ + public function getRemoveLabelIds() + { + return $this->removeLabelIds; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(FilterAction::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_FilterAction'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterCriteria.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterCriteria.php new file mode 100644 index 00000000..50ca5b90 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/FilterCriteria.php @@ -0,0 +1,186 @@ +excludeChats = $excludeChats; + } + /** + * @return bool + */ + public function getExcludeChats() + { + return $this->excludeChats; + } + /** + * @param string + */ + public function setFrom($from) + { + $this->from = $from; + } + /** + * @return string + */ + public function getFrom() + { + return $this->from; + } + /** + * @param bool + */ + public function setHasAttachment($hasAttachment) + { + $this->hasAttachment = $hasAttachment; + } + /** + * @return bool + */ + public function getHasAttachment() + { + return $this->hasAttachment; + } + /** + * @param string + */ + public function setNegatedQuery($negatedQuery) + { + $this->negatedQuery = $negatedQuery; + } + /** + * @return string + */ + public function getNegatedQuery() + { + return $this->negatedQuery; + } + /** + * @param string + */ + public function setQuery($query) + { + $this->query = $query; + } + /** + * @return string + */ + public function getQuery() + { + return $this->query; + } + /** + * @param int + */ + public function setSize($size) + { + $this->size = $size; + } + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + /** + * @param string + */ + public function setSizeComparison($sizeComparison) + { + $this->sizeComparison = $sizeComparison; + } + /** + * @return string + */ + public function getSizeComparison() + { + return $this->sizeComparison; + } + /** + * @param string + */ + public function setSubject($subject) + { + $this->subject = $subject; + } + /** + * @return string + */ + public function getSubject() + { + return $this->subject; + } + /** + * @param string + */ + public function setTo($to) + { + $this->to = $to; + } + /** + * @return string + */ + public function getTo() + { + return $this->to; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(FilterCriteria::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_FilterCriteria'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ForwardingAddress.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ForwardingAddress.php new file mode 100644 index 00000000..94a85509 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ForwardingAddress.php @@ -0,0 +1,60 @@ +forwardingEmail = $forwardingEmail; + } + /** + * @return string + */ + public function getForwardingEmail() + { + return $this->forwardingEmail; + } + /** + * @param string + */ + public function setVerificationStatus($verificationStatus) + { + $this->verificationStatus = $verificationStatus; + } + /** + * @return string + */ + public function getVerificationStatus() + { + return $this->verificationStatus; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ForwardingAddress::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ForwardingAddress'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HardwareKeyMetadata.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HardwareKeyMetadata.php new file mode 100644 index 00000000..a4de0d95 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HardwareKeyMetadata.php @@ -0,0 +1,42 @@ +description = $description; + } + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(HardwareKeyMetadata::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_HardwareKeyMetadata'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/History.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/History.php new file mode 100644 index 00000000..80cb1125 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/History.php @@ -0,0 +1,123 @@ +id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } + /** + * @param HistoryLabelAdded[] + */ + public function setLabelsAdded($labelsAdded) + { + $this->labelsAdded = $labelsAdded; + } + /** + * @return HistoryLabelAdded[] + */ + public function getLabelsAdded() + { + return $this->labelsAdded; + } + /** + * @param HistoryLabelRemoved[] + */ + public function setLabelsRemoved($labelsRemoved) + { + $this->labelsRemoved = $labelsRemoved; + } + /** + * @return HistoryLabelRemoved[] + */ + public function getLabelsRemoved() + { + return $this->labelsRemoved; + } + /** + * @param Message[] + */ + public function setMessages($messages) + { + $this->messages = $messages; + } + /** + * @return Message[] + */ + public function getMessages() + { + return $this->messages; + } + /** + * @param HistoryMessageAdded[] + */ + public function setMessagesAdded($messagesAdded) + { + $this->messagesAdded = $messagesAdded; + } + /** + * @return HistoryMessageAdded[] + */ + public function getMessagesAdded() + { + return $this->messagesAdded; + } + /** + * @param HistoryMessageDeleted[] + */ + public function setMessagesDeleted($messagesDeleted) + { + $this->messagesDeleted = $messagesDeleted; + } + /** + * @return HistoryMessageDeleted[] + */ + public function getMessagesDeleted() + { + return $this->messagesDeleted; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(History::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_History'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelAdded.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelAdded.php new file mode 100644 index 00000000..06ca3471 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelAdded.php @@ -0,0 +1,59 @@ +labelIds = $labelIds; + } + /** + * @return string[] + */ + public function getLabelIds() + { + return $this->labelIds; + } + /** + * @param Message + */ + public function setMessage(Message $message) + { + $this->message = $message; + } + /** + * @return Message + */ + public function getMessage() + { + return $this->message; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(HistoryLabelAdded::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_HistoryLabelAdded'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelRemoved.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelRemoved.php new file mode 100644 index 00000000..f2d01a09 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryLabelRemoved.php @@ -0,0 +1,59 @@ +labelIds = $labelIds; + } + /** + * @return string[] + */ + public function getLabelIds() + { + return $this->labelIds; + } + /** + * @param Message + */ + public function setMessage(Message $message) + { + $this->message = $message; + } + /** + * @return Message + */ + public function getMessage() + { + return $this->message; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(HistoryLabelRemoved::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_HistoryLabelRemoved'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageAdded.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageAdded.php new file mode 100644 index 00000000..782d19ba --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageAdded.php @@ -0,0 +1,40 @@ +message = $message; + } + /** + * @return Message + */ + public function getMessage() + { + return $this->message; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(HistoryMessageAdded::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_HistoryMessageAdded'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageDeleted.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageDeleted.php new file mode 100644 index 00000000..e16aed0a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/HistoryMessageDeleted.php @@ -0,0 +1,40 @@ +message = $message; + } + /** + * @return Message + */ + public function getMessage() + { + return $this->message; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(HistoryMessageDeleted::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_HistoryMessageDeleted'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ImapSettings.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ImapSettings.php new file mode 100644 index 00000000..1bd7983f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ImapSettings.php @@ -0,0 +1,96 @@ +autoExpunge = $autoExpunge; + } + /** + * @return bool + */ + public function getAutoExpunge() + { + return $this->autoExpunge; + } + /** + * @param bool + */ + public function setEnabled($enabled) + { + $this->enabled = $enabled; + } + /** + * @return bool + */ + public function getEnabled() + { + return $this->enabled; + } + /** + * @param string + */ + public function setExpungeBehavior($expungeBehavior) + { + $this->expungeBehavior = $expungeBehavior; + } + /** + * @return string + */ + public function getExpungeBehavior() + { + return $this->expungeBehavior; + } + /** + * @param int + */ + public function setMaxFolderSize($maxFolderSize) + { + $this->maxFolderSize = $maxFolderSize; + } + /** + * @return int + */ + public function getMaxFolderSize() + { + return $this->maxFolderSize; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ImapSettings::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ImapSettings'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/KaclsKeyMetadata.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/KaclsKeyMetadata.php new file mode 100644 index 00000000..86afa1b8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/KaclsKeyMetadata.php @@ -0,0 +1,60 @@ +kaclsData = $kaclsData; + } + /** + * @return string + */ + public function getKaclsData() + { + return $this->kaclsData; + } + /** + * @param string + */ + public function setKaclsUri($kaclsUri) + { + $this->kaclsUri = $kaclsUri; + } + /** + * @return string + */ + public function getKaclsUri() + { + return $this->kaclsUri; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(KaclsKeyMetadata::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_KaclsKeyMetadata'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Label.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Label.php new file mode 100644 index 00000000..69f8f5cb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Label.php @@ -0,0 +1,202 @@ +color = $color; + } + /** + * @return LabelColor + */ + public function getColor() + { + return $this->color; + } + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } + /** + * @param string + */ + public function setLabelListVisibility($labelListVisibility) + { + $this->labelListVisibility = $labelListVisibility; + } + /** + * @return string + */ + public function getLabelListVisibility() + { + return $this->labelListVisibility; + } + /** + * @param string + */ + public function setMessageListVisibility($messageListVisibility) + { + $this->messageListVisibility = $messageListVisibility; + } + /** + * @return string + */ + public function getMessageListVisibility() + { + return $this->messageListVisibility; + } + /** + * @param int + */ + public function setMessagesTotal($messagesTotal) + { + $this->messagesTotal = $messagesTotal; + } + /** + * @return int + */ + public function getMessagesTotal() + { + return $this->messagesTotal; + } + /** + * @param int + */ + public function setMessagesUnread($messagesUnread) + { + $this->messagesUnread = $messagesUnread; + } + /** + * @return int + */ + public function getMessagesUnread() + { + return $this->messagesUnread; + } + /** + * @param string + */ + public function setName($name) + { + $this->name = $name; + } + /** + * @return string + */ + public function getName() + { + return $this->name; + } + /** + * @param int + */ + public function setThreadsTotal($threadsTotal) + { + $this->threadsTotal = $threadsTotal; + } + /** + * @return int + */ + public function getThreadsTotal() + { + return $this->threadsTotal; + } + /** + * @param int + */ + public function setThreadsUnread($threadsUnread) + { + $this->threadsUnread = $threadsUnread; + } + /** + * @return int + */ + public function getThreadsUnread() + { + return $this->threadsUnread; + } + /** + * @param string + */ + public function setType($type) + { + $this->type = $type; + } + /** + * @return string + */ + public function getType() + { + return $this->type; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Label::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Label'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LabelColor.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LabelColor.php new file mode 100644 index 00000000..bc826593 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LabelColor.php @@ -0,0 +1,60 @@ +backgroundColor = $backgroundColor; + } + /** + * @return string + */ + public function getBackgroundColor() + { + return $this->backgroundColor; + } + /** + * @param string + */ + public function setTextColor($textColor) + { + $this->textColor = $textColor; + } + /** + * @return string + */ + public function getTextColor() + { + return $this->textColor; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(LabelColor::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_LabelColor'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LanguageSettings.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LanguageSettings.php new file mode 100644 index 00000000..9053063d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/LanguageSettings.php @@ -0,0 +1,42 @@ +displayLanguage = $displayLanguage; + } + /** + * @return string + */ + public function getDisplayLanguage() + { + return $this->displayLanguage; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(LanguageSettings::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_LanguageSettings'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseIdentitiesResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseIdentitiesResponse.php new file mode 100644 index 00000000..340c42d4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseIdentitiesResponse.php @@ -0,0 +1,59 @@ +cseIdentities = $cseIdentities; + } + /** + * @return CseIdentity[] + */ + public function getCseIdentities() + { + return $this->cseIdentities; + } + /** + * @param string + */ + public function setNextPageToken($nextPageToken) + { + $this->nextPageToken = $nextPageToken; + } + /** + * @return string + */ + public function getNextPageToken() + { + return $this->nextPageToken; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListCseIdentitiesResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListCseIdentitiesResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseKeyPairsResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseKeyPairsResponse.php new file mode 100644 index 00000000..b503c9c9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListCseKeyPairsResponse.php @@ -0,0 +1,59 @@ +cseKeyPairs = $cseKeyPairs; + } + /** + * @return CseKeyPair[] + */ + public function getCseKeyPairs() + { + return $this->cseKeyPairs; + } + /** + * @param string + */ + public function setNextPageToken($nextPageToken) + { + $this->nextPageToken = $nextPageToken; + } + /** + * @return string + */ + public function getNextPageToken() + { + return $this->nextPageToken; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListCseKeyPairsResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListCseKeyPairsResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDelegatesResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDelegatesResponse.php new file mode 100644 index 00000000..448516d9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDelegatesResponse.php @@ -0,0 +1,41 @@ +delegates = $delegates; + } + /** + * @return Delegate[] + */ + public function getDelegates() + { + return $this->delegates; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListDelegatesResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListDelegatesResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDraftsResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDraftsResponse.php new file mode 100644 index 00000000..054fb29c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListDraftsResponse.php @@ -0,0 +1,77 @@ +drafts = $drafts; + } + /** + * @return Draft[] + */ + public function getDrafts() + { + return $this->drafts; + } + /** + * @param string + */ + public function setNextPageToken($nextPageToken) + { + $this->nextPageToken = $nextPageToken; + } + /** + * @return string + */ + public function getNextPageToken() + { + return $this->nextPageToken; + } + /** + * @param string + */ + public function setResultSizeEstimate($resultSizeEstimate) + { + $this->resultSizeEstimate = $resultSizeEstimate; + } + /** + * @return string + */ + public function getResultSizeEstimate() + { + return $this->resultSizeEstimate; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListDraftsResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListDraftsResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListFiltersResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListFiltersResponse.php new file mode 100644 index 00000000..7f411663 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListFiltersResponse.php @@ -0,0 +1,41 @@ +filter = $filter; + } + /** + * @return Filter[] + */ + public function getFilter() + { + return $this->filter; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListFiltersResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListFiltersResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListForwardingAddressesResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListForwardingAddressesResponse.php new file mode 100644 index 00000000..c970118a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListForwardingAddressesResponse.php @@ -0,0 +1,41 @@ +forwardingAddresses = $forwardingAddresses; + } + /** + * @return ForwardingAddress[] + */ + public function getForwardingAddresses() + { + return $this->forwardingAddresses; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListForwardingAddressesResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListForwardingAddressesResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListHistoryResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListHistoryResponse.php new file mode 100644 index 00000000..523a4b37 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListHistoryResponse.php @@ -0,0 +1,77 @@ +history = $history; + } + /** + * @return History[] + */ + public function getHistory() + { + return $this->history; + } + /** + * @param string + */ + public function setHistoryId($historyId) + { + $this->historyId = $historyId; + } + /** + * @return string + */ + public function getHistoryId() + { + return $this->historyId; + } + /** + * @param string + */ + public function setNextPageToken($nextPageToken) + { + $this->nextPageToken = $nextPageToken; + } + /** + * @return string + */ + public function getNextPageToken() + { + return $this->nextPageToken; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListHistoryResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListHistoryResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListLabelsResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListLabelsResponse.php new file mode 100644 index 00000000..164103bd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListLabelsResponse.php @@ -0,0 +1,41 @@ +labels = $labels; + } + /** + * @return Label[] + */ + public function getLabels() + { + return $this->labels; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListLabelsResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListLabelsResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListMessagesResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListMessagesResponse.php new file mode 100644 index 00000000..43814e1e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListMessagesResponse.php @@ -0,0 +1,77 @@ +messages = $messages; + } + /** + * @return Message[] + */ + public function getMessages() + { + return $this->messages; + } + /** + * @param string + */ + public function setNextPageToken($nextPageToken) + { + $this->nextPageToken = $nextPageToken; + } + /** + * @return string + */ + public function getNextPageToken() + { + return $this->nextPageToken; + } + /** + * @param string + */ + public function setResultSizeEstimate($resultSizeEstimate) + { + $this->resultSizeEstimate = $resultSizeEstimate; + } + /** + * @return string + */ + public function getResultSizeEstimate() + { + return $this->resultSizeEstimate; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListMessagesResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListMessagesResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSendAsResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSendAsResponse.php new file mode 100644 index 00000000..8bd2ef67 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSendAsResponse.php @@ -0,0 +1,41 @@ +sendAs = $sendAs; + } + /** + * @return SendAs[] + */ + public function getSendAs() + { + return $this->sendAs; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListSendAsResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListSendAsResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSmimeInfoResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSmimeInfoResponse.php new file mode 100644 index 00000000..96da7839 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListSmimeInfoResponse.php @@ -0,0 +1,41 @@ +smimeInfo = $smimeInfo; + } + /** + * @return SmimeInfo[] + */ + public function getSmimeInfo() + { + return $this->smimeInfo; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListSmimeInfoResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListSmimeInfoResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListThreadsResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListThreadsResponse.php new file mode 100644 index 00000000..a80bf377 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ListThreadsResponse.php @@ -0,0 +1,77 @@ +nextPageToken = $nextPageToken; + } + /** + * @return string + */ + public function getNextPageToken() + { + return $this->nextPageToken; + } + /** + * @param string + */ + public function setResultSizeEstimate($resultSizeEstimate) + { + $this->resultSizeEstimate = $resultSizeEstimate; + } + /** + * @return string + */ + public function getResultSizeEstimate() + { + return $this->resultSizeEstimate; + } + /** + * @param Thread[] + */ + public function setThreads($threads) + { + $this->threads = $threads; + } + /** + * @return Thread[] + */ + public function getThreads() + { + return $this->threads; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ListThreadsResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ListThreadsResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Message.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Message.php new file mode 100644 index 00000000..8b7c84a9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Message.php @@ -0,0 +1,185 @@ +historyId = $historyId; + } + /** + * @return string + */ + public function getHistoryId() + { + return $this->historyId; + } + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } + /** + * @param string + */ + public function setInternalDate($internalDate) + { + $this->internalDate = $internalDate; + } + /** + * @return string + */ + public function getInternalDate() + { + return $this->internalDate; + } + /** + * @param string[] + */ + public function setLabelIds($labelIds) + { + $this->labelIds = $labelIds; + } + /** + * @return string[] + */ + public function getLabelIds() + { + return $this->labelIds; + } + /** + * @param MessagePart + */ + public function setPayload(MessagePart $payload) + { + $this->payload = $payload; + } + /** + * @return MessagePart + */ + public function getPayload() + { + return $this->payload; + } + /** + * @param string + */ + public function setRaw($raw) + { + $this->raw = $raw; + } + /** + * @return string + */ + public function getRaw() + { + return $this->raw; + } + /** + * @param int + */ + public function setSizeEstimate($sizeEstimate) + { + $this->sizeEstimate = $sizeEstimate; + } + /** + * @return int + */ + public function getSizeEstimate() + { + return $this->sizeEstimate; + } + /** + * @param string + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + } + /** + * @return string + */ + public function getSnippet() + { + return $this->snippet; + } + /** + * @param string + */ + public function setThreadId($threadId) + { + $this->threadId = $threadId; + } + /** + * @return string + */ + public function getThreadId() + { + return $this->threadId; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Message::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Message'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePart.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePart.php new file mode 100644 index 00000000..1336f2be --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePart.php @@ -0,0 +1,127 @@ +body = $body; + } + /** + * @return MessagePartBody + */ + public function getBody() + { + return $this->body; + } + /** + * @param string + */ + public function setFilename($filename) + { + $this->filename = $filename; + } + /** + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** + * @param MessagePartHeader[] + */ + public function setHeaders($headers) + { + $this->headers = $headers; + } + /** + * @return MessagePartHeader[] + */ + public function getHeaders() + { + return $this->headers; + } + /** + * @param string + */ + public function setMimeType($mimeType) + { + $this->mimeType = $mimeType; + } + /** + * @return string + */ + public function getMimeType() + { + return $this->mimeType; + } + /** + * @param string + */ + public function setPartId($partId) + { + $this->partId = $partId; + } + /** + * @return string + */ + public function getPartId() + { + return $this->partId; + } + /** + * @param MessagePart[] + */ + public function setParts($parts) + { + $this->parts = $parts; + } + /** + * @return MessagePart[] + */ + public function getParts() + { + return $this->parts; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(MessagePart::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_MessagePart'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartBody.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartBody.php new file mode 100644 index 00000000..d98bf833 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartBody.php @@ -0,0 +1,78 @@ +attachmentId = $attachmentId; + } + /** + * @return string + */ + public function getAttachmentId() + { + return $this->attachmentId; + } + /** + * @param string + */ + public function setData($data) + { + $this->data = $data; + } + /** + * @return string + */ + public function getData() + { + return $this->data; + } + /** + * @param int + */ + public function setSize($size) + { + $this->size = $size; + } + /** + * @return int + */ + public function getSize() + { + return $this->size; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(MessagePartBody::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_MessagePartBody'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartHeader.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartHeader.php new file mode 100644 index 00000000..d11d39d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/MessagePartHeader.php @@ -0,0 +1,60 @@ +name = $name; + } + /** + * @return string + */ + public function getName() + { + return $this->name; + } + /** + * @param string + */ + public function setValue($value) + { + $this->value = $value; + } + /** + * @return string + */ + public function getValue() + { + return $this->value; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(MessagePartHeader::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_MessagePartHeader'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyMessageRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyMessageRequest.php new file mode 100644 index 00000000..69243935 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyMessageRequest.php @@ -0,0 +1,61 @@ +addLabelIds = $addLabelIds; + } + /** + * @return string[] + */ + public function getAddLabelIds() + { + return $this->addLabelIds; + } + /** + * @param string[] + */ + public function setRemoveLabelIds($removeLabelIds) + { + $this->removeLabelIds = $removeLabelIds; + } + /** + * @return string[] + */ + public function getRemoveLabelIds() + { + return $this->removeLabelIds; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ModifyMessageRequest::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ModifyMessageRequest'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyThreadRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyThreadRequest.php new file mode 100644 index 00000000..7a3199a7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ModifyThreadRequest.php @@ -0,0 +1,61 @@ +addLabelIds = $addLabelIds; + } + /** + * @return string[] + */ + public function getAddLabelIds() + { + return $this->addLabelIds; + } + /** + * @param string[] + */ + public function setRemoveLabelIds($removeLabelIds) + { + $this->removeLabelIds = $removeLabelIds; + } + /** + * @return string[] + */ + public function getRemoveLabelIds() + { + return $this->removeLabelIds; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(ModifyThreadRequest::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_ModifyThreadRequest'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ObliterateCseKeyPairRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ObliterateCseKeyPairRequest.php new file mode 100644 index 00000000..0d2160df --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/ObliterateCseKeyPairRequest.php @@ -0,0 +1,24 @@ +description = $description; + } + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(PivKeyMetadata::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_PivKeyMetadata'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/PopSettings.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/PopSettings.php new file mode 100644 index 00000000..235f0cb0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/PopSettings.php @@ -0,0 +1,60 @@ +accessWindow = $accessWindow; + } + /** + * @return string + */ + public function getAccessWindow() + { + return $this->accessWindow; + } + /** + * @param string + */ + public function setDisposition($disposition) + { + $this->disposition = $disposition; + } + /** + * @return string + */ + public function getDisposition() + { + return $this->disposition; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(PopSettings::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_PopSettings'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Profile.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Profile.php new file mode 100644 index 00000000..b49f6cdd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Profile.php @@ -0,0 +1,96 @@ +emailAddress = $emailAddress; + } + /** + * @return string + */ + public function getEmailAddress() + { + return $this->emailAddress; + } + /** + * @param string + */ + public function setHistoryId($historyId) + { + $this->historyId = $historyId; + } + /** + * @return string + */ + public function getHistoryId() + { + return $this->historyId; + } + /** + * @param int + */ + public function setMessagesTotal($messagesTotal) + { + $this->messagesTotal = $messagesTotal; + } + /** + * @return int + */ + public function getMessagesTotal() + { + return $this->messagesTotal; + } + /** + * @param int + */ + public function setThreadsTotal($threadsTotal) + { + $this->threadsTotal = $threadsTotal; + } + /** + * @return int + */ + public function getThreadsTotal() + { + return $this->threadsTotal; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Profile::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Profile'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/Users.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/Users.php new file mode 100644 index 00000000..40cc99b9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/Users.php @@ -0,0 +1,83 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $users = $gmailService->users; + * + */ +class Users extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Gets the current user's Gmail profile. (users.getProfile) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * + * @opt_param bool temporaryEeccBypass + * @return Profile + * @throws \Google\Service\Exception + */ + public function getProfile($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('getProfile', [$params], Profile::class); + } + /** + * Stop receiving push notifications for the given user mailbox. (users.stop) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function stop($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('stop', [$params]); + } + /** + * Set up or update a push notification watch on the given user mailbox. + * (users.watch) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param WatchRequest $postBody + * @param array $optParams Optional parameters. + * @return WatchResponse + * @throws \Google\Service\Exception + */ + public function watch($userId, WatchRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('watch', [$params], WatchResponse::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Users::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_Users'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersDrafts.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersDrafts.php new file mode 100644 index 00000000..dcceee2b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersDrafts.php @@ -0,0 +1,144 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $drafts = $gmailService->users_drafts; + * + */ +class UsersDrafts extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates a new draft with the `DRAFT` label. (drafts.create) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param Draft $postBody + * @param array $optParams Optional parameters. + * @return Draft + * @throws \Google\Service\Exception + */ + public function create($userId, Draft $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], Draft::class); + } + /** + * Immediately and permanently deletes the specified draft. Does not simply + * trash it. (drafts.delete) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the draft to delete. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified draft. (drafts.get) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the draft to retrieve. + * @param array $optParams Optional parameters. + * + * @opt_param string format The format to return the draft in. + * @return Draft + * @throws \Google\Service\Exception + */ + public function get($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], Draft::class); + } + /** + * Lists the drafts in the user's mailbox. (drafts.listUsersDrafts) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * + * @opt_param bool includeSpamTrash Include drafts from `SPAM` and `TRASH` in + * the results. + * @opt_param string maxResults Maximum number of drafts to return. This field + * defaults to 100. The maximum allowed value for this field is 500. + * @opt_param string pageToken Page token to retrieve a specific page of results + * in the list. + * @opt_param string q Only return draft messages matching the specified query. + * Supports the same query format as the Gmail search box. For example, + * `"from:someuser@example.com rfc822msgid: is:unread"`. + * @return ListDraftsResponse + * @throws \Google\Service\Exception + */ + public function listUsersDrafts($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListDraftsResponse::class); + } + /** + * Sends the specified, existing draft to the recipients in the `To`, `Cc`, and + * `Bcc` headers. (drafts.send) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param Draft $postBody + * @param array $optParams Optional parameters. + * @return Message + * @throws \Google\Service\Exception + */ + public function send($userId, Draft $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('send', [$params], Message::class); + } + /** + * Replaces a draft's content. (drafts.update) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the draft to update. + * @param Draft $postBody + * @param array $optParams Optional parameters. + * @return Draft + * @throws \Google\Service\Exception + */ + public function update($userId, $id, Draft $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('update', [$params], Draft::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersDrafts::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersDrafts'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersHistory.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersHistory.php new file mode 100644 index 00000000..30fa5e0c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersHistory.php @@ -0,0 +1,68 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $history = $gmailService->users_history; + * + */ +class UsersHistory extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Lists the history of all changes to the given mailbox. History results are + * returned in chronological order (increasing `historyId`). + * (history.listUsersHistory) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * + * @opt_param string historyTypes History types to be returned by the function + * @opt_param string labelId Only return messages with a label matching the ID. + * @opt_param string maxResults Maximum number of history records to return. + * This field defaults to 100. The maximum allowed value for this field is 500. + * @opt_param string pageToken Page token to retrieve a specific page of results + * in the list. + * @opt_param string startHistoryId Required. Returns history records after the + * specified `startHistoryId`. The supplied `startHistoryId` should be obtained + * from the `historyId` of a message, thread, or previous `list` response. + * History IDs increase chronologically but are not contiguous with random gaps + * in between valid IDs. Supplying an invalid or out of date `startHistoryId` + * typically returns an `HTTP 404` error code. A `historyId` is typically valid + * for at least a week, but in some rare circumstances may be valid for only a + * few hours. If you receive an `HTTP 404` error response, your application + * should perform a full sync. If you receive no `nextPageToken` in the + * response, there are no updates to retrieve and you can store the returned + * `historyId` for a future request. + * @return ListHistoryResponse + * @throws \Google\Service\Exception + */ + public function listUsersHistory($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListHistoryResponse::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersHistory::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersHistory'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersLabels.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersLabels.php new file mode 100644 index 00000000..0e84be8a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersLabels.php @@ -0,0 +1,131 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $labels = $gmailService->users_labels; + * + */ +class UsersLabels extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates a new label. (labels.create) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param Label $postBody + * @param array $optParams Optional parameters. + * @return Label + * @throws \Google\Service\Exception + */ + public function create($userId, Label $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], Label::class); + } + /** + * Immediately and permanently deletes the specified label and removes it from + * any messages and threads that it is applied to. (labels.delete) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the label to delete. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified label. (labels.get) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the label to retrieve. + * @param array $optParams Optional parameters. + * @return Label + * @throws \Google\Service\Exception + */ + public function get($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], Label::class); + } + /** + * Lists all labels in the user's mailbox. (labels.listUsersLabels) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return ListLabelsResponse + * @throws \Google\Service\Exception + */ + public function listUsersLabels($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListLabelsResponse::class); + } + /** + * Patch the specified label. (labels.patch) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the label to update. + * @param Label $postBody + * @param array $optParams Optional parameters. + * @return Label + * @throws \Google\Service\Exception + */ + public function patch($userId, $id, Label $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('patch', [$params], Label::class); + } + /** + * Updates the specified label. (labels.update) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the label to update. + * @param Label $postBody + * @param array $optParams Optional parameters. + * @return Label + * @throws \Google\Service\Exception + */ + public function update($userId, $id, Label $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('update', [$params], Label::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersLabels::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersLabels'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessages.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessages.php new file mode 100644 index 00000000..f66f85a0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessages.php @@ -0,0 +1,261 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $messages = $gmailService->users_messages; + * + */ +class UsersMessages extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Deletes many messages by message ID. Provides no guarantees that messages + * were not already deleted or even existed at all. (messages.batchDelete) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param BatchDeleteMessagesRequest $postBody + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function batchDelete($userId, BatchDeleteMessagesRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('batchDelete', [$params]); + } + /** + * Modifies the labels on the specified messages. (messages.batchModify) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param BatchModifyMessagesRequest $postBody + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function batchModify($userId, BatchModifyMessagesRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('batchModify', [$params]); + } + /** + * Immediately and permanently deletes the specified message. This operation + * cannot be undone. Prefer `messages.trash` instead. (messages.delete) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the message to delete. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified message. (messages.get) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the message to retrieve. This ID is usually + * retrieved using `messages.list`. The ID is also contained in the result when + * a message is inserted (`messages.insert`) or imported (`messages.import`). + * @param array $optParams Optional parameters. + * + * @opt_param string format The format to return the message in. + * @opt_param string metadataHeaders When given and format is `METADATA`, only + * include headers specified. + * @opt_param bool temporaryEeccBypass + * @return Message + * @throws \Google\Service\Exception + */ + public function get($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], Message::class); + } + /** + * Imports a message into only this user's mailbox, with standard email delivery + * scanning and classification similar to receiving via SMTP. This method + * doesn't perform SPF checks, so it might not work for some spam messages, such + * as those attempting to perform domain spoofing. This method does not send a + * message. (messages.import) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param Message $postBody + * @param array $optParams Optional parameters. + * + * @opt_param bool deleted Mark the email as permanently deleted (not TRASH) and + * only visible in Google Vault to a Vault administrator. Only used for Google + * Workspace accounts. + * @opt_param string internalDateSource Source for Gmail's internal date of the + * message. + * @opt_param bool neverMarkSpam Ignore the Gmail spam classifier decision and + * never mark this email as SPAM in the mailbox. + * @opt_param bool processForCalendar Process calendar invites in the email and + * add any extracted meetings to the Google Calendar for this user. + * @return Message + * @throws \Google\Service\Exception + */ + public function import($userId, Message $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('import', [$params], Message::class); + } + /** + * Directly inserts a message into only this user's mailbox similar to `IMAP + * APPEND`, bypassing most scanning and classification. Does not send a message. + * (messages.insert) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param Message $postBody + * @param array $optParams Optional parameters. + * + * @opt_param bool deleted Mark the email as permanently deleted (not TRASH) and + * only visible in Google Vault to a Vault administrator. Only used for Google + * Workspace accounts. + * @opt_param string internalDateSource Source for Gmail's internal date of the + * message. + * @return Message + * @throws \Google\Service\Exception + */ + public function insert($userId, Message $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('insert', [$params], Message::class); + } + /** + * Lists the messages in the user's mailbox. (messages.listUsersMessages) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * + * @opt_param bool includeSpamTrash Include messages from `SPAM` and `TRASH` in + * the results. + * @opt_param string labelIds Only return messages with labels that match all of + * the specified label IDs. Messages in a thread might have labels that other + * messages in the same thread don't have. To learn more, see [Manage labels on + * messages and threads](https://developers.google.com/gmail/api/guides/labels#m + * anage_labels_on_messages_threads). + * @opt_param string maxResults Maximum number of messages to return. This field + * defaults to 100. The maximum allowed value for this field is 500. + * @opt_param string pageToken Page token to retrieve a specific page of results + * in the list. + * @opt_param string q Only return messages matching the specified query. + * Supports the same query format as the Gmail search box. For example, + * `"from:someuser@example.com rfc822msgid: is:unread"`. Parameter cannot be + * used when accessing the api using the gmail.metadata scope. + * @opt_param bool temporaryEeccBypass + * @return ListMessagesResponse + * @throws \Google\Service\Exception + */ + public function listUsersMessages($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListMessagesResponse::class); + } + /** + * Modifies the labels on the specified message. (messages.modify) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the message to modify. + * @param ModifyMessageRequest $postBody + * @param array $optParams Optional parameters. + * @return Message + * @throws \Google\Service\Exception + */ + public function modify($userId, $id, ModifyMessageRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('modify', [$params], Message::class); + } + /** + * Sends the specified message to the recipients in the `To`, `Cc`, and `Bcc` + * headers. For example usage, see [Sending + * email](https://developers.google.com/gmail/api/guides/sending). + * (messages.send) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param Message $postBody + * @param array $optParams Optional parameters. + * @return Message + * @throws \Google\Service\Exception + */ + public function send($userId, Message $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('send', [$params], Message::class); + } + /** + * Moves the specified message to the trash. (messages.trash) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the message to Trash. + * @param array $optParams Optional parameters. + * @return Message + * @throws \Google\Service\Exception + */ + public function trash($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('trash', [$params], Message::class); + } + /** + * Removes the specified message from the trash. (messages.untrash) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the message to remove from Trash. + * @param array $optParams Optional parameters. + * @return Message + * @throws \Google\Service\Exception + */ + public function untrash($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('untrash', [$params], Message::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersMessages::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersMessages'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessagesAttachments.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessagesAttachments.php new file mode 100644 index 00000000..d33d6cd3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersMessagesAttachments.php @@ -0,0 +1,52 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $attachments = $gmailService->users_messages_attachments; + * + */ +class UsersMessagesAttachments extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Gets the specified message attachment. (attachments.get) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $messageId The ID of the message containing the attachment. + * @param string $id The ID of the attachment. + * @param array $optParams Optional parameters. + * + * @opt_param bool temporaryEeccBypass + * @return MessagePartBody + * @throws \Google\Service\Exception + */ + public function get($userId, $messageId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'messageId' => $messageId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], MessagePartBody::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersMessagesAttachments::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersMessagesAttachments'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettings.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettings.php new file mode 100644 index 00000000..065bb2cf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettings.php @@ -0,0 +1,201 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $settings = $gmailService->users_settings; + * + */ +class UsersSettings extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Gets the auto-forwarding setting for the specified account. + * (settings.getAutoForwarding) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return AutoForwarding + * @throws \Google\Service\Exception + */ + public function getAutoForwarding($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('getAutoForwarding', [$params], AutoForwarding::class); + } + /** + * Gets IMAP settings. (settings.getImap) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return ImapSettings + * @throws \Google\Service\Exception + */ + public function getImap($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('getImap', [$params], ImapSettings::class); + } + /** + * Gets language settings. (settings.getLanguage) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return LanguageSettings + * @throws \Google\Service\Exception + */ + public function getLanguage($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('getLanguage', [$params], LanguageSettings::class); + } + /** + * Gets POP settings. (settings.getPop) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return PopSettings + * @throws \Google\Service\Exception + */ + public function getPop($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('getPop', [$params], PopSettings::class); + } + /** + * Gets vacation responder settings. (settings.getVacation) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return VacationSettings + * @throws \Google\Service\Exception + */ + public function getVacation($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('getVacation', [$params], VacationSettings::class); + } + /** + * Updates the auto-forwarding setting for the specified account. A verified + * forwarding address must be specified when auto-forwarding is enabled. This + * method is only available to service account clients that have been delegated + * domain-wide authority. (settings.updateAutoForwarding) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param AutoForwarding $postBody + * @param array $optParams Optional parameters. + * @return AutoForwarding + * @throws \Google\Service\Exception + */ + public function updateAutoForwarding($userId, AutoForwarding $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('updateAutoForwarding', [$params], AutoForwarding::class); + } + /** + * Updates IMAP settings. (settings.updateImap) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param ImapSettings $postBody + * @param array $optParams Optional parameters. + * @return ImapSettings + * @throws \Google\Service\Exception + */ + public function updateImap($userId, ImapSettings $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('updateImap', [$params], ImapSettings::class); + } + /** + * Updates language settings. If successful, the return object contains the + * `displayLanguage` that was saved for the user, which may differ from the + * value passed into the request. This is because the requested + * `displayLanguage` may not be directly supported by Gmail but have a close + * variant that is, and so the variant may be chosen and saved instead. + * (settings.updateLanguage) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param LanguageSettings $postBody + * @param array $optParams Optional parameters. + * @return LanguageSettings + * @throws \Google\Service\Exception + */ + public function updateLanguage($userId, LanguageSettings $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('updateLanguage', [$params], LanguageSettings::class); + } + /** + * Updates POP settings. (settings.updatePop) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param PopSettings $postBody + * @param array $optParams Optional parameters. + * @return PopSettings + * @throws \Google\Service\Exception + */ + public function updatePop($userId, PopSettings $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('updatePop', [$params], PopSettings::class); + } + /** + * Updates vacation responder settings. (settings.updateVacation) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param VacationSettings $postBody + * @param array $optParams Optional parameters. + * @return VacationSettings + * @throws \Google\Service\Exception + */ + public function updateVacation($userId, VacationSettings $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('updateVacation', [$params], VacationSettings::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettings::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettings'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCse.php new file mode 100644 index 00000000..017afd22 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCse.php @@ -0,0 +1,32 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $cse = $gmailService->users_settings_cse; + * + */ +class UsersSettingsCse extends \WPMailSMTP\Vendor\Google\Service\Resource +{ +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsCse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsCse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseIdentities.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseIdentities.php new file mode 100644 index 00000000..34314f62 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseIdentities.php @@ -0,0 +1,132 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $identities = $gmailService->users_settings_cse_identities; + * + */ +class UsersSettingsCseIdentities extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates and configures a client-side encryption identity that's authorized to + * send mail from the user account. Google publishes the S/MIME certificate to a + * shared domain-wide directory so that people within a Google Workspace + * organization can encrypt and send mail to the identity. (identities.create) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param CseIdentity $postBody + * @param array $optParams Optional parameters. + * @return CseIdentity + * @throws \Google\Service\Exception + */ + public function create($userId, CseIdentity $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], CseIdentity::class); + } + /** + * Deletes a client-side encryption identity. The authenticated user can no + * longer use the identity to send encrypted messages. You cannot restore the + * identity after you delete it. Instead, use the CreateCseIdentity method to + * create another identity with the same configuration. (identities.delete) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $cseEmailAddress The primary email address associated with the + * client-side encryption identity configuration that's removed. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $cseEmailAddress, $optParams = []) + { + $params = ['userId' => $userId, 'cseEmailAddress' => $cseEmailAddress]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Retrieves a client-side encryption identity configuration. (identities.get) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $cseEmailAddress The primary email address associated with the + * client-side encryption identity configuration that's retrieved. + * @param array $optParams Optional parameters. + * @return CseIdentity + * @throws \Google\Service\Exception + */ + public function get($userId, $cseEmailAddress, $optParams = []) + { + $params = ['userId' => $userId, 'cseEmailAddress' => $cseEmailAddress]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], CseIdentity::class); + } + /** + * Lists the client-side encrypted identities for an authenticated user. + * (identities.listUsersSettingsCseIdentities) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param array $optParams Optional parameters. + * + * @opt_param int pageSize The number of identities to return. If not provided, + * the page size will default to 20 entries. + * @opt_param string pageToken Pagination token indicating which page of + * identities to return. If the token is not supplied, then the API will return + * the first page of results. + * @return ListCseIdentitiesResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsCseIdentities($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListCseIdentitiesResponse::class); + } + /** + * Associates a different key pair with an existing client-side encryption + * identity. The updated key pair must validate against Google's [S/MIME + * certificate profiles](https://support.google.com/a/answer/7300887). + * (identities.patch) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $emailAddress The email address of the client-side encryption + * identity to update. + * @param CseIdentity $postBody + * @param array $optParams Optional parameters. + * @return CseIdentity + * @throws \Google\Service\Exception + */ + public function patch($userId, $emailAddress, CseIdentity $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'emailAddress' => $emailAddress, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('patch', [$params], CseIdentity::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsCseIdentities::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsCseIdentities'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseKeypairs.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseKeypairs.php new file mode 100644 index 00000000..5dababee --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsCseKeypairs.php @@ -0,0 +1,153 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $keypairs = $gmailService->users_settings_cse_keypairs; + * + */ +class UsersSettingsCseKeypairs extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates and uploads a client-side encryption S/MIME public key certificate + * chain and private key metadata for the authenticated user. (keypairs.create) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param CseKeyPair $postBody + * @param array $optParams Optional parameters. + * @return CseKeyPair + * @throws \Google\Service\Exception + */ + public function create($userId, CseKeyPair $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], CseKeyPair::class); + } + /** + * Turns off a client-side encryption key pair. The authenticated user can no + * longer use the key pair to decrypt incoming CSE message texts or sign + * outgoing CSE mail. To regain access, use the EnableCseKeyPair to turn on the + * key pair. After 30 days, you can permanently delete the key pair by using the + * ObliterateCseKeyPair method. (keypairs.disable) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $keyPairId The identifier of the key pair to turn off. + * @param DisableCseKeyPairRequest $postBody + * @param array $optParams Optional parameters. + * @return CseKeyPair + * @throws \Google\Service\Exception + */ + public function disable($userId, $keyPairId, DisableCseKeyPairRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'keyPairId' => $keyPairId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('disable', [$params], CseKeyPair::class); + } + /** + * Turns on a client-side encryption key pair that was turned off. The key pair + * becomes active again for any associated client-side encryption identities. + * (keypairs.enable) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $keyPairId The identifier of the key pair to turn on. + * @param EnableCseKeyPairRequest $postBody + * @param array $optParams Optional parameters. + * @return CseKeyPair + * @throws \Google\Service\Exception + */ + public function enable($userId, $keyPairId, EnableCseKeyPairRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'keyPairId' => $keyPairId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('enable', [$params], CseKeyPair::class); + } + /** + * Retrieves an existing client-side encryption key pair. (keypairs.get) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $keyPairId The identifier of the key pair to retrieve. + * @param array $optParams Optional parameters. + * @return CseKeyPair + * @throws \Google\Service\Exception + */ + public function get($userId, $keyPairId, $optParams = []) + { + $params = ['userId' => $userId, 'keyPairId' => $keyPairId]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], CseKeyPair::class); + } + /** + * Lists client-side encryption key pairs for an authenticated user. + * (keypairs.listUsersSettingsCseKeypairs) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param array $optParams Optional parameters. + * + * @opt_param int pageSize The number of key pairs to return. If not provided, + * the page size will default to 20 entries. + * @opt_param string pageToken Pagination token indicating which page of key + * pairs to return. If the token is not supplied, then the API will return the + * first page of results. + * @return ListCseKeyPairsResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsCseKeypairs($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListCseKeyPairsResponse::class); + } + /** + * Deletes a client-side encryption key pair permanently and immediately. You + * can only permanently delete key pairs that have been turned off for more than + * 30 days. To turn off a key pair, use the DisableCseKeyPair method. Gmail + * can't restore or decrypt any messages that were encrypted by an obliterated + * key. Authenticated users and Google Workspace administrators lose access to + * reading the encrypted messages. (keypairs.obliterate) + * + * @param string $userId The requester's primary email address. To indicate the + * authenticated user, you can use the special value `me`. + * @param string $keyPairId The identifier of the key pair to obliterate. + * @param ObliterateCseKeyPairRequest $postBody + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function obliterate($userId, $keyPairId, ObliterateCseKeyPairRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'keyPairId' => $keyPairId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('obliterate', [$params]); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsCseKeypairs::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsCseKeypairs'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsDelegates.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsDelegates.php new file mode 100644 index 00000000..9bc2426d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsDelegates.php @@ -0,0 +1,117 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $delegates = $gmailService->users_settings_delegates; + * + */ +class UsersSettingsDelegates extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Adds a delegate with its verification status set directly to `accepted`, + * without sending any verification email. The delegate user must be a member of + * the same Google Workspace organization as the delegator user. Gmail imposes + * limitations on the number of delegates and delegators each user in a Google + * Workspace organization can have. These limits depend on your organization, + * but in general each user can have up to 25 delegates and up to 10 delegators. + * Note that a delegate user must be referred to by their primary email address, + * and not an email alias. Also note that when a new delegate is created, there + * may be up to a one minute delay before the new delegate is available for use. + * This method is only available to service account clients that have been + * delegated domain-wide authority. (delegates.create) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param Delegate $postBody + * @param array $optParams Optional parameters. + * @return Delegate + * @throws \Google\Service\Exception + */ + public function create($userId, Delegate $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], Delegate::class); + } + /** + * Removes the specified delegate (which can be of any verification status), and + * revokes any verification that may have been required for using it. Note that + * a delegate user must be referred to by their primary email address, and not + * an email alias. This method is only available to service account clients that + * have been delegated domain-wide authority. (delegates.delete) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $delegateEmail The email address of the user to be removed as a + * delegate. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $delegateEmail, $optParams = []) + { + $params = ['userId' => $userId, 'delegateEmail' => $delegateEmail]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified delegate. Note that a delegate user must be referred to by + * their primary email address, and not an email alias. This method is only + * available to service account clients that have been delegated domain-wide + * authority. (delegates.get) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $delegateEmail The email address of the user whose delegate + * relationship is to be retrieved. + * @param array $optParams Optional parameters. + * @return Delegate + * @throws \Google\Service\Exception + */ + public function get($userId, $delegateEmail, $optParams = []) + { + $params = ['userId' => $userId, 'delegateEmail' => $delegateEmail]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], Delegate::class); + } + /** + * Lists the delegates for the specified account. This method is only available + * to service account clients that have been delegated domain-wide authority. + * (delegates.listUsersSettingsDelegates) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return ListDelegatesResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsDelegates($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListDelegatesResponse::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsDelegates::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsDelegates'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsFilters.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsFilters.php new file mode 100644 index 00000000..fd642e0d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsFilters.php @@ -0,0 +1,97 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $filters = $gmailService->users_settings_filters; + * + */ +class UsersSettingsFilters extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates a filter. Note: you can only create a maximum of 1,000 filters. + * (filters.create) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param Filter $postBody + * @param array $optParams Optional parameters. + * @return Filter + * @throws \Google\Service\Exception + */ + public function create($userId, Filter $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], Filter::class); + } + /** + * Immediately and permanently deletes the specified filter. (filters.delete) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $id The ID of the filter to be deleted. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets a filter. (filters.get) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $id The ID of the filter to be fetched. + * @param array $optParams Optional parameters. + * @return Filter + * @throws \Google\Service\Exception + */ + public function get($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], Filter::class); + } + /** + * Lists the message filters of a Gmail user. (filters.listUsersSettingsFilters) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return ListFiltersResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsFilters($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListFiltersResponse::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsFilters::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsFilters'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsForwardingAddresses.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsForwardingAddresses.php new file mode 100644 index 00000000..46b506d0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsForwardingAddresses.php @@ -0,0 +1,105 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $forwardingAddresses = $gmailService->users_settings_forwardingAddresses; + * + */ +class UsersSettingsForwardingAddresses extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates a forwarding address. If ownership verification is required, a + * message will be sent to the recipient and the resource's verification status + * will be set to `pending`; otherwise, the resource will be created with + * verification status set to `accepted`. This method is only available to + * service account clients that have been delegated domain-wide authority. + * (forwardingAddresses.create) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param ForwardingAddress $postBody + * @param array $optParams Optional parameters. + * @return ForwardingAddress + * @throws \Google\Service\Exception + */ + public function create($userId, ForwardingAddress $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], ForwardingAddress::class); + } + /** + * Deletes the specified forwarding address and revokes any verification that + * may have been required. This method is only available to service account + * clients that have been delegated domain-wide authority. + * (forwardingAddresses.delete) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $forwardingEmail The forwarding address to be deleted. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $forwardingEmail, $optParams = []) + { + $params = ['userId' => $userId, 'forwardingEmail' => $forwardingEmail]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified forwarding address. (forwardingAddresses.get) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $forwardingEmail The forwarding address to be retrieved. + * @param array $optParams Optional parameters. + * @return ForwardingAddress + * @throws \Google\Service\Exception + */ + public function get($userId, $forwardingEmail, $optParams = []) + { + $params = ['userId' => $userId, 'forwardingEmail' => $forwardingEmail]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], ForwardingAddress::class); + } + /** + * Lists the forwarding addresses for the specified account. + * (forwardingAddresses.listUsersSettingsForwardingAddresses) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return ListForwardingAddressesResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsForwardingAddresses($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListForwardingAddressesResponse::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsForwardingAddresses::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsForwardingAddresses'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAs.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAs.php new file mode 100644 index 00000000..801c79a9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAs.php @@ -0,0 +1,164 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $sendAs = $gmailService->users_settings_sendAs; + * + */ +class UsersSettingsSendAs extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Creates a custom "from" send-as alias. If an SMTP MSA is specified, Gmail + * will attempt to connect to the SMTP service to validate the configuration + * before creating the alias. If ownership verification is required for the + * alias, a message will be sent to the email address and the resource's + * verification status will be set to `pending`; otherwise, the resource will be + * created with verification status set to `accepted`. If a signature is + * provided, Gmail will sanitize the HTML before saving it with the alias. This + * method is only available to service account clients that have been delegated + * domain-wide authority. (sendAs.create) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param SendAs $postBody + * @param array $optParams Optional parameters. + * @return SendAs + * @throws \Google\Service\Exception + */ + public function create($userId, SendAs $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('create', [$params], SendAs::class); + } + /** + * Deletes the specified send-as alias. Revokes any verification that may have + * been required for using it. This method is only available to service account + * clients that have been delegated domain-wide authority. (sendAs.delete) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The send-as alias to be deleted. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $sendAsEmail, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified send-as alias. Fails with an HTTP 404 error if the + * specified address is not a member of the collection. (sendAs.get) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The send-as alias to be retrieved. + * @param array $optParams Optional parameters. + * @return SendAs + * @throws \Google\Service\Exception + */ + public function get($userId, $sendAsEmail, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], SendAs::class); + } + /** + * Lists the send-as aliases for the specified account. The result includes the + * primary send-as address associated with the account as well as any custom + * "from" aliases. (sendAs.listUsersSettingsSendAs) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * @return ListSendAsResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsSendAs($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListSendAsResponse::class); + } + /** + * Patch the specified send-as alias. (sendAs.patch) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The send-as alias to be updated. + * @param SendAs $postBody + * @param array $optParams Optional parameters. + * @return SendAs + * @throws \Google\Service\Exception + */ + public function patch($userId, $sendAsEmail, SendAs $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('patch', [$params], SendAs::class); + } + /** + * Updates a send-as alias. If a signature is provided, Gmail will sanitize the + * HTML before saving it with the alias. Addresses other than the primary + * address for the account can only be updated by service account clients that + * have been delegated domain-wide authority. (sendAs.update) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The send-as alias to be updated. + * @param SendAs $postBody + * @param array $optParams Optional parameters. + * @return SendAs + * @throws \Google\Service\Exception + */ + public function update($userId, $sendAsEmail, SendAs $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('update', [$params], SendAs::class); + } + /** + * Sends a verification email to the specified send-as alias address. The + * verification status must be `pending`. This method is only available to + * service account clients that have been delegated domain-wide authority. + * (sendAs.verify) + * + * @param string $userId User's email address. The special value "me" can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The send-as alias to be verified. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function verify($userId, $sendAsEmail, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail]; + $params = \array_merge($params, $optParams); + return $this->call('verify', [$params]); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsSendAs::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsSendAs'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAsSmimeInfo.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAsSmimeInfo.php new file mode 100644 index 00000000..c0e9d7c7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersSettingsSendAsSmimeInfo.php @@ -0,0 +1,126 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $smimeInfo = $gmailService->users_settings_sendAs_smimeInfo; + * + */ +class UsersSettingsSendAsSmimeInfo extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Deletes the specified S/MIME config for the specified send-as alias. + * (smimeInfo.delete) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The email address that appears in the "From:" + * header for mail sent using this alias. + * @param string $id The immutable ID for the SmimeInfo. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $sendAsEmail, $id, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified S/MIME config for the specified send-as alias. + * (smimeInfo.get) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The email address that appears in the "From:" + * header for mail sent using this alias. + * @param string $id The immutable ID for the SmimeInfo. + * @param array $optParams Optional parameters. + * @return SmimeInfo + * @throws \Google\Service\Exception + */ + public function get($userId, $sendAsEmail, $id, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], SmimeInfo::class); + } + /** + * Insert (upload) the given S/MIME config for the specified send-as alias. Note + * that pkcs12 format is required for the key. (smimeInfo.insert) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The email address that appears in the "From:" + * header for mail sent using this alias. + * @param SmimeInfo $postBody + * @param array $optParams Optional parameters. + * @return SmimeInfo + * @throws \Google\Service\Exception + */ + public function insert($userId, $sendAsEmail, SmimeInfo $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('insert', [$params], SmimeInfo::class); + } + /** + * Lists S/MIME configs for the specified send-as alias. + * (smimeInfo.listUsersSettingsSendAsSmimeInfo) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The email address that appears in the "From:" + * header for mail sent using this alias. + * @param array $optParams Optional parameters. + * @return ListSmimeInfoResponse + * @throws \Google\Service\Exception + */ + public function listUsersSettingsSendAsSmimeInfo($userId, $sendAsEmail, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListSmimeInfoResponse::class); + } + /** + * Sets the default S/MIME config for the specified send-as alias. + * (smimeInfo.setDefault) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $sendAsEmail The email address that appears in the "From:" + * header for mail sent using this alias. + * @param string $id The immutable ID for the SmimeInfo. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function setDefault($userId, $sendAsEmail, $id, $optParams = []) + { + $params = ['userId' => $userId, 'sendAsEmail' => $sendAsEmail, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('setDefault', [$params]); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersSettingsSendAsSmimeInfo::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersSettingsSendAsSmimeInfo'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersThreads.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersThreads.php new file mode 100644 index 00000000..52a3bba0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Resource/UsersThreads.php @@ -0,0 +1,154 @@ + + * $gmailService = new Google\Service\Gmail(...); + * $threads = $gmailService->users_threads; + * + */ +class UsersThreads extends \WPMailSMTP\Vendor\Google\Service\Resource +{ + /** + * Immediately and permanently deletes the specified thread. Any messages that + * belong to the thread are also deleted. This operation cannot be undone. + * Prefer `threads.trash` instead. (threads.delete) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id ID of the Thread to delete. + * @param array $optParams Optional parameters. + * @throws \Google\Service\Exception + */ + public function delete($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('delete', [$params]); + } + /** + * Gets the specified thread. (threads.get) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the thread to retrieve. + * @param array $optParams Optional parameters. + * + * @opt_param string format The format to return the messages in. + * @opt_param string metadataHeaders When given and format is METADATA, only + * include headers specified. + * @opt_param bool temporaryEeccBypass + * @return Thread + * @throws \Google\Service\Exception + */ + public function get($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('get', [$params], Thread::class); + } + /** + * Lists the threads in the user's mailbox. (threads.listUsersThreads) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param array $optParams Optional parameters. + * + * @opt_param bool includeSpamTrash Include threads from `SPAM` and `TRASH` in + * the results. + * @opt_param string labelIds Only return threads with labels that match all of + * the specified label IDs. + * @opt_param string maxResults Maximum number of threads to return. This field + * defaults to 100. The maximum allowed value for this field is 500. + * @opt_param string pageToken Page token to retrieve a specific page of results + * in the list. + * @opt_param string q Only return threads matching the specified query. + * Supports the same query format as the Gmail search box. For example, + * `"from:someuser@example.com rfc822msgid: is:unread"`. Parameter cannot be + * used when accessing the api using the gmail.metadata scope. + * @opt_param bool temporaryEeccBypass + * @return ListThreadsResponse + * @throws \Google\Service\Exception + */ + public function listUsersThreads($userId, $optParams = []) + { + $params = ['userId' => $userId]; + $params = \array_merge($params, $optParams); + return $this->call('list', [$params], ListThreadsResponse::class); + } + /** + * Modifies the labels applied to the thread. This applies to all messages in + * the thread. (threads.modify) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the thread to modify. + * @param ModifyThreadRequest $postBody + * @param array $optParams Optional parameters. + * @return Thread + * @throws \Google\Service\Exception + */ + public function modify($userId, $id, ModifyThreadRequest $postBody, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id, 'postBody' => $postBody]; + $params = \array_merge($params, $optParams); + return $this->call('modify', [$params], Thread::class); + } + /** + * Moves the specified thread to the trash. Any messages that belong to the + * thread are also moved to the trash. (threads.trash) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the thread to Trash. + * @param array $optParams Optional parameters. + * @return Thread + * @throws \Google\Service\Exception + */ + public function trash($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('trash', [$params], Thread::class); + } + /** + * Removes the specified thread from the trash. Any messages that belong to the + * thread are also removed from the trash. (threads.untrash) + * + * @param string $userId The user's email address. The special value `me` can be + * used to indicate the authenticated user. + * @param string $id The ID of the thread to remove from Trash. + * @param array $optParams Optional parameters. + * @return Thread + * @throws \Google\Service\Exception + */ + public function untrash($userId, $id, $optParams = []) + { + $params = ['userId' => $userId, 'id' => $id]; + $params = \array_merge($params, $optParams); + return $this->call('untrash', [$params], Thread::class); + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(UsersThreads::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Resource_UsersThreads'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SendAs.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SendAs.php new file mode 100644 index 00000000..e3fa4f6c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SendAs.php @@ -0,0 +1,184 @@ +displayName = $displayName; + } + /** + * @return string + */ + public function getDisplayName() + { + return $this->displayName; + } + /** + * @param bool + */ + public function setIsDefault($isDefault) + { + $this->isDefault = $isDefault; + } + /** + * @return bool + */ + public function getIsDefault() + { + return $this->isDefault; + } + /** + * @param bool + */ + public function setIsPrimary($isPrimary) + { + $this->isPrimary = $isPrimary; + } + /** + * @return bool + */ + public function getIsPrimary() + { + return $this->isPrimary; + } + /** + * @param string + */ + public function setReplyToAddress($replyToAddress) + { + $this->replyToAddress = $replyToAddress; + } + /** + * @return string + */ + public function getReplyToAddress() + { + return $this->replyToAddress; + } + /** + * @param string + */ + public function setSendAsEmail($sendAsEmail) + { + $this->sendAsEmail = $sendAsEmail; + } + /** + * @return string + */ + public function getSendAsEmail() + { + return $this->sendAsEmail; + } + /** + * @param string + */ + public function setSignature($signature) + { + $this->signature = $signature; + } + /** + * @return string + */ + public function getSignature() + { + return $this->signature; + } + /** + * @param SmtpMsa + */ + public function setSmtpMsa(SmtpMsa $smtpMsa) + { + $this->smtpMsa = $smtpMsa; + } + /** + * @return SmtpMsa + */ + public function getSmtpMsa() + { + return $this->smtpMsa; + } + /** + * @param bool + */ + public function setTreatAsAlias($treatAsAlias) + { + $this->treatAsAlias = $treatAsAlias; + } + /** + * @return bool + */ + public function getTreatAsAlias() + { + return $this->treatAsAlias; + } + /** + * @param string + */ + public function setVerificationStatus($verificationStatus) + { + $this->verificationStatus = $verificationStatus; + } + /** + * @return string + */ + public function getVerificationStatus() + { + return $this->verificationStatus; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(SendAs::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_SendAs'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SignAndEncryptKeyPairs.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SignAndEncryptKeyPairs.php new file mode 100644 index 00000000..2452e9f1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SignAndEncryptKeyPairs.php @@ -0,0 +1,60 @@ +encryptionKeyPairId = $encryptionKeyPairId; + } + /** + * @return string + */ + public function getEncryptionKeyPairId() + { + return $this->encryptionKeyPairId; + } + /** + * @param string + */ + public function setSigningKeyPairId($signingKeyPairId) + { + $this->signingKeyPairId = $signingKeyPairId; + } + /** + * @return string + */ + public function getSigningKeyPairId() + { + return $this->signingKeyPairId; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(SignAndEncryptKeyPairs::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_SignAndEncryptKeyPairs'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmimeInfo.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmimeInfo.php new file mode 100644 index 00000000..0dc237b0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmimeInfo.php @@ -0,0 +1,150 @@ +encryptedKeyPassword = $encryptedKeyPassword; + } + /** + * @return string + */ + public function getEncryptedKeyPassword() + { + return $this->encryptedKeyPassword; + } + /** + * @param string + */ + public function setExpiration($expiration) + { + $this->expiration = $expiration; + } + /** + * @return string + */ + public function getExpiration() + { + return $this->expiration; + } + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } + /** + * @param bool + */ + public function setIsDefault($isDefault) + { + $this->isDefault = $isDefault; + } + /** + * @return bool + */ + public function getIsDefault() + { + return $this->isDefault; + } + /** + * @param string + */ + public function setIssuerCn($issuerCn) + { + $this->issuerCn = $issuerCn; + } + /** + * @return string + */ + public function getIssuerCn() + { + return $this->issuerCn; + } + /** + * @param string + */ + public function setPem($pem) + { + $this->pem = $pem; + } + /** + * @return string + */ + public function getPem() + { + return $this->pem; + } + /** + * @param string + */ + public function setPkcs12($pkcs12) + { + $this->pkcs12 = $pkcs12; + } + /** + * @return string + */ + public function getPkcs12() + { + return $this->pkcs12; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(SmimeInfo::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_SmimeInfo'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmtpMsa.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmtpMsa.php new file mode 100644 index 00000000..a83e0f6c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/SmtpMsa.php @@ -0,0 +1,114 @@ +host = $host; + } + /** + * @return string + */ + public function getHost() + { + return $this->host; + } + /** + * @param string + */ + public function setPassword($password) + { + $this->password = $password; + } + /** + * @return string + */ + public function getPassword() + { + return $this->password; + } + /** + * @param int + */ + public function setPort($port) + { + $this->port = $port; + } + /** + * @return int + */ + public function getPort() + { + return $this->port; + } + /** + * @param string + */ + public function setSecurityMode($securityMode) + { + $this->securityMode = $securityMode; + } + /** + * @return string + */ + public function getSecurityMode() + { + return $this->securityMode; + } + /** + * @param string + */ + public function setUsername($username) + { + $this->username = $username; + } + /** + * @return string + */ + public function getUsername() + { + return $this->username; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(SmtpMsa::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_SmtpMsa'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Thread.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Thread.php new file mode 100644 index 00000000..30f020a1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/Thread.php @@ -0,0 +1,95 @@ +historyId = $historyId; + } + /** + * @return string + */ + public function getHistoryId() + { + return $this->historyId; + } + /** + * @param string + */ + public function setId($id) + { + $this->id = $id; + } + /** + * @return string + */ + public function getId() + { + return $this->id; + } + /** + * @param Message[] + */ + public function setMessages($messages) + { + $this->messages = $messages; + } + /** + * @return Message[] + */ + public function getMessages() + { + return $this->messages; + } + /** + * @param string + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + } + /** + * @return string + */ + public function getSnippet() + { + return $this->snippet; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(Thread::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_Thread'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/VacationSettings.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/VacationSettings.php new file mode 100644 index 00000000..35d1150b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/VacationSettings.php @@ -0,0 +1,168 @@ +enableAutoReply = $enableAutoReply; + } + /** + * @return bool + */ + public function getEnableAutoReply() + { + return $this->enableAutoReply; + } + /** + * @param string + */ + public function setEndTime($endTime) + { + $this->endTime = $endTime; + } + /** + * @return string + */ + public function getEndTime() + { + return $this->endTime; + } + /** + * @param string + */ + public function setResponseBodyHtml($responseBodyHtml) + { + $this->responseBodyHtml = $responseBodyHtml; + } + /** + * @return string + */ + public function getResponseBodyHtml() + { + return $this->responseBodyHtml; + } + /** + * @param string + */ + public function setResponseBodyPlainText($responseBodyPlainText) + { + $this->responseBodyPlainText = $responseBodyPlainText; + } + /** + * @return string + */ + public function getResponseBodyPlainText() + { + return $this->responseBodyPlainText; + } + /** + * @param string + */ + public function setResponseSubject($responseSubject) + { + $this->responseSubject = $responseSubject; + } + /** + * @return string + */ + public function getResponseSubject() + { + return $this->responseSubject; + } + /** + * @param bool + */ + public function setRestrictToContacts($restrictToContacts) + { + $this->restrictToContacts = $restrictToContacts; + } + /** + * @return bool + */ + public function getRestrictToContacts() + { + return $this->restrictToContacts; + } + /** + * @param bool + */ + public function setRestrictToDomain($restrictToDomain) + { + $this->restrictToDomain = $restrictToDomain; + } + /** + * @return bool + */ + public function getRestrictToDomain() + { + return $this->restrictToDomain; + } + /** + * @param string + */ + public function setStartTime($startTime) + { + $this->startTime = $startTime; + } + /** + * @return string + */ + public function getStartTime() + { + return $this->startTime; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(VacationSettings::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_VacationSettings'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchRequest.php new file mode 100644 index 00000000..604e17a6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchRequest.php @@ -0,0 +1,97 @@ +labelFilterAction = $labelFilterAction; + } + /** + * @return string + */ + public function getLabelFilterAction() + { + return $this->labelFilterAction; + } + /** + * @param string + */ + public function setLabelFilterBehavior($labelFilterBehavior) + { + $this->labelFilterBehavior = $labelFilterBehavior; + } + /** + * @return string + */ + public function getLabelFilterBehavior() + { + return $this->labelFilterBehavior; + } + /** + * @param string[] + */ + public function setLabelIds($labelIds) + { + $this->labelIds = $labelIds; + } + /** + * @return string[] + */ + public function getLabelIds() + { + return $this->labelIds; + } + /** + * @param string + */ + public function setTopicName($topicName) + { + $this->topicName = $topicName; + } + /** + * @return string + */ + public function getTopicName() + { + return $this->topicName; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(WatchRequest::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_WatchRequest'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchResponse.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchResponse.php new file mode 100644 index 00000000..e746058e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient-services/src/Gmail/WatchResponse.php @@ -0,0 +1,60 @@ +expiration = $expiration; + } + /** + * @return string + */ + public function getExpiration() + { + return $this->expiration; + } + /** + * @param string + */ + public function setHistoryId($historyId) + { + $this->historyId = $historyId; + } + /** + * @return string + */ + public function getHistoryId() + { + return $this->historyId; + } +} +// Adding a class alias for backwards compatibility with the previous class name. +\class_alias(WatchResponse::class, 'WPMailSMTP\\Vendor\\Google_Service_Gmail_WatchResponse'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/LICENSE new file mode 100644 index 00000000..a148ba56 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/LICENSE @@ -0,0 +1,203 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Revoke.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Revoke.php new file mode 100644 index 00000000..7e965493 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Revoke.php @@ -0,0 +1,65 @@ +http = $http; + } + /** + * Revoke an OAuth2 access token or refresh token. This method will revoke the current access + * token, if a token isn't provided. + * + * @param string|array $token The token (access token or a refresh token) that should be revoked. + * @return boolean Returns True if the revocation was successful, otherwise False. + */ + public function revokeToken($token) + { + if (\is_array($token)) { + if (isset($token['refresh_token'])) { + $token = $token['refresh_token']; + } else { + $token = $token['access_token']; + } + } + $body = Psr7\Utils::streamFor(\http_build_query(['token' => $token])); + $request = new Request('POST', Client::OAUTH2_REVOKE_URI, ['Cache-Control' => 'no-store', 'Content-Type' => 'application/x-www-form-urlencoded'], $body); + $httpHandler = HttpHandlerFactory::build($this->http); + $response = $httpHandler($request); + return $response->getStatusCode() == 200; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Verify.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Verify.php new file mode 100644 index 00000000..86c0490b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AccessToken/Verify.php @@ -0,0 +1,217 @@ +http = $http; + $this->cache = $cache; + $this->jwt = $jwt ?: $this->getJwtService(); + } + /** + * Verifies an id token and returns the authenticated apiLoginTicket. + * Throws an exception if the id token is not valid. + * The audience parameter can be used to control which id tokens are + * accepted. By default, the id token must have been issued to this OAuth2 client. + * + * @param string $idToken the ID token in JWT format + * @param string $audience Optional. The audience to verify against JWt "aud" + * @return array|false the token payload, if successful + */ + public function verifyIdToken($idToken, $audience = null) + { + if (empty($idToken)) { + throw new LogicException('id_token cannot be null'); + } + // set phpseclib constants if applicable + $this->setPhpsecConstants(); + // Check signature + $certs = $this->getFederatedSignOnCerts(); + foreach ($certs as $cert) { + try { + $args = [$idToken]; + $publicKey = $this->getPublicKey($cert); + if (\class_exists(Key::class)) { + $args[] = new Key($publicKey, 'RS256'); + } else { + $args[] = $publicKey; + $args[] = ['RS256']; + } + $payload = \call_user_func_array([$this->jwt, 'decode'], $args); + if (\property_exists($payload, 'aud')) { + if ($audience && $payload->aud != $audience) { + return \false; + } + } + // support HTTP and HTTPS issuers + // @see https://developers.google.com/identity/sign-in/web/backend-auth + $issuers = [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS]; + if (!isset($payload->iss) || !\in_array($payload->iss, $issuers)) { + return \false; + } + return (array) $payload; + } catch (ExpiredException $e) { + // @phpstan-ignore-line + return \false; + } catch (ExpiredExceptionV3 $e) { + return \false; + } catch (SignatureInvalidException $e) { + // continue + } catch (DomainException $e) { + // continue + } + } + return \false; + } + private function getCache() + { + return $this->cache; + } + /** + * Retrieve and cache a certificates file. + * + * @param string $url location + * @throws \Google\Exception + * @return array certificates + */ + private function retrieveCertsFromLocation($url) + { + // If we're retrieving a local file, just grab it. + if (0 !== \strpos($url, 'http')) { + if (!($file = \file_get_contents($url))) { + throw new GoogleException("Failed to retrieve verification certificates: '" . $url . "'."); + } + return \json_decode($file, \true); + } + // @phpstan-ignore-next-line + $response = $this->http->get($url); + if ($response->getStatusCode() == 200) { + return \json_decode((string) $response->getBody(), \true); + } + throw new GoogleException(\sprintf('Failed to retrieve verification certificates: "%s".', $response->getBody()->getContents()), $response->getStatusCode()); + } + // Gets federated sign-on certificates to use for verifying identity tokens. + // Returns certs as array structure, where keys are key ids, and values + // are PEM encoded certificates. + private function getFederatedSignOnCerts() + { + $certs = null; + if ($cache = $this->getCache()) { + $cacheItem = $cache->getItem('federated_signon_certs_v3'); + $certs = $cacheItem->get(); + } + if (!$certs) { + $certs = $this->retrieveCertsFromLocation(self::FEDERATED_SIGNON_CERT_URL); + if ($cache) { + $cacheItem->expiresAt(new DateTime('+1 hour')); + $cacheItem->set($certs); + $cache->save($cacheItem); + } + } + if (!isset($certs['keys'])) { + throw new InvalidArgumentException('federated sign-on certs expects "keys" to be set'); + } + return $certs['keys']; + } + private function getJwtService() + { + $jwt = new JWT(); + if ($jwt::$leeway < 1) { + // Ensures JWT leeway is at least 1 + // @see https://github.com/google/google-api-php-client/issues/827 + $jwt::$leeway = 1; + } + return $jwt; + } + private function getPublicKey($cert) + { + $modulus = new BigInteger($this->jwt->urlsafeB64Decode($cert['n']), 256); + $exponent = new BigInteger($this->jwt->urlsafeB64Decode($cert['e']), 256); + $component = ['n' => $modulus, 'e' => $exponent]; + $loader = PublicKeyLoader::load($component); + return $loader->toString('PKCS8'); + } + /** + * phpseclib calls "phpinfo" by default, which requires special + * whitelisting in the AppEngine VM environment. This function + * sets constants to bypass the need for phpseclib to check phpinfo + * + * @see phpseclib/Math/BigInteger + * @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85 + */ + private function setPhpsecConstants() + { + if (\filter_var(\getenv('GAE_VM'), \FILTER_VALIDATE_BOOLEAN)) { + if (!\defined('WPMailSMTP\\Vendor\\MATH_BIGINTEGER_OPENSSL_ENABLED')) { + \define('WPMailSMTP\\Vendor\\MATH_BIGINTEGER_OPENSSL_ENABLED', \true); + } + if (!\defined('WPMailSMTP\\Vendor\\CRYPT_RSA_MODE')) { + \define('WPMailSMTP\\Vendor\\CRYPT_RSA_MODE', AES::ENGINE_OPENSSL); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/AuthHandlerFactory.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/AuthHandlerFactory.php new file mode 100644 index 00000000..06a41454 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/AuthHandlerFactory.php @@ -0,0 +1,47 @@ +cache = $cache; + $this->cacheConfig = $cacheConfig; + } + public function attachCredentials(ClientInterface $http, CredentialsLoader $credentials, ?callable $tokenCallback = null) + { + // use the provided cache + if ($this->cache) { + $credentials = new FetchAuthTokenCache($credentials, $this->cacheConfig, $this->cache); + } + return $this->attachCredentialsCache($http, $credentials, $tokenCallback); + } + public function attachCredentialsCache(ClientInterface $http, FetchAuthTokenCache $credentials, ?callable $tokenCallback = null) + { + // if we end up needing to make an HTTP request to retrieve credentials, we + // can use our existing one, but we need to throw exceptions so the error + // bubbles up. + $authHttp = $this->createAuthHttp($http); + $authHttpHandler = HttpHandlerFactory::build($authHttp); + $middleware = new AuthTokenMiddleware($credentials, $authHttpHandler, $tokenCallback); + $config = $http->getConfig(); + $config['handler']->remove('google_auth'); + $config['handler']->push($middleware, 'google_auth'); + $config['auth'] = 'google_auth'; + $http = new Client($config); + return $http; + } + public function attachToken(ClientInterface $http, array $token, array $scopes) + { + $tokenFunc = function ($scopes) use($token) { + return $token['access_token']; + }; + $middleware = new ScopedAccessTokenMiddleware($tokenFunc, $scopes, $this->cacheConfig, $this->cache); + $config = $http->getConfig(); + $config['handler']->remove('google_auth'); + $config['handler']->push($middleware, 'google_auth'); + $config['auth'] = 'scoped'; + $http = new Client($config); + return $http; + } + public function attachKey(ClientInterface $http, $key) + { + $middleware = new SimpleMiddleware(['key' => $key]); + $config = $http->getConfig(); + $config['handler']->remove('google_auth'); + $config['handler']->push($middleware, 'google_auth'); + $config['auth'] = 'simple'; + $http = new Client($config); + return $http; + } + private function createAuthHttp(ClientInterface $http) + { + return new Client(['http_errors' => \true] + $http->getConfig()); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php new file mode 100644 index 00000000..b09d1810 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php @@ -0,0 +1,25 @@ +config = \array_merge(['application_name' => '', 'base_path' => self::API_BASE_PATH, 'client_id' => '', 'client_secret' => '', 'credentials' => null, 'scopes' => null, 'quota_project' => null, 'redirect_uri' => null, 'state' => null, 'developer_key' => '', 'use_application_default_credentials' => \false, 'signing_key' => null, 'signing_algorithm' => null, 'subject' => null, 'hd' => '', 'prompt' => '', 'openid.realm' => '', 'include_granted_scopes' => null, 'login_hint' => '', 'request_visible_actions' => '', 'access_type' => 'online', 'approval_prompt' => 'auto', 'retry' => [], 'retry_map' => null, 'cache' => null, 'cache_config' => [], 'token_callback' => null, 'jwt' => null, 'api_format_v2' => \false, 'universe_domain' => \getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN') ?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN], $config); + if (!\is_null($this->config['credentials'])) { + if ($this->config['credentials'] instanceof CredentialsLoader) { + $this->credentials = $this->config['credentials']; + } else { + $this->setAuthConfig($this->config['credentials']); + } + unset($this->config['credentials']); + } + if (!\is_null($this->config['scopes'])) { + $this->setScopes($this->config['scopes']); + unset($this->config['scopes']); + } + // Set a default token callback to update the in-memory access token + if (\is_null($this->config['token_callback'])) { + $this->config['token_callback'] = function ($cacheKey, $newAccessToken) { + $this->setAccessToken([ + 'access_token' => $newAccessToken, + 'expires_in' => 3600, + // Google default + 'created' => \time(), + ]); + }; + } + if (!\is_null($this->config['cache'])) { + $this->setCache($this->config['cache']); + unset($this->config['cache']); + } + } + /** + * Get a string containing the version of the library. + * + * @return string + */ + public function getLibraryVersion() + { + return self::LIBVER; + } + /** + * For backwards compatibility + * alias for fetchAccessTokenWithAuthCode + * + * @param string $code string code from accounts.google.com + * @return array access token + * @deprecated + */ + public function authenticate($code) + { + return $this->fetchAccessTokenWithAuthCode($code); + } + /** + * Attempt to exchange a code for an valid authentication token. + * Helper wrapped around the OAuth 2.0 implementation. + * + * @param string $code code from accounts.google.com + * @param string $codeVerifier the code verifier used for PKCE (if applicable) + * @return array access token + */ + public function fetchAccessTokenWithAuthCode($code, $codeVerifier = null) + { + if (\strlen($code) == 0) { + throw new InvalidArgumentException("Invalid code"); + } + $auth = $this->getOAuth2Service(); + $auth->setCode($code); + $auth->setRedirectUri($this->getRedirectUri()); + if ($codeVerifier) { + $auth->setCodeVerifier($codeVerifier); + } + $httpHandler = HttpHandlerFactory::build($this->getHttpClient()); + $creds = $auth->fetchAuthToken($httpHandler); + if ($creds && isset($creds['access_token'])) { + $creds['created'] = \time(); + $this->setAccessToken($creds); + } + return $creds; + } + /** + * For backwards compatibility + * alias for fetchAccessTokenWithAssertion + * + * @return array access token + * @deprecated + */ + public function refreshTokenWithAssertion() + { + return $this->fetchAccessTokenWithAssertion(); + } + /** + * Fetches a fresh access token with a given assertion token. + * @param ClientInterface $authHttp optional. + * @return array access token + */ + public function fetchAccessTokenWithAssertion(?ClientInterface $authHttp = null) + { + if (!$this->isUsingApplicationDefaultCredentials()) { + throw new DomainException('set the JSON service account credentials using' . ' Google\\Client::setAuthConfig or set the path to your JSON file' . ' with the "GOOGLE_APPLICATION_CREDENTIALS" environment variable' . ' and call Google\\Client::useApplicationDefaultCredentials to' . ' refresh a token with assertion.'); + } + $this->getLogger()->log('info', 'OAuth2 access token refresh with Signed JWT assertion grants.'); + $credentials = $this->createApplicationDefaultCredentials(); + $httpHandler = HttpHandlerFactory::build($authHttp); + $creds = $credentials->fetchAuthToken($httpHandler); + if ($creds && isset($creds['access_token'])) { + $creds['created'] = \time(); + $this->setAccessToken($creds); + } + return $creds; + } + /** + * For backwards compatibility + * alias for fetchAccessTokenWithRefreshToken + * + * @param string $refreshToken + * @return array access token + */ + public function refreshToken($refreshToken) + { + return $this->fetchAccessTokenWithRefreshToken($refreshToken); + } + /** + * Fetches a fresh OAuth 2.0 access token with the given refresh token. + * @param string $refreshToken + * @return array access token + */ + public function fetchAccessTokenWithRefreshToken($refreshToken = null) + { + if (null === $refreshToken) { + if (!isset($this->token['refresh_token'])) { + throw new LogicException('refresh token must be passed in or set as part of setAccessToken'); + } + $refreshToken = $this->token['refresh_token']; + } + $this->getLogger()->info('OAuth2 access token refresh'); + $auth = $this->getOAuth2Service(); + $auth->setRefreshToken($refreshToken); + $httpHandler = HttpHandlerFactory::build($this->getHttpClient()); + $creds = $auth->fetchAuthToken($httpHandler); + if ($creds && isset($creds['access_token'])) { + $creds['created'] = \time(); + if (!isset($creds['refresh_token'])) { + $creds['refresh_token'] = $refreshToken; + } + $this->setAccessToken($creds); + } + return $creds; + } + /** + * Create a URL to obtain user authorization. + * The authorization endpoint allows the user to first + * authenticate, and then grant/deny the access request. + * @param string|array $scope The scope is expressed as an array or list of space-delimited strings. + * @param array $queryParams Querystring params to add to the authorization URL. + * @return string + */ + public function createAuthUrl($scope = null, array $queryParams = []) + { + if (empty($scope)) { + $scope = $this->prepareScopes(); + } + if (\is_array($scope)) { + $scope = \implode(' ', $scope); + } + // only accept one of prompt or approval_prompt + $approvalPrompt = $this->config['prompt'] ? null : $this->config['approval_prompt']; + // include_granted_scopes should be string "true", string "false", or null + $includeGrantedScopes = $this->config['include_granted_scopes'] === null ? null : \var_export($this->config['include_granted_scopes'], \true); + $params = \array_filter(['access_type' => $this->config['access_type'], 'approval_prompt' => $approvalPrompt, 'hd' => $this->config['hd'], 'include_granted_scopes' => $includeGrantedScopes, 'login_hint' => $this->config['login_hint'], 'openid.realm' => $this->config['openid.realm'], 'prompt' => $this->config['prompt'], 'redirect_uri' => $this->config['redirect_uri'], 'response_type' => 'code', 'scope' => $scope, 'state' => $this->config['state']]) + $queryParams; + // If the list of scopes contains plus.login, add request_visible_actions + // to auth URL. + $rva = $this->config['request_visible_actions']; + if (\strlen($rva) > 0 && \false !== \strpos($scope, 'plus.login')) { + $params['request_visible_actions'] = $rva; + } + $auth = $this->getOAuth2Service(); + return (string) $auth->buildFullAuthorizationUri($params); + } + /** + * Adds auth listeners to the HTTP client based on the credentials + * set in the Google API Client object + * + * @param ClientInterface $http the http client object. + * @return ClientInterface the http client object + */ + public function authorize(?ClientInterface $http = null) + { + $http = $http ?: $this->getHttpClient(); + $authHandler = $this->getAuthHandler(); + // These conditionals represent the decision tree for authentication + // 1. Check if a Google\Auth\CredentialsLoader instance has been supplied via the "credentials" option + // 2. Check for Application Default Credentials + // 3a. Check for an Access Token + // 3b. If access token exists but is expired, try to refresh it + // 4. Check for API Key + if ($this->credentials) { + $this->checkUniverseDomain($this->credentials); + return $authHandler->attachCredentials($http, $this->credentials, $this->config['token_callback']); + } + if ($this->isUsingApplicationDefaultCredentials()) { + $credentials = $this->createApplicationDefaultCredentials(); + $this->checkUniverseDomain($credentials); + return $authHandler->attachCredentialsCache($http, $credentials, $this->config['token_callback']); + } + if ($token = $this->getAccessToken()) { + $scopes = $this->prepareScopes(); + // add refresh subscriber to request a new token + if (isset($token['refresh_token']) && $this->isAccessTokenExpired()) { + $credentials = $this->createUserRefreshCredentials($scopes, $token['refresh_token']); + $this->checkUniverseDomain($credentials); + return $authHandler->attachCredentials($http, $credentials, $this->config['token_callback']); + } + return $authHandler->attachToken($http, $token, (array) $scopes); + } + if ($key = $this->config['developer_key']) { + return $authHandler->attachKey($http, $key); + } + return $http; + } + /** + * Set the configuration to use application default credentials for + * authentication + * + * @see https://developers.google.com/identity/protocols/application-default-credentials + * @param boolean $useAppCreds + */ + public function useApplicationDefaultCredentials($useAppCreds = \true) + { + $this->config['use_application_default_credentials'] = $useAppCreds; + } + /** + * To prevent useApplicationDefaultCredentials from inappropriately being + * called in a conditional + * + * @see https://developers.google.com/identity/protocols/application-default-credentials + */ + public function isUsingApplicationDefaultCredentials() + { + return $this->config['use_application_default_credentials']; + } + /** + * Set the access token used for requests. + * + * Note that at the time requests are sent, tokens are cached. A token will be + * cached for each combination of service and authentication scopes. If a + * cache pool is not provided, creating a new instance of the client will + * allow modification of access tokens. If a persistent cache pool is + * provided, in order to change the access token, you must clear the cached + * token by calling `$client->getCache()->clear()`. (Use caution in this case, + * as calling `clear()` will remove all cache items, including any items not + * related to Google API PHP Client.) + * + * **NOTE:** The universe domain is assumed to be "googleapis.com" unless + * explicitly set. When setting an access token directly via this method, there + * is no way to verify the universe domain. Be sure to set the "universe_domain" + * option if "googleapis.com" is not intended. + * + * @param string|array $token + * @throws InvalidArgumentException + */ + public function setAccessToken($token) + { + if (\is_string($token)) { + if ($json = \json_decode($token, \true)) { + $token = $json; + } else { + // assume $token is just the token string + $token = ['access_token' => $token]; + } + } + if ($token == null) { + throw new InvalidArgumentException('invalid json token'); + } + if (!isset($token['access_token'])) { + throw new InvalidArgumentException("Invalid token format"); + } + $this->token = $token; + } + public function getAccessToken() + { + return $this->token; + } + /** + * @return string|null + */ + public function getRefreshToken() + { + if (isset($this->token['refresh_token'])) { + return $this->token['refresh_token']; + } + return null; + } + /** + * Returns if the access_token is expired. + * @return bool Returns True if the access_token is expired. + */ + public function isAccessTokenExpired() + { + if (!$this->token) { + return \true; + } + $created = 0; + if (isset($this->token['created'])) { + $created = $this->token['created']; + } elseif (isset($this->token['id_token'])) { + // check the ID token for "iat" + // signature verification is not required here, as we are just + // using this for convenience to save a round trip request + // to the Google API server + $idToken = $this->token['id_token']; + if (\substr_count($idToken, '.') == 2) { + $parts = \explode('.', $idToken); + $payload = \json_decode(\base64_decode($parts[1]), \true); + if ($payload && isset($payload['iat'])) { + $created = $payload['iat']; + } + } + } + if (!isset($this->token['expires_in'])) { + // if the token does not have an "expires_in", then it's considered expired + return \true; + } + // If the token is set to expire in the next 30 seconds. + return $created + ($this->token['expires_in'] - 30) < \time(); + } + /** + * @deprecated See UPGRADING.md for more information + */ + public function getAuth() + { + throw new BadMethodCallException('This function no longer exists. See UPGRADING.md for more information'); + } + /** + * @deprecated See UPGRADING.md for more information + */ + public function setAuth($auth) + { + throw new BadMethodCallException('This function no longer exists. See UPGRADING.md for more information'); + } + /** + * Set the OAuth 2.0 Client ID. + * @param string $clientId + */ + public function setClientId($clientId) + { + $this->config['client_id'] = $clientId; + } + public function getClientId() + { + return $this->config['client_id']; + } + /** + * Set the OAuth 2.0 Client Secret. + * @param string $clientSecret + */ + public function setClientSecret($clientSecret) + { + $this->config['client_secret'] = $clientSecret; + } + public function getClientSecret() + { + return $this->config['client_secret']; + } + /** + * Set the OAuth 2.0 Redirect URI. + * @param string $redirectUri + */ + public function setRedirectUri($redirectUri) + { + $this->config['redirect_uri'] = $redirectUri; + } + public function getRedirectUri() + { + return $this->config['redirect_uri']; + } + /** + * Set OAuth 2.0 "state" parameter to achieve per-request customization. + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2 + * @param string $state + */ + public function setState($state) + { + $this->config['state'] = $state; + } + /** + * @param string $accessType Possible values for access_type include: + * {@code "offline"} to request offline access from the user. + * {@code "online"} to request online access from the user. + */ + public function setAccessType($accessType) + { + $this->config['access_type'] = $accessType; + } + /** + * @param string $approvalPrompt Possible values for approval_prompt include: + * {@code "force"} to force the approval UI to appear. + * {@code "auto"} to request auto-approval when possible. (This is the default value) + */ + public function setApprovalPrompt($approvalPrompt) + { + $this->config['approval_prompt'] = $approvalPrompt; + } + /** + * Set the login hint, email address or sub id. + * @param string $loginHint + */ + public function setLoginHint($loginHint) + { + $this->config['login_hint'] = $loginHint; + } + /** + * Set the application name, this is included in the User-Agent HTTP header. + * @param string $applicationName + */ + public function setApplicationName($applicationName) + { + $this->config['application_name'] = $applicationName; + } + /** + * If 'plus.login' is included in the list of requested scopes, you can use + * this method to define types of app activities that your app will write. + * You can find a list of available types here: + * @link https://developers.google.com/+/api/moment-types + * + * @param array $requestVisibleActions Array of app activity types + */ + public function setRequestVisibleActions($requestVisibleActions) + { + if (\is_array($requestVisibleActions)) { + $requestVisibleActions = \implode(" ", $requestVisibleActions); + } + $this->config['request_visible_actions'] = $requestVisibleActions; + } + /** + * Set the developer key to use, these are obtained through the API Console. + * @see http://code.google.com/apis/console-help/#generatingdevkeys + * @param string $developerKey + */ + public function setDeveloperKey($developerKey) + { + $this->config['developer_key'] = $developerKey; + } + /** + * Set the hd (hosted domain) parameter streamlines the login process for + * Google Apps hosted accounts. By including the domain of the user, you + * restrict sign-in to accounts at that domain. + * @param string $hd the domain to use. + */ + public function setHostedDomain($hd) + { + $this->config['hd'] = $hd; + } + /** + * Set the prompt hint. Valid values are none, consent and select_account. + * If no value is specified and the user has not previously authorized + * access, then the user is shown a consent screen. + * @param string $prompt + * {@code "none"} Do not display any authentication or consent screens. Must not be specified with other values. + * {@code "consent"} Prompt the user for consent. + * {@code "select_account"} Prompt the user to select an account. + */ + public function setPrompt($prompt) + { + $this->config['prompt'] = $prompt; + } + /** + * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth + * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which + * an authentication request is valid. + * @param string $realm the URL-space to use. + */ + public function setOpenidRealm($realm) + { + $this->config['openid.realm'] = $realm; + } + /** + * If this is provided with the value true, and the authorization request is + * granted, the authorization will include any previous authorizations + * granted to this user/application combination for other scopes. + * @param bool $include the URL-space to use. + */ + public function setIncludeGrantedScopes($include) + { + $this->config['include_granted_scopes'] = $include; + } + /** + * sets function to be called when an access token is fetched + * @param callable $tokenCallback - function ($cacheKey, $accessToken) + */ + public function setTokenCallback(callable $tokenCallback) + { + $this->config['token_callback'] = $tokenCallback; + } + /** + * Revoke an OAuth2 access token or refresh token. This method will revoke the current access + * token, if a token isn't provided. + * + * @param string|array|null $token The token (access token or a refresh token) that should be revoked. + * @return boolean Returns True if the revocation was successful, otherwise False. + */ + public function revokeToken($token = null) + { + $tokenRevoker = new Revoke($this->getHttpClient()); + return $tokenRevoker->revokeToken($token ?: $this->getAccessToken()); + } + /** + * Verify an id_token. This method will verify the current id_token, if one + * isn't provided. + * + * @throws LogicException If no token was provided and no token was set using `setAccessToken`. + * @throws UnexpectedValueException If the token is not a valid JWT. + * @param string|null $idToken The token (id_token) that should be verified. + * @return array|false Returns the token payload as an array if the verification was + * successful, false otherwise. + */ + public function verifyIdToken($idToken = null) + { + $tokenVerifier = new Verify($this->getHttpClient(), $this->getCache(), $this->config['jwt']); + if (null === $idToken) { + $token = $this->getAccessToken(); + if (!isset($token['id_token'])) { + throw new LogicException('id_token must be passed in or set as part of setAccessToken'); + } + $idToken = $token['id_token']; + } + return $tokenVerifier->verifyIdToken($idToken, $this->getClientId()); + } + /** + * Set the scopes to be requested. Must be called before createAuthUrl(). + * Will remove any previously configured scopes. + * @param string|array $scope_or_scopes, ie: + * array( + * 'https://www.googleapis.com/auth/plus.login', + * 'https://www.googleapis.com/auth/moderator' + * ); + */ + public function setScopes($scope_or_scopes) + { + $this->requestedScopes = []; + $this->addScope($scope_or_scopes); + } + /** + * This functions adds a scope to be requested as part of the OAuth2.0 flow. + * Will append any scopes not previously requested to the scope parameter. + * A single string will be treated as a scope to request. An array of strings + * will each be appended. + * @param string|string[] $scope_or_scopes e.g. "profile" + */ + public function addScope($scope_or_scopes) + { + if (\is_string($scope_or_scopes) && !\in_array($scope_or_scopes, $this->requestedScopes)) { + $this->requestedScopes[] = $scope_or_scopes; + } elseif (\is_array($scope_or_scopes)) { + foreach ($scope_or_scopes as $scope) { + $this->addScope($scope); + } + } + } + /** + * Returns the list of scopes requested by the client + * @return array the list of scopes + * + */ + public function getScopes() + { + return $this->requestedScopes; + } + /** + * @return string|null + * @visible For Testing + */ + public function prepareScopes() + { + if (empty($this->requestedScopes)) { + return null; + } + return \implode(' ', $this->requestedScopes); + } + /** + * Helper method to execute deferred HTTP requests. + * + * @template T + * @param RequestInterface $request + * @param class-string|false|null $expectedClass + * @throws \Google\Exception + * @return mixed|T|ResponseInterface + */ + public function execute(RequestInterface $request, $expectedClass = null) + { + $request = $request->withHeader('User-Agent', \sprintf('%s %s%s', $this->config['application_name'], self::USER_AGENT_SUFFIX, $this->getLibraryVersion()))->withHeader('x-goog-api-client', \sprintf('gl-php/%s gdcl/%s', \phpversion(), $this->getLibraryVersion())); + if ($this->config['api_format_v2']) { + $request = $request->withHeader('X-GOOG-API-FORMAT-VERSION', '2'); + } + // call the authorize method + // this is where most of the grunt work is done + $http = $this->authorize(); + return REST::execute($http, $request, $expectedClass, $this->config['retry'], $this->config['retry_map']); + } + /** + * Declare whether batch calls should be used. This may increase throughput + * by making multiple requests in one connection. + * + * @param boolean $useBatch True if the batch support should + * be enabled. Defaults to False. + */ + public function setUseBatch($useBatch) + { + // This is actually an alias for setDefer. + $this->setDefer($useBatch); + } + /** + * Are we running in Google AppEngine? + * return bool + */ + public function isAppEngine() + { + return isset($_SERVER['SERVER_SOFTWARE']) && \strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== \false; + } + public function setConfig($name, $value) + { + $this->config[$name] = $value; + } + public function getConfig($name, $default = null) + { + return isset($this->config[$name]) ? $this->config[$name] : $default; + } + /** + * For backwards compatibility + * alias for setAuthConfig + * + * @param string $file the configuration file + * @throws \Google\Exception + * @deprecated + */ + public function setAuthConfigFile($file) + { + $this->setAuthConfig($file); + } + /** + * Set the auth config from new or deprecated JSON config. + * This structure should match the file downloaded from + * the "Download JSON" button on in the Google Developer + * Console. + * @param string|array $config the configuration json + * @throws \Google\Exception + */ + public function setAuthConfig($config) + { + if (\is_string($config)) { + if (!\file_exists($config)) { + throw new InvalidArgumentException(\sprintf('file "%s" does not exist', $config)); + } + $json = \file_get_contents($config); + if (!($config = \json_decode($json, \true))) { + throw new LogicException('invalid json for auth config'); + } + } + $key = isset($config['installed']) ? 'installed' : 'web'; + if (isset($config['type']) && $config['type'] == 'service_account') { + // application default credentials + $this->useApplicationDefaultCredentials(); + // set the information from the config + $this->setClientId($config['client_id']); + $this->config['client_email'] = $config['client_email']; + $this->config['signing_key'] = $config['private_key']; + $this->config['signing_algorithm'] = 'HS256'; + } elseif (isset($config[$key])) { + // old-style + $this->setClientId($config[$key]['client_id']); + $this->setClientSecret($config[$key]['client_secret']); + if (isset($config[$key]['redirect_uris'])) { + $this->setRedirectUri($config[$key]['redirect_uris'][0]); + } + } else { + // new-style + $this->setClientId($config['client_id']); + $this->setClientSecret($config['client_secret']); + if (isset($config['redirect_uris'])) { + $this->setRedirectUri($config['redirect_uris'][0]); + } + } + } + /** + * Use when the service account has been delegated domain wide access. + * + * @param string $subject an email address account to impersonate + */ + public function setSubject($subject) + { + $this->config['subject'] = $subject; + } + /** + * Declare whether making API calls should make the call immediately, or + * return a request which can be called with ->execute(); + * + * @param boolean $defer True if calls should not be executed right away. + */ + public function setDefer($defer) + { + $this->deferExecution = $defer; + } + /** + * Whether or not to return raw requests + * @return boolean + */ + public function shouldDefer() + { + return $this->deferExecution; + } + /** + * @return OAuth2 implementation + */ + public function getOAuth2Service() + { + if (!isset($this->auth)) { + $this->auth = $this->createOAuth2Service(); + } + return $this->auth; + } + /** + * create a default google auth object + */ + protected function createOAuth2Service() + { + $auth = new OAuth2(['clientId' => $this->getClientId(), 'clientSecret' => $this->getClientSecret(), 'authorizationUri' => self::OAUTH2_AUTH_URL, 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI, 'redirectUri' => $this->getRedirectUri(), 'issuer' => $this->config['client_id'], 'signingKey' => $this->config['signing_key'], 'signingAlgorithm' => $this->config['signing_algorithm']]); + return $auth; + } + /** + * Set the Cache object + * @param CacheItemPoolInterface $cache + */ + public function setCache(CacheItemPoolInterface $cache) + { + $this->cache = $cache; + } + /** + * @return CacheItemPoolInterface + */ + public function getCache() + { + if (!$this->cache) { + $this->cache = $this->createDefaultCache(); + } + return $this->cache; + } + /** + * @param array $cacheConfig + */ + public function setCacheConfig(array $cacheConfig) + { + $this->config['cache_config'] = $cacheConfig; + } + /** + * Set the Logger object + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + /** + * @return LoggerInterface + */ + public function getLogger() + { + if (!isset($this->logger)) { + $this->logger = $this->createDefaultLogger(); + } + return $this->logger; + } + protected function createDefaultLogger() + { + $logger = new Logger('google-api-php-client'); + if ($this->isAppEngine()) { + $handler = new MonologSyslogHandler('app', \LOG_USER, Logger::NOTICE); + } else { + $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE); + } + $logger->pushHandler($handler); + return $logger; + } + protected function createDefaultCache() + { + return new MemoryCacheItemPool(); + } + /** + * Set the Http Client object + * @param ClientInterface $http + */ + public function setHttpClient(ClientInterface $http) + { + $this->http = $http; + } + /** + * @return ClientInterface + */ + public function getHttpClient() + { + if (null === $this->http) { + $this->http = $this->createDefaultHttpClient(); + } + return $this->http; + } + /** + * Set the API format version. + * + * `true` will use V2, which may return more useful error messages. + * + * @param bool $value + */ + public function setApiFormatV2($value) + { + $this->config['api_format_v2'] = (bool) $value; + } + protected function createDefaultHttpClient() + { + $guzzleVersion = null; + if (\defined('\\WPMailSMTP\\Vendor\\GuzzleHttp\\ClientInterface::MAJOR_VERSION')) { + $guzzleVersion = ClientInterface::MAJOR_VERSION; + } elseif (\defined('\\WPMailSMTP\\Vendor\\GuzzleHttp\\ClientInterface::VERSION')) { + $guzzleVersion = (int) \substr(ClientInterface::VERSION, 0, 1); + } + if (5 === $guzzleVersion) { + $options = ['base_url' => $this->config['base_path'], 'defaults' => ['exceptions' => \false]]; + if ($this->isAppEngine()) { + if (\class_exists(StreamHandler::class)) { + // set StreamHandler on AppEngine by default + $options['handler'] = new StreamHandler(); + $options['defaults']['verify'] = '/etc/ca-certificates.crt'; + } + } + } elseif (6 === $guzzleVersion || 7 === $guzzleVersion) { + // guzzle 6 or 7 + $options = ['base_uri' => $this->config['base_path'], 'http_errors' => \false]; + } else { + throw new LogicException('Could not find supported version of Guzzle.'); + } + return new GuzzleClient($options); + } + /** + * @return FetchAuthTokenCache + */ + private function createApplicationDefaultCredentials() + { + $scopes = $this->prepareScopes(); + $sub = $this->config['subject']; + $signingKey = $this->config['signing_key']; + // create credentials using values supplied in setAuthConfig + if ($signingKey) { + $serviceAccountCredentials = ['client_id' => $this->config['client_id'], 'client_email' => $this->config['client_email'], 'private_key' => $signingKey, 'type' => 'service_account', 'quota_project_id' => $this->config['quota_project']]; + $credentials = CredentialsLoader::makeCredentials($scopes, $serviceAccountCredentials); + } else { + // When $sub is provided, we cannot pass cache classes to ::getCredentials + // because FetchAuthTokenCache::setSub does not exist. + // The result is when $sub is provided, calls to ::onGce are not cached. + $credentials = ApplicationDefaultCredentials::getCredentials($scopes, null, $sub ? null : $this->config['cache_config'], $sub ? null : $this->getCache(), $this->config['quota_project']); + } + // for service account domain-wide authority (impersonating a user) + // @see https://developers.google.com/identity/protocols/OAuth2ServiceAccount + if ($sub) { + if (!$credentials instanceof ServiceAccountCredentials) { + throw new DomainException('domain-wide authority requires service account credentials'); + } + $credentials->setSub($sub); + } + // If we are not using FetchAuthTokenCache yet, create it now + if (!$credentials instanceof FetchAuthTokenCache) { + $credentials = new FetchAuthTokenCache($credentials, $this->config['cache_config'], $this->getCache()); + } + return $credentials; + } + protected function getAuthHandler() + { + // Be very careful using the cache, as the underlying auth library's cache + // implementation is naive, and the cache keys do not account for user + // sessions. + // + // @see https://github.com/google/google-api-php-client/issues/821 + return AuthHandlerFactory::build($this->getCache(), $this->config['cache_config']); + } + private function createUserRefreshCredentials($scope, $refreshToken) + { + $creds = \array_filter(['client_id' => $this->getClientId(), 'client_secret' => $this->getClientSecret(), 'refresh_token' => $refreshToken]); + return new UserRefreshCredentials($scope, $creds); + } + private function checkUniverseDomain($credentials) + { + $credentialsUniverse = $credentials instanceof GetUniverseDomainInterface ? $credentials->getUniverseDomain() : GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; + if ($credentialsUniverse !== $this->getUniverseDomain()) { + throw new DomainException(\sprintf('The configured universe domain (%s) does not match the credential universe domain (%s)', $this->getUniverseDomain(), $credentialsUniverse)); + } + } + public function getUniverseDomain() + { + return $this->config['universe_domain']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Collection.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Collection.php new file mode 100644 index 00000000..860b1f5b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Collection.php @@ -0,0 +1,104 @@ +{$this->collection_key}) && \is_array($this->{$this->collection_key})) { + \reset($this->{$this->collection_key}); + } + } + /** @return mixed */ + #[\ReturnTypeWillChange] + public function current() + { + $this->coerceType($this->key()); + if (\is_array($this->{$this->collection_key})) { + return \current($this->{$this->collection_key}); + } + } + /** @return mixed */ + #[\ReturnTypeWillChange] + public function key() + { + if (isset($this->{$this->collection_key}) && \is_array($this->{$this->collection_key})) { + return \key($this->{$this->collection_key}); + } + } + /** @return mixed */ + #[\ReturnTypeWillChange] + public function next() + { + return \next($this->{$this->collection_key}); + } + /** @return bool */ + #[\ReturnTypeWillChange] + public function valid() + { + $key = $this->key(); + return $key !== null && $key !== \false; + } + /** @return int */ + #[\ReturnTypeWillChange] + public function count() + { + if (!isset($this->{$this->collection_key})) { + return 0; + } + return \count($this->{$this->collection_key}); + } + /** @return bool */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + if (!\is_numeric($offset)) { + return parent::offsetExists($offset); + } + return isset($this->{$this->collection_key}[$offset]); + } + /** @return mixed */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + if (!\is_numeric($offset)) { + return parent::offsetGet($offset); + } + $this->coerceType($offset); + return $this->{$this->collection_key}[$offset]; + } + /** @return void */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (!\is_numeric($offset)) { + parent::offsetSet($offset, $value); + } + $this->{$this->collection_key}[$offset] = $value; + } + /** @return void */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + if (!\is_numeric($offset)) { + parent::offsetUnset($offset); + } + unset($this->{$this->collection_key}[$offset]); + } + private function coerceType($offset) + { + $keyType = $this->keyType($this->collection_key); + if ($keyType && !\is_object($this->{$this->collection_key}[$offset])) { + $this->{$this->collection_key}[$offset] = new $keyType($this->{$this->collection_key}[$offset]); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Exception.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Exception.php new file mode 100644 index 00000000..923c54e8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Exception.php @@ -0,0 +1,23 @@ +client = $client; + $this->boundary = $boundary ?: \mt_rand(); + $rootUrl = \rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/'); + $this->rootUrl = \str_replace('UNIVERSE_DOMAIN', $this->client->getUniverseDomain(), $rootUrl); + $this->batchPath = $batchPath ?: self::BATCH_PATH; + } + public function add(RequestInterface $request, $key = \false) + { + if (\false == $key) { + $key = \mt_rand(); + } + $this->requests[$key] = $request; + } + public function execute() + { + $body = ''; + $classes = []; + $batchHttpTemplate = <<requests as $key => $request) { + $firstLine = \sprintf('%s %s HTTP/%s', $request->getMethod(), $request->getRequestTarget(), $request->getProtocolVersion()); + $content = (string) $request->getBody(); + $headers = ''; + foreach ($request->getHeaders() as $name => $values) { + $headers .= \sprintf("%s:%s\r\n", $name, \implode(', ', $values)); + } + $body .= \sprintf($batchHttpTemplate, $this->boundary, $key, $firstLine, $headers, $content ? "\n" . $content : ''); + $classes['response-' . $key] = $request->getHeaderLine('X-Php-Expected-Class'); + } + $body .= "--{$this->boundary}--"; + $body = \trim($body); + $url = $this->rootUrl . '/' . $this->batchPath; + $headers = ['Content-Type' => \sprintf('multipart/mixed; boundary=%s', $this->boundary), 'Content-Length' => (string) \strlen($body)]; + $request = new Request('POST', $url, $headers, $body); + $response = $this->client->execute($request); + return $this->parseResponse($response, $classes); + } + public function parseResponse(ResponseInterface $response, $classes = []) + { + $contentType = $response->getHeaderLine('content-type'); + $contentType = \explode(';', $contentType); + $boundary = \false; + foreach ($contentType as $part) { + $part = \explode('=', $part, 2); + if (isset($part[0]) && 'boundary' == \trim($part[0])) { + $boundary = $part[1]; + } + } + $body = (string) $response->getBody(); + if (!empty($body)) { + $body = \str_replace("--{$boundary}--", "--{$boundary}", $body); + $parts = \explode("--{$boundary}", $body); + $responses = []; + $requests = \array_values($this->requests); + foreach ($parts as $i => $part) { + $part = \trim($part); + if (!empty($part)) { + list($rawHeaders, $part) = \explode("\r\n\r\n", $part, 2); + $headers = $this->parseRawHeaders($rawHeaders); + $status = \substr($part, 0, \strpos($part, "\n")); + $status = \explode(" ", $status); + $status = $status[1]; + list($partHeaders, $partBody) = $this->parseHttpResponse($part, 0); + $response = new Response((int) $status, $partHeaders, Psr7\Utils::streamFor($partBody)); + // Need content id. + $key = $headers['content-id']; + try { + $response = REST::decodeHttpResponse($response, $requests[$i - 1]); + } catch (GoogleServiceException $e) { + // Store the exception as the response, so successful responses + // can be processed. + $response = $e; + } + $responses[$key] = $response; + } + } + return $responses; + } + return null; + } + private function parseRawHeaders($rawHeaders) + { + $headers = []; + $responseHeaderLines = \explode("\r\n", $rawHeaders); + foreach ($responseHeaderLines as $headerLine) { + if ($headerLine && \strpos($headerLine, ':') !== \false) { + list($header, $value) = \explode(': ', $headerLine, 2); + $header = \strtolower($header); + if (isset($headers[$header])) { + $headers[$header] = \array_merge((array) $headers[$header], (array) $value); + } else { + $headers[$header] = $value; + } + } + } + return $headers; + } + /** + * Used by the IO lib and also the batch processing. + * + * @param string $respData + * @param int $headerSize + * @return array + */ + private function parseHttpResponse($respData, $headerSize) + { + // check proxy header + foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) { + if (\stripos($respData, $established_header) !== \false) { + // existed, remove it + $respData = \str_ireplace($established_header, '', $respData); + // Subtract the proxy header size unless the cURL bug prior to 7.30.0 + // is present which prevented the proxy header size from being taken into + // account. + // @TODO look into this + // if (!$this->needsQuirk()) { + // $headerSize -= strlen($established_header); + // } + break; + } + } + if ($headerSize) { + $responseBody = \substr($respData, $headerSize); + $responseHeaders = \substr($respData, 0, $headerSize); + } else { + $responseSegments = \explode("\r\n\r\n", $respData, 2); + $responseHeaders = $responseSegments[0]; + $responseBody = isset($responseSegments[1]) ? $responseSegments[1] : null; + } + $responseHeaders = $this->parseRawHeaders($responseHeaders); + return [$responseHeaders, $responseBody]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/MediaFileUpload.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/MediaFileUpload.php new file mode 100644 index 00000000..7e5c3ff1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/MediaFileUpload.php @@ -0,0 +1,273 @@ +client = $client; + $this->request = $request; + $this->mimeType = $mimeType; + $this->data = $data; + $this->resumable = $resumable; + $this->chunkSize = $chunkSize; + $this->progress = 0; + $this->process(); + } + /** + * Set the size of the file that is being uploaded. + * @param int $size - int file size in bytes + */ + public function setFileSize($size) + { + $this->size = $size; + } + /** + * Return the progress on the upload + * @return int progress in bytes uploaded. + */ + public function getProgress() + { + return $this->progress; + } + /** + * Send the next part of the file to upload. + * @param string|bool $chunk Optional. The next set of bytes to send. If false will + * use $data passed at construct time. + */ + public function nextChunk($chunk = \false) + { + $resumeUri = $this->getResumeUri(); + if (\false == $chunk) { + $chunk = \substr($this->data, $this->progress, $this->chunkSize); + } + $lastBytePos = $this->progress + \strlen($chunk) - 1; + $headers = ['content-range' => "bytes {$this->progress}-{$lastBytePos}/{$this->size}", 'content-length' => (string) \strlen($chunk), 'expect' => '']; + $request = new Request('PUT', $resumeUri, $headers, Psr7\Utils::streamFor($chunk)); + return $this->makePutRequest($request); + } + /** + * Return the HTTP result code from the last call made. + * @return int code + */ + public function getHttpResultCode() + { + return $this->httpResultCode; + } + /** + * Sends a PUT-Request to google drive and parses the response, + * setting the appropiate variables from the response() + * + * @param RequestInterface $request the Request which will be send + * + * @return false|mixed false when the upload is unfinished or the decoded http response + * + */ + private function makePutRequest(RequestInterface $request) + { + $response = $this->client->execute($request); + $this->httpResultCode = $response->getStatusCode(); + if (308 == $this->httpResultCode) { + // Track the amount uploaded. + $range = $response->getHeaderLine('range'); + if ($range) { + $range_array = \explode('-', $range); + $this->progress = (int) $range_array[1] + 1; + } + // Allow for changing upload URLs. + $location = $response->getHeaderLine('location'); + if ($location) { + $this->resumeUri = $location; + } + // No problems, but upload not complete. + return \false; + } + return REST::decodeHttpResponse($response, $this->request); + } + /** + * Resume a previously unfinished upload + * @param string $resumeUri the resume-URI of the unfinished, resumable upload. + */ + public function resume($resumeUri) + { + $this->resumeUri = $resumeUri; + $headers = ['content-range' => "bytes */{$this->size}", 'content-length' => '0']; + $httpRequest = new Request('PUT', $this->resumeUri, $headers); + return $this->makePutRequest($httpRequest); + } + /** + * @return RequestInterface + * @visible for testing + */ + private function process() + { + $this->transformToUploadUrl(); + $request = $this->request; + $postBody = ''; + $contentType = \false; + $meta = \json_decode((string) $request->getBody(), \true); + $uploadType = $this->getUploadType($meta); + $request = $request->withUri(Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType)); + $mimeType = $this->mimeType ?: $request->getHeaderLine('content-type'); + if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) { + $contentType = $mimeType; + $postBody = \is_string($meta) ? $meta : \json_encode($meta); + } elseif (self::UPLOAD_MEDIA_TYPE == $uploadType) { + $contentType = $mimeType; + $postBody = $this->data; + } elseif (self::UPLOAD_MULTIPART_TYPE == $uploadType) { + // This is a multipart/related upload. + $boundary = $this->boundary ?: \mt_rand(); + $boundary = \str_replace('"', '', $boundary); + $contentType = 'multipart/related; boundary=' . $boundary; + $related = "--{$boundary}\r\n"; + $related .= "Content-Type: application/json; charset=UTF-8\r\n"; + $related .= "\r\n" . \json_encode($meta) . "\r\n"; + $related .= "--{$boundary}\r\n"; + $related .= "Content-Type: {$mimeType}\r\n"; + $related .= "Content-Transfer-Encoding: base64\r\n"; + $related .= "\r\n" . \base64_encode($this->data) . "\r\n"; + $related .= "--{$boundary}--"; + $postBody = $related; + } + $request = $request->withBody(Psr7\Utils::streamFor($postBody)); + if ($contentType) { + $request = $request->withHeader('content-type', $contentType); + } + return $this->request = $request; + } + /** + * Valid upload types: + * - resumable (UPLOAD_RESUMABLE_TYPE) + * - media (UPLOAD_MEDIA_TYPE) + * - multipart (UPLOAD_MULTIPART_TYPE) + * @param string|false $meta + * @return string + * @visible for testing + */ + public function getUploadType($meta) + { + if ($this->resumable) { + return self::UPLOAD_RESUMABLE_TYPE; + } + if (\false == $meta && $this->data) { + return self::UPLOAD_MEDIA_TYPE; + } + return self::UPLOAD_MULTIPART_TYPE; + } + public function getResumeUri() + { + if (null === $this->resumeUri) { + $this->resumeUri = $this->fetchResumeUri(); + } + return $this->resumeUri; + } + private function fetchResumeUri() + { + $body = $this->request->getBody(); + $headers = ['content-type' => 'application/json; charset=UTF-8', 'content-length' => $body->getSize(), 'x-upload-content-type' => $this->mimeType, 'x-upload-content-length' => $this->size, 'expect' => '']; + foreach ($headers as $key => $value) { + $this->request = $this->request->withHeader($key, $value); + } + $response = $this->client->execute($this->request, \false); + $location = $response->getHeaderLine('location'); + $code = $response->getStatusCode(); + if (200 == $code && \true == $location) { + return $location; + } + $message = $code; + $body = \json_decode((string) $this->request->getBody(), \true); + if (isset($body['error']['errors'])) { + $message .= ': '; + foreach ($body['error']['errors'] as $error) { + $message .= "{$error['domain']}, {$error['message']};"; + } + $message = \rtrim($message, ';'); + } + $error = "Failed to start the resumable upload (HTTP {$message})"; + $this->client->getLogger()->error($error); + throw new GoogleException($error); + } + private function transformToUploadUrl() + { + $parts = \parse_url((string) $this->request->getUri()); + if (!isset($parts['path'])) { + $parts['path'] = ''; + } + $parts['path'] = '/upload' . $parts['path']; + $uri = Uri::fromParts($parts); + $this->request = $this->request->withUri($uri); + } + public function setChunkSize($chunkSize) + { + $this->chunkSize = $chunkSize; + } + public function getRequest() + { + return $this->request; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/REST.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/REST.php new file mode 100644 index 00000000..696f00be --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Http/REST.php @@ -0,0 +1,153 @@ +|false|null $expectedClass + * @param array $config + * @param array $retryMap + * @return mixed|T|null + * @throws \Google\Service\Exception on server side error (ie: not authenticated, + * invalid or malformed post body, invalid url) + */ + public static function execute(ClientInterface $client, RequestInterface $request, $expectedClass = null, $config = [], $retryMap = null) + { + $runner = new Runner($config, \sprintf('%s %s', $request->getMethod(), (string) $request->getUri()), [self::class, 'doExecute'], [$client, $request, $expectedClass]); + if (null !== $retryMap) { + $runner->setRetryMap($retryMap); + } + return $runner->run(); + } + /** + * Executes a Psr\Http\Message\RequestInterface + * + * @template T + * @param ClientInterface $client + * @param RequestInterface $request + * @param class-string|false|null $expectedClass + * @return mixed|T|null + * @throws \Google\Service\Exception on server side error (ie: not authenticated, + * invalid or malformed post body, invalid url) + */ + public static function doExecute(ClientInterface $client, RequestInterface $request, $expectedClass = null) + { + try { + $httpHandler = HttpHandlerFactory::build($client); + $response = $httpHandler($request); + } catch (RequestException $e) { + // if Guzzle throws an exception, catch it and handle the response + if (!$e->hasResponse()) { + throw $e; + } + $response = $e->getResponse(); + // specific checking for Guzzle 5: convert to PSR7 response + if (\interface_exists('WPMailSMTP\\Vendor\\GuzzleHttp\\Message\\ResponseInterface') && $response instanceof \WPMailSMTP\Vendor\GuzzleHttp\Message\ResponseInterface) { + $response = new Response($response->getStatusCode(), $response->getHeaders() ?: [], $response->getBody(), $response->getProtocolVersion(), $response->getReasonPhrase()); + } + } + return self::decodeHttpResponse($response, $request, $expectedClass); + } + /** + * Decode an HTTP Response. + * @static + * + * @template T + * @param RequestInterface $response The http response to be decoded. + * @param ResponseInterface $response + * @param class-string|false|null $expectedClass + * @return mixed|T|null + * @throws \Google\Service\Exception + */ + public static function decodeHttpResponse(ResponseInterface $response, ?RequestInterface $request = null, $expectedClass = null) + { + $code = $response->getStatusCode(); + // retry strategy + if (\intVal($code) >= 400) { + // if we errored out, it should be safe to grab the response body + $body = (string) $response->getBody(); + // Check if we received errors, and add those to the Exception for convenience + throw new GoogleServiceException($body, $code, null, self::getResponseErrors($body)); + } + // Ensure we only pull the entire body into memory if the request is not + // of media type + $body = self::decodeBody($response, $request); + if ($expectedClass = self::determineExpectedClass($expectedClass, $request)) { + $json = \json_decode($body, \true); + return new $expectedClass($json); + } + return $response; + } + private static function decodeBody(ResponseInterface $response, ?RequestInterface $request = null) + { + if (self::isAltMedia($request)) { + // don't decode the body, it's probably a really long string + return ''; + } + return (string) $response->getBody(); + } + private static function determineExpectedClass($expectedClass, ?RequestInterface $request = null) + { + // "false" is used to explicitly prevent an expected class from being returned + if (\false === $expectedClass) { + return null; + } + // if we don't have a request, we just use what's passed in + if (null === $request) { + return $expectedClass; + } + // return what we have in the request header if one was not supplied + return $expectedClass ?: $request->getHeaderLine('X-Php-Expected-Class'); + } + private static function getResponseErrors($body) + { + $json = \json_decode($body, \true); + if (isset($json['error']['errors'])) { + return $json['error']['errors']; + } + return null; + } + private static function isAltMedia(?RequestInterface $request = null) + { + if ($request && ($qs = $request->getUri()->getQuery())) { + \parse_str($qs, $query); + if (isset($query['alt']) && $query['alt'] == 'media') { + return \true; + } + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Model.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Model.php new file mode 100644 index 00000000..66d31ac1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Model.php @@ -0,0 +1,301 @@ +mapTypes($array); + } + $this->gapiInit(); + } + /** + * Getter that handles passthrough access to the data array, and lazy object creation. + * @param string $key Property name. + * @return mixed The value if any, or null. + */ + public function __get($key) + { + $keyType = $this->keyType($key); + $keyDataType = $this->dataType($key); + if ($keyType && !isset($this->processed[$key])) { + if (isset($this->modelData[$key])) { + $val = $this->modelData[$key]; + } elseif ($keyDataType == 'array' || $keyDataType == 'map') { + $val = []; + } else { + $val = null; + } + if ($this->isAssociativeArray($val)) { + if ($keyDataType && 'map' == $keyDataType) { + foreach ($val as $arrayKey => $arrayItem) { + $this->modelData[$key][$arrayKey] = new $keyType($arrayItem); + } + } else { + $this->modelData[$key] = new $keyType($val); + } + } elseif (\is_array($val)) { + $arrayObject = []; + foreach ($val as $arrayIndex => $arrayItem) { + $arrayObject[$arrayIndex] = new $keyType($arrayItem); + } + $this->modelData[$key] = $arrayObject; + } + $this->processed[$key] = \true; + } + return isset($this->modelData[$key]) ? $this->modelData[$key] : null; + } + /** + * Initialize this object's properties from an array. + * + * @param array $array Used to seed this object's properties. + * @return void + */ + protected function mapTypes($array) + { + // Hard initialise simple types, lazy load more complex ones. + foreach ($array as $key => $val) { + if ($keyType = $this->keyType($key)) { + $dataType = $this->dataType($key); + if ($dataType == 'array' || $dataType == 'map') { + $this->{$key} = []; + foreach ($val as $itemKey => $itemVal) { + if ($itemVal instanceof $keyType) { + $this->{$key}[$itemKey] = $itemVal; + } else { + $this->{$key}[$itemKey] = new $keyType($itemVal); + } + } + } elseif ($val instanceof $keyType) { + $this->{$key} = $val; + } else { + $this->{$key} = new $keyType($val); + } + unset($array[$key]); + } elseif (\property_exists($this, $key)) { + $this->{$key} = $val; + unset($array[$key]); + } elseif (\property_exists($this, $camelKey = $this->camelCase($key))) { + // This checks if property exists as camelCase, leaving it in array as snake_case + // in case of backwards compatibility issues. + $this->{$camelKey} = $val; + } + } + $this->modelData = $array; + } + /** + * Blank initialiser to be used in subclasses to do post-construction initialisation - this + * avoids the need for subclasses to have to implement the variadics handling in their + * constructors. + */ + protected function gapiInit() + { + return; + } + /** + * Create a simplified object suitable for straightforward + * conversion to JSON. This is relatively expensive + * due to the usage of reflection, but shouldn't be called + * a whole lot, and is the most straightforward way to filter. + */ + public function toSimpleObject() + { + $object = new stdClass(); + // Process all other data. + foreach ($this->modelData as $key => $val) { + $result = $this->getSimpleValue($val); + if ($result !== null) { + $object->{$key} = $this->nullPlaceholderCheck($result); + } + } + // Process all public properties. + $reflect = new ReflectionObject($this); + $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC); + foreach ($props as $member) { + $name = $member->getName(); + $result = $this->getSimpleValue($this->{$name}); + if ($result !== null) { + $name = $this->getMappedName($name); + $object->{$name} = $this->nullPlaceholderCheck($result); + } + } + return $object; + } + /** + * Handle different types of values, primarily + * other objects and map and array data types. + */ + private function getSimpleValue($value) + { + if ($value instanceof Model) { + return $value->toSimpleObject(); + } elseif (\is_array($value)) { + $return = []; + foreach ($value as $key => $a_value) { + $a_value = $this->getSimpleValue($a_value); + if ($a_value !== null) { + $key = $this->getMappedName($key); + $return[$key] = $this->nullPlaceholderCheck($a_value); + } + } + return $return; + } + return $value; + } + /** + * Check whether the value is the null placeholder and return true null. + */ + private function nullPlaceholderCheck($value) + { + if ($value === self::NULL_VALUE) { + return null; + } + return $value; + } + /** + * If there is an internal name mapping, use that. + */ + private function getMappedName($key) + { + if (isset($this->internal_gapi_mappings, $this->internal_gapi_mappings[$key])) { + $key = $this->internal_gapi_mappings[$key]; + } + return $key; + } + /** + * Returns true only if the array is associative. + * @param array $array + * @return bool True if the array is associative. + */ + protected function isAssociativeArray($array) + { + if (!\is_array($array)) { + return \false; + } + $keys = \array_keys($array); + foreach ($keys as $key) { + if (\is_string($key)) { + return \true; + } + } + return \false; + } + /** + * Verify if $obj is an array. + * @throws \Google\Exception Thrown if $obj isn't an array. + * @param array $obj Items that should be validated. + * @param string $method Method expecting an array as an argument. + */ + public function assertIsArray($obj, $method) + { + if ($obj && !\is_array($obj)) { + throw new GoogleException("Incorrect parameter type passed to {$method}(). Expected an array."); + } + } + /** @return bool */ + #[\ReturnTypeWillChange] + public function offsetExists($offset) + { + return isset($this->{$offset}) || isset($this->modelData[$offset]); + } + /** @return mixed */ + #[\ReturnTypeWillChange] + public function offsetGet($offset) + { + return isset($this->{$offset}) ? $this->{$offset} : $this->__get($offset); + } + /** @return void */ + #[\ReturnTypeWillChange] + public function offsetSet($offset, $value) + { + if (\property_exists($this, $offset)) { + $this->{$offset} = $value; + } else { + $this->modelData[$offset] = $value; + $this->processed[$offset] = \true; + } + } + /** @return void */ + #[\ReturnTypeWillChange] + public function offsetUnset($offset) + { + unset($this->modelData[$offset]); + } + protected function keyType($key) + { + $keyType = $key . "Type"; + // ensure keyType is a valid class + if (\property_exists($this, $keyType) && $this->{$keyType} !== null && \class_exists($this->{$keyType})) { + return $this->{$keyType}; + } + } + protected function dataType($key) + { + $dataType = $key . "DataType"; + if (\property_exists($this, $dataType)) { + return $this->{$dataType}; + } + } + public function __isset($key) + { + return isset($this->modelData[$key]); + } + public function __unset($key) + { + unset($this->modelData[$key]); + } + /** + * Convert a string to camelCase + * @param string $value + * @return string + */ + private function camelCase($value) + { + $value = \ucwords(\str_replace(['-', '_'], ' ', $value)); + $value = \str_replace(' ', '', $value); + $value[0] = \strtolower($value[0]); + return $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service.php new file mode 100644 index 00000000..1dc2bc21 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service.php @@ -0,0 +1,67 @@ +client = $clientOrConfig; + } elseif (\is_array($clientOrConfig)) { + $this->client = new Client($clientOrConfig ?: []); + } else { + $errorMessage = 'WPMailSMTP\\Vendor\\constructor must be array or instance of Google\\Client'; + if (\class_exists('TypeError')) { + throw new TypeError($errorMessage); + } + \trigger_error($errorMessage, \E_USER_ERROR); + } + } + /** + * Return the associated Google\Client class. + * @return \Google\Client + */ + public function getClient() + { + return $this->client; + } + /** + * Create a new HTTP Batch handler for this service + * + * @return Batch + */ + public function createBatch() + { + return new Batch($this->client, \false, $this->rootUrlTemplate ?? $this->rootUrl, $this->batchPath); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Exception.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Exception.php new file mode 100644 index 00000000..b3f6dd89 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Exception.php @@ -0,0 +1,65 @@ +>|null $errors List of errors returned in an HTTP + * response or null. Defaults to []. + */ + public function __construct($message, $code = 0, ?Exception $previous = null, $errors = []) + { + if (\version_compare(\PHP_VERSION, '5.3.0') >= 0) { + parent::__construct($message, $code, $previous); + } else { + parent::__construct($message, $code); + } + $this->errors = $errors; + } + /** + * An example of the possible errors returned. + * + * [ + * { + * "domain": "global", + * "reason": "authError", + * "message": "Invalid Credentials", + * "locationType": "header", + * "location": "Authorization", + * } + * ] + * + * @return array>|null List of errors returned in an HTTP response or null. + */ + public function getErrors() + { + return $this->errors; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Resource.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Resource.php new file mode 100644 index 00000000..42a5899b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Service/Resource.php @@ -0,0 +1,216 @@ + ['type' => 'string', 'location' => 'query'], 'fields' => ['type' => 'string', 'location' => 'query'], 'trace' => ['type' => 'string', 'location' => 'query'], 'userIp' => ['type' => 'string', 'location' => 'query'], 'quotaUser' => ['type' => 'string', 'location' => 'query'], 'data' => ['type' => 'string', 'location' => 'body'], 'mimeType' => ['type' => 'string', 'location' => 'header'], 'uploadType' => ['type' => 'string', 'location' => 'query'], 'mediaUpload' => ['type' => 'complex', 'location' => 'query'], 'prettyPrint' => ['type' => 'string', 'location' => 'query']]; + /** @var string $rootUrlTemplate */ + private $rootUrlTemplate; + /** @var \Google\Client $client */ + private $client; + /** @var string $serviceName */ + private $serviceName; + /** @var string $servicePath */ + private $servicePath; + /** @var string $resourceName */ + private $resourceName; + /** @var array $methods */ + private $methods; + public function __construct($service, $serviceName, $resourceName, $resource) + { + $this->rootUrlTemplate = $service->rootUrlTemplate ?? $service->rootUrl; + $this->client = $service->getClient(); + $this->servicePath = $service->servicePath; + $this->serviceName = $serviceName; + $this->resourceName = $resourceName; + $this->methods = \is_array($resource) && isset($resource['methods']) ? $resource['methods'] : [$resourceName => $resource]; + } + /** + * TODO: This function needs simplifying. + * + * @template T + * @param string $name + * @param array $arguments + * @param class-string $expectedClass - optional, the expected class name + * @return mixed|T|ResponseInterface|RequestInterface + * @throws \Google\Exception + */ + public function call($name, $arguments, $expectedClass = null) + { + if (!isset($this->methods[$name])) { + $this->client->getLogger()->error('Service method unknown', ['service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name]); + throw new GoogleException("Unknown function: " . "{$this->serviceName}->{$this->resourceName}->{$name}()"); + } + $method = $this->methods[$name]; + $parameters = $arguments[0]; + // postBody is a special case since it's not defined in the discovery + // document as parameter, but we abuse the param entry for storing it. + $postBody = null; + if (isset($parameters['postBody'])) { + if ($parameters['postBody'] instanceof Model) { + // In the cases the post body is an existing object, we want + // to use the smart method to create a simple object for + // for JSONification. + $parameters['postBody'] = $parameters['postBody']->toSimpleObject(); + } elseif (\is_object($parameters['postBody'])) { + // If the post body is another kind of object, we will try and + // wrangle it into a sensible format. + $parameters['postBody'] = $this->convertToArrayAndStripNulls($parameters['postBody']); + } + $postBody = (array) $parameters['postBody']; + unset($parameters['postBody']); + } + // TODO: optParams here probably should have been + // handled already - this may well be redundant code. + if (isset($parameters['optParams'])) { + $optParams = $parameters['optParams']; + unset($parameters['optParams']); + $parameters = \array_merge($parameters, $optParams); + } + if (!isset($method['parameters'])) { + $method['parameters'] = []; + } + $method['parameters'] = \array_merge($this->stackParameters, $method['parameters']); + foreach ($parameters as $key => $val) { + if ($key != 'postBody' && !isset($method['parameters'][$key])) { + $this->client->getLogger()->error('Service parameter unknown', ['service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name, 'parameter' => $key]); + throw new GoogleException("({$name}) unknown parameter: '{$key}'"); + } + } + foreach ($method['parameters'] as $paramName => $paramSpec) { + if (isset($paramSpec['required']) && $paramSpec['required'] && !isset($parameters[$paramName])) { + $this->client->getLogger()->error('Service parameter missing', ['service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name, 'parameter' => $paramName]); + throw new GoogleException("({$name}) missing required param: '{$paramName}'"); + } + if (isset($parameters[$paramName])) { + $value = $parameters[$paramName]; + $parameters[$paramName] = $paramSpec; + $parameters[$paramName]['value'] = $value; + unset($parameters[$paramName]['required']); + } else { + // Ensure we don't pass nulls. + unset($parameters[$paramName]); + } + } + $this->client->getLogger()->info('Service Call', ['service' => $this->serviceName, 'resource' => $this->resourceName, 'method' => $name, 'arguments' => $parameters]); + // build the service uri + $url = $this->createRequestUri($method['path'], $parameters); + // NOTE: because we're creating the request by hand, + // and because the service has a rootUrl property + // the "base_uri" of the Http Client is not accounted for + $request = new Request($method['httpMethod'], $url, $postBody ? ['content-type' => 'application/json'] : [], $postBody ? \json_encode($postBody) : ''); + // support uploads + if (isset($parameters['data'])) { + $mimeType = isset($parameters['mimeType']) ? $parameters['mimeType']['value'] : 'application/octet-stream'; + $data = $parameters['data']['value']; + $upload = new MediaFileUpload($this->client, $request, $mimeType, $data); + // pull down the modified request + $request = $upload->getRequest(); + } + // if this is a media type, we will return the raw response + // rather than using an expected class + if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') { + $expectedClass = null; + } + // if the client is marked for deferring, rather than + // execute the request, return the response + if ($this->client->shouldDefer()) { + // @TODO find a better way to do this + $request = $request->withHeader('X-Php-Expected-Class', $expectedClass); + return $request; + } + return $this->client->execute($request, $expectedClass); + } + protected function convertToArrayAndStripNulls($o) + { + $o = (array) $o; + foreach ($o as $k => $v) { + if ($v === null) { + unset($o[$k]); + } elseif (\is_object($v) || \is_array($v)) { + $o[$k] = $this->convertToArrayAndStripNulls($o[$k]); + } + } + return $o; + } + /** + * Parse/expand request parameters and create a fully qualified + * request uri. + * @static + * @param string $restPath + * @param array $params + * @return string $requestUrl + */ + public function createRequestUri($restPath, $params) + { + // Override the default servicePath address if the $restPath use a / + if ('/' == \substr($restPath, 0, 1)) { + $requestUrl = \substr($restPath, 1); + } else { + $requestUrl = $this->servicePath . $restPath; + } + if ($this->rootUrlTemplate) { + // code for universe domain + $rootUrl = \str_replace('UNIVERSE_DOMAIN', $this->client->getUniverseDomain(), $this->rootUrlTemplate); + // code for leading slash + if ('/' !== \substr($rootUrl, -1) && '/' !== \substr($requestUrl, 0, 1)) { + $requestUrl = '/' . $requestUrl; + } + $requestUrl = $rootUrl . $requestUrl; + } + $uriTemplateVars = []; + $queryVars = []; + foreach ($params as $paramName => $paramSpec) { + if ($paramSpec['type'] == 'boolean') { + $paramSpec['value'] = $paramSpec['value'] ? 'true' : 'false'; + } + if ($paramSpec['location'] == 'path') { + $uriTemplateVars[$paramName] = $paramSpec['value']; + } elseif ($paramSpec['location'] == 'query') { + if (\is_array($paramSpec['value'])) { + foreach ($paramSpec['value'] as $value) { + $queryVars[] = $paramName . '=' . \rawurlencode(\rawurldecode($value)); + } + } else { + $queryVars[] = $paramName . '=' . \rawurlencode(\rawurldecode($paramSpec['value'])); + } + } + } + if (\count($uriTemplateVars)) { + $uriTemplateParser = new UriTemplate(); + $requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars); + } + if (\count($queryVars)) { + $requestUrl .= '?' . \implode('&', $queryVars); + } + return $requestUrl; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Composer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Composer.php new file mode 100644 index 00000000..93523302 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Composer.php @@ -0,0 +1,77 @@ +getComposer(); + $extra = $composer->getPackage()->getExtra(); + $servicesToKeep = isset($extra['google/apiclient-services']) ? $extra['google/apiclient-services'] : []; + if ($servicesToKeep) { + $vendorDir = $composer->getConfig()->get('vendor-dir'); + $serviceDir = \sprintf('%s/google/apiclient-services/src/Google/Service', $vendorDir); + if (!\is_dir($serviceDir)) { + // path for google/apiclient-services >= 0.200.0 + $serviceDir = \sprintf('%s/google/apiclient-services/src', $vendorDir); + } + self::verifyServicesToKeep($serviceDir, $servicesToKeep); + $finder = self::getServicesToRemove($serviceDir, $servicesToKeep); + $filesystem = $filesystem ?: new Filesystem(); + if (0 !== ($count = \count($finder))) { + $event->getIO()->write(\sprintf('Removing %s google services', $count)); + foreach ($finder as $file) { + $realpath = $file->getRealPath(); + $filesystem->remove($realpath); + $filesystem->remove($realpath . '.php'); + } + } + } + } + /** + * @throws InvalidArgumentException when the service doesn't exist + */ + private static function verifyServicesToKeep($serviceDir, array $servicesToKeep) + { + $finder = (new Finder())->directories()->depth('== 0'); + foreach ($servicesToKeep as $service) { + if (!\preg_match('/^[a-zA-Z0-9]*$/', $service)) { + throw new InvalidArgumentException(\sprintf('Invalid Google service name "%s"', $service)); + } + try { + $finder->in($serviceDir . '/' . $service); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException(\sprintf('Google service "%s" does not exist or was removed previously', $service)); + } + } + } + private static function getServicesToRemove($serviceDir, array $servicesToKeep) + { + // find all files in the current directory + return (new Finder())->directories()->depth('== 0')->in($serviceDir)->exclude($servicesToKeep); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Exception.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Exception.php new file mode 100644 index 00000000..e695be7c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Task/Exception.php @@ -0,0 +1,23 @@ + self::TASK_RETRY_ALWAYS, + '503' => self::TASK_RETRY_ALWAYS, + 'rateLimitExceeded' => self::TASK_RETRY_ALWAYS, + 'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS, + 6 => self::TASK_RETRY_ALWAYS, + // CURLE_COULDNT_RESOLVE_HOST + 7 => self::TASK_RETRY_ALWAYS, + // CURLE_COULDNT_CONNECT + 28 => self::TASK_RETRY_ALWAYS, + // CURLE_OPERATION_TIMEOUTED + 35 => self::TASK_RETRY_ALWAYS, + // CURLE_SSL_CONNECT_ERROR + 52 => self::TASK_RETRY_ALWAYS, + // CURLE_GOT_NOTHING + 'lighthouseError' => self::TASK_RETRY_NEVER, + ]; + /** + * Creates a new task runner with exponential backoff support. + * + * @param array $config The task runner config + * @param string $name The name of the current task (used for logging) + * @param callable $action The task to run and possibly retry + * @param array $arguments The task arguments + * @throws \Google\Task\Exception when misconfigured + */ + // @phpstan-ignore-next-line + public function __construct($config, $name, $action, array $arguments = []) + { + if (isset($config['initial_delay'])) { + if ($config['initial_delay'] < 0) { + throw new GoogleTaskException('Task configuration `initial_delay` must not be negative.'); + } + $this->delay = $config['initial_delay']; + } + if (isset($config['max_delay'])) { + if ($config['max_delay'] <= 0) { + throw new GoogleTaskException('Task configuration `max_delay` must be greater than 0.'); + } + $this->maxDelay = $config['max_delay']; + } + if (isset($config['factor'])) { + if ($config['factor'] <= 0) { + throw new GoogleTaskException('Task configuration `factor` must be greater than 0.'); + } + $this->factor = $config['factor']; + } + if (isset($config['jitter'])) { + if ($config['jitter'] <= 0) { + throw new GoogleTaskException('Task configuration `jitter` must be greater than 0.'); + } + $this->jitter = $config['jitter']; + } + if (isset($config['retries'])) { + if ($config['retries'] < 0) { + throw new GoogleTaskException('Task configuration `retries` must not be negative.'); + } + $this->maxAttempts += $config['retries']; + } + if (!\is_callable($action)) { + throw new GoogleTaskException('Task argument `$action` must be a valid callable.'); + } + $this->action = $action; + $this->arguments = $arguments; + } + /** + * Checks if a retry can be attempted. + * + * @return boolean + */ + public function canAttempt() + { + return $this->attempts < $this->maxAttempts; + } + /** + * Runs the task and (if applicable) automatically retries when errors occur. + * + * @return mixed + * @throws \Google\Service\Exception on failure when no retries are available. + */ + public function run() + { + while ($this->attempt()) { + try { + return \call_user_func_array($this->action, $this->arguments); + } catch (GoogleServiceException $exception) { + $allowedRetries = $this->allowedRetries($exception->getCode(), $exception->getErrors()); + if (!$this->canAttempt() || !$allowedRetries) { + throw $exception; + } + if ($allowedRetries > 0) { + $this->maxAttempts = \min($this->maxAttempts, $this->attempts + $allowedRetries); + } + } + } + } + /** + * Runs a task once, if possible. This is useful for bypassing the `run()` + * loop. + * + * NOTE: If this is not the first attempt, this function will sleep in + * accordance to the backoff configurations before running the task. + * + * @return boolean + */ + public function attempt() + { + if (!$this->canAttempt()) { + return \false; + } + if ($this->attempts > 0) { + $this->backOff(); + } + $this->attempts++; + return \true; + } + /** + * Sleeps in accordance to the backoff configurations. + */ + private function backOff() + { + $delay = $this->getDelay(); + \usleep((int) ($delay * 1000000)); + } + /** + * Gets the delay (in seconds) for the current backoff period. + * + * @return int + */ + private function getDelay() + { + $jitter = $this->getJitter(); + $factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + \abs($jitter); + return $this->delay = \min($this->maxDelay, $this->delay * $factor); + } + /** + * Gets the current jitter (random number between -$this->jitter and + * $this->jitter). + * + * @return float + */ + private function getJitter() + { + return $this->jitter * 2 * \mt_rand() / \mt_getrandmax() - $this->jitter; + } + /** + * Gets the number of times the associated task can be retried. + * + * NOTE: -1 is returned if the task can be retried indefinitely + * + * @return integer + */ + public function allowedRetries($code, $errors = []) + { + if (isset($this->retryMap[$code])) { + return $this->retryMap[$code]; + } + if (!empty($errors) && isset($errors[0]['reason'], $this->retryMap[$errors[0]['reason']])) { + return $this->retryMap[$errors[0]['reason']]; + } + return 0; + } + public function setRetryMap($retryMap) + { + $this->retryMap = $retryMap; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Utils/UriTemplate.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Utils/UriTemplate.php new file mode 100644 index 00000000..7f0b5fd7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/Utils/UriTemplate.php @@ -0,0 +1,264 @@ + "reserved", "/" => "segments", "." => "dotprefix", "#" => "fragment", ";" => "semicolon", "?" => "form", "&" => "continuation"]; + /** + * @var array + * These are the characters which should not be URL encoded in reserved + * strings. + */ + private $reserved = ["=", ",", "!", "@", "|", ":", "/", "?", "#", "[", "]", '$', "&", "'", "(", ")", "*", "+", ";"]; + private $reservedEncoded = ["%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F", "%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29", "%2A", "%2B", "%3B"]; + public function parse($string, array $parameters) + { + return $this->resolveNextSection($string, $parameters); + } + /** + * This function finds the first matching {...} block and + * executes the replacement. It then calls itself to find + * subsequent blocks, if any. + */ + private function resolveNextSection($string, $parameters) + { + $start = \strpos($string, "{"); + if ($start === \false) { + return $string; + } + $end = \strpos($string, "}"); + if ($end === \false) { + return $string; + } + $string = $this->replace($string, $start, $end, $parameters); + return $this->resolveNextSection($string, $parameters); + } + private function replace($string, $start, $end, $parameters) + { + // We know a data block will have {} round it, so we can strip that. + $data = \substr($string, $start + 1, $end - $start - 1); + // If the first character is one of the reserved operators, it effects + // the processing of the stream. + if (isset($this->operators[$data[0]])) { + $op = $this->operators[$data[0]]; + $data = \substr($data, 1); + $prefix = ""; + $prefix_on_missing = \false; + switch ($op) { + case "reserved": + // Reserved means certain characters should not be URL encoded + $data = $this->replaceVars($data, $parameters, ",", null, \true); + break; + case "fragment": + // Comma separated with fragment prefix. Bare values only. + $prefix = "#"; + $prefix_on_missing = \true; + $data = $this->replaceVars($data, $parameters, ",", null, \true); + break; + case "segments": + // Slash separated data. Bare values only. + $prefix = "/"; + $data = $this->replaceVars($data, $parameters, "/"); + break; + case "dotprefix": + // Dot separated data. Bare values only. + $prefix = "."; + $prefix_on_missing = \true; + $data = $this->replaceVars($data, $parameters, "."); + break; + case "semicolon": + // Semicolon prefixed and separated. Uses the key name + $prefix = ";"; + $data = $this->replaceVars($data, $parameters, ";", "=", \false, \true, \false); + break; + case "form": + // Standard URL format. Uses the key name + $prefix = "?"; + $data = $this->replaceVars($data, $parameters, "&", "="); + break; + case "continuation": + // Standard URL, but with leading ampersand. Uses key name. + $prefix = "&"; + $data = $this->replaceVars($data, $parameters, "&", "="); + break; + } + // Add the initial prefix character if data is valid. + if ($data || $data !== \false && $prefix_on_missing) { + $data = $prefix . $data; + } + } else { + // If no operator we replace with the defaults. + $data = $this->replaceVars($data, $parameters); + } + // This is chops out the {...} and replaces with the new section. + return \substr($string, 0, $start) . $data . \substr($string, $end + 1); + } + private function replaceVars($section, $parameters, $sep = ",", $combine = null, $reserved = \false, $tag_empty = \false, $combine_on_empty = \true) + { + if (\strpos($section, ",") === \false) { + // If we only have a single value, we can immediately process. + return $this->combine($section, $parameters, $sep, $combine, $reserved, $tag_empty, $combine_on_empty); + } else { + // If we have multiple values, we need to split and loop over them. + // Each is treated individually, then glued together with the + // separator character. + $vars = \explode(",", $section); + return $this->combineList( + $vars, + $sep, + $parameters, + $combine, + $reserved, + \false, + // Never emit empty strings in multi-param replacements + $combine_on_empty + ); + } + } + public function combine($key, $parameters, $sep, $combine, $reserved, $tag_empty, $combine_on_empty) + { + $length = \false; + $explode = \false; + $skip_final_combine = \false; + $value = \false; + // Check for length restriction. + if (\strpos($key, ":") !== \false) { + list($key, $length) = \explode(":", $key); + } + // Check for explode parameter. + if ($key[\strlen($key) - 1] == "*") { + $explode = \true; + $key = \substr($key, 0, -1); + $skip_final_combine = \true; + } + // Define the list separator. + $list_sep = $explode ? $sep : ","; + if (isset($parameters[$key])) { + $data_type = $this->getDataType($parameters[$key]); + switch ($data_type) { + case self::TYPE_SCALAR: + $value = $this->getValue($parameters[$key], $length); + break; + case self::TYPE_LIST: + $values = []; + foreach ($parameters[$key] as $pkey => $pvalue) { + $pvalue = $this->getValue($pvalue, $length); + if ($combine && $explode) { + $values[$pkey] = $key . $combine . $pvalue; + } else { + $values[$pkey] = $pvalue; + } + } + $value = \implode($list_sep, $values); + if ($value == '') { + return ''; + } + break; + case self::TYPE_MAP: + $values = []; + foreach ($parameters[$key] as $pkey => $pvalue) { + $pvalue = $this->getValue($pvalue, $length); + if ($explode) { + $pkey = $this->getValue($pkey, $length); + $values[] = $pkey . "=" . $pvalue; + // Explode triggers = combine. + } else { + $values[] = $pkey; + $values[] = $pvalue; + } + } + $value = \implode($list_sep, $values); + if ($value == '') { + return \false; + } + break; + } + } elseif ($tag_empty) { + // If we are just indicating empty values with their key name, return that. + return $key; + } else { + // Otherwise we can skip this variable due to not being defined. + return \false; + } + if ($reserved) { + $value = \str_replace($this->reservedEncoded, $this->reserved, $value); + } + // If we do not need to include the key name, we just return the raw + // value. + if (!$combine || $skip_final_combine) { + return $value; + } + // Else we combine the key name: foo=bar, if value is not the empty string. + return $key . ($value != '' || $combine_on_empty ? $combine . $value : ''); + } + /** + * Return the type of a passed in value + */ + private function getDataType($data) + { + if (\is_array($data)) { + \reset($data); + if (\key($data) !== 0) { + return self::TYPE_MAP; + } + return self::TYPE_LIST; + } + return self::TYPE_SCALAR; + } + /** + * Utility function that merges multiple combine calls + * for multi-key templates. + */ + private function combineList($vars, $sep, $parameters, $combine, $reserved, $tag_empty, $combine_on_empty) + { + $ret = []; + foreach ($vars as $var) { + $response = $this->combine($var, $parameters, $sep, $combine, $reserved, $tag_empty, $combine_on_empty); + if ($response === \false) { + continue; + } + $ret[] = $response; + } + return \implode($sep, $ret); + } + /** + * Utility function to encode and trim values + */ + private function getValue($value, $length) + { + if ($length) { + $value = \substr($value, 0, $length); + } + $value = \rawurlencode($value); + return $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/aliases.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/aliases.php new file mode 100644 index 00000000..63e33b86 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/apiclient/src/aliases.php @@ -0,0 +1,80 @@ + 'Google_Client', 'WPMailSMTP\\Vendor\\Google\\Service' => 'Google_Service', 'WPMailSMTP\\Vendor\\Google\\AccessToken\\Revoke' => 'Google_AccessToken_Revoke', 'WPMailSMTP\\Vendor\\Google\\AccessToken\\Verify' => 'Google_AccessToken_Verify', 'WPMailSMTP\\Vendor\\Google\\Model' => 'Google_Model', 'WPMailSMTP\\Vendor\\Google\\Utils\\UriTemplate' => 'Google_Utils_UriTemplate', 'WPMailSMTP\\Vendor\\Google\\AuthHandler\\Guzzle6AuthHandler' => 'Google_AuthHandler_Guzzle6AuthHandler', 'WPMailSMTP\\Vendor\\Google\\AuthHandler\\Guzzle7AuthHandler' => 'Google_AuthHandler_Guzzle7AuthHandler', 'WPMailSMTP\\Vendor\\Google\\AuthHandler\\AuthHandlerFactory' => 'Google_AuthHandler_AuthHandlerFactory', 'WPMailSMTP\\Vendor\\Google\\Http\\Batch' => 'Google_Http_Batch', 'WPMailSMTP\\Vendor\\Google\\Http\\MediaFileUpload' => 'Google_Http_MediaFileUpload', 'WPMailSMTP\\Vendor\\Google\\Http\\REST' => 'Google_Http_REST', 'WPMailSMTP\\Vendor\\Google\\Task\\Retryable' => 'Google_Task_Retryable', 'WPMailSMTP\\Vendor\\Google\\Task\\Exception' => 'Google_Task_Exception', 'WPMailSMTP\\Vendor\\Google\\Task\\Runner' => 'Google_Task_Runner', 'WPMailSMTP\\Vendor\\Google\\Collection' => 'Google_Collection', 'WPMailSMTP\\Vendor\\Google\\Service\\Exception' => 'Google_Service_Exception', 'WPMailSMTP\\Vendor\\Google\\Service\\Resource' => 'Google_Service_Resource', 'WPMailSMTP\\Vendor\\Google\\Exception' => 'Google_Exception']; +foreach ($classMap as $class => $alias) { + \class_alias($class, 'WPMailSMTP\\Vendor\\' . $alias); +} +/** + * This class needs to be defined explicitly as scripts must be recognized by + * the autoloader. + */ +class Google_Task_Composer extends \WPMailSMTP\Vendor\Google\Task\Composer +{ +} +/** @phpstan-ignore-next-line */ +if (\false) { + class Google_AccessToken_Revoke extends \WPMailSMTP\Vendor\Google\AccessToken\Revoke + { + } + class Google_AccessToken_Verify extends \WPMailSMTP\Vendor\Google\AccessToken\Verify + { + } + class Google_AuthHandler_AuthHandlerFactory extends \WPMailSMTP\Vendor\Google\AuthHandler\AuthHandlerFactory + { + } + class Google_AuthHandler_Guzzle6AuthHandler extends \WPMailSMTP\Vendor\Google\AuthHandler\Guzzle6AuthHandler + { + } + class Google_AuthHandler_Guzzle7AuthHandler extends \WPMailSMTP\Vendor\Google\AuthHandler\Guzzle7AuthHandler + { + } + class Google_Client extends \WPMailSMTP\Vendor\Google\Client + { + } + class Google_Collection extends \WPMailSMTP\Vendor\Google\Collection + { + } + class Google_Exception extends \WPMailSMTP\Vendor\Google\Exception + { + } + class Google_Http_Batch extends \WPMailSMTP\Vendor\Google\Http\Batch + { + } + class Google_Http_MediaFileUpload extends \WPMailSMTP\Vendor\Google\Http\MediaFileUpload + { + } + class Google_Http_REST extends \WPMailSMTP\Vendor\Google\Http\REST + { + } + class Google_Model extends \WPMailSMTP\Vendor\Google\Model + { + } + class Google_Service extends \WPMailSMTP\Vendor\Google\Service + { + } + class Google_Service_Exception extends \WPMailSMTP\Vendor\Google\Service\Exception + { + } + class Google_Service_Resource extends \WPMailSMTP\Vendor\Google\Service\Resource + { + } + class Google_Task_Exception extends \WPMailSMTP\Vendor\Google\Task\Exception + { + } + interface Google_Task_Retryable extends \WPMailSMTP\Vendor\Google\Task\Retryable + { + } + class Google_Task_Runner extends \WPMailSMTP\Vendor\Google\Task\Runner + { + } + class Google_Utils_UriTemplate extends \WPMailSMTP\Vendor\Google\Utils\UriTemplate + { + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/LICENSE new file mode 100644 index 00000000..a148ba56 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/LICENSE @@ -0,0 +1,203 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/autoload.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/autoload.php new file mode 100644 index 00000000..0eef41b0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/autoload.php @@ -0,0 +1,35 @@ + 3) { + // Maximum class file path depth in this project is 3. + $classPath = \array_slice($classPath, 0, 3); + } + $filePath = \dirname(__FILE__) . '/src/' . \implode('/', $classPath) . '.php'; + if (\file_exists($filePath)) { + require_once $filePath; + } +} +\spl_autoload_register('oauth2client_php_autoload'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/AccessToken.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/AccessToken.php new file mode 100644 index 00000000..9d3b09ad --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/AccessToken.php @@ -0,0 +1,390 @@ +httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + $this->cache = $cache ?: new MemoryCacheItemPool(); + } + /** + * Verifies an id token and returns the authenticated apiLoginTicket. + * Throws an exception if the id token is not valid. + * The audience parameter can be used to control which id tokens are + * accepted. By default, the id token must have been issued to this OAuth2 client. + * + * @param string $token The JSON Web Token to be verified. + * @param array $options [optional] { + * Configuration options. + * @type string $audience The indended recipient of the token. + * @type string $issuer The intended issuer of the token. + * @type string $cacheKey The cache key of the cached certs. Defaults to + * the sha1 of $certsLocation if provided, otherwise is set to + * "federated_signon_certs_v3". + * @type string $certsLocation The location (remote or local) from which + * to retrieve certificates, if not cached. This value should only be + * provided in limited circumstances in which you are sure of the + * behavior. + * @type bool $throwException Whether the function should throw an + * exception if the verification fails. This is useful for + * determining the reason verification failed. + * } + * @return array|false the token payload, if successful, or false if not. + * @throws InvalidArgumentException If certs could not be retrieved from a local file. + * @throws InvalidArgumentException If received certs are in an invalid format. + * @throws InvalidArgumentException If the cert alg is not supported. + * @throws RuntimeException If certs could not be retrieved from a remote location. + * @throws UnexpectedValueException If the token issuer does not match. + * @throws UnexpectedValueException If the token audience does not match. + */ + public function verify($token, array $options = []) + { + $audience = $options['audience'] ?? null; + $issuer = $options['issuer'] ?? null; + $certsLocation = $options['certsLocation'] ?? self::FEDERATED_SIGNON_CERT_URL; + $cacheKey = $options['cacheKey'] ?? $this->getCacheKeyFromCertLocation($certsLocation); + $throwException = $options['throwException'] ?? \false; + // for backwards compatibility + // Check signature against each available cert. + $certs = $this->getCerts($certsLocation, $cacheKey, $options); + $alg = $this->determineAlg($certs); + if (!\in_array($alg, ['RS256', 'ES256'])) { + throw new InvalidArgumentException('unrecognized "alg" in certs, expected ES256 or RS256'); + } + try { + if ($alg == 'RS256') { + return $this->verifyRs256($token, $certs, $audience, $issuer); + } + return $this->verifyEs256($token, $certs, $audience, $issuer); + } catch (ExpiredException $e) { + // firebase/php-jwt 5+ + } catch (SignatureInvalidException $e) { + // firebase/php-jwt 5+ + } catch (InvalidTokenException $e) { + // simplejwt + } catch (InvalidArgumentException $e) { + } catch (UnexpectedValueException $e) { + } + if ($throwException) { + throw $e; + } + return \false; + } + /** + * Identifies the expected algorithm to verify by looking at the "alg" key + * of the provided certs. + * + * @param array $certs Certificate array according to the JWK spec (see + * https://tools.ietf.org/html/rfc7517). + * @return string The expected algorithm, such as "ES256" or "RS256". + */ + private function determineAlg(array $certs) + { + $alg = null; + foreach ($certs as $cert) { + if (empty($cert['alg'])) { + throw new InvalidArgumentException('certs expects "alg" to be set'); + } + $alg = $alg ?: $cert['alg']; + if ($alg != $cert['alg']) { + throw new InvalidArgumentException('More than one alg detected in certs'); + } + } + return $alg; + } + /** + * Verifies an ES256-signed JWT. + * + * @param string $token The JSON Web Token to be verified. + * @param array $certs Certificate array according to the JWK spec (see + * https://tools.ietf.org/html/rfc7517). + * @param string|null $audience If set, returns false if the provided + * audience does not match the "aud" claim on the JWT. + * @param string|null $issuer If set, returns false if the provided + * issuer does not match the "iss" claim on the JWT. + * @return array the token payload, if successful, or false if not. + */ + private function verifyEs256($token, array $certs, $audience = null, $issuer = null) + { + $this->checkSimpleJwt(); + $jwkset = new KeySet(); + foreach ($certs as $cert) { + $jwkset->add(KeyFactory::create($cert, 'php')); + } + // Validate the signature using the key set and ES256 algorithm. + $jwt = $this->callSimpleJwtDecode([$token, $jwkset, 'ES256']); + $payload = $jwt->getClaims(); + if ($audience) { + if (!isset($payload['aud']) || $payload['aud'] != $audience) { + throw new UnexpectedValueException('Audience does not match'); + } + } + // @see https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload + $issuer = $issuer ?: self::IAP_ISSUER; + if (!isset($payload['iss']) || $payload['iss'] !== $issuer) { + throw new UnexpectedValueException('Issuer does not match'); + } + return $payload; + } + /** + * Verifies an RS256-signed JWT. + * + * @param string $token The JSON Web Token to be verified. + * @param array $certs Certificate array according to the JWK spec (see + * https://tools.ietf.org/html/rfc7517). + * @param string|null $audience If set, returns false if the provided + * audience does not match the "aud" claim on the JWT. + * @param string|null $issuer If set, returns false if the provided + * issuer does not match the "iss" claim on the JWT. + * @return array the token payload, if successful, or false if not. + */ + private function verifyRs256($token, array $certs, $audience = null, $issuer = null) + { + $this->checkAndInitializePhpsec(); + $keys = []; + foreach ($certs as $cert) { + if (empty($cert['kid'])) { + throw new InvalidArgumentException('certs expects "kid" to be set'); + } + if (empty($cert['n']) || empty($cert['e'])) { + throw new InvalidArgumentException('RSA certs expects "n" and "e" to be set'); + } + $publicKey = $this->loadPhpsecPublicKey($cert['n'], $cert['e']); + // create an array of key IDs to certs for the JWT library + $keys[$cert['kid']] = new Key($publicKey, 'RS256'); + } + $payload = $this->callJwtStatic('decode', [$token, $keys]); + if ($audience) { + if (!\property_exists($payload, 'aud') || $payload->aud != $audience) { + throw new UnexpectedValueException('Audience does not match'); + } + } + // support HTTP and HTTPS issuers + // @see https://developers.google.com/identity/sign-in/web/backend-auth + $issuers = $issuer ? [$issuer] : [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS]; + if (!isset($payload->iss) || !\in_array($payload->iss, $issuers)) { + throw new UnexpectedValueException('Issuer does not match'); + } + return (array) $payload; + } + /** + * Revoke an OAuth2 access token or refresh token. This method will revoke the current access + * token, if a token isn't provided. + * + * @param string|array $token The token (access token or a refresh token) that should be revoked. + * @param array $options [optional] Configuration options. + * @return bool Returns True if the revocation was successful, otherwise False. + */ + public function revoke($token, array $options = []) + { + if (\is_array($token)) { + if (isset($token['refresh_token'])) { + $token = $token['refresh_token']; + } else { + $token = $token['access_token']; + } + } + $body = Utils::streamFor(\http_build_query(['token' => $token])); + $request = new Request('POST', self::OAUTH2_REVOKE_URI, ['Cache-Control' => 'no-store', 'Content-Type' => 'application/x-www-form-urlencoded'], $body); + $httpHandler = $this->httpHandler; + $response = $httpHandler($request, $options); + return $response->getStatusCode() == 200; + } + /** + * Gets federated sign-on certificates to use for verifying identity tokens. + * Returns certs as array structure, where keys are key ids, and values + * are PEM encoded certificates. + * + * @param string $location The location from which to retrieve certs. + * @param string $cacheKey The key under which to cache the retrieved certs. + * @param array $options [optional] Configuration options. + * @return array + * @throws InvalidArgumentException If received certs are in an invalid format. + */ + private function getCerts($location, $cacheKey, array $options = []) + { + $cacheItem = $this->cache->getItem($cacheKey); + $certs = $cacheItem ? $cacheItem->get() : null; + $expireTime = null; + if (!$certs) { + list($certs, $expireTime) = $this->retrieveCertsFromLocation($location, $options); + } + if (!isset($certs['keys'])) { + if ($location !== self::IAP_CERT_URL) { + throw new InvalidArgumentException('federated sign-on certs expects "keys" to be set'); + } + throw new InvalidArgumentException('certs expects "keys" to be set'); + } + // Push caching off until after verifying certs are in a valid format. + // Don't want to cache bad data. + if ($expireTime) { + $cacheItem->expiresAt(new DateTime($expireTime)); + $cacheItem->set($certs); + $this->cache->save($cacheItem); + } + return $certs['keys']; + } + /** + * Retrieve and cache a certificates file. + * + * @param string $url location + * @param array $options [optional] Configuration options. + * @return array{array, string} + * @throws InvalidArgumentException If certs could not be retrieved from a local file. + * @throws RuntimeException If certs could not be retrieved from a remote location. + */ + private function retrieveCertsFromLocation($url, array $options = []) + { + // If we're retrieving a local file, just grab it. + $expireTime = '+1 hour'; + if (\strpos($url, 'http') !== 0) { + if (!\file_exists($url)) { + throw new InvalidArgumentException(\sprintf('Failed to retrieve verification certificates from path: %s.', $url)); + } + return [\json_decode((string) \file_get_contents($url), \true), $expireTime]; + } + $httpHandler = $this->httpHandler; + $response = $httpHandler(new Request('GET', $url), $options); + if ($response->getStatusCode() == 200) { + if ($cacheControl = $response->getHeaderLine('Cache-Control')) { + \array_map(function ($value) use(&$expireTime) { + list($key, $value) = \explode('=', $value) + [null, null]; + if (\trim($key) == 'max-age') { + $expireTime = '+' . $value . ' seconds'; + } + }, \explode(',', $cacheControl)); + } + return [\json_decode((string) $response->getBody(), \true), $expireTime]; + } + throw new RuntimeException(\sprintf('Failed to retrieve verification certificates: "%s".', $response->getBody()->getContents()), $response->getStatusCode()); + } + /** + * @return void + */ + private function checkAndInitializePhpsec() + { + if (!\class_exists(RSA::class)) { + throw new RuntimeException('Please require phpseclib/phpseclib v3 to use this utility.'); + } + } + /** + * @return string + * @throws TypeError If the key cannot be initialized to a string. + */ + private function loadPhpsecPublicKey(string $modulus, string $exponent) : string + { + $key = PublicKeyLoader::load(['n' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [$modulus]), 256), 'e' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [$exponent]), 256)]); + $formattedPublicKey = $key->toString('PKCS8'); + if (!\is_string($formattedPublicKey)) { + throw new TypeError('Failed to initialize the key'); + } + return $formattedPublicKey; + } + /** + * @return void + */ + private function checkSimpleJwt() + { + // @codeCoverageIgnoreStart + if (!\class_exists(SimpleJwt::class)) { + throw new RuntimeException('Please require kelvinmo/simplejwt ^0.2 to use this utility.'); + } + // @codeCoverageIgnoreEnd + } + /** + * Provide a hook to mock calls to the JWT static methods. + * + * @param string $method + * @param array $args + * @return mixed + */ + protected function callJwtStatic($method, array $args = []) + { + return \call_user_func_array([JWT::class, $method], $args); + // @phpstan-ignore-line + } + /** + * Provide a hook to mock calls to the JWT static methods. + * + * @param array $args + * @return mixed + */ + protected function callSimpleJwtDecode(array $args = []) + { + return \call_user_func_array([SimpleJwt::class, 'decode'], $args); + } + /** + * Generate a cache key based on the cert location using sha1 with the + * exception of using "federated_signon_certs_v3" to preserve BC. + * + * @param string $certsLocation + * @return string + */ + private function getCacheKeyFromCertLocation($certsLocation) + { + $key = $certsLocation === self::FEDERATED_SIGNON_CERT_URL ? 'federated_signon_certs_v3' : \sha1($certsLocation); + return 'google_auth_certs_cache|' . $key; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ApplicationDefaultCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ApplicationDefaultCredentials.php new file mode 100644 index 00000000..5f537a89 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ApplicationDefaultCredentials.php @@ -0,0 +1,301 @@ +push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', + * 'auth' => 'google_auth' // authorize all requests + * ]); + * + * $res = $client->get('myproject/taskqueues/myqueue'); + * ``` + */ +class ApplicationDefaultCredentials +{ + /** + * @deprecated + * + * Obtains an AuthTokenSubscriber that uses the default FetchAuthTokenInterface + * implementation to use in this environment. + * + * If supplied, $scope is used to in creating the credentials instance if + * this does not fallback to the compute engine defaults. + * + * @param string|string[] $scope the scope of the access request, expressed + * either as an Array or as a space-delimited String. + * @param callable $httpHandler callback which delivers psr7 request + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache A cache implementation, may be + * provided if you have one already available for use. + * @return AuthTokenSubscriber + * @throws DomainException if no implementation can be obtained. + */ + public static function getSubscriber( + // @phpstan-ignore-line + $scope = null, + ?callable $httpHandler = null, + ?array $cacheConfig = null, + ?CacheItemPoolInterface $cache = null + ) + { + $creds = self::getCredentials($scope, $httpHandler, $cacheConfig, $cache); + /** @phpstan-ignore-next-line */ + return new AuthTokenSubscriber($creds, $httpHandler); + } + /** + * Obtains an AuthTokenMiddleware that uses the default FetchAuthTokenInterface + * implementation to use in this environment. + * + * If supplied, $scope is used to in creating the credentials instance if + * this does not fallback to the compute engine defaults. + * + * @param string|string[] $scope the scope of the access request, expressed + * either as an Array or as a space-delimited String. + * @param callable $httpHandler callback which delivers psr7 request + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache A cache implementation, may be + * provided if you have one already available for use. + * @param string $quotaProject specifies a project to bill for access + * charges associated with the request. + * @return AuthTokenMiddleware + * @throws DomainException if no implementation can be obtained. + */ + public static function getMiddleware($scope = null, ?callable $httpHandler = null, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null, $quotaProject = null) + { + $creds = self::getCredentials($scope, $httpHandler, $cacheConfig, $cache, $quotaProject); + return new AuthTokenMiddleware($creds, $httpHandler); + } + /** + * Obtains the default FetchAuthTokenInterface implementation to use + * in this environment. + * + * @param string|string[] $scope the scope of the access request, expressed + * either as an Array or as a space-delimited String. + * @param callable $httpHandler callback which delivers psr7 request + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache A cache implementation, may be + * provided if you have one already available for use. + * @param string $quotaProject specifies a project to bill for access + * charges associated with the request. + * @param string|string[] $defaultScope The default scope to use if no + * user-defined scopes exist, expressed either as an Array or as a + * space-delimited string. + * @param string $universeDomain Specifies a universe domain to use for the + * calling client library + * + * @return FetchAuthTokenInterface + * @throws DomainException if no implementation can be obtained. + */ + public static function getCredentials($scope = null, ?callable $httpHandler = null, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null, $quotaProject = null, $defaultScope = null, ?string $universeDomain = null) + { + $creds = null; + $jsonKey = CredentialsLoader::fromEnv() ?: CredentialsLoader::fromWellKnownFile(); + $anyScope = $scope ?: $defaultScope; + if (!$httpHandler) { + if (!($client = HttpClientCache::getHttpClient())) { + $client = new Client(); + HttpClientCache::setHttpClient($client); + } + $httpHandler = HttpHandlerFactory::build($client); + } + if (\is_null($quotaProject)) { + // if a quota project isn't specified, try to get one from the env var + $quotaProject = CredentialsLoader::quotaProjectFromEnv(); + } + if (!\is_null($jsonKey)) { + if ($quotaProject) { + $jsonKey['quota_project_id'] = $quotaProject; + } + if ($universeDomain) { + $jsonKey['universe_domain'] = $universeDomain; + } + $creds = CredentialsLoader::makeCredentials($scope, $jsonKey, $defaultScope); + } elseif (AppIdentityCredentials::onAppEngine() && !GCECredentials::onAppEngineFlexible()) { + $creds = new AppIdentityCredentials($anyScope); + } elseif (self::onGce($httpHandler, $cacheConfig, $cache)) { + $creds = new GCECredentials(null, $anyScope, null, $quotaProject, null, $universeDomain); + $creds->setIsOnGce(\true); + // save the credentials a trip to the metadata server + } + if (\is_null($creds)) { + throw new DomainException(self::notFound()); + } + if (!\is_null($cache)) { + $creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache); + } + return $creds; + } + /** + * Obtains an AuthTokenMiddleware which will fetch an ID token to use in the + * Authorization header. The middleware is configured with the default + * FetchAuthTokenInterface implementation to use in this environment. + * + * If supplied, $targetAudience is used to set the "aud" on the resulting + * ID token. + * + * @param string $targetAudience The audience for the ID token. + * @param callable $httpHandler callback which delivers psr7 request + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache A cache implementation, may be + * provided if you have one already available for use. + * @return AuthTokenMiddleware + * @throws DomainException if no implementation can be obtained. + */ + public static function getIdTokenMiddleware($targetAudience, ?callable $httpHandler = null, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $creds = self::getIdTokenCredentials($targetAudience, $httpHandler, $cacheConfig, $cache); + return new AuthTokenMiddleware($creds, $httpHandler); + } + /** + * Obtains an ProxyAuthTokenMiddleware which will fetch an ID token to use in the + * Authorization header. The middleware is configured with the default + * FetchAuthTokenInterface implementation to use in this environment. + * + * If supplied, $targetAudience is used to set the "aud" on the resulting + * ID token. + * + * @param string $targetAudience The audience for the ID token. + * @param callable $httpHandler callback which delivers psr7 request + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache A cache implementation, may be + * provided if you have one already available for use. + * @return ProxyAuthTokenMiddleware + * @throws DomainException if no implementation can be obtained. + */ + public static function getProxyIdTokenMiddleware($targetAudience, ?callable $httpHandler = null, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $creds = self::getIdTokenCredentials($targetAudience, $httpHandler, $cacheConfig, $cache); + return new ProxyAuthTokenMiddleware($creds, $httpHandler); + } + /** + * Obtains the default FetchAuthTokenInterface implementation to use + * in this environment, configured with a $targetAudience for fetching an ID + * token. + * + * @param string $targetAudience The audience for the ID token. + * @param callable $httpHandler callback which delivers psr7 request + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache A cache implementation, may be + * provided if you have one already available for use. + * @return FetchAuthTokenInterface + * @throws DomainException if no implementation can be obtained. + * @throws InvalidArgumentException if JSON "type" key is invalid + */ + public static function getIdTokenCredentials($targetAudience, ?callable $httpHandler = null, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $creds = null; + $jsonKey = CredentialsLoader::fromEnv() ?: CredentialsLoader::fromWellKnownFile(); + if (!$httpHandler) { + if (!($client = HttpClientCache::getHttpClient())) { + $client = new Client(); + HttpClientCache::setHttpClient($client); + } + $httpHandler = HttpHandlerFactory::build($client); + } + if (!\is_null($jsonKey)) { + if (!\array_key_exists('type', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the type field'); + } + if ($jsonKey['type'] == 'authorized_user') { + throw new InvalidArgumentException('ID tokens are not supported for end user credentials'); + } + if ($jsonKey['type'] != 'service_account') { + throw new InvalidArgumentException('invalid value in the type field'); + } + $creds = new ServiceAccountCredentials(null, $jsonKey, null, $targetAudience); + } elseif (self::onGce($httpHandler, $cacheConfig, $cache)) { + $creds = new GCECredentials(null, null, $targetAudience); + $creds->setIsOnGce(\true); + // save the credentials a trip to the metadata server + } + if (\is_null($creds)) { + throw new DomainException(self::notFound()); + } + if (!\is_null($cache)) { + $creds = new FetchAuthTokenCache($creds, $cacheConfig, $cache); + } + return $creds; + } + /** + * @return string + */ + private static function notFound() + { + $msg = 'Your default credentials were not found. To set up '; + $msg .= 'Application Default Credentials, see '; + $msg .= 'https://cloud.google.com/docs/authentication/external/set-up-adc'; + return $msg; + } + /** + * @param callable $httpHandler + * @param array $cacheConfig + * @param CacheItemPoolInterface $cache + * @return bool + */ + private static function onGce(?callable $httpHandler = null, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $gceCacheConfig = []; + foreach (['lifetime', 'prefix'] as $key) { + if (isset($cacheConfig['gce_' . $key])) { + $gceCacheConfig[$key] = $cacheConfig['gce_' . $key]; + } + } + return (new GCECache($gceCacheConfig, $cache))->onGce($httpHandler); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/InvalidArgumentException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/InvalidArgumentException.php new file mode 100644 index 00000000..458ec5a9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/InvalidArgumentException.php @@ -0,0 +1,23 @@ +key = $key; + } + /** + * {@inheritdoc} + */ + public function getKey() + { + return $this->key; + } + /** + * {@inheritdoc} + */ + public function get() + { + return $this->isHit() ? $this->value : null; + } + /** + * {@inheritdoc} + */ + public function isHit() + { + if (!$this->isHit) { + return \false; + } + if ($this->expiration === null) { + return \true; + } + return $this->currentTime()->getTimestamp() < $this->expiration->getTimestamp(); + } + /** + * {@inheritdoc} + */ + public function set($value) + { + $this->isHit = \true; + $this->value = $value; + return $this; + } + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) + { + if ($this->isValidExpiration($expiration)) { + $this->expiration = $expiration; + return $this; + } + $error = \sprintf('Argument 1 passed to %s::expiresAt() must implement interface DateTimeInterface, %s given', \get_class($this), \gettype($expiration)); + throw new TypeError($error); + } + /** + * {@inheritdoc} + */ + public function expiresAfter($time) + { + if (\is_int($time)) { + $this->expiration = $this->currentTime()->add(new \DateInterval("PT{$time}S")); + } elseif ($time instanceof \DateInterval) { + $this->expiration = $this->currentTime()->add($time); + } elseif ($time === null) { + $this->expiration = $time; + } else { + $message = 'Argument 1 passed to %s::expiresAfter() must be an ' . 'instance of DateInterval or of the type integer, %s given'; + $error = \sprintf($message, \get_class($this), \gettype($time)); + throw new TypeError($error); + } + return $this; + } + /** + * Determines if an expiration is valid based on the rules defined by PSR6. + * + * @param mixed $expiration + * @return bool + */ + private function isValidExpiration($expiration) + { + if ($expiration === null) { + return \true; + } + if ($expiration instanceof DateTimeInterface) { + return \true; + } + return \false; + } + /** + * @return DateTime + */ + protected function currentTime() + { + return new DateTime('now', new DateTimeZone('UTC')); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/MemoryCacheItemPool.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/MemoryCacheItemPool.php new file mode 100644 index 00000000..b4360677 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/MemoryCacheItemPool.php @@ -0,0 +1,161 @@ +getItems([$key])); + // @phpstan-ignore-line + } + /** + * {@inheritdoc} + * + * @return iterable + * A traversable collection of Cache Items keyed by the cache keys of + * each item. A Cache item will be returned for each key, even if that + * key is not found. However, if no keys are specified then an empty + * traversable MUST be returned instead. + */ + public function getItems(array $keys = []) : iterable + { + $items = []; + $itemClass = \PHP_VERSION_ID >= 80000 ? TypedItem::class : Item::class; + foreach ($keys as $key) { + $items[$key] = $this->hasItem($key) ? clone $this->items[$key] : new $itemClass($key); + } + return $items; + } + /** + * {@inheritdoc} + * + * @return bool + * True if item exists in the cache, false otherwise. + */ + public function hasItem($key) : bool + { + $this->isValidKey($key); + return isset($this->items[$key]) && $this->items[$key]->isHit(); + } + /** + * {@inheritdoc} + * + * @return bool + * True if the pool was successfully cleared. False if there was an error. + */ + public function clear() : bool + { + $this->items = []; + $this->deferredItems = []; + return \true; + } + /** + * {@inheritdoc} + * + * @return bool + * True if the item was successfully removed. False if there was an error. + */ + public function deleteItem($key) : bool + { + return $this->deleteItems([$key]); + } + /** + * {@inheritdoc} + * + * @return bool + * True if the items were successfully removed. False if there was an error. + */ + public function deleteItems(array $keys) : bool + { + \array_walk($keys, [$this, 'isValidKey']); + foreach ($keys as $key) { + unset($this->items[$key]); + } + return \true; + } + /** + * {@inheritdoc} + * + * @return bool + * True if the item was successfully persisted. False if there was an error. + */ + public function save(CacheItemInterface $item) : bool + { + $this->items[$item->getKey()] = $item; + return \true; + } + /** + * {@inheritdoc} + * + * @return bool + * False if the item could not be queued or if a commit was attempted and failed. True otherwise. + */ + public function saveDeferred(CacheItemInterface $item) : bool + { + $this->deferredItems[$item->getKey()] = $item; + return \true; + } + /** + * {@inheritdoc} + * + * @return bool + * True if all not-yet-saved items were successfully saved or there were none. False otherwise. + */ + public function commit() : bool + { + foreach ($this->deferredItems as $item) { + $this->save($item); + } + $this->deferredItems = []; + return \true; + } + /** + * Determines if the provided key is valid. + * + * @param string $key + * @return bool + * @throws InvalidArgumentException + */ + private function isValidKey($key) + { + $invalidCharacters = '{}()/\\\\@:'; + if (!\is_string($key) || \preg_match("#[{$invalidCharacters}]#", $key)) { + throw new InvalidArgumentException('The provided key is not valid: ' . \var_export($key, \true)); + } + return \true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/SysVCacheItemPool.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/SysVCacheItemPool.php new file mode 100644 index 00000000..41689f30 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/SysVCacheItemPool.php @@ -0,0 +1,207 @@ + + */ + private $options; + /** + * @var bool + */ + private $hasLoadedItems = \false; + /** + * Create a SystemV shared memory based CacheItemPool. + * + * @param array $options { + * [optional] Configuration options. + * + * @type int $variableKey The variable key for getting the data from the shared memory. **Defaults to** 1. + * @type string $proj The project identifier for ftok. This needs to be a one character string. + * **Defaults to** 'A'. + * @type int $memsize The memory size in bytes for shm_attach. **Defaults to** 10000. + * @type int $perm The permission for shm_attach. **Defaults to** 0600. + * } + */ + public function __construct($options = []) + { + if (!\extension_loaded('sysvshm')) { + throw new \RuntimeException('sysvshm extension is required to use this ItemPool'); + } + $this->options = $options + ['variableKey' => self::VAR_KEY, 'proj' => self::DEFAULT_PROJ, 'memsize' => self::DEFAULT_MEMSIZE, 'perm' => self::DEFAULT_PERM]; + $this->items = []; + $this->deferredItems = []; + $this->sysvKey = \ftok(__FILE__, $this->options['proj']); + } + /** + * @param mixed $key + * @return CacheItemInterface + */ + public function getItem($key) : CacheItemInterface + { + $this->loadItems(); + return \current($this->getItems([$key])); + // @phpstan-ignore-line + } + /** + * @param array $keys + * @return iterable + */ + public function getItems(array $keys = []) : iterable + { + $this->loadItems(); + $items = []; + $itemClass = \PHP_VERSION_ID >= 80000 ? TypedItem::class : Item::class; + foreach ($keys as $key) { + $items[$key] = $this->hasItem($key) ? clone $this->items[$key] : new $itemClass($key); + } + return $items; + } + /** + * {@inheritdoc} + */ + public function hasItem($key) : bool + { + $this->loadItems(); + return isset($this->items[$key]) && $this->items[$key]->isHit(); + } + /** + * {@inheritdoc} + */ + public function clear() : bool + { + $this->items = []; + $this->deferredItems = []; + return $this->saveCurrentItems(); + } + /** + * {@inheritdoc} + */ + public function deleteItem($key) : bool + { + return $this->deleteItems([$key]); + } + /** + * {@inheritdoc} + */ + public function deleteItems(array $keys) : bool + { + if (!$this->hasLoadedItems) { + $this->loadItems(); + } + foreach ($keys as $key) { + unset($this->items[$key]); + } + return $this->saveCurrentItems(); + } + /** + * {@inheritdoc} + */ + public function save(CacheItemInterface $item) : bool + { + if (!$this->hasLoadedItems) { + $this->loadItems(); + } + $this->items[$item->getKey()] = $item; + return $this->saveCurrentItems(); + } + /** + * {@inheritdoc} + */ + public function saveDeferred(CacheItemInterface $item) : bool + { + $this->deferredItems[$item->getKey()] = $item; + return \true; + } + /** + * {@inheritdoc} + */ + public function commit() : bool + { + foreach ($this->deferredItems as $item) { + if ($this->save($item) === \false) { + return \false; + } + } + $this->deferredItems = []; + return \true; + } + /** + * Save the current items. + * + * @return bool true when success, false upon failure + */ + private function saveCurrentItems() + { + $shmid = \shm_attach($this->sysvKey, $this->options['memsize'], $this->options['perm']); + if ($shmid !== \false) { + $ret = \shm_put_var($shmid, $this->options['variableKey'], $this->items); + \shm_detach($shmid); + return $ret; + } + return \false; + } + /** + * Load the items from the shared memory. + * + * @return bool true when success, false upon failure + */ + private function loadItems() + { + $shmid = \shm_attach($this->sysvKey, $this->options['memsize'], $this->options['perm']); + if ($shmid !== \false) { + $data = @\shm_get_var($shmid, $this->options['variableKey']); + if (!empty($data)) { + $this->items = $data; + } else { + $this->items = []; + } + \shm_detach($shmid); + $this->hasLoadedItems = \true; + return \true; + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/TypedItem.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/TypedItem.php new file mode 100644 index 00000000..6a687e52 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Cache/TypedItem.php @@ -0,0 +1,142 @@ +key = $key; + $this->expiration = null; + } + /** + * {@inheritdoc} + */ + public function getKey() : string + { + return $this->key; + } + /** + * {@inheritdoc} + */ + public function get() : mixed + { + return $this->isHit() ? $this->value : null; + } + /** + * {@inheritdoc} + */ + public function isHit() : bool + { + if (!$this->isHit) { + return \false; + } + if ($this->expiration === null) { + return \true; + } + return $this->currentTime()->getTimestamp() < $this->expiration->getTimestamp(); + } + /** + * {@inheritdoc} + */ + public function set(mixed $value) : static + { + $this->isHit = \true; + $this->value = $value; + return $this; + } + /** + * {@inheritdoc} + */ + public function expiresAt($expiration) : static + { + if ($this->isValidExpiration($expiration)) { + $this->expiration = $expiration; + return $this; + } + $error = \sprintf('Argument 1 passed to %s::expiresAt() must implement interface DateTimeInterface, %s given', \get_class($this), \gettype($expiration)); + throw new \TypeError($error); + } + /** + * {@inheritdoc} + */ + public function expiresAfter($time) : static + { + if (\is_int($time)) { + $this->expiration = $this->currentTime()->add(new \DateInterval("PT{$time}S")); + } elseif ($time instanceof \DateInterval) { + $this->expiration = $this->currentTime()->add($time); + } elseif ($time === null) { + $this->expiration = $time; + } else { + $message = 'Argument 1 passed to %s::expiresAfter() must be an ' . 'instance of DateInterval or of the type integer, %s given'; + $error = \sprintf($message, \get_class($this), \gettype($time)); + throw new \TypeError($error); + } + return $this; + } + /** + * Determines if an expiration is valid based on the rules defined by PSR6. + * + * @param mixed $expiration + * @return bool + */ + private function isValidExpiration($expiration) + { + if ($expiration === null) { + return \true; + } + // We test for two types here due to the fact the DateTimeInterface + // was not introduced until PHP 5.5. Checking for the DateTime type as + // well allows us to support 5.4. + if ($expiration instanceof \DateTimeInterface) { + return \true; + } + return \false; + } + /** + * @return \DateTime + */ + protected function currentTime() + { + return new \DateTime('now', new \DateTimeZone('UTC')); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CacheTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CacheTrait.php new file mode 100644 index 00000000..b13d9bdd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CacheTrait.php @@ -0,0 +1,96 @@ + + */ + private $cacheConfig; + /** + * @var ?CacheItemPoolInterface + */ + private $cache; + /** + * Gets the cached value if it is present in the cache when that is + * available. + * + * @param mixed $k + * + * @return mixed + */ + private function getCachedValue($k) + { + if (\is_null($this->cache)) { + return null; + } + $key = $this->getFullCacheKey($k); + if (\is_null($key)) { + return null; + } + $cacheItem = $this->cache->getItem($key); + if ($cacheItem->isHit()) { + return $cacheItem->get(); + } + } + /** + * Saves the value in the cache when that is available. + * + * @param mixed $k + * @param mixed $v + * @return mixed + */ + private function setCachedValue($k, $v) + { + if (\is_null($this->cache)) { + return null; + } + $key = $this->getFullCacheKey($k); + if (\is_null($key)) { + return null; + } + $cacheItem = $this->cache->getItem($key); + $cacheItem->set($v); + $cacheItem->expiresAfter($this->cacheConfig['lifetime']); + return $this->cache->save($cacheItem); + } + /** + * @param null|string $key + * @return null|string + */ + private function getFullCacheKey($key) + { + if (\is_null($key)) { + return null; + } + $key = $this->cacheConfig['prefix'] . $key; + // ensure we do not have illegal characters + $key = \preg_replace('|[^a-zA-Z0-9_\\.!]|', '', $key); + // Hash keys if they exceed $maxKeyLength (defaults to 64) + if ($this->maxKeyLength && \strlen($key) > $this->maxKeyLength) { + $key = \substr(\hash('sha256', $key), 0, $this->maxKeyLength); + } + return $key; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/AwsNativeSource.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/AwsNativeSource.php new file mode 100644 index 00000000..e10ec6d2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/AwsNativeSource.php @@ -0,0 +1,270 @@ +audience = $audience; + $this->regionalCredVerificationUrl = $regionalCredVerificationUrl; + $this->regionUrl = $regionUrl; + $this->securityCredentialsUrl = $securityCredentialsUrl; + $this->imdsv2SessionTokenUrl = $imdsv2SessionTokenUrl; + } + public function fetchSubjectToken(?callable $httpHandler = null) : string + { + if (\is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + $headers = []; + if ($this->imdsv2SessionTokenUrl) { + $headers = ['X-aws-ec2-metadata-token' => self::getImdsV2SessionToken($this->imdsv2SessionTokenUrl, $httpHandler)]; + } + if (!($signingVars = self::getSigningVarsFromEnv())) { + if (!$this->securityCredentialsUrl) { + throw new \LogicException('Unable to get credentials from ENV, and no security credentials URL provided'); + } + $signingVars = self::getSigningVarsFromUrl($httpHandler, $this->securityCredentialsUrl, self::getRoleName($httpHandler, $this->securityCredentialsUrl, $headers), $headers); + } + if (!($region = self::getRegionFromEnv())) { + if (!$this->regionUrl) { + throw new \LogicException('Unable to get region from ENV, and no region URL provided'); + } + $region = self::getRegionFromUrl($httpHandler, $this->regionUrl, $headers); + } + $url = \str_replace('{region}', $region, $this->regionalCredVerificationUrl); + $host = \parse_url($url)['host'] ?? ''; + // From here we use the signing vars to create the signed request to receive a token + [$accessKeyId, $secretAccessKey, $securityToken] = $signingVars; + $headers = self::getSignedRequestHeaders($region, $host, $accessKeyId, $secretAccessKey, $securityToken); + // Inject x-goog-cloud-target-resource into header + $headers['x-goog-cloud-target-resource'] = $this->audience; + // Format headers as they're expected in the subject token + $formattedHeaders = \array_map(fn($k, $v) => ['key' => $k, 'value' => $v], \array_keys($headers), $headers); + $request = ['headers' => $formattedHeaders, 'method' => 'POST', 'url' => $url]; + return \urlencode(\json_encode($request) ?: ''); + } + /** + * @internal + */ + public static function getImdsV2SessionToken(string $imdsV2Url, callable $httpHandler) : string + { + $headers = ['X-aws-ec2-metadata-token-ttl-seconds' => '21600']; + $request = new Request('PUT', $imdsV2Url, $headers); + $response = $httpHandler($request); + return (string) $response->getBody(); + } + /** + * @see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + * + * @internal + * + * @return array + */ + public static function getSignedRequestHeaders(string $region, string $host, string $accessKeyId, string $secretAccessKey, ?string $securityToken) : array + { + $service = 'sts'; + # Create a date for headers and the credential string in ISO-8601 format + $amzdate = \gmdate('Ymd\\THis\\Z'); + $datestamp = \gmdate('Ymd'); + # Date w/o time, used in credential scope + # Create the canonical headers and signed headers. Header names + # must be trimmed and lowercase, and sorted in code point order from + # low to high. Note that there is a trailing \n. + $canonicalHeaders = \sprintf("host:%s\nx-amz-date:%s\n", $host, $amzdate); + if ($securityToken) { + $canonicalHeaders .= \sprintf("x-amz-security-token:%s\n", $securityToken); + } + # Step 5: Create the list of signed headers. This lists the headers + # in the canonicalHeaders list, delimited with ";" and in alpha order. + # Note: The request can include any headers; $canonicalHeaders and + # $signedHeaders lists those that you want to be included in the + # hash of the request. "Host" and "x-amz-date" are always required. + $signedHeaders = 'host;x-amz-date'; + if ($securityToken) { + $signedHeaders .= ';x-amz-security-token'; + } + # Step 6: Create payload hash (hash of the request body content). For GET + # requests, the payload is an empty string (""). + $payloadHash = \hash('sha256', ''); + # Step 7: Combine elements to create canonical request + $canonicalRequest = \implode("\n", [ + 'POST', + // method + '/', + // canonical URL + self::CRED_VERIFICATION_QUERY, + // query string + $canonicalHeaders, + $signedHeaders, + $payloadHash, + ]); + # ************* TASK 2: CREATE THE STRING TO SIGN************* + # Match the algorithm to the hashing algorithm you use, either SHA-1 or + # SHA-256 (recommended) + $algorithm = 'AWS4-HMAC-SHA256'; + $scope = \implode('/', [$datestamp, $region, $service, 'aws4_request']); + $stringToSign = \implode("\n", [$algorithm, $amzdate, $scope, \hash('sha256', $canonicalRequest)]); + # ************* TASK 3: CALCULATE THE SIGNATURE ************* + # Create the signing key using the function defined above. + // (done above) + $signingKey = self::getSignatureKey($secretAccessKey, $datestamp, $region, $service); + # Sign the string_to_sign using the signing_key + $signature = \bin2hex(self::hmacSign($signingKey, $stringToSign)); + # ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST ************* + # The signing information can be either in a query string value or in + # a header named Authorization. This code shows how to use a header. + # Create authorization header and add to request headers + $authorizationHeader = \sprintf('%s Credential=%s/%s, SignedHeaders=%s, Signature=%s', $algorithm, $accessKeyId, $scope, $signedHeaders, $signature); + # The request can include any headers, but MUST include "host", "x-amz-date", + # and (for this scenario) "Authorization". "host" and "x-amz-date" must + # be included in the canonical_headers and signed_headers, as noted + # earlier. Order here is not significant. + $headers = ['host' => $host, 'x-amz-date' => $amzdate, 'Authorization' => $authorizationHeader]; + if ($securityToken) { + $headers['x-amz-security-token'] = $securityToken; + } + return $headers; + } + /** + * @internal + */ + public static function getRegionFromEnv() : ?string + { + $region = \getenv('AWS_REGION'); + if (empty($region)) { + $region = \getenv('AWS_DEFAULT_REGION'); + } + return $region ?: null; + } + /** + * @internal + * + * @param callable $httpHandler + * @param string $regionUrl + * @param array $headers Request headers to send in with the request. + */ + public static function getRegionFromUrl(callable $httpHandler, string $regionUrl, array $headers) : string + { + // get the region/zone from the region URL + $regionRequest = new Request('GET', $regionUrl, $headers); + $regionResponse = $httpHandler($regionRequest); + // Remove last character. For example, if us-east-2b is returned, + // the region would be us-east-2. + return \substr((string) $regionResponse->getBody(), 0, -1); + } + /** + * @internal + * + * @param callable $httpHandler + * @param string $securityCredentialsUrl + * @param array $headers Request headers to send in with the request. + */ + public static function getRoleName(callable $httpHandler, string $securityCredentialsUrl, array $headers) : string + { + // Get the AWS role name + $roleRequest = new Request('GET', $securityCredentialsUrl, $headers); + $roleResponse = $httpHandler($roleRequest); + $roleName = (string) $roleResponse->getBody(); + return $roleName; + } + /** + * @internal + * + * @param callable $httpHandler + * @param string $securityCredentialsUrl + * @param array $headers Request headers to send in with the request. + * @return array{string, string, ?string} + */ + public static function getSigningVarsFromUrl(callable $httpHandler, string $securityCredentialsUrl, string $roleName, array $headers) : array + { + // Get the AWS credentials + $credsRequest = new Request('GET', $securityCredentialsUrl . '/' . $roleName, $headers); + $credsResponse = $httpHandler($credsRequest); + $awsCreds = \json_decode((string) $credsResponse->getBody(), \true); + return [ + $awsCreds['AccessKeyId'], + // accessKeyId + $awsCreds['SecretAccessKey'], + // secretAccessKey + $awsCreds['Token'], + ]; + } + /** + * @internal + * + * @return array{string, string, ?string} + */ + public static function getSigningVarsFromEnv() : ?array + { + $accessKeyId = \getenv('AWS_ACCESS_KEY_ID'); + $secretAccessKey = \getenv('AWS_SECRET_ACCESS_KEY'); + if ($accessKeyId && $secretAccessKey) { + return [$accessKeyId, $secretAccessKey, \getenv('AWS_SESSION_TOKEN') ?: null]; + } + return null; + } + /** + * Return HMAC hash in binary string + */ + private static function hmacSign(string $key, string $msg) : string + { + return \hash_hmac('sha256', self::utf8Encode($msg), $key, \true); + } + /** + * @TODO add a fallback when mbstring is not available + */ + private static function utf8Encode(string $string) : string + { + return \mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1'); + } + private static function getSignatureKey(string $key, string $dateStamp, string $regionName, string $serviceName) : string + { + $kDate = self::hmacSign(self::utf8Encode('AWS4' . $key), $dateStamp); + $kRegion = self::hmacSign($kDate, $regionName); + $kService = self::hmacSign($kRegion, $serviceName); + $kSigning = self::hmacSign($kService, 'aws4_request'); + return $kSigning; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/FileSource.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/FileSource.php new file mode 100644 index 00000000..8a29dc85 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/FileSource.php @@ -0,0 +1,60 @@ +file = $file; + if ($format === 'json' && \is_null($subjectTokenFieldName)) { + throw new InvalidArgumentException('subject_token_field_name must be set when format is JSON'); + } + $this->format = $format; + $this->subjectTokenFieldName = $subjectTokenFieldName; + } + public function fetchSubjectToken(?callable $httpHandler = null) : string + { + $contents = \file_get_contents($this->file); + if ($this->format === 'json') { + if (!($json = \json_decode((string) $contents, \true))) { + throw new UnexpectedValueException('Unable to decode JSON file'); + } + if (!isset($json[$this->subjectTokenFieldName])) { + throw new UnexpectedValueException('subject_token_field_name not found in JSON file'); + } + $contents = $json[$this->subjectTokenFieldName]; + } + return $contents; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/UrlSource.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/UrlSource.php new file mode 100644 index 00000000..83d8d75b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialSource/UrlSource.php @@ -0,0 +1,74 @@ + + */ + private ?array $headers; + /** + * @param string $url The URL to fetch the subject token from. + * @param string $format The format of the token in the response. Can be null or "json". + * @param string $subjectTokenFieldName The name of the field containing the token in the response. This is required + * when format is "json". + * @param array $headers Request headers to send in with the request to the URL. + */ + public function __construct(string $url, ?string $format = null, ?string $subjectTokenFieldName = null, ?array $headers = null) + { + $this->url = $url; + if ($format === 'json' && \is_null($subjectTokenFieldName)) { + throw new InvalidArgumentException('subject_token_field_name must be set when format is JSON'); + } + $this->format = $format; + $this->subjectTokenFieldName = $subjectTokenFieldName; + $this->headers = $headers; + } + public function fetchSubjectToken(?callable $httpHandler = null) : string + { + if (\is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + $request = new Request('GET', $this->url, $this->headers ?: []); + $response = $httpHandler($request); + $body = (string) $response->getBody(); + if ($this->format === 'json') { + if (!($json = \json_decode((string) $body, \true))) { + throw new UnexpectedValueException('Unable to decode JSON response'); + } + if (!isset($json[$this->subjectTokenFieldName])) { + throw new UnexpectedValueException('subject_token_field_name not found in JSON file'); + } + $body = $json[$this->subjectTokenFieldName]; + } + return $body; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/AppIdentityCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/AppIdentityCredentials.php new file mode 100644 index 00000000..d5de0c30 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/AppIdentityCredentials.php @@ -0,0 +1,209 @@ +push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/books/v1', + * 'auth' => 'google_auth' + * ]); + * + * $res = $client->get('volumes?q=Henry+David+Thoreau&country=US'); + * ``` + */ +class AppIdentityCredentials extends CredentialsLoader implements SignBlobInterface, ProjectIdProviderInterface +{ + /** + * Result of fetchAuthToken. + * + * @var array + */ + protected $lastReceivedToken; + /** + * Array of OAuth2 scopes to be requested. + * + * @var string[] + */ + private $scope; + /** + * @var string + */ + private $clientName; + /** + * @param string|string[] $scope One or more scopes. + */ + public function __construct($scope = []) + { + $this->scope = \is_array($scope) ? $scope : \explode(' ', (string) $scope); + } + /** + * Determines if this an App Engine instance, by accessing the + * SERVER_SOFTWARE environment variable (prod) or the APPENGINE_RUNTIME + * environment variable (dev). + * + * @return bool true if this an App Engine Instance, false otherwise + */ + public static function onAppEngine() + { + $appEngineProduction = isset($_SERVER['SERVER_SOFTWARE']) && 0 === \strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine'); + if ($appEngineProduction) { + return \true; + } + $appEngineDevAppServer = isset($_SERVER['APPENGINE_RUNTIME']) && $_SERVER['APPENGINE_RUNTIME'] == 'php'; + if ($appEngineDevAppServer) { + return \true; + } + return \false; + } + /** + * Implements FetchAuthTokenInterface#fetchAuthToken. + * + * Fetches the auth tokens using the AppIdentityService if available. + * As the AppIdentityService uses protobufs to fetch the access token, + * the GuzzleHttp\ClientInterface instance passed in will not be used. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type string $expiration_time + * } + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + try { + $this->checkAppEngineContext(); + } catch (\Exception $e) { + return []; + } + /** @phpstan-ignore-next-line */ + $token = AppIdentityService::getAccessToken($this->scope); + $this->lastReceivedToken = $token; + return $token; + } + /** + * Sign a string using AppIdentityService. + * + * @param string $stringToSign The string to sign. + * @param bool $forceOpenSsl [optional] Does not apply to this credentials + * type. + * @return string The signature, base64-encoded. + * @throws \Exception If AppEngine SDK or mock is not available. + */ + public function signBlob($stringToSign, $forceOpenSsl = \false) + { + $this->checkAppEngineContext(); + /** @phpstan-ignore-next-line */ + return \base64_encode(AppIdentityService::signForApp($stringToSign)['signature']); + } + /** + * Get the project ID from AppIdentityService. + * + * Returns null if AppIdentityService is unavailable. + * + * @param callable $httpHandler Not used by this type. + * @return string|null + */ + public function getProjectId(?callable $httpHandler = null) + { + try { + $this->checkAppEngineContext(); + } catch (\Exception $e) { + return null; + } + /** @phpstan-ignore-next-line */ + return AppIdentityService::getApplicationId(); + } + /** + * Get the client name from AppIdentityService. + * + * Subsequent calls to this method will return a cached value. + * + * @param callable $httpHandler Not used in this implementation. + * @return string + * @throws \Exception If AppEngine SDK or mock is not available. + */ + public function getClientName(?callable $httpHandler = null) + { + $this->checkAppEngineContext(); + if (!$this->clientName) { + /** @phpstan-ignore-next-line */ + $this->clientName = AppIdentityService::getServiceAccountName(); + } + return $this->clientName; + } + /** + * @return array{access_token:string,expires_at:int}|null + */ + public function getLastReceivedToken() + { + if ($this->lastReceivedToken) { + return ['access_token' => $this->lastReceivedToken['access_token'], 'expires_at' => $this->lastReceivedToken['expiration_time']]; + } + return null; + } + /** + * Caching is handled by the underlying AppIdentityService, return empty string + * to prevent caching. + * + * @return string + */ + public function getCacheKey() + { + return ''; + } + /** + * @return void + */ + private function checkAppEngineContext() + { + if (!self::onAppEngine() || !\class_exists('WPMailSMTP\\Vendor\\google\\appengine\\api\\app_identity\\AppIdentityService')) { + throw new \Exception('This class must be run in App Engine, or you must include the AppIdentityService ' . 'mock class defined in tests/mocks/AppIdentityService.php'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ExternalAccountCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ExternalAccountCredentials.php new file mode 100644 index 00000000..c1cb38fd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ExternalAccountCredentials.php @@ -0,0 +1,229 @@ + $jsonKey JSON credentials as an associative array. + */ + public function __construct($scope, array $jsonKey) + { + if (!\array_key_exists('type', $jsonKey)) { + throw new InvalidArgumentException('json key is missing the type field'); + } + if ($jsonKey['type'] !== self::EXTERNAL_ACCOUNT_TYPE) { + throw new InvalidArgumentException(\sprintf('expected "%s" type but received "%s"', self::EXTERNAL_ACCOUNT_TYPE, $jsonKey['type'])); + } + if (!\array_key_exists('token_url', $jsonKey)) { + throw new InvalidArgumentException('json key is missing the token_url field'); + } + if (!\array_key_exists('audience', $jsonKey)) { + throw new InvalidArgumentException('json key is missing the audience field'); + } + if (!\array_key_exists('subject_token_type', $jsonKey)) { + throw new InvalidArgumentException('json key is missing the subject_token_type field'); + } + if (!\array_key_exists('credential_source', $jsonKey)) { + throw new InvalidArgumentException('json key is missing the credential_source field'); + } + if (\array_key_exists('service_account_impersonation_url', $jsonKey)) { + $this->serviceAccountImpersonationUrl = $jsonKey['service_account_impersonation_url']; + } + $this->quotaProject = $jsonKey['quota_project_id'] ?? null; + $this->workforcePoolUserProject = $jsonKey['workforce_pool_user_project'] ?? null; + $this->universeDomain = $jsonKey['universe_domain'] ?? GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; + $this->auth = new OAuth2(['tokenCredentialUri' => $jsonKey['token_url'], 'audience' => $jsonKey['audience'], 'scope' => $scope, 'subjectTokenType' => $jsonKey['subject_token_type'], 'subjectTokenFetcher' => self::buildCredentialSource($jsonKey), 'additionalOptions' => $this->workforcePoolUserProject ? ['userProject' => $this->workforcePoolUserProject] : []]); + if (!$this->isWorkforcePool() && $this->workforcePoolUserProject) { + throw new InvalidArgumentException('workforce_pool_user_project should not be set for non-workforce pool credentials.'); + } + } + /** + * @param array $jsonKey + */ + private static function buildCredentialSource(array $jsonKey) : ExternalAccountCredentialSourceInterface + { + $credentialSource = $jsonKey['credential_source']; + if (isset($credentialSource['file'])) { + return new FileSource($credentialSource['file'], $credentialSource['format']['type'] ?? null, $credentialSource['format']['subject_token_field_name'] ?? null); + } + if (isset($credentialSource['environment_id']) && 1 === \preg_match('/^aws(\\d+)$/', $credentialSource['environment_id'], $matches)) { + if ($matches[1] !== '1') { + throw new InvalidArgumentException("aws version \"{$matches[1]}\" is not supported in the current build."); + } + if (!\array_key_exists('regional_cred_verification_url', $credentialSource)) { + throw new InvalidArgumentException('The regional_cred_verification_url field is required for aws1 credential source.'); + } + if (!\array_key_exists('audience', $jsonKey)) { + throw new InvalidArgumentException('aws1 credential source requires an audience to be set in the JSON file.'); + } + return new AwsNativeSource( + $jsonKey['audience'], + $credentialSource['regional_cred_verification_url'], + // $regionalCredVerificationUrl + $credentialSource['region_url'] ?? null, + // $regionUrl + $credentialSource['url'] ?? null, + // $securityCredentialsUrl + $credentialSource['imdsv2_session_token_url'] ?? null + ); + } + if (isset($credentialSource['url'])) { + return new UrlSource($credentialSource['url'], $credentialSource['format']['type'] ?? null, $credentialSource['format']['subject_token_field_name'] ?? null, $credentialSource['headers'] ?? null); + } + throw new InvalidArgumentException('Unable to determine credential source from json key.'); + } + /** + * @param string $stsToken + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_at + * } + */ + private function getImpersonatedAccessToken(string $stsToken, ?callable $httpHandler = null) : array + { + if (!isset($this->serviceAccountImpersonationUrl)) { + throw new InvalidArgumentException('service_account_impersonation_url must be set in JSON credentials.'); + } + $request = new Request('POST', $this->serviceAccountImpersonationUrl, ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $stsToken], (string) \json_encode(['lifetime' => \sprintf('%ss', OAuth2::DEFAULT_EXPIRY_SECONDS), 'scope' => \explode(' ', $this->auth->getScope())])); + if (\is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + $response = $httpHandler($request); + $body = \json_decode((string) $response->getBody(), \true); + return ['access_token' => $body['accessToken'], 'expires_at' => \strtotime($body['expireTime'])]; + } + /** + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_at (impersonated service accounts only) + * @type int $expires_in (identity pool only) + * @type string $issued_token_type (identity pool only) + * @type string $token_type (identity pool only) + * } + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + $stsToken = $this->auth->fetchAuthToken($httpHandler); + if (isset($this->serviceAccountImpersonationUrl)) { + return $this->getImpersonatedAccessToken($stsToken['access_token'], $httpHandler); + } + return $stsToken; + } + public function getCacheKey() + { + return $this->auth->getCacheKey(); + } + public function getLastReceivedToken() + { + return $this->auth->getLastReceivedToken(); + } + /** + * Get the quota project used for this API request + * + * @return string|null + */ + public function getQuotaProject() + { + return $this->quotaProject; + } + /** + * Get the universe domain used for this API request + * + * @return string + */ + public function getUniverseDomain() : string + { + return $this->universeDomain; + } + /** + * Get the project ID. + * + * @param callable $httpHandler Callback which delivers psr7 request + * @param string $accessToken The access token to use to sign the blob. If + * provided, saves a call to the metadata server for a new access + * token. **Defaults to** `null`. + * @return string|null + */ + public function getProjectId(?callable $httpHandler = null, ?string $accessToken = null) + { + if (isset($this->projectId)) { + return $this->projectId; + } + $projectNumber = $this->getProjectNumber() ?: $this->workforcePoolUserProject; + if (!$projectNumber) { + return null; + } + if (\is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + $url = \str_replace('UNIVERSE_DOMAIN', $this->getUniverseDomain(), \sprintf(self::CLOUD_RESOURCE_MANAGER_URL, $projectNumber)); + if (\is_null($accessToken)) { + $accessToken = $this->fetchAuthToken($httpHandler)['access_token']; + } + $request = new Request('GET', $url, ['authorization' => 'Bearer ' . $accessToken]); + $response = $httpHandler($request); + $body = \json_decode((string) $response->getBody(), \true); + return $this->projectId = $body['projectId']; + } + private function getProjectNumber() : ?string + { + $parts = \explode('/', $this->auth->getAudience()); + $i = \array_search('projects', $parts); + return $parts[$i + 1] ?? null; + } + private function isWorkforcePool() : bool + { + $regex = '#//iam\\.googleapis\\.com/locations/[^/]+/workforcePools/#'; + return \preg_match($regex, $this->auth->getAudience()) === 1; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/GCECredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/GCECredentials.php new file mode 100644 index 00000000..8d7b5f08 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/GCECredentials.php @@ -0,0 +1,493 @@ +push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', + * 'auth' => 'google_auth' + * ]); + * + * $res = $client->get('myproject/taskqueues/myqueue'); + */ +class GCECredentials extends CredentialsLoader implements SignBlobInterface, ProjectIdProviderInterface, GetQuotaProjectInterface +{ + use IamSignerTrait; + // phpcs:disable + const cacheKey = 'GOOGLE_AUTH_PHP_GCE'; + // phpcs:enable + /** + * The metadata IP address on appengine instances. + * + * The IP is used instead of the domain 'metadata' to avoid slow responses + * when not on Compute Engine. + */ + const METADATA_IP = '169.254.169.254'; + /** + * The metadata path of the default token. + */ + const TOKEN_URI_PATH = 'v1/instance/service-accounts/default/token'; + /** + * The metadata path of the default id token. + */ + const ID_TOKEN_URI_PATH = 'v1/instance/service-accounts/default/identity'; + /** + * The metadata path of the client ID. + */ + const CLIENT_ID_URI_PATH = 'v1/instance/service-accounts/default/email'; + /** + * The metadata path of the project ID. + */ + const PROJECT_ID_URI_PATH = 'v1/project/project-id'; + /** + * The metadata path of the project ID. + */ + const UNIVERSE_DOMAIN_URI_PATH = 'v1/universe/universe_domain'; + /** + * The header whose presence indicates GCE presence. + */ + const FLAVOR_HEADER = 'Metadata-Flavor'; + /** + * The Linux file which contains the product name. + */ + private const GKE_PRODUCT_NAME_FILE = '/sys/class/dmi/id/product_name'; + /** + * Note: the explicit `timeout` and `tries` below is a workaround. The underlying + * issue is that resolving an unknown host on some networks will take + * 20-30 seconds; making this timeout short fixes the issue, but + * could lead to false negatives in the event that we are on GCE, but + * the metadata resolution was particularly slow. The latter case is + * "unlikely" since the expected 4-nines time is about 0.5 seconds. + * This allows us to limit the total ping maximum timeout to 1.5 seconds + * for developer desktop scenarios. + */ + const MAX_COMPUTE_PING_TRIES = 3; + const COMPUTE_PING_CONNECTION_TIMEOUT_S = 0.5; + /** + * Flag used to ensure that the onGCE test is only done once;. + * + * @var bool + */ + private $hasCheckedOnGce = \false; + /** + * Flag that stores the value of the onGCE check. + * + * @var bool + */ + private $isOnGce = \false; + /** + * Result of fetchAuthToken. + * + * @var array + */ + protected $lastReceivedToken; + /** + * @var string|null + */ + private $clientName; + /** + * @var string|null + */ + private $projectId; + /** + * @var string + */ + private $tokenUri; + /** + * @var string + */ + private $targetAudience; + /** + * @var string|null + */ + private $quotaProject; + /** + * @var string|null + */ + private $serviceAccountIdentity; + /** + * @var string + */ + private ?string $universeDomain; + /** + * @param Iam $iam [optional] An IAM instance. + * @param string|string[] $scope [optional] the scope of the access request, + * expressed either as an array or as a space-delimited string. + * @param string $targetAudience [optional] The audience for the ID token. + * @param string $quotaProject [optional] Specifies a project to bill for access + * charges associated with the request. + * @param string $serviceAccountIdentity [optional] Specify a service + * account identity name to use instead of "default". + * @param string $universeDomain [optional] Specify a universe domain to use + * instead of fetching one from the metadata server. + */ + public function __construct(?Iam $iam = null, $scope = null, $targetAudience = null, $quotaProject = null, $serviceAccountIdentity = null, ?string $universeDomain = null) + { + $this->iam = $iam; + if ($scope && $targetAudience) { + throw new InvalidArgumentException('Scope and targetAudience cannot both be supplied'); + } + $tokenUri = self::getTokenUri($serviceAccountIdentity); + if ($scope) { + if (\is_string($scope)) { + $scope = \explode(' ', $scope); + } + $scope = \implode(',', $scope); + $tokenUri = $tokenUri . '?scopes=' . $scope; + } elseif ($targetAudience) { + $tokenUri = self::getIdTokenUri($serviceAccountIdentity); + $tokenUri = $tokenUri . '?audience=' . $targetAudience; + $this->targetAudience = $targetAudience; + } + $this->tokenUri = $tokenUri; + $this->quotaProject = $quotaProject; + $this->serviceAccountIdentity = $serviceAccountIdentity; + $this->universeDomain = $universeDomain; + } + /** + * The full uri for accessing the default token. + * + * @param string $serviceAccountIdentity [optional] Specify a service + * account identity name to use instead of "default". + * @return string + */ + public static function getTokenUri($serviceAccountIdentity = null) + { + $base = 'http://' . self::METADATA_IP . '/computeMetadata/'; + $base .= self::TOKEN_URI_PATH; + if ($serviceAccountIdentity) { + return \str_replace('/default/', '/' . $serviceAccountIdentity . '/', $base); + } + return $base; + } + /** + * The full uri for accessing the default service account. + * + * @param string $serviceAccountIdentity [optional] Specify a service + * account identity name to use instead of "default". + * @return string + */ + public static function getClientNameUri($serviceAccountIdentity = null) + { + $base = 'http://' . self::METADATA_IP . '/computeMetadata/'; + $base .= self::CLIENT_ID_URI_PATH; + if ($serviceAccountIdentity) { + return \str_replace('/default/', '/' . $serviceAccountIdentity . '/', $base); + } + return $base; + } + /** + * The full uri for accesesing the default identity token. + * + * @param string $serviceAccountIdentity [optional] Specify a service + * account identity name to use instead of "default". + * @return string + */ + private static function getIdTokenUri($serviceAccountIdentity = null) + { + $base = 'http://' . self::METADATA_IP . '/computeMetadata/'; + $base .= self::ID_TOKEN_URI_PATH; + if ($serviceAccountIdentity) { + return \str_replace('/default/', '/' . $serviceAccountIdentity . '/', $base); + } + return $base; + } + /** + * The full uri for accessing the default project ID. + * + * @return string + */ + private static function getProjectIdUri() + { + $base = 'http://' . self::METADATA_IP . '/computeMetadata/'; + return $base . self::PROJECT_ID_URI_PATH; + } + /** + * The full uri for accessing the default universe domain. + * + * @return string + */ + private static function getUniverseDomainUri() + { + $base = 'http://' . self::METADATA_IP . '/computeMetadata/'; + return $base . self::UNIVERSE_DOMAIN_URI_PATH; + } + /** + * Determines if this an App Engine Flexible instance, by accessing the + * GAE_INSTANCE environment variable. + * + * @return bool true if this an App Engine Flexible Instance, false otherwise + */ + public static function onAppEngineFlexible() + { + return \substr((string) \getenv('GAE_INSTANCE'), 0, 4) === 'aef-'; + } + /** + * Determines if this a GCE instance, by accessing the expected metadata + * host. + * If $httpHandler is not specified a the default HttpHandler is used. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return bool True if this a GCEInstance, false otherwise + */ + public static function onGce(?callable $httpHandler = null) + { + $httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + $checkUri = 'http://' . self::METADATA_IP; + for ($i = 1; $i <= self::MAX_COMPUTE_PING_TRIES; $i++) { + try { + // Comment from: oauth2client/client.py + // + // Note: the explicit `timeout` below is a workaround. The underlying + // issue is that resolving an unknown host on some networks will take + // 20-30 seconds; making this timeout short fixes the issue, but + // could lead to false negatives in the event that we are on GCE, but + // the metadata resolution was particularly slow. The latter case is + // "unlikely". + $resp = $httpHandler(new Request('GET', $checkUri, [self::FLAVOR_HEADER => 'Google']), ['timeout' => self::COMPUTE_PING_CONNECTION_TIMEOUT_S]); + return $resp->getHeaderLine(self::FLAVOR_HEADER) == 'Google'; + } catch (ClientException $e) { + } catch (ServerException $e) { + } catch (RequestException $e) { + } catch (ConnectException $e) { + } + } + if (\PHP_OS === 'Windows') { + // @TODO: implement GCE residency detection on Windows + return \false; + } + // Detect GCE residency on Linux + return self::detectResidencyLinux(self::GKE_PRODUCT_NAME_FILE); + } + private static function detectResidencyLinux(string $productNameFile) : bool + { + if (\file_exists($productNameFile)) { + $productName = \trim((string) \file_get_contents($productNameFile)); + return 0 === \strpos($productName, 'Google'); + } + return \false; + } + /** + * Implements FetchAuthTokenInterface#fetchAuthToken. + * + * Fetches the auth tokens from the GCE metadata host if it is available. + * If $httpHandler is not specified a the default HttpHandler is used. + * + * @param callable $httpHandler callback which delivers psr7 request + * + * @return array { + * A set of auth related metadata, based on the token type. + * + * @type string $access_token for access tokens + * @type int $expires_in for access tokens + * @type string $token_type for access tokens + * @type string $id_token for ID tokens + * } + * @throws \Exception + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + $httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + if (!$this->hasCheckedOnGce) { + $this->isOnGce = self::onGce($httpHandler); + $this->hasCheckedOnGce = \true; + } + if (!$this->isOnGce) { + return []; + // return an empty array with no access token + } + $response = $this->getFromMetadata($httpHandler, $this->tokenUri); + if ($this->targetAudience) { + return $this->lastReceivedToken = ['id_token' => $response]; + } + if (null === ($json = \json_decode($response, \true))) { + throw new \Exception('Invalid JSON response'); + } + $json['expires_at'] = \time() + $json['expires_in']; + // store this so we can retrieve it later + $this->lastReceivedToken = $json; + return $json; + } + /** + * @return string + */ + public function getCacheKey() + { + return self::cacheKey; + } + /** + * @return array|null + */ + public function getLastReceivedToken() + { + if ($this->lastReceivedToken) { + if (\array_key_exists('id_token', $this->lastReceivedToken)) { + return $this->lastReceivedToken; + } + return ['access_token' => $this->lastReceivedToken['access_token'], 'expires_at' => $this->lastReceivedToken['expires_at']]; + } + return null; + } + /** + * Get the client name from GCE metadata. + * + * Subsequent calls will return a cached value. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return string + */ + public function getClientName(?callable $httpHandler = null) + { + if ($this->clientName) { + return $this->clientName; + } + $httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + if (!$this->hasCheckedOnGce) { + $this->isOnGce = self::onGce($httpHandler); + $this->hasCheckedOnGce = \true; + } + if (!$this->isOnGce) { + return ''; + } + $this->clientName = $this->getFromMetadata($httpHandler, self::getClientNameUri($this->serviceAccountIdentity)); + return $this->clientName; + } + /** + * Fetch the default Project ID from compute engine. + * + * Returns null if called outside GCE. + * + * @param callable $httpHandler Callback which delivers psr7 request + * @return string|null + */ + public function getProjectId(?callable $httpHandler = null) + { + if ($this->projectId) { + return $this->projectId; + } + $httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + if (!$this->hasCheckedOnGce) { + $this->isOnGce = self::onGce($httpHandler); + $this->hasCheckedOnGce = \true; + } + if (!$this->isOnGce) { + return null; + } + $this->projectId = $this->getFromMetadata($httpHandler, self::getProjectIdUri()); + return $this->projectId; + } + /** + * Fetch the default universe domain from the metadata server. + * + * @param callable $httpHandler Callback which delivers psr7 request + * @return string + */ + public function getUniverseDomain(?callable $httpHandler = null) : string + { + if (null !== $this->universeDomain) { + return $this->universeDomain; + } + $httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + if (!$this->hasCheckedOnGce) { + $this->isOnGce = self::onGce($httpHandler); + $this->hasCheckedOnGce = \true; + } + try { + $this->universeDomain = $this->getFromMetadata($httpHandler, self::getUniverseDomainUri()); + } catch (ClientException $e) { + // If the metadata server exists, but returns a 404 for the universe domain, the auth + // libraries should safely assume this is an older metadata server running in GCU, and + // should return the default universe domain. + if (!$e->hasResponse() || 404 != $e->getResponse()->getStatusCode()) { + throw $e; + } + $this->universeDomain = self::DEFAULT_UNIVERSE_DOMAIN; + } + // We expect in some cases the metadata server will return an empty string for the universe + // domain. In this case, the auth library MUST return the default universe domain. + if ('' === $this->universeDomain) { + $this->universeDomain = self::DEFAULT_UNIVERSE_DOMAIN; + } + return $this->universeDomain; + } + /** + * Fetch the value of a GCE metadata server URI. + * + * @param callable $httpHandler An HTTP Handler to deliver PSR7 requests. + * @param string $uri The metadata URI. + * @return string + */ + private function getFromMetadata(callable $httpHandler, $uri) + { + $resp = $httpHandler(new Request('GET', $uri, [self::FLAVOR_HEADER => 'Google'])); + return (string) $resp->getBody(); + } + /** + * Get the quota project used for this API request + * + * @return string|null + */ + public function getQuotaProject() + { + return $this->quotaProject; + } + /** + * Set whether or not we've already checked the GCE environment. + * + * @param bool $isOnGce + * + * @return void + */ + public function setIsOnGce($isOnGce) + { + // Implicitly set hasCheckedGce to true + $this->hasCheckedOnGce = \true; + // Set isOnGce + $this->isOnGce = $isOnGce; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/IAMCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/IAMCredentials.php new file mode 100644 index 00000000..3ebe72df --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/IAMCredentials.php @@ -0,0 +1,77 @@ +selector = $selector; + $this->token = $token; + } + /** + * export a callback function which updates runtime metadata. + * + * @return callable updateMetadata function + */ + public function getUpdateMetadataFunc() + { + return [$this, 'updateMetadata']; + } + /** + * Updates metadata with the appropriate header metadata. + * + * @param array $metadata metadata hashmap + * @param string $unusedAuthUri optional auth uri + * @param callable $httpHandler callback which delivers psr7 request + * Note: this param is unused here, only included here for + * consistency with other credentials class + * + * @return array updated metadata hashmap + */ + public function updateMetadata($metadata, $unusedAuthUri = null, ?callable $httpHandler = null) + { + $metadata_copy = $metadata; + $metadata_copy[self::SELECTOR_KEY] = $this->selector; + $metadata_copy[self::TOKEN_KEY] = $this->token; + return $metadata_copy; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php new file mode 100644 index 00000000..a85a3f06 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ImpersonatedServiceAccountCredentials.php @@ -0,0 +1,120 @@ + $jsonKey JSON credential file path or JSON credentials + * as an associative array. + */ + public function __construct($scope, $jsonKey) + { + if (\is_string($jsonKey)) { + if (!\file_exists($jsonKey)) { + throw new \InvalidArgumentException('file does not exist'); + } + $json = \file_get_contents($jsonKey); + if (!($jsonKey = \json_decode((string) $json, \true))) { + throw new \LogicException('invalid json for auth config'); + } + } + if (!\array_key_exists('service_account_impersonation_url', $jsonKey)) { + throw new \LogicException('json key is missing the service_account_impersonation_url field'); + } + if (!\array_key_exists('source_credentials', $jsonKey)) { + throw new \LogicException('json key is missing the source_credentials field'); + } + $this->impersonatedServiceAccountName = $this->getImpersonatedServiceAccountNameFromUrl($jsonKey['service_account_impersonation_url']); + $this->sourceCredentials = new UserRefreshCredentials($scope, $jsonKey['source_credentials']); + } + /** + * Helper function for extracting the Server Account Name from the URL saved in the account + * credentials file. + * + * @param $serviceAccountImpersonationUrl string URL from "service_account_impersonation_url" + * @return string Service account email or ID. + */ + private function getImpersonatedServiceAccountNameFromUrl(string $serviceAccountImpersonationUrl) : string + { + $fields = \explode('/', $serviceAccountImpersonationUrl); + $lastField = \end($fields); + $splitter = \explode(':', $lastField); + return $splitter[0]; + } + /** + * Get the client name from the keyfile + * + * In this implementation, it will return the issuers email from the oauth token. + * + * @param callable|null $unusedHttpHandler not used by this credentials type. + * @return string Token issuer email + */ + public function getClientName(?callable $unusedHttpHandler = null) + { + return $this->impersonatedServiceAccountName; + } + /** + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_in + * @type string $scope + * @type string $token_type + * @type string $id_token + * } + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + return $this->sourceCredentials->fetchAuthToken($httpHandler); + } + /** + * @return string + */ + public function getCacheKey() + { + return $this->sourceCredentials->getCacheKey(); + } + /** + * @return array + */ + public function getLastReceivedToken() + { + return $this->sourceCredentials->getLastReceivedToken(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/InsecureCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/InsecureCredentials.php new file mode 100644 index 00000000..a2b3090a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/InsecureCredentials.php @@ -0,0 +1,62 @@ + '']; + /** + * Fetches the auth token. In this case it returns an empty string. + * + * @param callable $httpHandler + * @return array{access_token:string} A set of auth related metadata + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + return $this->token; + } + /** + * Returns the cache key. In this case it returns a null value, disabling + * caching. + * + * @return string|null + */ + public function getCacheKey() + { + return null; + } + /** + * Fetches the last received token. In this case, it returns the same empty string + * auth token. + * + * @return array{access_token:string} + */ + public function getLastReceivedToken() + { + return $this->token; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountCredentials.php new file mode 100644 index 00000000..ef871174 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountCredentials.php @@ -0,0 +1,312 @@ +push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', + * 'auth' => 'google_auth' // authorize all requests + * ]); + * + * $res = $client->get('myproject/taskqueues/myqueue'); + */ +class ServiceAccountCredentials extends CredentialsLoader implements GetQuotaProjectInterface, SignBlobInterface, ProjectIdProviderInterface +{ + use ServiceAccountSignerTrait; + /** + * The OAuth2 instance used to conduct authorization. + * + * @var OAuth2 + */ + protected $auth; + /** + * The quota project associated with the JSON credentials + * + * @var string + */ + protected $quotaProject; + /** + * @var string|null + */ + protected $projectId; + /** + * @var array|null + */ + private $lastReceivedJwtAccessToken; + /** + * @var bool + */ + private $useJwtAccessWithScope = \false; + /** + * @var ServiceAccountJwtAccessCredentials|null + */ + private $jwtAccessCredentials; + /** + * @var string + */ + private string $universeDomain; + /** + * Create a new ServiceAccountCredentials. + * + * @param string|string[]|null $scope the scope of the access request, expressed + * either as an Array or as a space-delimited String. + * @param string|array $jsonKey JSON credential file path or JSON credentials + * as an associative array + * @param string $sub an email address account to impersonate, in situations when + * the service account has been delegated domain wide access. + * @param string $targetAudience The audience for the ID token. + */ + public function __construct($scope, $jsonKey, $sub = null, $targetAudience = null) + { + if (\is_string($jsonKey)) { + if (!\file_exists($jsonKey)) { + throw new \InvalidArgumentException('file does not exist'); + } + $jsonKeyStream = \file_get_contents($jsonKey); + if (!($jsonKey = \json_decode((string) $jsonKeyStream, \true))) { + throw new \LogicException('invalid json for auth config'); + } + } + if (!\array_key_exists('client_email', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the client_email field'); + } + if (!\array_key_exists('private_key', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the private_key field'); + } + if (\array_key_exists('quota_project_id', $jsonKey)) { + $this->quotaProject = (string) $jsonKey['quota_project_id']; + } + if ($scope && $targetAudience) { + throw new InvalidArgumentException('Scope and targetAudience cannot both be supplied'); + } + $additionalClaims = []; + if ($targetAudience) { + $additionalClaims = ['target_audience' => $targetAudience]; + } + $this->auth = new OAuth2(['audience' => self::TOKEN_CREDENTIAL_URI, 'issuer' => $jsonKey['client_email'], 'scope' => $scope, 'signingAlgorithm' => 'RS256', 'signingKey' => $jsonKey['private_key'], 'sub' => $sub, 'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI, 'additionalClaims' => $additionalClaims]); + $this->projectId = $jsonKey['project_id'] ?? null; + $this->universeDomain = $jsonKey['universe_domain'] ?? self::DEFAULT_UNIVERSE_DOMAIN; + } + /** + * When called, the ServiceAccountCredentials will use an instance of + * ServiceAccountJwtAccessCredentials to fetch (self-sign) an access token + * even when only scopes are supplied. Otherwise, + * ServiceAccountJwtAccessCredentials is only called when no scopes and an + * authUrl (audience) is suppled. + * + * @return void + */ + public function useJwtAccessWithScope() + { + $this->useJwtAccessWithScope = \true; + } + /** + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_in + * @type string $token_type + * } + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + if ($this->useSelfSignedJwt()) { + $jwtCreds = $this->createJwtAccessCredentials(); + $accessToken = $jwtCreds->fetchAuthToken($httpHandler); + if ($lastReceivedToken = $jwtCreds->getLastReceivedToken()) { + // Keep self-signed JWTs in memory as the last received token + $this->lastReceivedJwtAccessToken = $lastReceivedToken; + } + return $accessToken; + } + return $this->auth->fetchAuthToken($httpHandler); + } + /** + * @return string + */ + public function getCacheKey() + { + $key = $this->auth->getIssuer() . ':' . $this->auth->getCacheKey(); + if ($sub = $this->auth->getSub()) { + $key .= ':' . $sub; + } + return $key; + } + /** + * @return array + */ + public function getLastReceivedToken() + { + // If self-signed JWTs are being used, fetch the last received token + // from memory. Else, fetch it from OAuth2 + return $this->useSelfSignedJwt() ? $this->lastReceivedJwtAccessToken : $this->auth->getLastReceivedToken(); + } + /** + * Get the project ID from the service account keyfile. + * + * Returns null if the project ID does not exist in the keyfile. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string|null + */ + public function getProjectId(?callable $httpHandler = null) + { + return $this->projectId; + } + /** + * Updates metadata with the authorization token. + * + * @param array $metadata metadata hashmap + * @param string $authUri optional auth uri + * @param callable $httpHandler callback which delivers psr7 request + * @return array updated metadata hashmap + */ + public function updateMetadata($metadata, $authUri = null, ?callable $httpHandler = null) + { + // scope exists. use oauth implementation + if (!$this->useSelfSignedJwt()) { + return parent::updateMetadata($metadata, $authUri, $httpHandler); + } + $jwtCreds = $this->createJwtAccessCredentials(); + if ($this->auth->getScope()) { + // Prefer user-provided "scope" to "audience" + $updatedMetadata = $jwtCreds->updateMetadata($metadata, null, $httpHandler); + } else { + $updatedMetadata = $jwtCreds->updateMetadata($metadata, $authUri, $httpHandler); + } + if ($lastReceivedToken = $jwtCreds->getLastReceivedToken()) { + // Keep self-signed JWTs in memory as the last received token + $this->lastReceivedJwtAccessToken = $lastReceivedToken; + } + return $updatedMetadata; + } + /** + * @return ServiceAccountJwtAccessCredentials + */ + private function createJwtAccessCredentials() + { + if (!$this->jwtAccessCredentials) { + // Create credentials for self-signing a JWT (JwtAccess) + $credJson = ['private_key' => $this->auth->getSigningKey(), 'client_email' => $this->auth->getIssuer()]; + $this->jwtAccessCredentials = new ServiceAccountJwtAccessCredentials($credJson, $this->auth->getScope()); + } + return $this->jwtAccessCredentials; + } + /** + * @param string $sub an email address account to impersonate, in situations when + * the service account has been delegated domain wide access. + * @return void + */ + public function setSub($sub) + { + $this->auth->setSub($sub); + } + /** + * Get the client name from the keyfile. + * + * In this case, it returns the keyfile's client_email key. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string + */ + public function getClientName(?callable $httpHandler = null) + { + return $this->auth->getIssuer(); + } + /** + * Get the quota project used for this API request + * + * @return string|null + */ + public function getQuotaProject() + { + return $this->quotaProject; + } + /** + * Get the universe domain configured in the JSON credential. + * + * @return string + */ + public function getUniverseDomain() : string + { + return $this->universeDomain; + } + /** + * @return bool + */ + private function useSelfSignedJwt() + { + // When a sub is supplied, the user is using domain-wide delegation, which not available + // with self-signed JWTs + if (null !== $this->auth->getSub()) { + // If we are outside the GDU, we can't use domain-wide delegation + if ($this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) { + throw new \LogicException(\sprintf('Service Account subject is configured for the credential. Domain-wide ' . 'delegation is not supported in universes other than %s.', self::DEFAULT_UNIVERSE_DOMAIN)); + } + return \false; + } + // If claims are set, this call is for "id_tokens" + if ($this->auth->getAdditionalClaims()) { + return \false; + } + // When true, ServiceAccountCredentials will always use JwtAccess for access tokens + if ($this->useJwtAccessWithScope) { + return \true; + } + // If the universe domain is outside the GDU, use JwtAccess for access tokens + if ($this->getUniverseDomain() !== self::DEFAULT_UNIVERSE_DOMAIN) { + return \true; + } + return \is_null($this->auth->getScope()); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php new file mode 100644 index 00000000..d9dff7fa --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/ServiceAccountJwtAccessCredentials.php @@ -0,0 +1,171 @@ + $jsonKey JSON credential file path or JSON credentials + * as an associative array + * @param string|string[] $scope the scope of the access request, expressed + * either as an Array or as a space-delimited String. + */ + public function __construct($jsonKey, $scope = null) + { + if (\is_string($jsonKey)) { + if (!\file_exists($jsonKey)) { + throw new \InvalidArgumentException('file does not exist'); + } + $jsonKeyStream = \file_get_contents($jsonKey); + if (!($jsonKey = \json_decode((string) $jsonKeyStream, \true))) { + throw new \LogicException('invalid json for auth config'); + } + } + if (!\array_key_exists('client_email', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the client_email field'); + } + if (!\array_key_exists('private_key', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the private_key field'); + } + if (\array_key_exists('quota_project_id', $jsonKey)) { + $this->quotaProject = (string) $jsonKey['quota_project_id']; + } + $this->auth = new OAuth2(['issuer' => $jsonKey['client_email'], 'sub' => $jsonKey['client_email'], 'signingAlgorithm' => 'RS256', 'signingKey' => $jsonKey['private_key'], 'scope' => $scope]); + $this->projectId = $jsonKey['project_id'] ?? null; + } + /** + * Updates metadata with the authorization token. + * + * @param array $metadata metadata hashmap + * @param string $authUri optional auth uri + * @param callable $httpHandler callback which delivers psr7 request + * @return array updated metadata hashmap + */ + public function updateMetadata($metadata, $authUri = null, ?callable $httpHandler = null) + { + $scope = $this->auth->getScope(); + if (empty($authUri) && empty($scope)) { + return $metadata; + } + $this->auth->setAudience($authUri); + return parent::updateMetadata($metadata, $authUri, $httpHandler); + } + /** + * Implements FetchAuthTokenInterface#fetchAuthToken. + * + * @param callable $httpHandler + * + * @return null|array{access_token:string} A set of auth related metadata + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + $audience = $this->auth->getAudience(); + $scope = $this->auth->getScope(); + if (empty($audience) && empty($scope)) { + return null; + } + if (!empty($audience) && !empty($scope)) { + throw new \UnexpectedValueException('Cannot sign both audience and scope in JwtAccess'); + } + $access_token = $this->auth->toJwt(); + // Set the self-signed access token in OAuth2 for getLastReceivedToken + $this->auth->setAccessToken($access_token); + return ['access_token' => $access_token, 'expires_in' => $this->auth->getExpiry(), 'token_type' => 'Bearer']; + } + /** + * @return string + */ + public function getCacheKey() + { + return $this->auth->getCacheKey(); + } + /** + * @return array + */ + public function getLastReceivedToken() + { + return $this->auth->getLastReceivedToken(); + } + /** + * Get the project ID from the service account keyfile. + * + * Returns null if the project ID does not exist in the keyfile. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string|null + */ + public function getProjectId(?callable $httpHandler = null) + { + return $this->projectId; + } + /** + * Get the client name from the keyfile. + * + * In this case, it returns the keyfile's client_email key. + * + * @param callable $httpHandler Not used by this credentials type. + * @return string + */ + public function getClientName(?callable $httpHandler = null) + { + return $this->auth->getIssuer(); + } + /** + * Get the quota project used for this API request + * + * @return string|null + */ + public function getQuotaProject() + { + return $this->quotaProject; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/UserRefreshCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/UserRefreshCredentials.php new file mode 100644 index 00000000..482c65cc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Credentials/UserRefreshCredentials.php @@ -0,0 +1,130 @@ + $jsonKey JSON credential file path or JSON credentials + * as an associative array + */ + public function __construct($scope, $jsonKey) + { + if (\is_string($jsonKey)) { + if (!\file_exists($jsonKey)) { + throw new \InvalidArgumentException('file does not exist'); + } + $json = \file_get_contents($jsonKey); + if (!($jsonKey = \json_decode((string) $json, \true))) { + throw new \LogicException('invalid json for auth config'); + } + } + if (!\array_key_exists('client_id', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the client_id field'); + } + if (!\array_key_exists('client_secret', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the client_secret field'); + } + if (!\array_key_exists('refresh_token', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the refresh_token field'); + } + $this->auth = new OAuth2(['clientId' => $jsonKey['client_id'], 'clientSecret' => $jsonKey['client_secret'], 'refresh_token' => $jsonKey['refresh_token'], 'scope' => $scope, 'tokenCredentialUri' => self::TOKEN_CREDENTIAL_URI]); + if (\array_key_exists('quota_project_id', $jsonKey)) { + $this->quotaProject = (string) $jsonKey['quota_project_id']; + } + } + /** + * @param callable $httpHandler + * + * @return array { + * A set of auth related metadata, containing the following + * + * @type string $access_token + * @type int $expires_in + * @type string $scope + * @type string $token_type + * @type string $id_token + * } + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + return $this->auth->fetchAuthToken($httpHandler); + } + /** + * @return string + */ + public function getCacheKey() + { + return $this->auth->getClientId() . ':' . $this->auth->getCacheKey(); + } + /** + * @return array + */ + public function getLastReceivedToken() + { + return $this->auth->getLastReceivedToken(); + } + /** + * Get the quota project used for this API request + * + * @return string|null + */ + public function getQuotaProject() + { + return $this->quotaProject; + } + /** + * Get the granted scopes (if they exist) for the last fetched token. + * + * @return string|null + */ + public function getGrantedScope() + { + return $this->auth->getGrantedScope(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialsLoader.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialsLoader.php new file mode 100644 index 00000000..0d4fea47 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/CredentialsLoader.php @@ -0,0 +1,242 @@ +|null JSON key | null + */ + public static function fromEnv() + { + $path = \getenv(self::ENV_VAR); + if (empty($path)) { + return null; + } + if (!\file_exists($path)) { + $cause = 'file ' . $path . ' does not exist'; + throw new \DomainException(self::unableToReadEnv($cause)); + } + $jsonKey = \file_get_contents($path); + return \json_decode((string) $jsonKey, \true); + } + /** + * Load a JSON key from a well known path. + * + * The well known path is OS dependent: + * + * * windows: %APPDATA%/gcloud/application_default_credentials.json + * * others: $HOME/.config/gcloud/application_default_credentials.json + * + * If the file does not exist, this returns null. + * + * @return array|null JSON key | null + */ + public static function fromWellKnownFile() + { + $rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME'; + $path = [\getenv($rootEnv)]; + if (!self::isOnWindows()) { + $path[] = self::NON_WINDOWS_WELL_KNOWN_PATH_BASE; + } + $path[] = self::WELL_KNOWN_PATH; + $path = \implode(\DIRECTORY_SEPARATOR, $path); + if (!\file_exists($path)) { + return null; + } + $jsonKey = \file_get_contents($path); + return \json_decode((string) $jsonKey, \true); + } + /** + * Create a new Credentials instance. + * + * @param string|string[] $scope the scope of the access request, expressed + * either as an Array or as a space-delimited String. + * @param array $jsonKey the JSON credentials. + * @param string|string[] $defaultScope The default scope to use if no + * user-defined scopes exist, expressed either as an Array or as a + * space-delimited string. + * + * @return ServiceAccountCredentials|UserRefreshCredentials|ImpersonatedServiceAccountCredentials|ExternalAccountCredentials + */ + public static function makeCredentials($scope, array $jsonKey, $defaultScope = null) + { + if (!\array_key_exists('type', $jsonKey)) { + throw new \InvalidArgumentException('json key is missing the type field'); + } + if ($jsonKey['type'] == 'service_account') { + // Do not pass $defaultScope to ServiceAccountCredentials + return new ServiceAccountCredentials($scope, $jsonKey); + } + if ($jsonKey['type'] == 'authorized_user') { + $anyScope = $scope ?: $defaultScope; + return new UserRefreshCredentials($anyScope, $jsonKey); + } + if ($jsonKey['type'] == 'impersonated_service_account') { + $anyScope = $scope ?: $defaultScope; + return new ImpersonatedServiceAccountCredentials($anyScope, $jsonKey); + } + if ($jsonKey['type'] == 'external_account') { + $anyScope = $scope ?: $defaultScope; + return new ExternalAccountCredentials($anyScope, $jsonKey); + } + throw new \InvalidArgumentException('invalid value in the type field'); + } + /** + * Create an authorized HTTP Client from an instance of FetchAuthTokenInterface. + * + * @param FetchAuthTokenInterface $fetcher is used to fetch the auth token + * @param array $httpClientOptions (optional) Array of request options to apply. + * @param callable $httpHandler (optional) http client to fetch the token. + * @param callable $tokenCallback (optional) function to be called when a new token is fetched. + * @return \GuzzleHttp\Client + */ + public static function makeHttpClient(FetchAuthTokenInterface $fetcher, array $httpClientOptions = [], ?callable $httpHandler = null, ?callable $tokenCallback = null) + { + $middleware = new Middleware\AuthTokenMiddleware($fetcher, $httpHandler, $tokenCallback); + $stack = \WPMailSMTP\Vendor\GuzzleHttp\HandlerStack::create(); + $stack->push($middleware); + return new \WPMailSMTP\Vendor\GuzzleHttp\Client(['handler' => $stack, 'auth' => 'google_auth'] + $httpClientOptions); + } + /** + * Create a new instance of InsecureCredentials. + * + * @return InsecureCredentials + */ + public static function makeInsecureCredentials() + { + return new InsecureCredentials(); + } + /** + * Fetch a quota project from the environment variable + * GOOGLE_CLOUD_QUOTA_PROJECT. Return null if + * GOOGLE_CLOUD_QUOTA_PROJECT is not specified. + * + * @return string|null + */ + public static function quotaProjectFromEnv() + { + return \getenv(self::QUOTA_PROJECT_ENV_VAR) ?: null; + } + /** + * Gets a callable which returns the default device certification. + * + * @throws UnexpectedValueException + * @return callable|null + */ + public static function getDefaultClientCertSource() + { + if (!($clientCertSourceJson = self::loadDefaultClientCertSourceFile())) { + return null; + } + $clientCertSourceCmd = $clientCertSourceJson['cert_provider_command']; + return function () use($clientCertSourceCmd) { + $cmd = \array_map('escapeshellarg', $clientCertSourceCmd); + \exec(\implode(' ', $cmd), $output, $returnVar); + if (0 === $returnVar) { + return \implode(\PHP_EOL, $output); + } + throw new RuntimeException('"cert_provider_command" failed with a nonzero exit code'); + }; + } + /** + * Determines whether or not the default device certificate should be loaded. + * + * @return bool + */ + public static function shouldLoadClientCertSource() + { + return \filter_var(\getenv(self::MTLS_CERT_ENV_VAR), \FILTER_VALIDATE_BOOLEAN); + } + /** + * @return array{cert_provider_command:string[]}|null + */ + private static function loadDefaultClientCertSourceFile() + { + $rootEnv = self::isOnWindows() ? 'APPDATA' : 'HOME'; + $path = \sprintf('%s/%s', \getenv($rootEnv), self::MTLS_WELL_KNOWN_PATH); + if (!\file_exists($path)) { + return null; + } + $jsonKey = \file_get_contents($path); + $clientCertSourceJson = \json_decode((string) $jsonKey, \true); + if (!$clientCertSourceJson) { + throw new UnexpectedValueException('Invalid client cert source JSON'); + } + if (!isset($clientCertSourceJson['cert_provider_command'])) { + throw new UnexpectedValueException('cert source requires "cert_provider_command"'); + } + if (!\is_array($clientCertSourceJson['cert_provider_command'])) { + throw new UnexpectedValueException('cert source expects "cert_provider_command" to be an array'); + } + return $clientCertSourceJson; + } + /** + * Get the universe domain from the credential. Defaults to "googleapis.com" + * for all credential types which do not support universe domain. + * + * @return string + */ + public function getUniverseDomain() : string + { + return self::DEFAULT_UNIVERSE_DOMAIN; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ExternalAccountCredentialSourceInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ExternalAccountCredentialSourceInterface.php new file mode 100644 index 00000000..d47171c3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ExternalAccountCredentialSourceInterface.php @@ -0,0 +1,23 @@ + $cacheConfig Configuration for the cache + * @param CacheItemPoolInterface $cache + */ + public function __construct(FetchAuthTokenInterface $fetcher, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $this->fetcher = $fetcher; + $this->cache = $cache; + $this->cacheConfig = \array_merge(['lifetime' => 1500, 'prefix' => '', 'cacheUniverseDomain' => $fetcher instanceof Credentials\GCECredentials], (array) $cacheConfig); + } + /** + * @return FetchAuthTokenInterface + */ + public function getFetcher() + { + return $this->fetcher; + } + /** + * Implements FetchAuthTokenInterface#fetchAuthToken. + * + * Checks the cache for a valid auth token and fetches the auth tokens + * from the supplied fetcher. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return array the response + * @throws \Exception + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + if ($cached = $this->fetchAuthTokenFromCache()) { + return $cached; + } + $auth_token = $this->fetcher->fetchAuthToken($httpHandler); + $this->saveAuthTokenInCache($auth_token); + return $auth_token; + } + /** + * @return string + */ + public function getCacheKey() + { + return $this->getFullCacheKey($this->fetcher->getCacheKey()); + } + /** + * @return array|null + */ + public function getLastReceivedToken() + { + return $this->fetcher->getLastReceivedToken(); + } + /** + * Get the client name from the fetcher. + * + * @param callable $httpHandler An HTTP handler to deliver PSR7 requests. + * @return string + */ + public function getClientName(?callable $httpHandler = null) + { + if (!$this->fetcher instanceof SignBlobInterface) { + throw new \RuntimeException('Credentials fetcher does not implement ' . 'Google\\Auth\\SignBlobInterface'); + } + return $this->fetcher->getClientName($httpHandler); + } + /** + * Sign a blob using the fetcher. + * + * @param string $stringToSign The string to sign. + * @param bool $forceOpenSsl Require use of OpenSSL for local signing. Does + * not apply to signing done using external services. **Defaults to** + * `false`. + * @return string The resulting signature. + * @throws \RuntimeException If the fetcher does not implement + * `Google\Auth\SignBlobInterface`. + */ + public function signBlob($stringToSign, $forceOpenSsl = \false) + { + if (!$this->fetcher instanceof SignBlobInterface) { + throw new \RuntimeException('Credentials fetcher does not implement ' . 'Google\\Auth\\SignBlobInterface'); + } + // Pass the access token from cache for credentials that sign blobs + // using the IAM API. This saves a call to fetch an access token when a + // cached token exists. + if ($this->fetcher instanceof Credentials\GCECredentials || $this->fetcher instanceof Credentials\ImpersonatedServiceAccountCredentials) { + $cached = $this->fetchAuthTokenFromCache(); + $accessToken = $cached['access_token'] ?? null; + return $this->fetcher->signBlob($stringToSign, $forceOpenSsl, $accessToken); + } + return $this->fetcher->signBlob($stringToSign, $forceOpenSsl); + } + /** + * Get the quota project used for this API request from the credentials + * fetcher. + * + * @return string|null + */ + public function getQuotaProject() + { + if ($this->fetcher instanceof GetQuotaProjectInterface) { + return $this->fetcher->getQuotaProject(); + } + return null; + } + /* + * Get the Project ID from the fetcher. + * + * @param callable $httpHandler Callback which delivers psr7 request + * @return string|null + * @throws \RuntimeException If the fetcher does not implement + * `Google\Auth\ProvidesProjectIdInterface`. + */ + public function getProjectId(?callable $httpHandler = null) + { + if (!$this->fetcher instanceof ProjectIdProviderInterface) { + throw new \RuntimeException('Credentials fetcher does not implement ' . 'Google\\Auth\\ProvidesProjectIdInterface'); + } + // Pass the access token from cache for credentials that require an + // access token to fetch the project ID. This saves a call to fetch an + // access token when a cached token exists. + if ($this->fetcher instanceof Credentials\ExternalAccountCredentials) { + $cached = $this->fetchAuthTokenFromCache(); + $accessToken = $cached['access_token'] ?? null; + return $this->fetcher->getProjectId($httpHandler, $accessToken); + } + return $this->fetcher->getProjectId($httpHandler); + } + /* + * Get the Universe Domain from the fetcher. + * + * @return string + */ + public function getUniverseDomain() : string + { + if ($this->fetcher instanceof GetUniverseDomainInterface) { + if ($this->cacheConfig['cacheUniverseDomain']) { + return $this->getCachedUniverseDomain($this->fetcher); + } + return $this->fetcher->getUniverseDomain(); + } + return GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN; + } + /** + * Updates metadata with the authorization token. + * + * @param array $metadata metadata hashmap + * @param string $authUri optional auth uri + * @param callable $httpHandler callback which delivers psr7 request + * @return array updated metadata hashmap + * @throws \RuntimeException If the fetcher does not implement + * `Google\Auth\UpdateMetadataInterface`. + */ + public function updateMetadata($metadata, $authUri = null, ?callable $httpHandler = null) + { + if (!$this->fetcher instanceof UpdateMetadataInterface) { + throw new \RuntimeException('Credentials fetcher does not implement ' . 'Google\\Auth\\UpdateMetadataInterface'); + } + $cached = $this->fetchAuthTokenFromCache($authUri); + if ($cached) { + // Set the access token in the `Authorization` metadata header so + // the downstream call to updateMetadata know they don't need to + // fetch another token. + if (isset($cached['access_token'])) { + $metadata[self::AUTH_METADATA_KEY] = ['Bearer ' . $cached['access_token']]; + } elseif (isset($cached['id_token'])) { + $metadata[self::AUTH_METADATA_KEY] = ['Bearer ' . $cached['id_token']]; + } + } + $newMetadata = $this->fetcher->updateMetadata($metadata, $authUri, $httpHandler); + if (!$cached && ($token = $this->fetcher->getLastReceivedToken())) { + $this->saveAuthTokenInCache($token, $authUri); + } + return $newMetadata; + } + /** + * @param string|null $authUri + * @return array|null + */ + private function fetchAuthTokenFromCache($authUri = null) + { + // Use the cached value if its available. + // + // TODO: correct caching; update the call to setCachedValue to set the expiry + // to the value returned with the auth token. + // + // TODO: correct caching; enable the cache to be cleared. + // if $authUri is set, use it as the cache key + $cacheKey = $authUri ? $this->getFullCacheKey($authUri) : $this->fetcher->getCacheKey(); + $cached = $this->getCachedValue($cacheKey); + if (\is_array($cached)) { + if (empty($cached['expires_at'])) { + // If there is no expiration data, assume token is not expired. + // (for JwtAccess and ID tokens) + return $cached; + } + if (\time() + $this->eagerRefreshThresholdSeconds < $cached['expires_at']) { + // access token is not expired + return $cached; + } + } + return null; + } + /** + * @param array $authToken + * @param string|null $authUri + * @return void + */ + private function saveAuthTokenInCache($authToken, $authUri = null) + { + if (isset($authToken['access_token']) || isset($authToken['id_token'])) { + // if $authUri is set, use it as the cache key + $cacheKey = $authUri ? $this->getFullCacheKey($authUri) : $this->fetcher->getCacheKey(); + $this->setCachedValue($cacheKey, $authToken); + } + } + private function getCachedUniverseDomain(GetUniverseDomainInterface $fetcher) : string + { + $cacheKey = $this->getFullCacheKey($fetcher->getCacheKey() . 'universe_domain'); + // @phpstan-ignore-line + if ($universeDomain = $this->getCachedValue($cacheKey)) { + return $universeDomain; + } + $universeDomain = $fetcher->getUniverseDomain(); + $this->setCachedValue($cacheKey, $universeDomain); + return $universeDomain; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/FetchAuthTokenInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/FetchAuthTokenInterface.php new file mode 100644 index 00000000..6b74dd50 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/FetchAuthTokenInterface.php @@ -0,0 +1,52 @@ + a hash of auth tokens + */ + public function fetchAuthToken(?callable $httpHandler = null); + /** + * Obtains a key that can used to cache the results of #fetchAuthToken. + * + * If the value is empty, the auth token is not cached. + * + * @return string a key that may be used to cache the auth token. + */ + public function getCacheKey(); + /** + * Returns an associative array with the token and + * expiration time. + * + * @return null|array { + * The last received access token. + * + * @type string $access_token The access token string. + * @type int $expires_at The time the token expires as a UNIX timestamp. + * } + */ + public function getLastReceivedToken(); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GCECache.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GCECache.php new file mode 100644 index 00000000..006c3b7d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GCECache.php @@ -0,0 +1,70 @@ + $cacheConfig Configuration for the cache + * @param CacheItemPoolInterface $cache + */ + public function __construct(?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $this->cache = $cache; + $this->cacheConfig = \array_merge(['lifetime' => 1500, 'prefix' => ''], (array) $cacheConfig); + } + /** + * Caches the result of onGce so the metadata server is not called multiple + * times. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return bool True if this a GCEInstance, false otherwise + */ + public function onGce(?callable $httpHandler = null) + { + if (\is_null($this->cache)) { + return GCECredentials::onGce($httpHandler); + } + $cacheKey = self::GCE_CACHE_KEY; + $onGce = $this->getCachedValue($cacheKey); + if (\is_null($onGce)) { + $onGce = GCECredentials::onGce($httpHandler); + $this->setCachedValue($cacheKey, $onGce); + } + return $onGce; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GetQuotaProjectInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GetQuotaProjectInterface.php new file mode 100644 index 00000000..da6c3327 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/GetQuotaProjectInterface.php @@ -0,0 +1,32 @@ +client = $client; + } + /** + * Accepts a PSR-7 request and an array of options and returns a PSR-7 response. + * + * @param RequestInterface $request + * @param array $options + * @return ResponseInterface + */ + public function __invoke(RequestInterface $request, array $options = []) + { + return $this->client->send($request, $options); + } + /** + * Accepts a PSR-7 request and an array of options and returns a PromiseInterface + * + * @param RequestInterface $request + * @param array $options + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function async(RequestInterface $request, array $options = []) + { + return $this->client->sendAsync($request, $options); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/Guzzle7HttpHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/Guzzle7HttpHandler.php new file mode 100644 index 00000000..5a2b7ea9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/HttpHandler/Guzzle7HttpHandler.php @@ -0,0 +1,22 @@ +remove('http_errors'); + $stack->unshift(Middleware::httpErrors($bodySummarizer), 'http_errors'); + } + $client = new Client(['handler' => $stack]); + } + $version = null; + if (\defined('WPMailSMTP\\Vendor\\GuzzleHttp\\ClientInterface::MAJOR_VERSION')) { + $version = ClientInterface::MAJOR_VERSION; + } elseif (\defined('WPMailSMTP\\Vendor\\GuzzleHttp\\ClientInterface::VERSION')) { + $version = (int) \substr(ClientInterface::VERSION, 0, 1); + } + switch ($version) { + case 6: + return new Guzzle6HttpHandler($client); + case 7: + return new Guzzle7HttpHandler($client); + default: + throw new \Exception('Version not supported'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Iam.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Iam.php new file mode 100644 index 00000000..3816027a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Iam.php @@ -0,0 +1,86 @@ +httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + $this->universeDomain = $universeDomain; + } + /** + * Sign a string using the IAM signBlob API. + * + * Note that signing using IAM requires your service account to have the + * `iam.serviceAccounts.signBlob` permission, part of the "Service Account + * Token Creator" IAM role. + * + * @param string $email The service account email. + * @param string $accessToken An access token from the service account. + * @param string $stringToSign The string to be signed. + * @param array $delegates [optional] A list of service account emails to + * add to the delegate chain. If omitted, the value of `$email` will + * be used. + * @return string The signed string, base64-encoded. + */ + public function signBlob($email, $accessToken, $stringToSign, array $delegates = []) + { + $httpHandler = $this->httpHandler; + $name = \sprintf(self::SERVICE_ACCOUNT_NAME, $email); + $apiRoot = \str_replace('UNIVERSE_DOMAIN', $this->universeDomain, self::IAM_API_ROOT_TEMPLATE); + $uri = $apiRoot . '/' . \sprintf(self::SIGN_BLOB_PATH, $name); + if ($delegates) { + foreach ($delegates as &$delegate) { + $delegate = \sprintf(self::SERVICE_ACCOUNT_NAME, $delegate); + } + } else { + $delegates = [$name]; + } + $body = ['delegates' => $delegates, 'payload' => \base64_encode($stringToSign)]; + $headers = ['Authorization' => 'Bearer ' . $accessToken]; + $request = new Psr7\Request('POST', $uri, $headers, Utils::streamFor(\json_encode($body))); + $res = $httpHandler($request); + $body = \json_decode((string) $res->getBody(), \true); + return $body['signedBlob']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/IamSignerTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/IamSignerTrait.php new file mode 100644 index 00000000..9b60029b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/IamSignerTrait.php @@ -0,0 +1,61 @@ +iam; + if (!$signer) { + $signer = $this instanceof GetUniverseDomainInterface ? new Iam($httpHandler, $this->getUniverseDomain()) : new Iam($httpHandler); + } + $email = $this->getClientName($httpHandler); + if (\is_null($accessToken)) { + $previousToken = $this->getLastReceivedToken(); + $accessToken = $previousToken ? $previousToken['access_token'] : $this->fetchAuthToken($httpHandler)['access_token']; + } + return $signer->signBlob($email, $accessToken, $stringToSign); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/AuthTokenMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/AuthTokenMiddleware.php new file mode 100644 index 00000000..d43cb116 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/AuthTokenMiddleware.php @@ -0,0 +1,138 @@ +' + */ +class AuthTokenMiddleware +{ + /** + * @var callable + */ + private $httpHandler; + /** + * It must be an implementation of FetchAuthTokenInterface. + * It may also implement UpdateMetadataInterface allowing direct + * retrieval of auth related headers + * @var FetchAuthTokenInterface + */ + private $fetcher; + /** + * @var ?callable + */ + private $tokenCallback; + /** + * Creates a new AuthTokenMiddleware. + * + * @param FetchAuthTokenInterface $fetcher is used to fetch the auth token + * @param callable $httpHandler (optional) callback which delivers psr7 request + * @param callable $tokenCallback (optional) function to be called when a new token is fetched. + */ + public function __construct(FetchAuthTokenInterface $fetcher, ?callable $httpHandler = null, ?callable $tokenCallback = null) + { + $this->fetcher = $fetcher; + $this->httpHandler = $httpHandler; + $this->tokenCallback = $tokenCallback; + } + /** + * Updates the request with an Authorization header when auth is 'google_auth'. + * + * use Google\Auth\Middleware\AuthTokenMiddleware; + * use Google\Auth\OAuth2; + * use GuzzleHttp\Client; + * use GuzzleHttp\HandlerStack; + * + * $config = [...]; + * $oauth2 = new OAuth2($config) + * $middleware = new AuthTokenMiddleware($oauth2); + * $stack = HandlerStack::create(); + * $stack->push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', + * 'auth' => 'google_auth' // authorize all requests + * ]); + * + * $res = $client->get('myproject/taskqueues/myqueue'); + * + * @param callable $handler + * @return \Closure + */ + public function __invoke(callable $handler) + { + return function (RequestInterface $request, array $options) use($handler) { + // Requests using "auth"="google_auth" will be authorized. + if (!isset($options['auth']) || $options['auth'] !== 'google_auth') { + return $handler($request, $options); + } + $request = $this->addAuthHeaders($request); + if ($quotaProject = $this->getQuotaProject()) { + $request = $request->withHeader(GetQuotaProjectInterface::X_GOOG_USER_PROJECT_HEADER, $quotaProject); + } + return $handler($request, $options); + }; + } + /** + * Adds auth related headers to the request. + * + * @param RequestInterface $request + * @return RequestInterface + */ + private function addAuthHeaders(RequestInterface $request) + { + if (!$this->fetcher instanceof UpdateMetadataInterface || $this->fetcher instanceof FetchAuthTokenCache && !$this->fetcher->getFetcher() instanceof UpdateMetadataInterface) { + $token = $this->fetcher->fetchAuthToken(); + $request = $request->withHeader('authorization', 'Bearer ' . ($token['access_token'] ?? $token['id_token'] ?? '')); + } else { + $headers = $this->fetcher->updateMetadata($request->getHeaders(), null, $this->httpHandler); + $request = Utils::modifyRequest($request, ['set_headers' => $headers]); + } + if ($this->tokenCallback && ($token = $this->fetcher->getLastReceivedToken())) { + if (\array_key_exists('access_token', $token)) { + \call_user_func($this->tokenCallback, $this->fetcher->getCacheKey(), $token['access_token']); + } + } + return $request; + } + /** + * @return string|null + */ + private function getQuotaProject() + { + if ($this->fetcher instanceof GetQuotaProjectInterface) { + return $this->fetcher->getQuotaProject(); + } + return null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php new file mode 100644 index 00000000..46686177 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ProxyAuthTokenMiddleware.php @@ -0,0 +1,130 @@ +' + */ +class ProxyAuthTokenMiddleware +{ + /** + * @var callable + */ + private $httpHandler; + /** + * @var FetchAuthTokenInterface + */ + private $fetcher; + /** + * @var ?callable + */ + private $tokenCallback; + /** + * Creates a new ProxyAuthTokenMiddleware. + * + * @param FetchAuthTokenInterface $fetcher is used to fetch the auth token + * @param callable $httpHandler (optional) callback which delivers psr7 request + * @param callable $tokenCallback (optional) function to be called when a new token is fetched. + */ + public function __construct(FetchAuthTokenInterface $fetcher, ?callable $httpHandler = null, ?callable $tokenCallback = null) + { + $this->fetcher = $fetcher; + $this->httpHandler = $httpHandler; + $this->tokenCallback = $tokenCallback; + } + /** + * Updates the request with an Authorization header when auth is 'google_auth'. + * + * use Google\Auth\Middleware\ProxyAuthTokenMiddleware; + * use Google\Auth\OAuth2; + * use GuzzleHttp\Client; + * use GuzzleHttp\HandlerStack; + * + * $config = [...]; + * $oauth2 = new OAuth2($config) + * $middleware = new ProxyAuthTokenMiddleware($oauth2); + * $stack = HandlerStack::create(); + * $stack->push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', + * 'proxy_auth' => 'google_auth' // authorize all requests + * ]); + * + * $res = $client->get('myproject/taskqueues/myqueue'); + * + * @param callable $handler + * @return \Closure + */ + public function __invoke(callable $handler) + { + return function (RequestInterface $request, array $options) use($handler) { + // Requests using "proxy_auth"="google_auth" will be authorized. + if (!isset($options['proxy_auth']) || $options['proxy_auth'] !== 'google_auth') { + return $handler($request, $options); + } + $request = $request->withHeader('proxy-authorization', 'Bearer ' . $this->fetchToken()); + if ($quotaProject = $this->getQuotaProject()) { + $request = $request->withHeader(GetQuotaProjectInterface::X_GOOG_USER_PROJECT_HEADER, $quotaProject); + } + return $handler($request, $options); + }; + } + /** + * Call fetcher to fetch the token. + * + * @return string|null + */ + private function fetchToken() + { + $auth_tokens = $this->fetcher->fetchAuthToken($this->httpHandler); + if (\array_key_exists('access_token', $auth_tokens)) { + // notify the callback if applicable + if ($this->tokenCallback) { + \call_user_func($this->tokenCallback, $this->fetcher->getCacheKey(), $auth_tokens['access_token']); + } + return $auth_tokens['access_token']; + } + if (\array_key_exists('id_token', $auth_tokens)) { + return $auth_tokens['id_token']; + } + return null; + } + /** + * @return string|null; + */ + private function getQuotaProject() + { + if ($this->fetcher instanceof GetQuotaProjectInterface) { + return $this->fetcher->getQuotaProject(); + } + return null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php new file mode 100644 index 00000000..c8e1391b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/ScopedAccessTokenMiddleware.php @@ -0,0 +1,140 @@ +' + */ +class ScopedAccessTokenMiddleware +{ + use CacheTrait; + const DEFAULT_CACHE_LIFETIME = 1500; + /** + * @var callable + */ + private $tokenFunc; + /** + * @var array|string + */ + private $scopes; + /** + * Creates a new ScopedAccessTokenMiddleware. + * + * @param callable $tokenFunc a token generator function + * @param array|string $scopes the token authentication scopes + * @param array $cacheConfig configuration for the cache when it's present + * @param CacheItemPoolInterface $cache an implementation of CacheItemPoolInterface + */ + public function __construct(callable $tokenFunc, $scopes, ?array $cacheConfig = null, ?CacheItemPoolInterface $cache = null) + { + $this->tokenFunc = $tokenFunc; + if (!(\is_string($scopes) || \is_array($scopes))) { + throw new \InvalidArgumentException('wants scope should be string or array'); + } + $this->scopes = $scopes; + if (!\is_null($cache)) { + $this->cache = $cache; + $this->cacheConfig = \array_merge(['lifetime' => self::DEFAULT_CACHE_LIFETIME, 'prefix' => ''], $cacheConfig); + } + } + /** + * Updates the request with an Authorization header when auth is 'scoped'. + * + * E.g this could be used to authenticate using the AppEngine + * AppIdentityService. + * + * use google\appengine\api\app_identity\AppIdentityService; + * use Google\Auth\Middleware\ScopedAccessTokenMiddleware; + * use GuzzleHttp\Client; + * use GuzzleHttp\HandlerStack; + * + * $scope = 'https://www.googleapis.com/auth/taskqueue' + * $middleware = new ScopedAccessTokenMiddleware( + * 'AppIdentityService::getAccessToken', + * $scope, + * [ 'prefix' => 'Google\Auth\ScopedAccessToken::' ], + * $cache = new Memcache() + * ); + * $stack = HandlerStack::create(); + * $stack->push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_url' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/', + * 'auth' => 'scoped' // authorize all requests + * ]); + * + * $res = $client->get('myproject/taskqueues/myqueue'); + * + * @param callable $handler + * @return \Closure + */ + public function __invoke(callable $handler) + { + return function (RequestInterface $request, array $options) use($handler) { + // Requests using "auth"="scoped" will be authorized. + if (!isset($options['auth']) || $options['auth'] !== 'scoped') { + return $handler($request, $options); + } + $request = $request->withHeader('authorization', 'Bearer ' . $this->fetchToken()); + return $handler($request, $options); + }; + } + /** + * @return string + */ + private function getCacheKey() + { + $key = null; + if (\is_string($this->scopes)) { + $key .= $this->scopes; + } elseif (\is_array($this->scopes)) { + $key .= \implode(':', $this->scopes); + } + return $key; + } + /** + * Determine if token is available in the cache, if not call tokenFunc to + * fetch it. + * + * @return string + */ + private function fetchToken() + { + $cacheKey = $this->getCacheKey(); + $cached = $this->getCachedValue($cacheKey); + if (!empty($cached)) { + return $cached; + } + $token = \call_user_func($this->tokenFunc, $this->scopes); + $this->setCachedValue($cacheKey, $token); + return $token; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/SimpleMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/SimpleMiddleware.php new file mode 100644 index 00000000..c7d83129 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/Middleware/SimpleMiddleware.php @@ -0,0 +1,86 @@ + + */ + private $config; + /** + * Create a new Simple plugin. + * + * The configuration array expects one option + * - key: required, otherwise InvalidArgumentException is thrown + * + * @param array $config Configuration array + */ + public function __construct(array $config) + { + if (!isset($config['key'])) { + throw new \InvalidArgumentException('requires a key to have been set'); + } + $this->config = \array_merge(['key' => null], $config); + } + /** + * Updates the request query with the developer key if auth is set to simple. + * + * use Google\Auth\Middleware\SimpleMiddleware; + * use GuzzleHttp\Client; + * use GuzzleHttp\HandlerStack; + * + * $my_key = 'is not the same as yours'; + * $middleware = new SimpleMiddleware(['key' => $my_key]); + * $stack = HandlerStack::create(); + * $stack->push($middleware); + * + * $client = new Client([ + * 'handler' => $stack, + * 'base_uri' => 'https://www.googleapis.com/discovery/v1/', + * 'auth' => 'simple' + * ]); + * + * $res = $client->get('drive/v2/rest'); + * + * @param callable $handler + * @return \Closure + */ + public function __invoke(callable $handler) + { + return function (RequestInterface $request, array $options) use($handler) { + // Requests using "auth"="scoped" will be authorized. + if (!isset($options['auth']) || $options['auth'] !== 'simple') { + return $handler($request, $options); + } + $query = Query::parse($request->getUri()->getQuery()); + $params = \array_merge($query, $this->config); + $uri = $request->getUri()->withQuery(Query::build($params)); + $request = $request->withUri($uri); + return $handler($request, $options); + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/OAuth2.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/OAuth2.php new file mode 100644 index 00000000..27041922 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/OAuth2.php @@ -0,0 +1,1520 @@ + + */ + public static $knownSigningAlgorithms = ['HS256', 'HS512', 'HS384', 'RS256']; + /** + * The well known grant types. + * + * @var array + */ + public static $knownGrantTypes = ['authorization_code', 'refresh_token', 'password', 'client_credentials']; + /** + * - authorizationUri + * The authorization server's HTTP endpoint capable of + * authenticating the end-user and obtaining authorization. + * + * @var ?UriInterface + */ + private $authorizationUri; + /** + * - tokenCredentialUri + * The authorization server's HTTP endpoint capable of issuing + * tokens and refreshing expired tokens. + * + * @var UriInterface + */ + private $tokenCredentialUri; + /** + * The redirection URI used in the initial request. + * + * @var ?string + */ + private $redirectUri; + /** + * A unique identifier issued to the client to identify itself to the + * authorization server. + * + * @var string + */ + private $clientId; + /** + * A shared symmetric secret issued by the authorization server, which is + * used to authenticate the client. + * + * @var string + */ + private $clientSecret; + /** + * The resource owner's username. + * + * @var ?string + */ + private $username; + /** + * The resource owner's password. + * + * @var ?string + */ + private $password; + /** + * The scope of the access request, expressed either as an Array or as a + * space-delimited string. + * + * @var ?array + */ + private $scope; + /** + * An arbitrary string designed to allow the client to maintain state. + * + * @var string + */ + private $state; + /** + * The authorization code issued to this client. + * + * Only used by the authorization code access grant type. + * + * @var ?string + */ + private $code; + /** + * The issuer ID when using assertion profile. + * + * @var ?string + */ + private $issuer; + /** + * The target audience for assertions. + * + * @var string + */ + private $audience; + /** + * The target sub when issuing assertions. + * + * @var string + */ + private $sub; + /** + * The number of seconds assertions are valid for. + * + * @var int + */ + private $expiry; + /** + * The signing key when using assertion profile. + * + * @var ?string + */ + private $signingKey; + /** + * The signing key id when using assertion profile. Param kid in jwt header + * + * @var string + */ + private $signingKeyId; + /** + * The signing algorithm when using an assertion profile. + * + * @var ?string + */ + private $signingAlgorithm; + /** + * The refresh token associated with the access token to be refreshed. + * + * @var ?string + */ + private $refreshToken; + /** + * The current access token. + * + * @var string + */ + private $accessToken; + /** + * The current ID token. + * + * @var string + */ + private $idToken; + /** + * The scopes granted to the current access token + * + * @var string + */ + private $grantedScope; + /** + * The lifetime in seconds of the current access token. + * + * @var ?int + */ + private $expiresIn; + /** + * The expiration time of the access token as a number of seconds since the + * unix epoch. + * + * @var ?int + */ + private $expiresAt; + /** + * The issue time of the access token as a number of seconds since the unix + * epoch. + * + * @var ?int + */ + private $issuedAt; + /** + * The current grant type. + * + * @var ?string + */ + private $grantType; + /** + * When using an extension grant type, this is the set of parameters used by + * that extension. + * + * @var array + */ + private $extensionParams; + /** + * When using the toJwt function, these claims will be added to the JWT + * payload. + * + * @var array + */ + private $additionalClaims; + /** + * The code verifier for PKCE for OAuth 2.0. When set, the authorization + * URI will contain the Code Challenge and Code Challenge Method querystring + * parameters, and the token URI will contain the Code Verifier parameter. + * + * @see https://datatracker.ietf.org/doc/html/rfc7636 + * @var ?string + */ + private $codeVerifier; + /** + * For STS requests. + * A URI that indicates the target service or resource where the client + * intends to use the requested security token. + */ + private ?string $resource; + /** + * For STS requests. + * A fetcher for the "subject_token", which is a security token that + * represents the identity of the party on behalf of whom the request is + * being made. + */ + private ?ExternalAccountCredentialSourceInterface $subjectTokenFetcher; + /** + * For STS requests. + * An identifier, that indicates the type of the security token in the + * subjectToken parameter. + */ + private ?string $subjectTokenType; + /** + * For STS requests. + * A security token that represents the identity of the acting party. + */ + private ?string $actorToken; + /** + * For STS requests. + * An identifier that indicates the type of the security token in the + * actorToken parameter. + */ + private ?string $actorTokenType; + /** + * From STS response. + * An identifier for the representation of the issued security token. + */ + private ?string $issuedTokenType = null; + /** + * From STS response. + * An identifier for the representation of the issued security token. + * + * @var array + */ + private array $additionalOptions; + /** + * Create a new OAuthCredentials. + * + * The configuration array accepts various options + * + * - authorizationUri + * The authorization server's HTTP endpoint capable of + * authenticating the end-user and obtaining authorization. + * + * - tokenCredentialUri + * The authorization server's HTTP endpoint capable of issuing + * tokens and refreshing expired tokens. + * + * - clientId + * A unique identifier issued to the client to identify itself to the + * authorization server. + * + * - clientSecret + * A shared symmetric secret issued by the authorization server, + * which is used to authenticate the client. + * + * - scope + * The scope of the access request, expressed either as an Array + * or as a space-delimited String. + * + * - state + * An arbitrary string designed to allow the client to maintain state. + * + * - redirectUri + * The redirection URI used in the initial request. + * + * - username + * The resource owner's username. + * + * - password + * The resource owner's password. + * + * - issuer + * Issuer ID when using assertion profile + * + * - audience + * Target audience for assertions + * + * - expiry + * Number of seconds assertions are valid for + * + * - signingKey + * Signing key when using assertion profile + * + * - signingKeyId + * Signing key id when using assertion profile + * + * - refreshToken + * The refresh token associated with the access token + * to be refreshed. + * + * - accessToken + * The current access token for this client. + * + * - idToken + * The current ID token for this client. + * + * - extensionParams + * When using an extension grant type, this is the set of parameters used + * by that extension. + * + * - codeVerifier + * The code verifier for PKCE for OAuth 2.0. + * + * - resource + * The target service or resource where the client ntends to use the + * requested security token. + * + * - subjectTokenFetcher + * A fetcher for the "subject_token", which is a security token that + * represents the identity of the party on behalf of whom the request is + * being made. + * + * - subjectTokenType + * An identifier that indicates the type of the security token in the + * subjectToken parameter. + * + * - actorToken + * A security token that represents the identity of the acting party. + * + * - actorTokenType + * An identifier for the representation of the issued security token. + * + * @param array $config Configuration array + */ + public function __construct(array $config) + { + $opts = \array_merge(['expiry' => self::DEFAULT_EXPIRY_SECONDS, 'extensionParams' => [], 'authorizationUri' => null, 'redirectUri' => null, 'tokenCredentialUri' => null, 'state' => null, 'username' => null, 'password' => null, 'clientId' => null, 'clientSecret' => null, 'issuer' => null, 'sub' => null, 'audience' => null, 'signingKey' => null, 'signingKeyId' => null, 'signingAlgorithm' => null, 'scope' => null, 'additionalClaims' => [], 'codeVerifier' => null, 'resource' => null, 'subjectTokenFetcher' => null, 'subjectTokenType' => null, 'actorToken' => null, 'actorTokenType' => null, 'additionalOptions' => []], $config); + $this->setAuthorizationUri($opts['authorizationUri']); + $this->setRedirectUri($opts['redirectUri']); + $this->setTokenCredentialUri($opts['tokenCredentialUri']); + $this->setState($opts['state']); + $this->setUsername($opts['username']); + $this->setPassword($opts['password']); + $this->setClientId($opts['clientId']); + $this->setClientSecret($opts['clientSecret']); + $this->setIssuer($opts['issuer']); + $this->setSub($opts['sub']); + $this->setExpiry($opts['expiry']); + $this->setAudience($opts['audience']); + $this->setSigningKey($opts['signingKey']); + $this->setSigningKeyId($opts['signingKeyId']); + $this->setSigningAlgorithm($opts['signingAlgorithm']); + $this->setScope($opts['scope']); + $this->setExtensionParams($opts['extensionParams']); + $this->setAdditionalClaims($opts['additionalClaims']); + $this->setCodeVerifier($opts['codeVerifier']); + // for STS + $this->resource = $opts['resource']; + $this->subjectTokenFetcher = $opts['subjectTokenFetcher']; + $this->subjectTokenType = $opts['subjectTokenType']; + $this->actorToken = $opts['actorToken']; + $this->actorTokenType = $opts['actorTokenType']; + $this->additionalOptions = $opts['additionalOptions']; + $this->updateToken($opts); + } + /** + * Verifies the idToken if present. + * + * - if none is present, return null + * - if present, but invalid, raises DomainException. + * - otherwise returns the payload in the idtoken as a PHP object. + * + * The behavior of this method varies depending on the version of + * `firebase/php-jwt` you are using. In versions 6.0 and above, you cannot + * provide multiple $allowed_algs, and instead must provide an array of Key + * objects as the $publicKey. + * + * @param string|Key|Key[] $publicKey The public key to use to authenticate the token + * @param string|array $allowed_algs algorithm or array of supported verification algorithms. + * Providing more than one algorithm will throw an exception. + * @throws \DomainException if the token is missing an audience. + * @throws \DomainException if the audience does not match the one set in + * the OAuth2 class instance. + * @throws \UnexpectedValueException If the token is invalid + * @throws \InvalidArgumentException If more than one value for allowed_algs is supplied + * @throws \Firebase\JWT\SignatureInvalidException If the signature is invalid. + * @throws \Firebase\JWT\BeforeValidException If the token is not yet valid. + * @throws \Firebase\JWT\ExpiredException If the token has expired. + * @return null|object + */ + public function verifyIdToken($publicKey = null, $allowed_algs = []) + { + $idToken = $this->getIdToken(); + if (\is_null($idToken)) { + return null; + } + $resp = $this->jwtDecode($idToken, $publicKey, $allowed_algs); + if (!\property_exists($resp, 'aud')) { + throw new \DomainException('No audience found the id token'); + } + if ($resp->aud != $this->getAudience()) { + throw new \DomainException('Wrong audience present in the id token'); + } + return $resp; + } + /** + * Obtains the encoded jwt from the instance data. + * + * @param array $config array optional configuration parameters + * @return string + */ + public function toJwt(array $config = []) + { + if (\is_null($this->getSigningKey())) { + throw new \DomainException('No signing key available'); + } + if (\is_null($this->getSigningAlgorithm())) { + throw new \DomainException('No signing algorithm specified'); + } + $now = \time(); + $opts = \array_merge(['skew' => self::DEFAULT_SKEW_SECONDS], $config); + $assertion = ['iss' => $this->getIssuer(), 'exp' => $now + $this->getExpiry(), 'iat' => $now - $opts['skew']]; + foreach ($assertion as $k => $v) { + if (\is_null($v)) { + throw new \DomainException($k . ' should not be null'); + } + } + if (!\is_null($this->getAudience())) { + $assertion['aud'] = $this->getAudience(); + } + if (!\is_null($this->getScope())) { + $assertion['scope'] = $this->getScope(); + } + if (empty($assertion['scope']) && empty($assertion['aud'])) { + throw new \DomainException('one of scope or aud should not be null'); + } + if (!\is_null($this->getSub())) { + $assertion['sub'] = $this->getSub(); + } + $assertion += $this->getAdditionalClaims(); + return JWT::encode($assertion, $this->getSigningKey(), $this->getSigningAlgorithm(), $this->getSigningKeyId()); + } + /** + * Generates a request for token credentials. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return RequestInterface the authorization Url. + */ + public function generateCredentialsRequest(?callable $httpHandler = null) + { + $uri = $this->getTokenCredentialUri(); + if (\is_null($uri)) { + throw new \DomainException('No token credential URI was set.'); + } + $grantType = $this->getGrantType(); + $params = ['grant_type' => $grantType]; + switch ($grantType) { + case 'authorization_code': + $params['code'] = $this->getCode(); + $params['redirect_uri'] = $this->getRedirectUri(); + if ($this->codeVerifier) { + $params['code_verifier'] = $this->codeVerifier; + } + $this->addClientCredentials($params); + break; + case 'password': + $params['username'] = $this->getUsername(); + $params['password'] = $this->getPassword(); + $this->addClientCredentials($params); + break; + case 'refresh_token': + $params['refresh_token'] = $this->getRefreshToken(); + $this->addClientCredentials($params); + break; + case self::JWT_URN: + $params['assertion'] = $this->toJwt(); + break; + case self::STS_URN: + $token = $this->subjectTokenFetcher->fetchSubjectToken($httpHandler); + $params['subject_token'] = $token; + $params['subject_token_type'] = $this->subjectTokenType; + $params += \array_filter(['resource' => $this->resource, 'audience' => $this->audience, 'scope' => $this->getScope(), 'requested_token_type' => self::STS_REQUESTED_TOKEN_TYPE, 'actor_token' => $this->actorToken, 'actor_token_type' => $this->actorTokenType]); + if ($this->additionalOptions) { + $params['options'] = \json_encode($this->additionalOptions); + } + break; + default: + if (!\is_null($this->getRedirectUri())) { + # Grant type was supposed to be 'authorization_code', as there + # is a redirect URI. + throw new \DomainException('Missing authorization code'); + } + unset($params['grant_type']); + if (!\is_null($grantType)) { + $params['grant_type'] = $grantType; + } + $params = \array_merge($params, $this->getExtensionParams()); + } + $headers = ['Cache-Control' => 'no-store', 'Content-Type' => 'application/x-www-form-urlencoded']; + return new Request('POST', $uri, $headers, Query::build($params)); + } + /** + * Fetches the auth tokens based on the current state. + * + * @param callable $httpHandler callback which delivers psr7 request + * @return array the response + */ + public function fetchAuthToken(?callable $httpHandler = null) + { + if (\is_null($httpHandler)) { + $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient()); + } + $response = $httpHandler($this->generateCredentialsRequest($httpHandler)); + $credentials = $this->parseTokenResponse($response); + $this->updateToken($credentials); + if (isset($credentials['scope'])) { + $this->setGrantedScope($credentials['scope']); + } + return $credentials; + } + /** + * Obtains a key that can used to cache the results of #fetchAuthToken. + * + * The key is derived from the scopes. + * + * @return ?string a key that may be used to cache the auth token. + */ + public function getCacheKey() + { + if (\is_array($this->scope)) { + return \implode(':', $this->scope); + } + if ($this->audience) { + return $this->audience; + } + // If scope has not set, return null to indicate no caching. + return null; + } + /** + * Parses the fetched tokens. + * + * @param ResponseInterface $resp the response. + * @return array the tokens parsed from the response body. + * @throws \Exception + */ + public function parseTokenResponse(ResponseInterface $resp) + { + $body = (string) $resp->getBody(); + if ($resp->hasHeader('Content-Type') && $resp->getHeaderLine('Content-Type') == 'application/x-www-form-urlencoded') { + $res = []; + \parse_str($body, $res); + return $res; + } + // Assume it's JSON; if it's not throw an exception + if (null === ($res = \json_decode($body, \true))) { + throw new \Exception('Invalid JSON response'); + } + return $res; + } + /** + * Updates an OAuth 2.0 client. + * + * Example: + * ``` + * $oauth->updateToken([ + * 'refresh_token' => 'n4E9O119d', + * 'access_token' => 'FJQbwq9', + * 'expires_in' => 3600 + * ]); + * ``` + * + * @param array $config + * The configuration parameters related to the token. + * + * - refresh_token + * The refresh token associated with the access token + * to be refreshed. + * + * - access_token + * The current access token for this client. + * + * - id_token + * The current ID token for this client. + * + * - expires_in + * The time in seconds until access token expiration. + * + * - expires_at + * The time as an integer number of seconds since the Epoch + * + * - issued_at + * The timestamp that the token was issued at. + * @return void + */ + public function updateToken(array $config) + { + $opts = \array_merge(['extensionParams' => [], 'access_token' => null, 'id_token' => null, 'expires_in' => null, 'expires_at' => null, 'issued_at' => null, 'scope' => null], $config); + $this->setExpiresAt($opts['expires_at']); + $this->setExpiresIn($opts['expires_in']); + // By default, the token is issued at `Time.now` when `expiresIn` is set, + // but this can be used to supply a more precise time. + if (!\is_null($opts['issued_at'])) { + $this->setIssuedAt($opts['issued_at']); + } + $this->setAccessToken($opts['access_token']); + $this->setIdToken($opts['id_token']); + // The refresh token should only be updated if a value is explicitly + // passed in, as some access token responses do not include a refresh + // token. + if (\array_key_exists('refresh_token', $opts)) { + $this->setRefreshToken($opts['refresh_token']); + } + // Required for STS response. An identifier for the representation of + // the issued security token. + if (\array_key_exists('issued_token_type', $opts)) { + $this->issuedTokenType = $opts['issued_token_type']; + } + } + /** + * Builds the authorization Uri that the user should be redirected to. + * + * @param array $config configuration options that customize the return url. + * @return UriInterface the authorization Url. + * @throws InvalidArgumentException + */ + public function buildFullAuthorizationUri(array $config = []) + { + if (\is_null($this->getAuthorizationUri())) { + throw new InvalidArgumentException('requires an authorizationUri to have been set'); + } + $params = \array_merge(['response_type' => 'code', 'access_type' => 'offline', 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUri, 'state' => $this->state, 'scope' => $this->getScope()], $config); + // Validate the auth_params + if (\is_null($params['client_id'])) { + throw new InvalidArgumentException('missing the required client identifier'); + } + if (\is_null($params['redirect_uri'])) { + throw new InvalidArgumentException('missing the required redirect URI'); + } + if (!empty($params['prompt']) && !empty($params['approval_prompt'])) { + throw new InvalidArgumentException('prompt and approval_prompt are mutually exclusive'); + } + if ($this->codeVerifier) { + $params['code_challenge'] = $this->getCodeChallenge($this->codeVerifier); + $params['code_challenge_method'] = $this->getCodeChallengeMethod(); + } + // Construct the uri object; return it if it is valid. + $result = clone $this->authorizationUri; + $existingParams = Query::parse($result->getQuery()); + $result = $result->withQuery(Query::build(\array_merge($existingParams, $params))); + if ($result->getScheme() != 'https') { + throw new InvalidArgumentException('Authorization endpoint must be protected by TLS'); + } + return $result; + } + /** + * @return string|null + */ + public function getCodeVerifier() : ?string + { + return $this->codeVerifier; + } + /** + * A cryptographically random string that is used to correlate the + * authorization request to the token request. + * + * The code verifier for PKCE for OAuth 2.0. When set, the authorization + * URI will contain the Code Challenge and Code Challenge Method querystring + * parameters, and the token URI will contain the Code Verifier parameter. + * + * @see https://datatracker.ietf.org/doc/html/rfc7636 + * + * @param string|null $codeVerifier + */ + public function setCodeVerifier(?string $codeVerifier) : void + { + $this->codeVerifier = $codeVerifier; + } + /** + * Generates a random 128-character string for the "code_verifier" parameter + * in PKCE for OAuth 2.0. This is a cryptographically random string that is + * determined using random_int, hashed using "hash" and sha256, and base64 + * encoded. + * + * When this method is called, the code verifier is set on the object. + * + * @return string + */ + public function generateCodeVerifier() : string + { + return $this->codeVerifier = $this->generateRandomString(128); + } + private function getCodeChallenge(string $randomString) : string + { + return \rtrim(\strtr(\base64_encode(\hash('sha256', $randomString, \true)), '+/', '-_'), '='); + } + private function getCodeChallengeMethod() : string + { + return 'S256'; + } + private function generateRandomString(int $length) : string + { + $validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'; + $validCharsLen = \strlen($validChars); + $str = ''; + $i = 0; + while ($i++ < $length) { + $str .= $validChars[\random_int(0, $validCharsLen - 1)]; + } + return $str; + } + /** + * Sets the authorization server's HTTP endpoint capable of authenticating + * the end-user and obtaining authorization. + * + * @param string $uri + * @return void + */ + public function setAuthorizationUri($uri) + { + $this->authorizationUri = $this->coerceUri($uri); + } + /** + * Gets the authorization server's HTTP endpoint capable of authenticating + * the end-user and obtaining authorization. + * + * @return ?UriInterface + */ + public function getAuthorizationUri() + { + return $this->authorizationUri; + } + /** + * Gets the authorization server's HTTP endpoint capable of issuing tokens + * and refreshing expired tokens. + * + * @return ?UriInterface + */ + public function getTokenCredentialUri() + { + return $this->tokenCredentialUri; + } + /** + * Sets the authorization server's HTTP endpoint capable of issuing tokens + * and refreshing expired tokens. + * + * @param string $uri + * @return void + */ + public function setTokenCredentialUri($uri) + { + $this->tokenCredentialUri = $this->coerceUri($uri); + } + /** + * Gets the redirection URI used in the initial request. + * + * @return ?string + */ + public function getRedirectUri() + { + return $this->redirectUri; + } + /** + * Sets the redirection URI used in the initial request. + * + * @param ?string $uri + * @return void + */ + public function setRedirectUri($uri) + { + if (\is_null($uri)) { + $this->redirectUri = null; + return; + } + // redirect URI must be absolute + if (!$this->isAbsoluteUri($uri)) { + // "postmessage" is a reserved URI string in Google-land + // @see https://developers.google.com/identity/sign-in/web/server-side-flow + if ('postmessage' !== (string) $uri) { + throw new InvalidArgumentException('Redirect URI must be absolute'); + } + } + $this->redirectUri = (string) $uri; + } + /** + * Gets the scope of the access requests as a space-delimited String. + * + * @return ?string + */ + public function getScope() + { + if (\is_null($this->scope)) { + return $this->scope; + } + return \implode(' ', $this->scope); + } + /** + * Sets the scope of the access request, expressed either as an Array or as + * a space-delimited String. + * + * @param string|array|null $scope + * @return void + * @throws InvalidArgumentException + */ + public function setScope($scope) + { + if (\is_null($scope)) { + $this->scope = null; + } elseif (\is_string($scope)) { + $this->scope = \explode(' ', $scope); + } elseif (\is_array($scope)) { + foreach ($scope as $s) { + $pos = \strpos($s, ' '); + if ($pos !== \false) { + throw new InvalidArgumentException('array scope values should not contain spaces'); + } + } + $this->scope = $scope; + } else { + throw new InvalidArgumentException('scopes should be a string or array of strings'); + } + } + /** + * Gets the current grant type. + * + * @return ?string + */ + public function getGrantType() + { + if (!\is_null($this->grantType)) { + return $this->grantType; + } + // Returns the inferred grant type, based on the current object instance + // state. + if (!\is_null($this->code)) { + return 'authorization_code'; + } + if (!\is_null($this->refreshToken)) { + return 'refresh_token'; + } + if (!\is_null($this->username) && !\is_null($this->password)) { + return 'password'; + } + if (!\is_null($this->issuer) && !\is_null($this->signingKey)) { + return self::JWT_URN; + } + if (!\is_null($this->subjectTokenFetcher) && !\is_null($this->subjectTokenType)) { + return self::STS_URN; + } + return null; + } + /** + * Sets the current grant type. + * + * @param string $grantType + * @return void + * @throws InvalidArgumentException + */ + public function setGrantType($grantType) + { + if (\in_array($grantType, self::$knownGrantTypes)) { + $this->grantType = $grantType; + } else { + // validate URI + if (!$this->isAbsoluteUri($grantType)) { + throw new InvalidArgumentException('invalid grant type'); + } + $this->grantType = (string) $grantType; + } + } + /** + * Gets an arbitrary string designed to allow the client to maintain state. + * + * @return string + */ + public function getState() + { + return $this->state; + } + /** + * Sets an arbitrary string designed to allow the client to maintain state. + * + * @param string $state + * @return void + */ + public function setState($state) + { + $this->state = $state; + } + /** + * Gets the authorization code issued to this client. + * + * @return string + */ + public function getCode() + { + return $this->code; + } + /** + * Sets the authorization code issued to this client. + * + * @param string $code + * @return void + */ + public function setCode($code) + { + $this->code = $code; + } + /** + * Gets the resource owner's username. + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + /** + * Sets the resource owner's username. + * + * @param string $username + * @return void + */ + public function setUsername($username) + { + $this->username = $username; + } + /** + * Gets the resource owner's password. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + /** + * Sets the resource owner's password. + * + * @param string $password + * @return void + */ + public function setPassword($password) + { + $this->password = $password; + } + /** + * Sets a unique identifier issued to the client to identify itself to the + * authorization server. + * + * @return string + */ + public function getClientId() + { + return $this->clientId; + } + /** + * Sets a unique identifier issued to the client to identify itself to the + * authorization server. + * + * @param string $clientId + * @return void + */ + public function setClientId($clientId) + { + $this->clientId = $clientId; + } + /** + * Gets a shared symmetric secret issued by the authorization server, which + * is used to authenticate the client. + * + * @return string + */ + public function getClientSecret() + { + return $this->clientSecret; + } + /** + * Sets a shared symmetric secret issued by the authorization server, which + * is used to authenticate the client. + * + * @param string $clientSecret + * @return void + */ + public function setClientSecret($clientSecret) + { + $this->clientSecret = $clientSecret; + } + /** + * Gets the Issuer ID when using assertion profile. + * + * @return ?string + */ + public function getIssuer() + { + return $this->issuer; + } + /** + * Sets the Issuer ID when using assertion profile. + * + * @param string $issuer + * @return void + */ + public function setIssuer($issuer) + { + $this->issuer = $issuer; + } + /** + * Gets the target sub when issuing assertions. + * + * @return ?string + */ + public function getSub() + { + return $this->sub; + } + /** + * Sets the target sub when issuing assertions. + * + * @param string $sub + * @return void + */ + public function setSub($sub) + { + $this->sub = $sub; + } + /** + * Gets the target audience when issuing assertions. + * + * @return ?string + */ + public function getAudience() + { + return $this->audience; + } + /** + * Sets the target audience when issuing assertions. + * + * @param string $audience + * @return void + */ + public function setAudience($audience) + { + $this->audience = $audience; + } + /** + * Gets the signing key when using an assertion profile. + * + * @return ?string + */ + public function getSigningKey() + { + return $this->signingKey; + } + /** + * Sets the signing key when using an assertion profile. + * + * @param string $signingKey + * @return void + */ + public function setSigningKey($signingKey) + { + $this->signingKey = $signingKey; + } + /** + * Gets the signing key id when using an assertion profile. + * + * @return ?string + */ + public function getSigningKeyId() + { + return $this->signingKeyId; + } + /** + * Sets the signing key id when using an assertion profile. + * + * @param string $signingKeyId + * @return void + */ + public function setSigningKeyId($signingKeyId) + { + $this->signingKeyId = $signingKeyId; + } + /** + * Gets the signing algorithm when using an assertion profile. + * + * @return ?string + */ + public function getSigningAlgorithm() + { + return $this->signingAlgorithm; + } + /** + * Sets the signing algorithm when using an assertion profile. + * + * @param ?string $signingAlgorithm + * @return void + */ + public function setSigningAlgorithm($signingAlgorithm) + { + if (\is_null($signingAlgorithm)) { + $this->signingAlgorithm = null; + } elseif (!\in_array($signingAlgorithm, self::$knownSigningAlgorithms)) { + throw new InvalidArgumentException('unknown signing algorithm'); + } else { + $this->signingAlgorithm = $signingAlgorithm; + } + } + /** + * Gets the set of parameters used by extension when using an extension + * grant type. + * + * @return array + */ + public function getExtensionParams() + { + return $this->extensionParams; + } + /** + * Sets the set of parameters used by extension when using an extension + * grant type. + * + * @param array $extensionParams + * @return void + */ + public function setExtensionParams($extensionParams) + { + $this->extensionParams = $extensionParams; + } + /** + * Gets the number of seconds assertions are valid for. + * + * @return int + */ + public function getExpiry() + { + return $this->expiry; + } + /** + * Sets the number of seconds assertions are valid for. + * + * @param int $expiry + * @return void + */ + public function setExpiry($expiry) + { + $this->expiry = $expiry; + } + /** + * Gets the lifetime of the access token in seconds. + * + * @return int + */ + public function getExpiresIn() + { + return $this->expiresIn; + } + /** + * Sets the lifetime of the access token in seconds. + * + * @param ?int $expiresIn + * @return void + */ + public function setExpiresIn($expiresIn) + { + if (\is_null($expiresIn)) { + $this->expiresIn = null; + $this->issuedAt = null; + } else { + $this->issuedAt = \time(); + $this->expiresIn = (int) $expiresIn; + } + } + /** + * Gets the time the current access token expires at. + * + * @return ?int + */ + public function getExpiresAt() + { + if (!\is_null($this->expiresAt)) { + return $this->expiresAt; + } + if (!\is_null($this->issuedAt) && !\is_null($this->expiresIn)) { + return $this->issuedAt + $this->expiresIn; + } + return null; + } + /** + * Returns true if the acccess token has expired. + * + * @return bool + */ + public function isExpired() + { + $expiration = $this->getExpiresAt(); + $now = \time(); + return !\is_null($expiration) && $now >= $expiration; + } + /** + * Sets the time the current access token expires at. + * + * @param int $expiresAt + * @return void + */ + public function setExpiresAt($expiresAt) + { + $this->expiresAt = $expiresAt; + } + /** + * Gets the time the current access token was issued at. + * + * @return ?int + */ + public function getIssuedAt() + { + return $this->issuedAt; + } + /** + * Sets the time the current access token was issued at. + * + * @param int $issuedAt + * @return void + */ + public function setIssuedAt($issuedAt) + { + $this->issuedAt = $issuedAt; + } + /** + * Gets the current access token. + * + * @return ?string + */ + public function getAccessToken() + { + return $this->accessToken; + } + /** + * Sets the current access token. + * + * @param string $accessToken + * @return void + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + } + /** + * Gets the current ID token. + * + * @return ?string + */ + public function getIdToken() + { + return $this->idToken; + } + /** + * Sets the current ID token. + * + * @param string $idToken + * @return void + */ + public function setIdToken($idToken) + { + $this->idToken = $idToken; + } + /** + * Get the granted space-separated scopes (if they exist) for the last + * fetched token. + * + * @return string|null + */ + public function getGrantedScope() + { + return $this->grantedScope; + } + /** + * Sets the current ID token. + * + * @param string $grantedScope + * @return void + */ + public function setGrantedScope($grantedScope) + { + $this->grantedScope = $grantedScope; + } + /** + * Gets the refresh token associated with the current access token. + * + * @return ?string + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + /** + * Sets the refresh token associated with the current access token. + * + * @param string $refreshToken + * @return void + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + } + /** + * Sets additional claims to be included in the JWT token + * + * @param array $additionalClaims + * @return void + */ + public function setAdditionalClaims(array $additionalClaims) + { + $this->additionalClaims = $additionalClaims; + } + /** + * Gets the additional claims to be included in the JWT token. + * + * @return array + */ + public function getAdditionalClaims() + { + return $this->additionalClaims; + } + /** + * Gets the additional claims to be included in the JWT token. + * + * @return ?string + */ + public function getIssuedTokenType() + { + return $this->issuedTokenType; + } + /** + * The expiration of the last received token. + * + * @return array|null + */ + public function getLastReceivedToken() + { + if ($token = $this->getAccessToken()) { + // the bare necessity of an auth token + $authToken = ['access_token' => $token, 'expires_at' => $this->getExpiresAt()]; + } elseif ($idToken = $this->getIdToken()) { + $authToken = ['id_token' => $idToken, 'expires_at' => $this->getExpiresAt()]; + } else { + return null; + } + if ($expiresIn = $this->getExpiresIn()) { + $authToken['expires_in'] = $expiresIn; + } + if ($issuedAt = $this->getIssuedAt()) { + $authToken['issued_at'] = $issuedAt; + } + if ($refreshToken = $this->getRefreshToken()) { + $authToken['refresh_token'] = $refreshToken; + } + return $authToken; + } + /** + * Get the client ID. + * + * Alias of {@see Google\Auth\OAuth2::getClientId()}. + * + * @param callable $httpHandler + * @return string + * @access private + */ + public function getClientName(?callable $httpHandler = null) + { + return $this->getClientId(); + } + /** + * @todo handle uri as array + * + * @param ?string $uri + * @return null|UriInterface + */ + private function coerceUri($uri) + { + if (\is_null($uri)) { + return null; + } + return Utils::uriFor($uri); + } + /** + * @param string $idToken + * @param Key|Key[]|string|string[] $publicKey + * @param string|string[] $allowedAlgs + * @return object + */ + private function jwtDecode($idToken, $publicKey, $allowedAlgs) + { + $keys = $this->getFirebaseJwtKeys($publicKey, $allowedAlgs); + // Default exception if none are caught. We are using the same exception + // class and message from firebase/php-jwt to preserve backwards + // compatibility. + $e = new \InvalidArgumentException('Key may not be empty'); + foreach ($keys as $key) { + try { + return JWT::decode($idToken, $key); + } catch (\Exception $e) { + // try next alg + } + } + throw $e; + } + /** + * @param Key|Key[]|string|string[] $publicKey + * @param string|string[] $allowedAlgs + * @return Key[] + */ + private function getFirebaseJwtKeys($publicKey, $allowedAlgs) + { + // If $publicKey is instance of Key, return it + if ($publicKey instanceof Key) { + return [$publicKey]; + } + // If $allowedAlgs is empty, $publicKey must be Key or Key[]. + if (empty($allowedAlgs)) { + $keys = []; + foreach ((array) $publicKey as $kid => $pubKey) { + if (!$pubKey instanceof Key) { + throw new \InvalidArgumentException(\sprintf('When allowed algorithms is empty, the public key must' . 'be an instance of %s or an array of %s objects', Key::class, Key::class)); + } + $keys[$kid] = $pubKey; + } + return $keys; + } + $allowedAlg = null; + if (\is_string($allowedAlgs)) { + $allowedAlg = $allowedAlgs; + } elseif (\is_array($allowedAlgs)) { + if (\count($allowedAlgs) > 1) { + throw new \InvalidArgumentException('To have multiple allowed algorithms, You must provide an' . ' array of Firebase\\JWT\\Key objects.' . ' See https://github.com/firebase/php-jwt for more information.'); + } + $allowedAlg = \array_pop($allowedAlgs); + } else { + throw new \InvalidArgumentException('allowed algorithms must be a string or array.'); + } + if (\is_array($publicKey)) { + // When publicKey is greater than 1, create keys with the single alg. + $keys = []; + foreach ($publicKey as $kid => $pubKey) { + if ($pubKey instanceof Key) { + $keys[$kid] = $pubKey; + } else { + $keys[$kid] = new Key($pubKey, $allowedAlg); + } + } + return $keys; + } + return [new Key($publicKey, $allowedAlg)]; + } + /** + * Determines if the URI is absolute based on its scheme and host or path + * (RFC 3986). + * + * @param string $uri + * @return bool + */ + private function isAbsoluteUri($uri) + { + $uri = $this->coerceUri($uri); + return $uri->getScheme() && ($uri->getHost() || $uri->getPath()); + } + /** + * @param array $params + * @return array + */ + private function addClientCredentials(&$params) + { + $clientId = $this->getClientId(); + $clientSecret = $this->getClientSecret(); + if ($clientId && $clientSecret) { + $params['client_id'] = $clientId; + $params['client_secret'] = $clientSecret; + } + return $params; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ProjectIdProviderInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ProjectIdProviderInterface.php new file mode 100644 index 00000000..0e5910d8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/ProjectIdProviderInterface.php @@ -0,0 +1,32 @@ +auth->getSigningKey(); + $signedString = ''; + if (\class_exists(phpseclib3\Crypt\RSA::class) && !$forceOpenssl) { + $key = PublicKeyLoader::load($privateKey); + $rsa = $key->withHash('sha256')->withPadding(RSA::SIGNATURE_PKCS1); + $signedString = $rsa->sign($stringToSign); + } elseif (\extension_loaded('openssl')) { + \openssl_sign($stringToSign, $signedString, $privateKey, 'sha256WithRSAEncryption'); + } else { + // @codeCoverageIgnoreStart + throw new \RuntimeException('OpenSSL is not installed.'); + } + // @codeCoverageIgnoreEnd + return \base64_encode($signedString); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/SignBlobInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/SignBlobInterface.php new file mode 100644 index 00000000..2fdbda5e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/SignBlobInterface.php @@ -0,0 +1,43 @@ + $metadata metadata hashmap + * @param string $authUri optional auth uri + * @param callable $httpHandler callback which delivers psr7 request + * @return array updated metadata hashmap + */ + public function updateMetadata($metadata, $authUri = null, ?callable $httpHandler = null); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/UpdateMetadataTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/UpdateMetadataTrait.php new file mode 100644 index 00000000..8d0f10d4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/google/auth/src/UpdateMetadataTrait.php @@ -0,0 +1,62 @@ + $metadata metadata hashmap + * @param string $authUri optional auth uri + * @param callable $httpHandler callback which delivers psr7 request + * @return array updated metadata hashmap + */ + public function updateMetadata($metadata, $authUri = null, ?callable $httpHandler = null) + { + if (isset($metadata[self::AUTH_METADATA_KEY])) { + // Auth metadata has already been set + return $metadata; + } + $result = $this->fetchAuthToken($httpHandler); + $metadata_copy = $metadata; + if (isset($result['access_token'])) { + $metadata_copy[self::AUTH_METADATA_KEY] = ['Bearer ' . $result['access_token']]; + } elseif (isset($result['id_token'])) { + $metadata_copy[self::AUTH_METADATA_KEY] = ['Bearer ' . $result['id_token']]; + } + return $metadata_copy; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/LICENSE new file mode 100644 index 00000000..fd2375d8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/LICENSE @@ -0,0 +1,27 @@ +The MIT License (MIT) + +Copyright (c) 2011 Michael Dowling +Copyright (c) 2012 Jeremy Lindblom +Copyright (c) 2014 Graham Campbell +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Tobias Schultze +Copyright (c) 2016 Tobias Nyholm +Copyright (c) 2016 George Mponos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizer.php new file mode 100644 index 00000000..95fee101 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizer.php @@ -0,0 +1,23 @@ +truncateAt = $truncateAt; + } + /** + * Returns a summarized message body. + */ + public function summarize(MessageInterface $message) : ?string + { + return $this->truncateAt === null ? Psr7\Message::bodySummary($message) : Psr7\Message::bodySummary($message, $this->truncateAt); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizerInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizerInterface.php new file mode 100644 index 00000000..17f486e5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/BodySummarizerInterface.php @@ -0,0 +1,12 @@ + 'http://www.foo.com/1.0/', + * 'timeout' => 0, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ]); + * + * Client configuration settings include the following options: + * + * - handler: (callable) Function that transfers HTTP requests over the + * wire. The function is called with a Psr7\Http\Message\RequestInterface + * and array of transfer options, and must return a + * GuzzleHttp\Promise\PromiseInterface that is fulfilled with a + * Psr7\Http\Message\ResponseInterface on success. + * If no handler is provided, a default handler will be created + * that enables all of the request options below by attaching all of the + * default middleware to the handler. + * - base_uri: (string|UriInterface) Base URI of the client that is merged + * into relative URIs. Can be a string or instance of UriInterface. + * - transport_sharing: (string|null) Transport sharing mode for the + * default handler. Accepts TransportSharing::* or null. Defaults to null. + * - **: any request option + * + * @param array $config Client configuration settings. + * + * @see RequestOptions for a list of available request options. + */ + public function __construct(array $config = []) + { + $transportSharing = \array_key_exists('transport_sharing', $config) ? $config['transport_sharing'] : null; + $transportSharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing'); + unset($config['transport_sharing']); + if (!isset($config['handler'])) { + $config['handler'] = $transportSharingMode === TransportSharing::NONE ? HandlerStack::create() : HandlerStack::create(Utils::chooseHandler(['transport_sharing' => $transportSharingMode])); + } elseif (!\is_callable($config['handler'])) { + throw new InvalidArgumentException('handler must be a callable'); + } elseif ($transportSharingMode === TransportSharing::HANDLER_REQUIRE) { + throw new InvalidArgumentException('The "transport_sharing" client option can only require sharing when Guzzle creates the default handler. Configure the "transport_sharing" option on CurlHandler or CurlMultiHandler when providing a custom cURL handler.'); + } + // Convert the base_uri to a UriInterface + if (isset($config['base_uri'])) { + $config['base_uri'] = Psr7\Utils::uriFor($config['base_uri']); + } + $this->configureDefaults($config); + } + /** + * @param string $method + * @param array $args + * + * @return PromiseInterface|ResponseInterface + * + * @deprecated Client::__call will be removed in guzzlehttp/guzzle:8.0. + */ + public function __call($method, $args) + { + if (\count($args) < 1) { + throw new InvalidArgumentException('Magic request methods require a URI and optional options array'); + } + $uri = $args[0]; + $opts = $args[1] ?? []; + $isAsync = \substr($method, -5) === 'Async'; + $method = $isAsync ? \substr($method, 0, -5) : $method; + $method = \strtoupper($method); + return $isAsync ? $this->requestAsync($method, $uri, $opts) : $this->request($method, $uri, $opts); + } + /** + * Asynchronously send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See {@see RequestOptions}. + */ + public function sendAsync(RequestInterface $request, array $options = []) : PromiseInterface + { + // Merge the base URI into the request URI if needed. + $options = $this->prepareDefaults($options); + return $this->transfer($request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')), $options); + } + /** + * Send an HTTP request. + * + * @param array $options Request options to apply to the given + * request and to the transfer. See {@see RequestOptions}. + * + * @throws GuzzleException + */ + public function send(RequestInterface $request, array $options = []) : ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = \true; + return $this->sendAsync($request, $options)->wait(); + } + /** + * The HttpClient PSR (PSR-18) specify this method. + * + * {@inheritDoc} + */ + public function sendRequest(RequestInterface $request) : ResponseInterface + { + $options[RequestOptions::SYNCHRONOUS] = \true; + $options[RequestOptions::ALLOW_REDIRECTS] = \false; + $options[RequestOptions::HTTP_ERRORS] = \false; + return $this->sendAsync($request, $options)->wait(); + } + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See {@see RequestOptions}. + */ + public function requestAsync(string $method, $uri = '', array $options = []) : PromiseInterface + { + $normalizedMethod = \strtoupper($method); + if ($method !== $normalizedMethod) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing a non-uppercase HTTP method to Client::requestAsync() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.'); + $method = $normalizedMethod; + } + $options = $this->prepareDefaults($options); + // Remove request modifying parameter because it can be done up-front. + $headers = $options['headers'] ?? []; + $droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers); + if ($droppedHeaderNames !== [] && isset($options['_conditional'])) { + $options['_conditional'] = Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']); + } + $body = $options['body'] ?? null; + $version = self::normalizeProtocolVersion($options['version'] ?? '1.1'); + // Merge the URI into the base URI. + $uri = $this->buildUri(Psr7\Utils::uriFor($uri), $options); + if (\is_array($body)) { + throw $this->invalidBody(); + } + $body = self::createBodyStream($body); + $request = new Psr7\Request($method, $uri, $headers, $body, $version); + // Remove the option so that they are not doubly-applied. + unset($options['headers'], $options['body'], $options['version']); + return $this->transfer($request, $options); + } + /** + * Create and send an HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string $method HTTP method. + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. See {@see RequestOptions}. + * + * @throws GuzzleException + */ + public function request(string $method, $uri = '', array $options = []) : ResponseInterface + { + $normalizedMethod = \strtoupper($method); + if ($method !== $normalizedMethod) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing a non-uppercase HTTP method to Client::request() is deprecated; guzzlehttp/guzzle 8.0 will preserve HTTP method casing. Pass an uppercase method explicitly if uppercase is required.'); + $method = $normalizedMethod; + } + $options[RequestOptions::SYNCHRONOUS] = \true; + return $this->requestAsync($method, $uri, $options)->wait(); + } + /** + * Get a client configuration option. + * + * These options include default request options of the client, a "handler" + * (if utilized by the concrete client), and a "base_uri" if utilized by + * the concrete client. + * + * @param string|null $option The config option to retrieve. + * + * @return mixed + */ + public function getConfig(?string $option = null) + { + return $option === null ? $this->config : $this->config[$option] ?? null; + } + private function buildUri(UriInterface $uri, array $config) : UriInterface + { + if (isset($config['base_uri'])) { + $uri = Psr7\UriResolver::resolve(Psr7\Utils::uriFor($config['base_uri']), $uri); + } + $idnOptions = Utils::normalizeIdnConversionOption($config['idn_conversion'] ?? null); + if ($idnOptions !== null) { + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri; + } + /** + * Configures the default options for a client. + */ + private function configureDefaults(array $config) : void + { + $defaults = ['allow_redirects' => RedirectMiddleware::$defaultSettings, 'http_errors' => \true, 'decode_content' => \true, 'verify' => \true, 'cookies' => \false, 'idn_conversion' => \false, 'protocols' => ['http', 'https']]; + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (\PHP_SAPI === 'cli' && ($proxy = Utils::getenv('HTTP_PROXY'))) { + $defaults['proxy']['http'] = $proxy; + } + if ($proxy = Utils::getenv('HTTPS_PROXY')) { + $defaults['proxy']['https'] = $proxy; + } + if ($noProxy = Utils::getenv('NO_PROXY')) { + $cleanedNoProxy = \str_replace(' ', '', $noProxy); + $defaults['proxy']['no'] = \explode(',', $cleanedNoProxy); + } + $this->config = $config + $defaults; + if (!empty($config['cookies']) && $config['cookies'] === \true) { + $this->config['cookies'] = new CookieJar(); + } + // Add the default user-agent header. + if (!isset($this->config['headers'])) { + $this->config['headers'] = ['User-Agent' => Utils::defaultUserAgent()]; + } else { + // Add the User-Agent header if one was not already set. + $hasUserAgent = \false; + foreach (\array_keys($this->config['headers']) as $name) { + if (\strtolower((string) $name) === 'user-agent') { + $hasUserAgent = \true; + break; + } + } + if (!$hasUserAgent) { + $this->config['headers']['User-Agent'] = Utils::defaultUserAgent(); + } + } + if (\is_array($this->config['headers'])) { + self::warnAboutInvalidHeaderOptionTypes($this->config['headers']); + self::castDeprecatedHeaderOptionValues($this->config['headers']); + } + } + /** + * Merges default options into the array. + * + * @param array $options Options to modify by reference + */ + private function prepareDefaults(array $options) : array + { + self::warnAboutRequestLevelHandler($options); + $defaults = $this->config; + if (!empty($defaults['headers'])) { + // Default headers are only added if they are not present. + $defaults['_conditional'] = $defaults['headers']; + unset($defaults['headers']); + } + // Special handling for headers is required as they are added as + // conditional headers and as headers passed to a request ctor. + if (\array_key_exists('headers', $options)) { + // Allows default headers to be unset. + if ($options['headers'] === null) { + $defaults['_conditional'] = []; + unset($options['headers']); + } elseif (!\is_array($options['headers'])) { + throw new InvalidArgumentException('headers must be an array'); + } + } + // Shallow merge defaults underneath options. + $result = $options + $defaults; + // Remove null values. + foreach ($result as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + self::warnAboutInvalidRequestOptionTypes($result); + return $result; + } + private static function warnAboutRequestLevelHandler(array $options) : void + { + if (!\array_key_exists('handler', $options)) { + return; + } + \trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing the "handler" request option is deprecated; guzzlehttp/guzzle 8.0 will ignore request-level handlers. Configure the handler when creating the Client, or use a separate Client instance for requests that need a different handler.'); + } + private static function warnAboutInvalidRequestOptionTypes(array $options) : void + { + if (isset($options['handler']) && !\is_callable($options['handler'])) { + self::warnInvalidRequestOptionType('handler', 'callable', $options['handler']); + } + if (isset($options['allow_redirects']) && \is_array($options['allow_redirects'])) { + self::warnAboutInvalidAllowRedirectsOptionTypes($options['allow_redirects']); + } + if (isset($options['auth'])) { + self::warnAboutInvalidAuthOptionTypes($options['auth']); + } + if (isset($options['body']) && \is_array($options['body'])) { + self::warnInvalidRequestOptionType('body', 'resource|string|null|int|float|bool|StreamInterface|(callable&object)|\\Iterator|\\Stringable', $options['body']); + } + self::warnAboutInvalidTlsFileOptionTypes($options, 'cert'); + self::warnIfPresentAndNotString($options, 'cert_type'); + self::warnIfPresentAndNotNumber($options, 'connect_timeout'); + self::warnIfPresentAndNotInt($options, 'crypto_method'); + self::warnIfPresentAndNotBoolOrResource($options, 'debug'); + self::warnIfPresentAndNotBoolOrString($options, 'decode_content'); + self::warnIfPresentAndNotNumber($options, 'delay'); + self::warnIfPresentAndNotBoolOrInt($options, 'expect'); + if (isset($options['form_params'])) { + self::warnAboutInvalidFormParamTypes($options['form_params']); + } + if (isset($options['force_ip_resolve']) && !\is_string($options['force_ip_resolve'])) { + self::warnInvalidRequestOptionType('force_ip_resolve', 'string', $options['force_ip_resolve']); + } + if (isset($options['headers'])) { + self::warnAboutInvalidHeaderOptionTypes($options['headers']); + } + self::warnIfPresentAndNotBool($options, 'http_errors'); + if (isset($options['multipart'])) { + self::warnAboutInvalidMultipartOptionTypes($options['multipart']); + } + self::warnIfPresentAndNotCallable($options, 'on_headers'); + self::warnIfPresentAndNotCallable($options, 'on_stats'); + self::warnIfPresentAndNotCallable($options, 'progress'); + self::warnIfPresentAndNotStringArray($options, 'protocols', \true); + self::warnAboutInvalidProxyOptionTypes($options); + self::warnIfPresentAndNotNumber($options, 'read_timeout'); + self::warnIfPresentAndNotInt($options, 'retries'); + if (isset($options['sink']) && !\is_bool($options['sink']) && !\is_resource($options['sink']) && !\is_string($options['sink']) && !$options['sink'] instanceof StreamInterface) { + self::warnInvalidRequestOptionType('sink', 'resource|string|StreamInterface', $options['sink']); + } + self::warnAboutInvalidTlsFileOptionTypes($options, 'ssl_key'); + self::warnIfPresentAndNotString($options, 'ssl_key_type'); + self::warnIfPresentAndNotBool($options, 'stream'); + self::warnIfPresentAndNotArray($options, 'stream_context', 'array'); + self::warnIfPresentAndNotBool($options, 'synchronous'); + self::warnIfPresentAndNotNumber($options, 'timeout'); + self::warnIfPresentAndNotBoolOrString($options, 'verify'); + self::warnIfPresentAndNotStringOrNumber($options, 'version'); + self::warnIfPresentAndNotArray($options, 'curl', 'array'); + if (isset($options['cookies']) && $options['cookies'] === \true) { + self::warnInvalidRequestOptionType('cookies', 'false|CookieJarInterface', $options['cookies']); + } + } + private static function warnAboutInvalidAllowRedirectsOptionTypes(array $allowRedirects) : void + { + self::warnIfPresentAndNotInt($allowRedirects, 'max', 'allow_redirects.max'); + self::warnIfPresentAndNotBool($allowRedirects, 'strict', 'allow_redirects.strict'); + self::warnIfPresentAndNotBool($allowRedirects, 'referer', 'allow_redirects.referer'); + self::warnIfPresentAndNotStringArray($allowRedirects, 'protocols', \true, 'allow_redirects.protocols'); + self::warnIfPresentAndNotCallable($allowRedirects, 'on_redirect', 'allow_redirects.on_redirect'); + self::warnIfPresentAndNotBool($allowRedirects, 'track_redirects', 'allow_redirects.track_redirects'); + } + /** + * @param mixed $auth + */ + private static function warnAboutInvalidAuthOptionTypes($auth) : void + { + if ($auth === \false || \is_string($auth) || $auth === []) { + return; + } + if (!\is_array($auth)) { + self::warnInvalidRequestOptionType('auth', 'array{0: string, 1: string, 2?: string|null}|string|false|null', $auth); + return; + } + if (!\array_key_exists(0, $auth) || !\is_string($auth[0])) { + self::warnInvalidRequestOptionType('auth.0', 'string', $auth[0] ?? null); + } + if (!\array_key_exists(1, $auth) || !\is_string($auth[1])) { + self::warnInvalidRequestOptionType('auth.1', 'string', $auth[1] ?? null); + } + if (\array_key_exists(2, $auth) && $auth[2] !== null && !\is_string($auth[2])) { + self::warnInvalidRequestOptionType('auth.2', 'string|null', $auth[2]); + } + } + /** + * @param mixed $value + */ + private static function warnAboutInvalidFormParamTypes($value) : void + { + if (!\is_array($value)) { + self::warnInvalidRequestOptionType('form_params', 'array', $value); + return; + } + self::warnAboutInvalidFormParamArray($value, 'form_params'); + } + private static function warnAboutInvalidFormParamArray(array $values, string $path) : bool + { + foreach ($values as $key => $item) { + $itemPath = $path . '.' . (string) $key; + if (\is_array($item)) { + if (!self::warnAboutInvalidFormParamArray($item, $itemPath)) { + return \false; + } + continue; + } + if ($item !== null && !\is_scalar($item)) { + self::warnInvalidRequestOptionType($itemPath, 'string|int|float|bool|null|array', $item); + return \false; + } + } + return \true; + } + /** + * @param mixed $headers + */ + private static function warnAboutInvalidHeaderOptionTypes($headers) : void + { + if (!\is_array($headers)) { + self::warnInvalidRequestOptionType('headers', 'array>|null', $headers); + return; + } + foreach ($headers as $name => $value) { + $path = 'headers.' . (string) $name; + if (\is_array($value)) { + if ($value === []) { + self::warnInvalidRequestOptionType($path, 'string|non-empty-array', $value); + break; + } + foreach ($value as $index => $item) { + if (!\is_string($item)) { + self::warnInvalidRequestOptionType($path . '.' . (string) $index, 'string', $item); + break 2; + } + } + } elseif (!\is_string($value)) { + self::warnInvalidRequestOptionType($path, 'string|non-empty-array', $value); + break; + } + } + } + /** + * @param mixed $multipart + */ + private static function warnAboutInvalidMultipartOptionTypes($multipart) : void + { + if (!\is_array($multipart)) { + self::warnInvalidRequestOptionType('multipart', 'array, filename?: string}>', $multipart); + return; + } + foreach ($multipart as $index => $part) { + $path = 'multipart.' . (string) $index; + if (!\is_array($part)) { + self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array, filename?: string}', $part); + return; + } + if (!\array_key_exists('name', $part) || !\is_string($part['name']) && !\is_int($part['name'])) { + self::warnInvalidRequestOptionType($path . '.name', 'string|int', $part['name'] ?? null); + } + if (!\array_key_exists('contents', $part)) { + self::warnInvalidRequestOptionType($path, 'array{name: string|int, contents: mixed, headers?: array, filename?: string}', $part); + } + if (\array_key_exists('headers', $part)) { + if (!\is_array($part['headers'])) { + self::warnInvalidRequestOptionType($path . '.headers', 'array', $part['headers']); + } else { + foreach ($part['headers'] as $name => $value) { + if (!\is_string($value)) { + self::warnInvalidRequestOptionType($path . '.headers.' . (string) $name, 'string', $value); + break 2; + } + } + } + } + if (\array_key_exists('filename', $part) && !\is_string($part['filename'])) { + self::warnInvalidRequestOptionType($path . '.filename', 'string', $part['filename']); + } + } + } + private static function warnAboutInvalidProxyOptionTypes(array $options) : void + { + if (!isset($options['proxy'])) { + return; + } + if (!\is_string($options['proxy']) && !\is_array($options['proxy'])) { + self::warnInvalidRequestOptionType('proxy', 'string|array{http?: string|null, https?: string|null, no?: string|array|null}', $options['proxy']); + return; + } + if (!\is_array($options['proxy'])) { + return; + } + foreach (['http', 'https'] as $scheme) { + if (\array_key_exists($scheme, $options['proxy']) && $options['proxy'][$scheme] !== null && !\is_string($options['proxy'][$scheme])) { + self::warnInvalidRequestOptionType('proxy.' . $scheme, 'string|null', $options['proxy'][$scheme]); + } + } + if (!\array_key_exists('no', $options['proxy']) || $options['proxy']['no'] === null) { + return; + } + if (\is_string($options['proxy']['no'])) { + return; + } + if (!\is_array($options['proxy']['no'])) { + self::warnInvalidRequestOptionType('proxy.no', 'string|array|null', $options['proxy']['no']); + return; + } + foreach ($options['proxy']['no'] as $index => $noProxy) { + if (!\is_string($noProxy)) { + self::warnInvalidRequestOptionType('proxy.no.' . (string) $index, 'string', $noProxy); + return; + } + } + } + private static function warnAboutInvalidTlsFileOptionTypes(array $options, string $option) : void + { + if (!isset($options[$option])) { + return; + } + if (\is_string($options[$option])) { + return; + } + if (!\is_array($options[$option])) { + self::warnInvalidRequestOptionType($option, 'string|array{0: string, 1?: string}', $options[$option]); + return; + } + if (!\array_key_exists(0, $options[$option]) || !\is_string($options[$option][0])) { + self::warnInvalidRequestOptionType($option . '.0', 'string', $options[$option][0] ?? null); + } + if (\array_key_exists(1, $options[$option]) && $options[$option][1] !== null && !\is_string($options[$option][1])) { + self::warnInvalidRequestOptionType($option . '.1', 'string|null', $options[$option][1]); + } + } + private static function warnIfPresentAndNotArray(array $options, string $option, string $expected) : void + { + if (\array_key_exists($option, $options) && !\is_array($options[$option])) { + self::warnInvalidRequestOptionType($option, $expected, $options[$option]); + } + } + private static function warnIfPresentAndNotBool(array $options, string $option, ?string $path = null) : void + { + if (\array_key_exists($option, $options) && !\is_bool($options[$option])) { + self::warnInvalidRequestOptionType($path ?? $option, 'bool', $options[$option]); + } + } + private static function warnIfPresentAndNotBoolOrInt(array $options, string $option) : void + { + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_int($options[$option])) { + self::warnInvalidRequestOptionType($option, 'bool|int', $options[$option]); + } + } + private static function warnIfPresentAndNotBoolOrResource(array $options, string $option) : void + { + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_resource($options[$option])) { + self::warnInvalidRequestOptionType($option, 'bool|resource', $options[$option]); + } + } + private static function warnIfPresentAndNotBoolOrString(array $options, string $option) : void + { + if (\array_key_exists($option, $options) && !\is_bool($options[$option]) && !\is_string($options[$option])) { + self::warnInvalidRequestOptionType($option, 'bool|string', $options[$option]); + } + } + private static function warnIfPresentAndNotCallable(array $options, string $option, ?string $path = null) : void + { + if (\array_key_exists($option, $options) && !\is_callable($options[$option])) { + self::warnInvalidRequestOptionType($path ?? $option, 'callable', $options[$option]); + } + } + private static function warnIfPresentAndNotInt(array $options, string $option, ?string $path = null) : void + { + if (\array_key_exists($option, $options) && !\is_int($options[$option])) { + self::warnInvalidRequestOptionType($path ?? $option, 'int', $options[$option]); + } + } + private static function warnIfPresentAndNotNumber(array $options, string $option) : void + { + if (\array_key_exists($option, $options) && !\is_int($options[$option]) && !\is_float($options[$option])) { + self::warnInvalidRequestOptionType($option, 'int|float', $options[$option]); + } + } + private static function warnIfPresentAndNotString(array $options, string $option) : void + { + if (\array_key_exists($option, $options) && !\is_string($options[$option])) { + self::warnInvalidRequestOptionType($option, 'string', $options[$option]); + } + } + private static function warnIfPresentAndNotStringArray(array $options, string $option, bool $nonEmpty, ?string $path = null) : void + { + if (!\array_key_exists($option, $options)) { + return; + } + $path = $path ?? $option; + $expected = ($nonEmpty ? 'non-empty-' : '') . 'array'; + if (!\is_array($options[$option]) || $nonEmpty && $options[$option] === []) { + self::warnInvalidRequestOptionType($path, $expected, $options[$option]); + return; + } + foreach ($options[$option] as $index => $item) { + if (!\is_string($item)) { + self::warnInvalidRequestOptionType($path . '.' . (string) $index, 'string', $item); + return; + } + } + } + private static function warnIfPresentAndNotStringOrNumber(array $options, string $option) : void + { + if (\array_key_exists($option, $options) && !\is_string($options[$option]) && !\is_int($options[$option]) && !\is_float($options[$option])) { + self::warnInvalidRequestOptionType($option, 'string|int|float', $options[$option]); + } + } + /** + * @param mixed $value + */ + private static function warnInvalidRequestOptionType(string $option, string $expected, $value) : void + { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing %s to request option "%s" is deprecated; guzzlehttp/guzzle 8.0 requires %s.', \get_debug_type($value), $option, $expected); + } + /** + * Transfers the given request and applies request options. + * + * The URI of the request is not modified and the request options are used + * as-is without merging in default options. + * + * @param array $options See {@see RequestOptions}. + */ + private function transfer(RequestInterface $request, array $options) : PromiseInterface + { + $request = $this->applyOptions($request, $options); + $protocolVersion = $request->getProtocolVersion(); + if ('' === $protocolVersion) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.'); + $request = Psr7\Utils::modifyRequest($request, ['version' => '1.1']); + } elseif (!self::isProtocolVersionValid($protocolVersion)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with a malformed protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject malformed protocol versions.'); + } + /** @var HandlerStack $handler */ + $handler = $options['handler']; + try { + return P\Create::promiseFor($handler($request, $options)); + } catch (\Exception $e) { + return P\Create::rejectionFor($e); + } + } + /** + * Applies the array of request options to a request. + */ + private function applyOptions(RequestInterface $request, array &$options) : RequestInterface + { + $modify = ['set_headers' => []]; + if (isset($options['headers'])) { + if (\array_keys($options['headers']) === \range(0, \count($options['headers']) - 1)) { + throw new InvalidArgumentException('The headers array must have header name as keys.'); + } + $headers = $options['headers']; + $droppedHeaderNames = self::castDeprecatedHeaderOptionValues($headers); + if ($droppedHeaderNames !== [] && isset($options['_conditional'])) { + $options['_conditional'] = Psr7\Utils::caselessRemove($droppedHeaderNames, $options['_conditional']); + } + $modify['set_headers'] = $headers; + unset($options['headers']); + } + if (isset($options['form_params'])) { + if (isset($options['multipart'])) { + throw new InvalidArgumentException('You cannot use ' . 'form_params and multipart at the same time. Use the ' . 'form_params option if you want to send application/' . 'x-www-form-urlencoded requests, and the multipart ' . 'option to send multipart/form-data requests.'); + } + $options['body'] = \http_build_query(self::normalizeNonFiniteFloats($options['form_params'], 'form_params'), '', '&'); + unset($options['form_params']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded'; + } + if (isset($options['multipart'])) { + $options['body'] = new Psr7\MultipartStream($options['multipart']); + unset($options['multipart']); + } + if (isset($options['json'])) { + $options['body'] = Utils::jsonEncode($options['json']); + unset($options['json']); + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'application/json'; + } + if (!empty($options['decode_content']) && $options['decode_content'] !== \true) { + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Accept-Encoding'], $options['_conditional']); + $modify['set_headers']['Accept-Encoding'] = (string) $options['decode_content']; + } + if (isset($options['body'])) { + if (\is_array($options['body'])) { + throw $this->invalidBody(); + } + $modify['body'] = self::createBodyStream($options['body']); + unset($options['body']); + } + if (!empty($options['auth']) && \is_array($options['auth'])) { + $value = $options['auth']; + $type = isset($value[2]) ? \strtolower($value[2]) : 'basic'; + switch ($type) { + case 'basic': + // Ensure that we don't have the header in different case and set the new value. + $modify['set_headers'] = Psr7\Utils::caselessRemove(['Authorization'], $modify['set_headers']); + $modify['set_headers']['Authorization'] = 'Basic ' . \base64_encode("{$value[0]}:{$value[1]}"); + break; + case 'digest': + // @todo: Do not rely on curl + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_DIGEST; + $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}"; + break; + case 'ntlm': + \trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing "ntlm" as the built-in auth type is deprecated; guzzlehttp/guzzle 8.0 will no longer apply NTLM through the "auth" request option. Configure NTLM with cURL HTTP authentication options instead.'); + $options['curl'][\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM; + $options['curl'][\CURLOPT_USERPWD] = "{$value[0]}:{$value[1]}"; + break; + } + } + if (isset($options['query'])) { + $value = $options['query']; + if (\is_array($value)) { + $value = \http_build_query(self::normalizeNonFiniteFloats($value, 'query'), '', '&', \PHP_QUERY_RFC3986); + } + if (!\is_string($value)) { + throw new InvalidArgumentException('query must be a string or array'); + } + $modify['query'] = $value; + unset($options['query']); + } + // Ensure that sink is not an invalid value. + if (isset($options['sink'])) { + // TODO: Add more sink validation? + if (\is_bool($options['sink'])) { + throw new InvalidArgumentException('sink must not be a boolean'); + } + } + if (isset($options['version'])) { + $modify['version'] = self::normalizeProtocolVersion($options['version']); + } + $request = Psr7\Utils::modifyRequest($request, $modify); + if ($request->getBody() instanceof Psr7\MultipartStream) { + // Use a multipart/form-data POST if a Content-Type is not set. + // Ensure that we don't have the header in different case and set the new value. + $options['_conditional'] = Psr7\Utils::caselessRemove(['Content-Type'], $options['_conditional']); + $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary=' . $request->getBody()->getBoundary(); + } + // Merge in conditional headers if they are not present. + if (isset($options['_conditional'])) { + // Build up the changes so it's in a single clone of the message. + $modify = []; + foreach ($options['_conditional'] as $k => $v) { + $name = (string) $k; + if (!$request->hasHeader($name)) { + $modify['set_headers'][$name] = $v; + } + } + $request = Psr7\Utils::modifyRequest($request, $modify); + // Don't pass this internal value along to middleware/handlers. + unset($options['_conditional']); + } + return $request; + } + /** + * @param array $headers + * + * @return list + */ + private static function castDeprecatedHeaderOptionValues(array &$headers) : array + { + $droppedHeaderNames = []; + foreach ($headers as $name => $value) { + if (\is_array($value)) { + if ($value === []) { + $droppedHeaderNames[] = (string) $name; + unset($headers[$name]); + continue; + } + foreach ($value as $index => $item) { + if ($item === null || !\is_string($item) && \is_scalar($item)) { + if (\is_float($item) && !\is_finite($item)) { + $item = \is_nan($item) ? 'NAN' : ($item > 0 ? 'INF' : '-INF'); + } + $value[$index] = (string) $item; + } + } + $headers[$name] = $value; + continue; + } + if ($value === null || !\is_string($value) && \is_scalar($value)) { + if (\is_float($value) && !\is_finite($value)) { + $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + } + $headers[$name] = (string) $value; + } + } + return $droppedHeaderNames; + } + /** + * @param mixed $body + */ + private static function createBodyStream($body) : StreamInterface + { + if ($body instanceof StreamInterface) { + return $body; + } + if (\is_resource($body) || $body === null || \is_string($body) || $body instanceof \Iterator) { + return Psr7\Utils::streamFor($body); + } + if (\is_scalar($body)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing a non-string scalar to the "body" request option is deprecated; guzzlehttp/guzzle 8.0 will reject non-string scalar bodies.'); + return Psr7\Utils::streamFor(self::stringifyScalar($body)); + } + if (\is_object($body) && \method_exists($body, '__toString')) { + return Psr7\Utils::streamFor((string) $body); + } + if (\is_callable($body)) { + return Psr7\Utils::streamFor($body); + } + throw new InvalidArgumentException(\sprintf('Passing %s to request option "body" is invalid; expected resource|string|null|int|float|bool|StreamInterface|callable&object|Iterator|Stringable.', \get_debug_type($body))); + } + /** + * @param bool|float|int|string $value + */ + private static function stringifyScalar($value) : string + { + // Normalize non-finite floats to dodge PHP 8.5's (string) NAN + // coercion warning while the value is still accepted. + if (\is_float($value) && !\is_finite($value)) { + $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + } + return (string) $value; + } + /** + * Converts non-finite floats in the array to the strings PHP coerces + * them to, as implicit coercion of NAN emits a warning on PHP 8.5. + */ + private static function normalizeNonFiniteFloats(array $values, string $option) : array + { + foreach ($values as $key => $value) { + if (\is_array($value)) { + $values[$key] = self::normalizeNonFiniteFloats($value, $option); + } elseif (\is_float($value) && !\is_finite($value)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.12', 'Passing a non-finite float in the "%s" request option is deprecated; guzzlehttp/guzzle 8.0 will reject non-finite floats.', $option); + $values[$key] = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + } + } + return $values; + } + /** + * @param string|int|float $version + */ + private static function normalizeProtocolVersion($version) : string + { + if ('' === $version) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing an empty "version" request option is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.'); + return '1.1'; + } + return \is_float($version) ? \number_format($version, 1, '.', '') : (string) $version; + } + private static function isProtocolVersionValid(string $version) : bool + { + return 1 === \preg_match('/^\\d+(?:\\.\\d+)?$/D', $version); + } + /** + * Return an InvalidArgumentException with pre-set message. + */ + private function invalidBody() : InvalidArgumentException + { + return new InvalidArgumentException('Passing in the "body" request ' . 'option as an array to send a request is not supported. ' . 'Please use the "form_params" request option to send a ' . 'application/x-www-form-urlencoded request, or the "multipart" ' . 'request option to send a multipart/form-data request.'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/ClientInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 00000000..34204087 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,78 @@ +request('GET', $uri, $options); + } + /** + * Create and send an HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function head($uri, array $options = []) : ResponseInterface + { + return $this->request('HEAD', $uri, $options); + } + /** + * Create and send an HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function put($uri, array $options = []) : ResponseInterface + { + return $this->request('PUT', $uri, $options); + } + /** + * Create and send an HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function post($uri, array $options = []) : ResponseInterface + { + return $this->request('POST', $uri, $options); + } + /** + * Create and send an HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function patch($uri, array $options = []) : ResponseInterface + { + return $this->request('PATCH', $uri, $options); + } + /** + * Create and send an HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + * + * @throws GuzzleException + */ + public function delete($uri, array $options = []) : ResponseInterface + { + return $this->request('DELETE', $uri, $options); + } + /** + * Create and send an asynchronous HTTP request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string $method HTTP method + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public abstract function requestAsync(string $method, $uri, array $options = []) : PromiseInterface; + /** + * Create and send an asynchronous HTTP GET request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function getAsync($uri, array $options = []) : PromiseInterface + { + return $this->requestAsync('GET', $uri, $options); + } + /** + * Create and send an asynchronous HTTP HEAD request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function headAsync($uri, array $options = []) : PromiseInterface + { + return $this->requestAsync('HEAD', $uri, $options); + } + /** + * Create and send an asynchronous HTTP PUT request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function putAsync($uri, array $options = []) : PromiseInterface + { + return $this->requestAsync('PUT', $uri, $options); + } + /** + * Create and send an asynchronous HTTP POST request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function postAsync($uri, array $options = []) : PromiseInterface + { + return $this->requestAsync('POST', $uri, $options); + } + /** + * Create and send an asynchronous HTTP PATCH request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function patchAsync($uri, array $options = []) : PromiseInterface + { + return $this->requestAsync('PATCH', $uri, $options); + } + /** + * Create and send an asynchronous HTTP DELETE request. + * + * Use an absolute path to override the base path of the client, or a + * relative path to append to the base path of the client. The URL can + * contain the query string as well. Use an array to provide a URL + * template and additional variables to use in the URL template expansion. + * + * @param string|UriInterface $uri URI object or string. + * @param array $options Request options to apply. + */ + public function deleteAsync($uri, array $options = []) : PromiseInterface + { + return $this->requestAsync('DELETE', $uri, $options); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 00000000..4090abcc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,240 @@ +strictMode = $strictMode; + foreach ($cookieArray as $cookie) { + if (!$cookie instanceof SetCookie) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + */ + public static function fromArray(array $cookies, string $domain) : self + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie(['Domain' => $domain, 'Name' => $name, 'Value' => $value, 'Discard' => \true])); + } + return $cookieJar; + } + /** + * Evaluate if this cookie should be persisted to storage + * that survives between requests. + * + * @param SetCookie $cookie Being evaluated. + * @param bool $allowSessionCookies If we should persist session cookies + */ + public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = \false) : bool + { + if ($cookie->getExpires() || $allowSessionCookies) { + if (!$cookie->getDiscard()) { + return \true; + } + } + return \false; + } + /** + * Finds and returns the cookie based on the name + * + * @param string $name cookie name to search for + * + * @return SetCookie|null cookie that was found or null if not found + */ + public function getCookieByName(string $name) : ?SetCookie + { + foreach ($this->cookies as $cookie) { + if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { + return $cookie; + } + } + return null; + } + public function toArray() : array + { + return \array_map(static function (SetCookie $cookie) : array { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + public function clear(?string $domain = null, ?string $path = null, ?string $name = null) : void + { + if ($domain === null) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = \array_filter($this->cookies, static function (SetCookie $cookie) use($domain) : bool { + return $cookie->getDomain() === null || !$cookie->matchesDomain($domain); + }); + } elseif (!$name) { + $this->cookies = \array_filter($this->cookies, static function (SetCookie $cookie) use($path, $domain) : bool { + return !($cookie->getDomain() !== null && $cookie->matchesPath($path) && $cookie->matchesDomain($domain)); + }); + } else { + $this->cookies = \array_filter($this->cookies, static function (SetCookie $cookie) use($path, $domain, $name) { + return !($cookie->getDomain() !== null && $cookie->getName() == $name && $cookie->matchesPath($path) && $cookie->matchesDomain($domain)); + }); + } + } + public function clearSessionCookies() : void + { + $this->cookies = \array_filter($this->cookies, static function (SetCookie $cookie) : bool { + return !$cookie->getDiscard() && $cookie->getExpires(); + }); + } + public function setCookie(SetCookie $cookie) : bool + { + // If the name string is empty (but not 0), ignore the set-cookie + // string entirely. + $name = $cookie->getName(); + if (!$name && $name !== '0') { + return \false; + } + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== \true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } + $this->removeCookieIfEmpty($cookie); + return \false; + } + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || $c->getDomain() != $cookie->getDomain() || $c->getName() != $cookie->getName()) { + continue; + } + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + // The cookie exists, so no need to continue + return \false; + } + $this->cookies[] = $cookie; + return \true; + } + public function count() : int + { + return \count($this->cookies); + } + /** + * @return \ArrayIterator + */ + public function getIterator() : \ArrayIterator + { + return new \ArrayIterator(\array_values($this->cookies)); + } + public function extractCookies(RequestInterface $request, ResponseInterface $response) : void + { + if ($cookieHeader = $response->getHeader('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getUri()->getHost()); + } + if (0 !== \strpos($sc->getPath(), '/')) { + $sc->setPath($this->getCookiePathFromRequest($request)); + } + if (!$sc->matchesDomain($request->getUri()->getHost())) { + continue; + } + // Note: At this point `$sc->getDomain()` being a public suffix should + // be rejected, but we don't want to pull in the full PSL dependency. + $this->setCookie($sc); + } + } + } + /** + * Computes cookie path following RFC 6265 section 5.1.4 + * + * @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4 + */ + private function getCookiePathFromRequest(RequestInterface $request) : string + { + $uriPath = $request->getUri()->getPath(); + if ('' === $uriPath) { + return '/'; + } + if (0 !== \strpos($uriPath, '/')) { + return '/'; + } + if ('/' === $uriPath) { + return '/'; + } + $lastSlashPos = \strrpos($uriPath, '/'); + if (0 === $lastSlashPos || \false === $lastSlashPos) { + return '/'; + } + return \substr($uriPath, 0, $lastSlashPos); + } + public function withCookieHeader(RequestInterface $request) : RequestInterface + { + $values = []; + $uri = $request->getUri(); + $scheme = $uri->getScheme(); + $host = $uri->getHost(); + $path = $uri->getPath() ?: '/'; + foreach ($this->cookies as $cookie) { + if ($cookie->getDomain() !== null && $cookie->matchesPath($path) && $cookie->matchesDomain($host) && !$cookie->isExpired() && (!$cookie->getSecure() || $scheme === 'https')) { + $values[] = $cookie->getName() . '=' . $cookie->getValue(); + } + } + return $values ? $request->withHeader('Cookie', \implode('; ', $values)) : $request; + } + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + */ + private function removeCookieIfEmpty(SetCookie $cookie) : void + { + $cookieValue = $cookie->getValue(); + if (($cookieValue === null || $cookieValue === '') && $cookie->getDomain() !== null) { + $this->clear($cookie->getDomain(), $cookie->getPath(), $cookie->getName()); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 00000000..21343bf3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,74 @@ + + */ +interface CookieJarInterface extends \Countable, \IteratorAggregate +{ + /** + * Create a request with added cookie headers. + * + * If no matching cookies are found in the cookie jar, then no Cookie + * header is added to the request and the same request is returned. + * + * @param RequestInterface $request Request object to modify. + * + * @return RequestInterface returns the modified request. + */ + public function withCookieHeader(RequestInterface $request) : RequestInterface; + /** + * Extract cookies from an HTTP response and store them in the CookieJar. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface $response Response that was received + */ + public function extractCookies(RequestInterface $request, ResponseInterface $response) : void; + /** + * Sets a cookie in the cookie jar. + * + * @param SetCookie $cookie Cookie to set. + * + * @return bool Returns true on success or false on failure + */ + public function setCookie(SetCookie $cookie) : bool; + /** + * Remove cookies currently held in the cookie jar. + * + * Invoking this method without arguments will empty the whole cookie jar. + * If given a $domain argument only cookies belonging to that domain will + * be removed. If given a $domain and $path argument, cookies belonging to + * the specified path within that domain are removed. If given all three + * arguments, then the cookie with the specified name, path and domain is + * removed. + * + * @param string|null $domain Clears cookies matching a domain + * @param string|null $path Clears cookies matching a domain and path + * @param string|null $name Clears cookies matching a domain, path, and name + */ + public function clear(?string $domain = null, ?string $path = null, ?string $name = null) : void; + /** + * Discard all sessions cookies. + * + * Removes cookies that don't have an expire field or a have a discard + * field set to true. To be called when the user agent shuts down according + * to RFC 2965. + */ + public function clearSessionCookies() : void; + /** + * Converts the cookie jar to an array. + */ + public function toArray() : array; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php new file mode 100644 index 00000000..8bf65d30 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php @@ -0,0 +1,92 @@ +filename = $cookieFile; + $this->storeSessionCookies = $storeSessionCookies; + if (\file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * + * @throws \RuntimeException if the file cannot be found or created + */ + public function save(string $filename) : void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + $jsonStr = Utils::jsonEncode($json); + if (\false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) { + throw new \RuntimeException("Unable to save file {$filename}"); + } + } + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load(string $filename) : void + { + $json = \file_get_contents($filename); + if (\false === $json) { + throw new \RuntimeException("Unable to load file {$filename}"); + } + if ($json === '') { + return; + } + $data = Utils::jsonDecode($json, \true); + if (\is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && !empty($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 00000000..e506af8a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,82 @@ +sessionKey = $sessionKey; + $this->storeSessionCookies = $storeSessionCookies; + $this->load(); + } + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + /** + * Save cookies to the client session + */ + public function save() : void + { + $json = []; + /** @var SetCookie $cookie */ + foreach ($this as $cookie) { + if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) { + $json[] = $cookie->toArray(); + } + } + $json = \json_encode($json); + if (\false === $json) { + throw new \RuntimeException('Unable to encode cookie data'); + } + $_SESSION[$this->sessionKey] = $json; + } + /** + * Load the contents of the client session into the data array + */ + protected function load() : void + { + if (!isset($_SESSION[$this->sessionKey])) { + return; + } + $json = $_SESSION[$this->sessionKey]; + if (!\is_string($json)) { + throw new \RuntimeException('Invalid cookie data'); + } + $data = \json_decode($json, \true); + if (\is_array($data)) { + foreach ($data as $cookie) { + if (!\is_array($cookie)) { + throw new \RuntimeException('Invalid cookie data'); + } + $this->setCookie(new SetCookie($cookie)); + } + } elseif (\is_scalar($data) && \strlen((string) $data)) { + throw new \RuntimeException('Invalid cookie data'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 00000000..e47ad402 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,421 @@ + null, 'Value' => null, 'Domain' => null, 'Path' => '/', 'Max-Age' => null, 'Expires' => null, 'Secure' => \false, 'Discard' => \false, 'HttpOnly' => \false]; + /** + * @var array Cookie data + */ + private $data; + /** + * Create a new SetCookie object from a string. + * + * @param string $cookie Set-Cookie header string + */ + public static function fromString(string $cookie) : self + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = \array_filter(\array_map('trim', \explode(';', $cookie))); + // The name of the cookie (first kvp) must exist and include an equal sign. + if (!isset($pieces[0]) || \strpos($pieces[0], '=') === \false) { + return new self($data); + } + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + $cookieParts = \explode('=', $part, 2); + $key = \trim($cookieParts[0]); + $value = isset($cookieParts[1]) ? \trim($cookieParts[1], " \n\r\t\x00\v") : \true; + // Only check for non-cookies when cookies have been found + if (!isset($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (\array_keys(self::$defaults) as $search) { + if (!\strcasecmp($search, $key)) { + if ($search === 'Max-Age') { + if (\is_numeric($value)) { + $data[$search] = (int) $value; + } + } elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') { + if ($value) { + $data[$search] = \true; + } + } else { + $data[$search] = $value; + } + continue 2; + } + } + $data[$key] = $value; + } + } + return new self($data); + } + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = self::$defaults; + if (isset($data['Name'])) { + $this->setName($data['Name']); + } + if (isset($data['Value'])) { + $this->setValue($data['Value']); + } + if (isset($data['Domain'])) { + $this->setDomain($data['Domain']); + } + if (isset($data['Path'])) { + $this->setPath($data['Path']); + } + if (isset($data['Max-Age'])) { + $this->setMaxAge($data['Max-Age']); + } + if (isset($data['Expires'])) { + $this->setExpires($data['Expires']); + } + if (isset($data['Secure'])) { + $this->setSecure($data['Secure']); + } + if (isset($data['Discard'])) { + $this->setDiscard($data['Discard']); + } + if (isset($data['HttpOnly'])) { + $this->setHttpOnly($data['HttpOnly']); + } + // Set the remaining values that don't have extra validation logic + foreach (\array_diff(\array_keys($data), \array_keys(self::$defaults)) as $key) { + $this->data[$key] = $data[$key]; + } + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(\time() + $this->getMaxAge()); + } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) { + $this->setExpires($expires); + } + } + public function __toString() + { + $str = $this->data['Name'] . '=' . ($this->data['Value'] ?? '') . '; '; + foreach ($this->data as $k => $v) { + if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== \false) { + if ($k === 'Expires') { + $str .= 'Expires=' . \gmdate('D, d M Y H:i:s \\G\\M\\T', $v) . '; '; + } else { + $str .= ($v === \true ? $k : "{$k}={$v}") . '; '; + } + } + } + return \rtrim($str, '; '); + } + public function toArray() : array + { + return $this->data; + } + /** + * Get the cookie name. + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + /** + * Set the cookie name. + * + * @param string $name Cookie name + */ + public function setName($name) : void + { + if (!\is_string($name)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Name'] = (string) $name; + } + /** + * Get the cookie value. + * + * @return string|null + */ + public function getValue() + { + return $this->data['Value']; + } + /** + * Set the cookie value. + * + * @param string $value Cookie value + */ + public function setValue($value) : void + { + if (!\is_string($value)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Value'] = (string) $value; + } + /** + * Get the domain. + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + /** + * Set the domain of the cookie. + * + * @param string|null $domain + */ + public function setDomain($domain) : void + { + if (!\is_string($domain) && null !== $domain) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Domain'] = null === $domain ? null : (string) $domain; + } + /** + * Get the path. + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + /** + * Set the path of the cookie. + * + * @param string $path Path of the cookie + */ + public function setPath($path) : void + { + if (!\is_string($path)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Path'] = (string) $path; + } + /** + * Maximum lifetime of the cookie in seconds. + * + * @return int|null + */ + public function getMaxAge() + { + return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age']; + } + /** + * Set the max-age of the cookie. + * + * @param int|null $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) : void + { + if (!\is_int($maxAge) && null !== $maxAge) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge; + } + /** + * The UNIX timestamp when the cookie Expires. + * + * @return string|int|null + */ + public function getExpires() + { + return $this->data['Expires']; + } + /** + * Set the unix timestamp for which the cookie will expire. + * + * @param int|string|null $timestamp Unix timestamp or any English textual datetime description. + */ + public function setExpires($timestamp) : void + { + if (!\is_int($timestamp) && !\is_string($timestamp) && null !== $timestamp) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + if (null === $timestamp) { + $this->data['Expires'] = null; + } elseif (\is_numeric($timestamp)) { + $this->data['Expires'] = (int) $timestamp; + } else { + // Store unparseable dates as session cookies, not as expired cookies. + $expires = \strtotime((string) $timestamp); + $this->data['Expires'] = $expires === \false ? null : $expires; + } + } + /** + * Get whether or not this is a secure cookie. + * + * @return bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + /** + * Set whether or not the cookie is secure. + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) : void + { + if (!\is_bool($secure)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Secure'] = (bool) $secure; + } + /** + * Get whether or not this is a session cookie. + * + * @return bool|null + */ + public function getDiscard() + { + return $this->data['Discard']; + } + /** + * Set whether or not this is a session cookie. + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) : void + { + if (!\is_bool($discard)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['Discard'] = (bool) $discard; + } + /** + * Get whether or not this is an HTTP only cookie. + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + /** + * Set whether or not this is an HTTP only cookie. + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) : void + { + if (!\is_bool($httpOnly)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->data['HttpOnly'] = (bool) $httpOnly; + } + /** + * Check if the cookie matches a path value. + * + * A request-path path-matches a given cookie-path if at least one of + * the following conditions holds: + * + * - The cookie-path and the request-path are identical. + * - The cookie-path is a prefix of the request-path, and the last + * character of the cookie-path is %x2F ("/"). + * - The cookie-path is a prefix of the request-path, and the first + * character of the request-path that is not included in the cookie- + * path is a %x2F ("/") character. + * + * @param string $requestPath Path to check against + */ + public function matchesPath(string $requestPath) : bool + { + $cookiePath = $this->getPath(); + // Match on exact matches or when path is the default empty "/" + if ($cookiePath === '/' || $cookiePath == $requestPath) { + return \true; + } + // Ensure that the cookie-path is a prefix of the request path. + if (0 !== \strpos($requestPath, $cookiePath)) { + return \false; + } + // Match if the last character of the cookie-path is "/" + if (\substr($cookiePath, -1, 1) === '/') { + return \true; + } + // Match if the first character not included in cookie path is "/" + return \substr($requestPath, \strlen($cookiePath), 1) === '/'; + } + /** + * Check if the cookie matches a domain value. + * + * @param string $domain Domain to check against + */ + public function matchesDomain(string $domain) : bool + { + $cookieDomain = $this->getDomain(); + if (null === $cookieDomain) { + return \true; + } + // Remove the leading '.' as per spec in RFC 6265. + // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3 + $cookieDomain = \ltrim(\strtolower($cookieDomain), '.'); + if ('' === $cookieDomain) { + return \false; + } + $domain = \strtolower($domain); + if ($domain === $cookieDomain) { + return \true; + } + // Matching the subdomain according to RFC 6265. + // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3 + if (\filter_var($domain, \FILTER_VALIDATE_IP)) { + return \false; + } + return (bool) \preg_match('/\\.' . \preg_quote($cookieDomain, '/') . '$/', $domain); + } + /** + * Check if the cookie is expired. + */ + public function isExpired() : bool + { + return $this->getExpires() !== null && \time() > $this->getExpires(); + } + /** + * Check if the cookie is valid according to RFC 6265. + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + $name = $this->getName(); + if ($name === '') { + return 'The cookie name must not be empty'; + } + // Check if any of the invalid characters are present in the cookie name + if (\preg_match('/[\\x00-\\x20\\x22\\x28-\\x29\\x2c\\x2f\\x3a-\\x40\\x5c\\x7b\\x7d\\x7f]/', $name)) { + return 'Cookie name must not contain invalid characters: ASCII ' . 'Control characters (0-31;127), space, tab and the ' . 'following characters: ()<>@,;:\\"/?={}'; + } + // Value must not be null. 0 and empty string are valid. Empty strings + // are technically against RFC 6265, but known to happen in the wild. + $value = $this->getValue(); + if ($value === null) { + return 'The cookie value must not be empty'; + } + // Domains must not be empty, but may be omitted. "0" is not a valid + // internet domain, but may be used as server name in a private network. + $domain = $this->getDomain(); + if ($domain === '' || null !== $domain && '' === \ltrim(\trim($domain), '.')) { + return 'The cookie domain must not be empty'; + } + return \true; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 00000000..89617dde --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,31 @@ +request = $request; + $this->handlerContext = $handlerContext; + } + /** + * Get the request that caused the exception + */ + public function getRequest() : RequestInterface + { + return $this->request; + } + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext() : array + { + return $this->handlerContext; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/GuzzleException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/GuzzleException.php new file mode 100644 index 00000000..ad5a9a54 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/GuzzleException.php @@ -0,0 +1,8 @@ +getStatusCode() : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + $this->handlerContext = $handlerContext; + } + /** + * Wrap non-RequestExceptions with a RequestException + * + * @deprecated since 7.11. Create a RequestException directly instead. + */ + public static function wrapException(RequestInterface $request, \Throwable $e) : RequestException + { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', '%s::wrapException() is deprecated and will be removed in 8.0. Create a %s directly instead.', self::class, self::class); + return $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + } + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request sent + * @param ResponseInterface $response Response received + * @param \Throwable|null $previous Previous exception + * @param array $handlerContext Optional handler context + * @param BodySummarizerInterface|null $bodySummarizer Optional body summarizer + */ + public static function create(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $previous = null, array $handlerContext = [], ?BodySummarizerInterface $bodySummarizer = null) : self + { + if (!$response) { + return new self('Error completing request', $request, null, $previous, $handlerContext); + } + $level = (int) \floor($response->getStatusCode() / 100); + if ($level === 4) { + $label = 'Client error'; + $className = ClientException::class; + } elseif ($level === 5) { + $label = 'Server error'; + $className = ServerException::class; + } else { + $label = 'Unsuccessful request'; + $className = __CLASS__; + } + $uri = \WPMailSMTP\Vendor\GuzzleHttp\Psr7\Utils::redactUserInfo($request->getUri()); + // Client Error: `GET /` resulted in a `404 Not Found` response: + // ... (truncated) + $message = \sprintf('%s: `%s %s` resulted in a `%s %s` response', $label, $request->getMethod(), $uri->__toString(), $response->getStatusCode(), $response->getReasonPhrase()); + $summary = ($bodySummarizer ?? new BodySummarizer())->summarize($response); + if ($summary !== null) { + $message .= ":\n{$summary}\n"; + } + return new $className($message, $request, $response, $previous, $handlerContext); + } + /** + * Get the request that caused the exception + */ + public function getRequest() : RequestInterface + { + return $this->request; + } + /** + * Get the associated response + */ + public function getResponse() : ?ResponseInterface + { + return $this->response; + } + /** + * Check if a response was received + */ + public function hasResponse() : bool + { + return $this->response !== null; + } + /** + * Get contextual information about the error from the underlying handler. + * + * The contents of this array will vary depending on which handler you are + * using. It may also be just an empty array. Relying on this data will + * couple you to a specific handler, but can give more debug information + * when needed. + */ + public function getHandlerContext() : array + { + return $this->handlerContext; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/ServerException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 00000000..c00261f4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,10 @@ +maxHandles = $maxHandles; + $this->shareMode = CurlShareHandleState::normalizeMode($shareMode, 'transport_sharing'); + if ($this->shareMode === TransportSharing::NONE && $shareHandle !== null) { + throw new \InvalidArgumentException('A cURL share handle cannot be provided when transport sharing is disabled.'); + } + if ($this->shareMode !== TransportSharing::NONE && $shareHandle === null) { + throw new \InvalidArgumentException('A cURL share handle is required when transport sharing is enabled.'); + } + if ($shareHandle !== null && !self::isCurlShareHandle($shareHandle)) { + throw new \InvalidArgumentException('A cURL share handle must be an instance of CurlShareHandle or a curl_share resource.'); + } + $this->shareHandle = $shareHandle; + } + /** + * @param mixed $value + */ + private static function isCurlShareHandle($value) : bool + { + if (\PHP_VERSION_ID < 80000) { + return \is_resource($value) && \get_resource_type($value) === 'curl_share'; + } + return $value instanceof \CurlShareHandle; + } + public function create(RequestInterface $request, array $options) : EasyHandle + { + $protocolVersion = $request->getProtocolVersion(); + if ('' === $protocolVersion) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.'); + $protocolVersion = '1.1'; + $request = Psr7\Utils::modifyRequest($request, ['version' => $protocolVersion]); + } + if ('2' === $protocolVersion || '2.0' === $protocolVersion) { + if (!CurlVersion::supportsHttp2()) { + throw new ConnectException('HTTP/2 is supported by the cURL handler, however libcurl is built without HTTP/2 support.', $request); + } + } elseif ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) { + throw new ConnectException(\sprintf('HTTP/%s is not supported by the cURL handler.', $protocolVersion), $request); + } + if (isset($options['curl']['body_as_string'])) { + $options['_body_as_string'] = $options['curl']['body_as_string']; + unset($options['curl']['body_as_string']); + } + self::triggerUnsupportedRequestOptionDeprecations($options); + $this->rejectRequestLevelShareConflict($options); + self::triggerUnsupportedCurlOptionDeprecations($options); + self::triggerConflictingCurlOptionDeprecations($options); + $easy = new EasyHandle(); + $easy->request = $request; + $easy->options = $options; + $conf = $this->getDefaultConf($easy); + $this->applyMethod($easy, $conf); + $this->applyHandlerOptions($easy, $conf); + $this->applyHeaders($easy, $conf); + unset($conf['_headers']); + // Add handler options from the request configuration options + if (isset($options['curl'])) { + $conf = \array_replace($conf, $options['curl']); + } + $easy->effectiveProxy = self::getEffectiveProxy($conf); + $conf[\CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy); + if ($this->shareHandle !== null) { + if (!\defined('CURLOPT_SHARE')) { + throw new \InvalidArgumentException('The configured cURL share handle requires CURLOPT_SHARE, but it is not available in the installed PHP cURL extension.'); + } + $conf[(int) \constant('CURLOPT_SHARE')] = $this->shareHandle; + } + $handle = $this->handles ? \array_pop($this->handles) : \curl_init(); + if (\false === $handle) { + throw new \RuntimeException('Can not initialize cURL handle.'); + } + $easy->handle = $handle; + try { + $this->applyCurlOptions($handle, $conf); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID < 80000 && \is_resource($handle)) { + \curl_close($handle); + } + unset($easy->handle); + throw $e; + } + return $easy; + } + /** + * @param resource|\CurlHandle $handle + * @param array $conf + */ + private function applyCurlOptions($handle, array $conf) : void + { + foreach ($conf as $option => $value) { + if (!\is_int($option)) { + throw new \InvalidArgumentException(\sprintf('Invalid cURL option %s.', self::formatCurlOption($option))); + } + try { + $success = \curl_setopt($handle, $option, $value); + } catch (\Throwable $e) { + throw new \InvalidArgumentException(\sprintf('Unable to set cURL option %s: %s', self::formatCurlOption($option), $e->getMessage()), 0, $e); + } + if (!$success) { + throw new \InvalidArgumentException(\sprintf('Unable to set cURL option %s.', self::formatCurlOption($option))); + } + } + } + private function rejectRequestLevelShareConflict(array $options) : void + { + if ($this->shareHandle === null) { + return; + } + if (!\defined('CURLOPT_SHARE') || !isset($options['curl']) || !\is_array($options['curl']) || !\array_key_exists((int) \constant('CURLOPT_SHARE'), $options['curl'])) { + return; + } + throw new \InvalidArgumentException('The request-level CURLOPT_SHARE cURL option cannot be combined with configured transport sharing.'); + } + /** + * @param int|string $option + */ + private static function formatCurlOption($option) : string + { + if (!\is_int($option)) { + return \sprintf('"%s"', $option); + } + static $names = null; + if (null === $names) { + $names = []; + foreach (\get_defined_constants(\true)['curl'] ?? [] as $name => $value) { + if (\is_int($value) && \strpos($name, 'CURLOPT_') === 0 && !isset($names[$value])) { + $names[$value] = $name; + } + } + } + if (isset($names[$option])) { + return \sprintf('%s (%d)', $names[$option], $option); + } + return (string) $option; + } + private static function triggerConflictingCurlOptionDeprecations(array $options) : void + { + if (!isset($options['curl']) || !\is_array($options['curl']) || $options['curl'] === []) { + return; + } + $conflictingOptions = self::conflictingCurlOptions(); + foreach ($options['curl'] as $option => $_) { + if (!\array_key_exists($option, $conflictingOptions)) { + continue; + } + $name = self::formatCurlOption($option); + $replacement = $conflictingOptions[$option]; + if ($replacement !== null) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', \sprintf('Passing %s in the "curl" request option is deprecated; guzzlehttp/guzzle 8.0 will reject this option because it conflicts with Guzzle-managed request handling. Use %s instead.', $name, $replacement)); + continue; + } + \trigger_deprecation('guzzlehttp/guzzle', '7.11', \sprintf('Passing %s in the "curl" request option is deprecated; guzzlehttp/guzzle 8.0 will reject this option because it conflicts with Guzzle-managed cURL internals.', $name)); + } + } + private static function triggerUnsupportedCurlOptionDeprecations(array $options) : void + { + if (!isset($options['curl']) || !\is_array($options['curl']) || $options['curl'] === []) { + return; + } + $supportedOptions = self::supportedCurlOptions(); + $conflictingOptions = self::conflictingCurlOptions(); + foreach ($options['curl'] as $option => $_) { + if (!\is_int($option) || \array_key_exists($option, $supportedOptions) || \array_key_exists($option, $conflictingOptions)) { + continue; + } + \trigger_deprecation('guzzlehttp/guzzle', '7.12', \sprintf('Passing %s in the "curl" request option is deprecated; guzzlehttp/guzzle 8.0 will reject raw cURL options outside the built-in cURL handlers\' allow-list.', self::formatCurlOption($option))); + } + } + private static function triggerUnsupportedRequestOptionDeprecations(array $options) : void + { + if (\array_key_exists('stream_context', $options)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "stream_context" request option to a cURL handler is deprecated; guzzlehttp/guzzle 8.0 will reject this option because cURL handlers ignore PHP stream context options.'); + } + } + /** + * @return array + */ + private static function conflictingCurlOptions() : array + { + static $options = null; + if ($options !== null) { + return $options; + } + $options = []; + self::addConflictingCurlOption($options, 'CURLOPT_SHARE', 'the "transport_sharing" client option or cURL handler option'); + self::addConflictingCurlOption($options, 'CURLOPT_URL', 'the request URI'); + self::addConflictingCurlOption($options, 'CURLOPT_PORT', 'the request URI'); + self::addConflictingCurlOption($options, 'CURLOPT_CUSTOMREQUEST', 'the request method'); + self::addConflictingCurlOption($options, 'CURLOPT_HTTPGET', 'the request method'); + self::addConflictingCurlOption($options, 'CURLOPT_POST', 'the request method and body'); + self::addConflictingCurlOption($options, 'CURLOPT_PUT', 'the request method and body'); + self::addConflictingCurlOption($options, 'CURLOPT_NOBODY', 'the request method'); + self::addConflictingCurlOption($options, 'CURLOPT_UPLOAD', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_POSTFIELDS', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_READFUNCTION', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_READDATA', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_INFILE', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_INFILESIZE', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_INFILESIZE_LARGE', 'the request body'); + self::addConflictingCurlOption($options, 'CURLOPT_HTTPHEADER', 'the request headers'); + self::addConflictingCurlOption($options, 'CURLOPT_USERAGENT', 'the request headers'); + self::addConflictingCurlOption($options, 'CURLOPT_REFERER', 'the request headers'); + self::addConflictingCurlOption($options, 'CURLOPT_HEADERFUNCTION', 'the "on_headers" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_WRITEFUNCTION', 'the "sink" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_FILE', 'the "sink" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_TIMEOUT', 'the "timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_TIMEOUT_MS', 'the "timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CONNECTTIMEOUT', 'the "connect_timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CONNECTTIMEOUT_MS', 'the "connect_timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_NOSIGNAL', 'the "timeout" or "connect_timeout" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_NOPROGRESS', 'the "progress" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROGRESSFUNCTION', 'the "progress" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_XFERINFOFUNCTION', 'the "progress" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_VERBOSE', 'the "debug" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_STDERR', 'the "debug" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROXY', 'the "proxy" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_NOPROXY', 'the "proxy" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_FOLLOWLOCATION', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_MAXREDIRS', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_POSTREDIR', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_REDIR_PROTOCOLS', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_REDIR_PROTOCOLS_STR', 'the "allow_redirects" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROTOCOLS', 'the "protocols" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_PROTOCOLS_STR', 'the "protocols" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_HTTP_VERSION', 'the request protocol version'); + self::addConflictingCurlOption($options, 'CURLOPT_IPRESOLVE', 'the "force_ip_resolve" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSL_VERIFYPEER', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSL_VERIFYHOST', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CAINFO', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_CAPATH', 'the "verify" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLVERSION', 'the "crypto_method" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLCERT', 'the "cert" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLCERTPASSWD', 'the "cert" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLCERTTYPE', 'the "cert_type" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLKEY', 'the "ssl_key" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLKEYPASSWD', 'the "ssl_key" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_KEYPASSWD', 'the "ssl_key" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_SSLKEYTYPE', 'the "ssl_key_type" request option'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIE', 'the "Cookie" request header or Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIEFILE', 'Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIEJAR', 'Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIELIST', 'Guzzle cookie middleware'); + self::addConflictingCurlOption($options, 'CURLOPT_COOKIESESSION', 'Guzzle cookie middleware'); + return $options; + } + /** + * @return array + */ + private static function supportedCurlOptions() : array + { + static $options = null; + if ($options !== null) { + return $options; + } + $options = []; + self::addSupportedCurlOption($options, 'CURLOPT_ADDRESS_SCOPE'); + self::addSupportedCurlOption($options, 'CURLOPT_CONNECT_TO'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_CACHE_TIMEOUT'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_INTERFACE'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_LOCAL_IP4'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_LOCAL_IP6'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_SERVERS'); + self::addSupportedCurlOption($options, 'CURLOPT_DNS_SHUFFLE_ADDRESSES'); + self::addSupportedCurlOption($options, 'CURLOPT_ENCODING'); + self::addSupportedCurlOption($options, 'CURLOPT_FORBID_REUSE'); + self::addSupportedCurlOption($options, 'CURLOPT_FRESH_CONNECT'); + self::addSupportedCurlOption($options, 'CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS'); + self::addSupportedCurlOption($options, 'CURLOPT_HTTPAUTH'); + self::addSupportedCurlOption($options, 'CURLOPT_INTERFACE'); + self::addSupportedCurlOption($options, 'CURLOPT_LOCALPORT'); + self::addSupportedCurlOption($options, 'CURLOPT_LOCALPORTRANGE'); + self::addSupportedCurlOption($options, 'CURLOPT_LOW_SPEED_LIMIT'); + self::addSupportedCurlOption($options, 'CURLOPT_LOW_SPEED_TIME'); + self::addSupportedCurlOption($options, 'CURLOPT_MAXAGE_CONN'); + self::addSupportedCurlOption($options, 'CURLOPT_MAXCONNECTS'); + self::addSupportedCurlOption($options, 'CURLOPT_MAXLIFETIME_CONN'); + self::addSupportedCurlOption($options, 'CURLOPT_HTTPPROXYTUNNEL'); + self::addSupportedCurlOption($options, 'CURLOPT_PROXYHEADER'); + self::addSupportedCurlOption($options, 'CURLOPT_PROXYUSERPWD'); + self::addSupportedCurlOption($options, 'CURLOPT_RESOLVE'); + self::addSupportedCurlOption($options, 'CURLOPT_SSL_CIPHER_LIST'); + self::addSupportedCurlOption($options, 'CURLOPT_SSL_EC_CURVES'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_FASTOPEN'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPALIVE'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPIDLE'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPINTVL'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_KEEPCNT'); + self::addSupportedCurlOption($options, 'CURLOPT_TCP_NODELAY'); + self::addSupportedCurlOption($options, 'CURLOPT_TLS13_CIPHERS'); + self::addSupportedCurlOption($options, 'CURLOPT_UNIX_SOCKET_PATH'); + self::addSupportedCurlOption($options, 'CURLOPT_USERPWD'); + return $options; + } + /** + * @param array $options + */ + private static function addSupportedCurlOption(array &$options, string $constant) : void + { + if (!\defined($constant)) { + return; + } + $value = \constant($constant); + if (\is_int($value)) { + $options[$value] = \true; + } + } + /** + * @param array $options + */ + private static function addConflictingCurlOption(array &$options, string $constant, ?string $replacement) : void + { + if (!\defined($constant)) { + return; + } + $value = \constant($constant); + if (\is_int($value)) { + $options[$value] = $replacement; + } + } + public function release(EasyHandle $easy) : void + { + $resource = $easy->handle; + unset($easy->handle); + if (\count($this->handles) >= $this->maxHandles) { + if (\PHP_VERSION_ID < 80000) { + \curl_close($resource); + } + } else { + // Remove all callback functions as they can hold onto references + // and are not cleaned up by curl_reset. Using curl_setopt_array + // does not work for some reason, so removing each one + // individually. + \curl_setopt($resource, \CURLOPT_HEADERFUNCTION, null); + \curl_setopt($resource, \CURLOPT_READFUNCTION, null); + \curl_setopt($resource, \CURLOPT_WRITEFUNCTION, null); + \curl_setopt($resource, \CURLOPT_PROGRESSFUNCTION, null); + \curl_reset($resource); + $this->handles[] = $resource; + } + } + /** + * Completes a cURL transaction, either returning a response promise or a + * rejected promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + * @param CurlFactoryInterface $factory Dictates how the handle is released + */ + public static function finish(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory) : PromiseInterface + { + if (isset($easy->options['on_stats'])) { + self::invokeStats($easy); + } + if (!$easy->response || $easy->errno) { + return self::finishError($handler, $easy, $factory); + } + // Return the response if it is present and there is no error. + $factory->release($easy); + // Rewind the body of the response if possible. + $body = $easy->response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + return new FulfilledPromise($easy->response); + } + private static function invokeStats(EasyHandle $easy) : void + { + $curlStats = \curl_getinfo($easy->handle); + $curlStats['appconnect_time'] = \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME); + $stats = new TransferStats($easy->request, $easy->response, $curlStats['total_time'], $easy->errno, $curlStats); + $easy->options['on_stats']($stats); + } + /** + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function finishError(callable $handler, EasyHandle $easy, CurlFactoryInterface $factory) : PromiseInterface + { + // Get error information and release the handle to the factory. + $ctx = ['errno' => $easy->errno, 'error' => \curl_error($easy->handle), 'appconnect_time' => \curl_getinfo($easy->handle, \CURLINFO_APPCONNECT_TIME)] + \curl_getinfo($easy->handle); + $ctx[self::CURL_VERSION_STR] = CurlVersion::getVersion() ?? ''; + $factory->release($easy); + // Retry when nothing is present or when curl failed to rewind. + if (empty($easy->options['_err_message']) && (!$easy->errno || $easy->errno == 65)) { + return self::retryFailedRewind($handler, $easy, $ctx); + } + return self::createRejection($easy, $ctx); + } + private static function createRejection(EasyHandle $easy, array $ctx) : PromiseInterface + { + static $connectionErrors = [\CURLE_OPERATION_TIMEOUTED => \true, \CURLE_COULDNT_RESOLVE_HOST => \true, \CURLE_COULDNT_CONNECT => \true, \CURLE_SSL_CONNECT_ERROR => \true, \CURLE_GOT_NOTHING => \true]; + if ($easy->createResponseException) { + return P\Create::rejectionFor(new RequestException('An error was encountered while creating the response', $easy->request, null, $easy->createResponseException, $ctx)); + } + // If an exception was encountered during the onHeaders event, then + // return a rejected promise that wraps that exception. + if ($easy->onHeadersException) { + return P\Create::rejectionFor(new RequestException('An error was encountered during the on_headers event', $easy->request, $easy->response, $easy->onHeadersException, $ctx)); + } + $uri = $easy->request->getUri(); + $sanitizedError = self::sanitizeCurlError($ctx['error'] ?? '', $uri, $easy->effectiveProxy); + $message = \sprintf('cURL error %s: %s (%s)', $ctx['errno'], $sanitizedError, 'see https://curl.se/libcurl/c/libcurl-errors.html'); + if ('' !== $sanitizedError) { + $redactedUriString = Psr7\Utils::redactUserInfo($uri)->__toString(); + if ($redactedUriString !== '' && \false === \strpos($sanitizedError, $redactedUriString)) { + $message .= \sprintf(' for %s', $redactedUriString); + } + } + // Create a connection exception if it was a specific error code. + $error = isset($connectionErrors[$easy->errno]) ? new ConnectException($message, $easy->request, null, $ctx) : new RequestException($message, $easy->request, $easy->response, null, $ctx); + return P\Create::rejectionFor($error); + } + private static function sanitizeCurlError(string $error, UriInterface $uri, ?string $proxy = null) : string + { + if ('' === $error) { + return $error; + } + $error = self::redactProxyUserInfo($error, $proxy); + $baseUri = $uri->withQuery('')->withFragment(''); + $baseUriString = $baseUri->__toString(); + if ('' === $baseUriString) { + return $error; + } + $redactedUriString = Psr7\Utils::redactUserInfo($baseUri)->__toString(); + return \str_replace($baseUriString, $redactedUriString, $error); + } + private static function redactProxyUserInfo(string $error, ?string $proxy) : string + { + if ($proxy === null || $proxy === '' || \strpos($proxy, '@') === \false) { + return $error; + } + // The error message embeds the proxy string exactly as configured, + // so the userinfo needle is extracted with parse_url(): Psr7\Uri + // normalizes the components, which could make the replacement miss. + $proxyForParsing = \strpos($proxy, '://') === \false ? 'http://' . $proxy : $proxy; + $proxyParts = \parse_url($proxyForParsing); + if (!\is_array($proxyParts)) { + // Proxy strings that defeat parse_url() are exactly the ones + // libcurl embeds verbatim in error text such as "Unsupported + // proxy syntax in '...'": redact everything up to the last '@' + // of the authority as a safe-side fallback. + $authority = \substr($proxyForParsing, \strpos($proxyForParsing, '://') + 3); + $atPosition = \strrpos($authority, '@'); + if ($atPosition === \false || $atPosition === 0) { + return $error; + } + return \str_replace(\substr($authority, 0, $atPosition) . '@', '***@', $error); + } + if (!isset($proxyParts['user']) && !isset($proxyParts['pass'])) { + return $error; + } + $userInfo = $proxyParts['user'] ?? ''; + if (isset($proxyParts['pass'])) { + $userInfo .= ':' . $proxyParts['pass']; + } + if ($userInfo === '') { + return $error; + } + $redactedUserInfo = '***'; + try { + $proxyUri = new Uri($proxyForParsing); + $redactedUserInfo = Psr7\Utils::redactUserInfo($proxyUri)->getUserInfo(); + if ($redactedUserInfo === $proxyUri->getUserInfo()) { + return $error; + } + } catch (\InvalidArgumentException $e) { + // Unparseable as a URI: fall back to redacting the whole userinfo. + } + return \str_replace($userInfo . '@', $redactedUserInfo . '@', $error); + } + /** + * @param array $conf + */ + private static function getEffectiveProxy(array $conf) : ?string + { + if (!\array_key_exists(\CURLOPT_PROXY, $conf)) { + return null; + } + $proxy = $conf[\CURLOPT_PROXY]; + return \is_string($proxy) && $proxy !== '' ? $proxy : null; + } + private static function proxyScheme(string $proxy) : ?string + { + $position = \strpos($proxy, '://'); + return $position === \false ? null : \strtolower(\substr($proxy, 0, $position)); + } + /** + * @return array + */ + private function getDefaultConf(EasyHandle $easy) : array + { + $conf = ['_headers' => $easy->request->getHeaders(), \CURLOPT_CUSTOMREQUEST => $easy->request->getMethod(), \CURLOPT_URL => (string) $easy->request->getUri()->withFragment(''), \CURLOPT_RETURNTRANSFER => \false, \CURLOPT_HEADER => \false, \CURLOPT_CONNECTTIMEOUT => 300]; + $protocols = Utils::normalizeProtocols($easy->options['protocols'] ?? ['http', 'https']); + $scheme = $easy->request->getUri()->getScheme(); + if (!\in_array($scheme, $protocols, \true)) { + throw new RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', $scheme), $easy->request); + } + if (\defined('CURLOPT_PROTOCOLS')) { + $conf[\CURLOPT_PROTOCOLS] = self::curlProtocolMask($protocols); + } + $version = $easy->request->getProtocolVersion(); + if ('2' === $version || '2.0' === $version) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0; + } elseif ('1.1' === $version) { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1; + } else { + $conf[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0; + } + return $conf; + } + /** + * @param string[] $protocols + */ + private static function curlProtocolMask(array $protocols) : int + { + $mask = 0; + if (\in_array('http', $protocols, \true)) { + $mask |= \CURLPROTO_HTTP; + } + if (\in_array('https', $protocols, \true)) { + $mask |= \CURLPROTO_HTTPS; + } + return $mask; + } + /** + * @param mixed $type + */ + private static function normalizeTlsFileType(string $option, $type) : string + { + if (!\is_string($type) || $type === '') { + throw new \InvalidArgumentException(\sprintf('%s must be a non-empty string', $option)); + } + return \strtoupper($type); + } + private static function shouldValidateSslKeyFile(?string $type) : bool + { + return $type !== 'ENG' && $type !== 'PROV'; + } + private function applyMethod(EasyHandle $easy, array &$conf) : void + { + $body = $easy->request->getBody(); + $size = $body->getSize(); + if ($size === null || $size > 0) { + $this->applyBody($easy->request, $easy->options, $conf); + return; + } + $method = $easy->request->getMethod(); + if ($method === 'PUT' || $method === 'POST') { + // See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 + if (!$easy->request->hasHeader('Content-Length')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + } elseif ($method === 'HEAD') { + $conf[\CURLOPT_NOBODY] = \true; + unset($conf[\CURLOPT_WRITEFUNCTION], $conf[\CURLOPT_READFUNCTION], $conf[\CURLOPT_FILE], $conf[\CURLOPT_INFILE]); + } + } + private function applyBody(RequestInterface $request, array $options, array &$conf) : void + { + $size = $request->hasHeader('Content-Length') ? (int) $request->getHeaderLine('Content-Length') : null; + // Send the body as a string if the size is less than 1MB OR if the + // [curl][body_as_string] request value is set. + if ($size !== null && $size < 1000000 || !empty($options['_body_as_string'])) { + $conf[\CURLOPT_POSTFIELDS] = (string) $request->getBody(); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $conf); + $this->removeHeader('Transfer-Encoding', $conf); + } else { + $conf[\CURLOPT_UPLOAD] = \true; + if ($size !== null) { + $conf[\CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $conf); + } + $body = $request->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $conf[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use($body) { + return $body->read($length); + }; + } + // If the Expect header is not present, prevent curl from adding it + if (!$request->hasHeader('Expect')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Expect:'; + } + // cURL sometimes adds a content-type by default. Prevent this. + if (!$request->hasHeader('Content-Type')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + private function applyHeaders(EasyHandle $easy, array &$conf) : void + { + foreach ($conf['_headers'] as $name => $values) { + foreach ($values as $value) { + $value = (string) $value; + if ($value === '') { + // cURL requires a special format for empty headers. + // See https://github.com/guzzle/guzzle/issues/1882 for more details. + $conf[\CURLOPT_HTTPHEADER][] = "{$name};"; + } else { + $conf[\CURLOPT_HTTPHEADER][] = "{$name}: {$value}"; + } + } + } + // Remove the Accept header if one was not set + if (!$easy->request->hasHeader('Accept')) { + $conf[\CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader(string $name, array &$options) : void + { + foreach (\array_keys($options['_headers']) as $key) { + if (!\strcasecmp((string) $key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + private function applyHandlerOptions(EasyHandle $easy, array &$conf) : void + { + $options = $easy->options; + if (isset($options['verify'])) { + if ($options['verify'] === \false) { + unset($conf[\CURLOPT_CAINFO]); + $conf[\CURLOPT_SSL_VERIFYHOST] = 0; + $conf[\CURLOPT_SSL_VERIFYPEER] = \false; + } else { + $conf[\CURLOPT_SSL_VERIFYHOST] = 2; + $conf[\CURLOPT_SSL_VERIFYPEER] = \true; + if (\is_string($options['verify'])) { + // Throw an error if the file/folder/link path is not valid or doesn't exist. + if (!\file_exists($options['verify'])) { + throw new \InvalidArgumentException("SSL CA bundle not found: {$options['verify']}"); + } + // If it's a directory or a link to a directory use CURLOPT_CAPATH. + // If not, it's probably a file, or a link to a file, so use CURLOPT_CAINFO. + if (\is_dir($options['verify']) || \is_link($options['verify']) === \true && ($verifyLink = \readlink($options['verify'])) !== \false && \is_dir($verifyLink)) { + $conf[\CURLOPT_CAPATH] = $options['verify']; + } else { + $conf[\CURLOPT_CAINFO] = $options['verify']; + } + } + } + } + if (!isset($options['curl'][\CURLOPT_ENCODING]) && !empty($options['decode_content'])) { + $accept = $easy->request->getHeaderLine('Accept-Encoding'); + if ($accept) { + $conf[\CURLOPT_ENCODING] = $accept; + } else { + // The empty string enables all available decoders and implicitly + // sets a matching 'Accept-Encoding' header. + $conf[\CURLOPT_ENCODING] = ''; + // But as the user did not specify any encoding preference, + // let's leave it up to server by preventing curl from sending + // the header, which will be interpreted as 'Accept-Encoding: *'. + // https://www.rfc-editor.org/rfc/rfc9110#field.accept-encoding + $conf[\CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + } + if (!isset($options['sink'])) { + // Use a default temp stream if no sink was set. + $options['sink'] = Psr7\Utils::tryFopen('php://temp', 'w+'); + } + $sink = $options['sink']; + if (!\is_string($sink)) { + $sink = Psr7\Utils::streamFor($sink); + } elseif (!\is_dir(\dirname($sink))) { + // Ensure that the directory exists before failing in curl. + throw new \RuntimeException(\sprintf('Directory %s does not exist for sink value of %s', \dirname($sink), $sink)); + } else { + $sink = new LazyOpenStream($sink, 'w+'); + } + $easy->sink = $sink; + $conf[\CURLOPT_WRITEFUNCTION] = static function ($ch, $write) use($sink) : int { + return $sink->write($write); + }; + $timeoutRequiresNoSignal = \false; + if (isset($options['timeout'])) { + $timeoutRequiresNoSignal |= $options['timeout'] < 1; + $conf[\CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000; + } + // CURL default value is CURL_IPRESOLVE_WHATEVER + if (isset($options['force_ip_resolve'])) { + if ('v4' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V4; + } elseif ('v6' === $options['force_ip_resolve']) { + $conf[\CURLOPT_IPRESOLVE] = \CURL_IPRESOLVE_V6; + } + } + if (isset($options['connect_timeout'])) { + $timeoutRequiresNoSignal |= $options['connect_timeout'] < 1; + $conf[\CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000; + } + if ($timeoutRequiresNoSignal && \strtoupper(\substr(\PHP_OS, 0, 3)) !== 'WIN') { + $conf[\CURLOPT_NOSIGNAL] = \true; + } + // Always pin CURLOPT_PROXY (and CURLOPT_NOPROXY when available) so + // that libcurl never falls back to reading proxy environment + // variables itself. When the proxy request option makes no decision, + // the environment is resolved here with libcurl's own semantics. + $proxyConf = null; + $noProxyConf = ''; + if (isset($options['proxy'])) { + if (!\is_array($options['proxy'])) { + $proxyConf = $options['proxy']; + } else { + $scheme = $easy->request->getUri()->getScheme(); + if (isset($options['proxy'][$scheme])) { + if (isset($options['proxy']['no']) && Utils::isUriInNoProxy($easy->request->getUri(), $options['proxy']['no'])) { + $proxyConf = ''; + $noProxyConf = '*'; + } else { + $proxyConf = $options['proxy'][$scheme]; + } + } + } + } + if ($proxyConf === null) { + $proxyConf = ProxyEnvironment::getProxyForScheme($easy->request->getUri()->getScheme()); + if ($proxyConf === null) { + $proxyConf = ''; + } elseif (($noProxy = ProxyEnvironment::getNoProxy()) !== null && Utils::isUriInNoProxy($easy->request->getUri(), ProxyEnvironment::splitNoProxy($noProxy))) { + // The environment no_proxy list is tokenized the way libcurl + // tokenizes it and matched here with the same rules as the + // proxy option's "no" list, so behavior does not depend on + // the installed libcurl's matcher. + $proxyConf = ''; + $noProxyConf = '*'; + } + } + if (\is_string($proxyConf) && $proxyConf !== '') { + $scheme = self::proxyScheme($proxyConf); + if ($scheme !== null && \preg_match('/^[a-z][a-z0-9.+-]*$/D', $scheme) !== 1) { + // A "://" with a prefix that is not a valid scheme (leading + // junk such as a space or non-breaking space) is treated by + // libcurl as an unknown scheme and silently downgraded to a + // plaintext HTTP proxy. Fail closed before any bytes reach the + // wire. + throw new RequestException('The proxy URL is malformed.', $easy->request); + } + if ($scheme === 'https' && !CurlVersion::supportsHttpsProxy()) { + // libcurl before 7.50.2 silently downgrades an https:// proxy + // to a plaintext HTTP proxy; 7.50.2 through 7.51, and builds + // without HTTPS-proxy support, fail at connect time. Fail + // closed before any bytes reach the wire. + throw new RequestException('HTTPS proxies are not supported by the installed libcurl; libcurl 7.52.0 or newer built with HTTPS-proxy support is required.', $easy->request); + } + } + $conf[\CURLOPT_PROXY] = $proxyConf; + if (\defined('CURLOPT_NOPROXY')) { + $conf[(int) \constant('CURLOPT_NOPROXY')] = $noProxyConf; + } + if (isset($options['crypto_method'])) { + $protocolVersion = $easy->request->getProtocolVersion(); + // If HTTP/2, upgrade TLS 1.0 and 1.1 to 1.2 + if ('2' === $protocolVersion || '2.0' === $protocolVersion) { + if (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method'] || \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method'] || \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) { + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2; + } elseif (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) { + if (!CurlVersion::supportsTls13()) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3; + } else { + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT === $options['crypto_method']) { + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_0; + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT === $options['crypto_method']) { + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_1; + } elseif (\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT === $options['crypto_method']) { + if (!CurlVersion::supportsTls12()) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.2 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_2; + } elseif (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT === $options['crypto_method']) { + if (!CurlVersion::supportsTls13()) { + throw new \InvalidArgumentException('Invalid crypto_method request option: TLS 1.3 not supported by your version of cURL'); + } + $conf[\CURLOPT_SSLVERSION] = \CURL_SSLVERSION_TLSv1_3; + } else { + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + } + $certType = null; + if (isset($options['cert_type'])) { + $certType = self::normalizeTlsFileType('cert_type', $options['cert_type']); + $conf[\CURLOPT_SSLCERTTYPE] = $certType; + } + if (isset($options['cert'])) { + $cert = $options['cert']; + if (\is_array($cert)) { + if (!isset($cert[0]) || !\is_string($cert[0])) { + throw new \InvalidArgumentException('Invalid cert request option'); + } + if (isset($cert[1])) { + if (!\is_string($cert[1])) { + throw new \InvalidArgumentException('Invalid cert request option'); + } + $conf[\CURLOPT_SSLCERTPASSWD] = $cert[1]; + } + $cert = $cert[0]; + } + if (!\is_string($cert)) { + throw new \InvalidArgumentException('Invalid cert request option'); + } + if (!\file_exists($cert)) { + throw new \InvalidArgumentException("SSL certificate not found: {$cert}"); + } + // OpenSSL (versions 0.9.3 and later) also support "P12" for PKCS#12-encoded files. + // see https://curl.se/libcurl/c/CURLOPT_SSLCERTTYPE.html + $ext = \pathinfo($cert, \PATHINFO_EXTENSION); + if ($certType === null && \preg_match('#^(der|p12)$#i', $ext)) { + $conf[\CURLOPT_SSLCERTTYPE] = \strtoupper($ext); + } + $conf[\CURLOPT_SSLCERT] = $cert; + } + $sslKeyType = null; + if (isset($options['ssl_key_type'])) { + $sslKeyType = self::normalizeTlsFileType('ssl_key_type', $options['ssl_key_type']); + $conf[\CURLOPT_SSLKEYTYPE] = $sslKeyType; + } + if (isset($options['ssl_key'])) { + if (\is_array($options['ssl_key'])) { + if (!isset($options['ssl_key'][0]) || !\is_string($options['ssl_key'][0])) { + throw new \InvalidArgumentException('Invalid ssl_key request option'); + } + if (isset($options['ssl_key'][1])) { + if (!\is_string($options['ssl_key'][1])) { + throw new \InvalidArgumentException('Invalid ssl_key request option'); + } + $conf[\CURLOPT_SSLKEYPASSWD] = $options['ssl_key'][1]; + } + $sslKey = $options['ssl_key'][0]; + } + $sslKey = $sslKey ?? $options['ssl_key']; + if (!\is_string($sslKey)) { + throw new \InvalidArgumentException('Invalid ssl_key request option'); + } + if (self::shouldValidateSslKeyFile($sslKeyType) && !\file_exists($sslKey)) { + throw new \InvalidArgumentException("SSL private key not found: {$sslKey}"); + } + $conf[\CURLOPT_SSLKEY] = $sslKey; + } + if (isset($options['progress'])) { + $progress = $options['progress']; + if (!\is_callable($progress)) { + throw new \InvalidArgumentException('progress client option must be callable'); + } + $conf[\CURLOPT_NOPROGRESS] = \false; + $conf[\CURLOPT_PROGRESSFUNCTION] = static function ($resource, int $downloadSize, int $downloaded, int $uploadSize, int $uploaded) use($progress) { + $progress($downloadSize, $downloaded, $uploadSize, $uploaded); + }; + } + if (!empty($options['debug'])) { + $conf[\CURLOPT_STDERR] = Utils::debugResource($options['debug']); + $conf[\CURLOPT_VERBOSE] = \true; + } + } + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + * + * @param callable(RequestInterface, array): PromiseInterface $handler + */ + private static function retryFailedRewind(callable $handler, EasyHandle $easy, array $ctx) : PromiseInterface + { + try { + // Only rewind if the body has been read from. + $body = $easy->request->getBody(); + if ($body->tell() > 0) { + $body->rewind(); + } + } catch (\RuntimeException $e) { + $ctx['error'] = 'The connection unexpectedly failed without ' . 'providing an error. The request would have been retried, ' . 'but attempting to rewind the request body failed. ' . 'Exception: ' . $e; + return self::createRejection($easy, $ctx); + } + // Retry no more than 3 times before giving up. + if (!isset($easy->options['_curl_retries'])) { + $easy->options['_curl_retries'] = 1; + } elseif ($easy->options['_curl_retries'] == 2) { + $ctx['error'] = 'The cURL request was retried 3 times ' . 'and did not succeed. The most likely reason for the failure ' . 'is that cURL was unable to rewind the body of the request ' . 'and subsequent retries resulted in the same error. Turn on ' . 'the debug option to see what went wrong. See ' . 'https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createRejection($easy, $ctx); + } else { + ++$easy->options['_curl_retries']; + } + return $handler($easy->request, $easy->options); + } + private function createHeaderFn(EasyHandle $easy) : callable + { + if (isset($easy->options['on_headers'])) { + $onHeaders = $easy->options['on_headers']; + if (!\is_callable($onHeaders)) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + } else { + $onHeaders = null; + } + return static function ($ch, $h) use($onHeaders, $easy, &$startingResponse) { + $value = \trim($h); + if ($value === '') { + $startingResponse = \true; + try { + $easy->createResponse(); + } catch (\Throwable $e) { + $easy->response = null; + $easy->createResponseException = $e; + return -1; + } + if ($onHeaders !== null) { + try { + $onHeaders($easy->response); + } catch (\Throwable $e) { + // Associate the exception with the handle and trigger + // a curl header write error by returning 0. + $easy->onHeadersException = $e; + return -1; + } + } + } elseif ($startingResponse) { + $startingResponse = \false; + $easy->headers = [$value]; + } else { + $easy->headers[] = $value; + } + return \strlen($h); + }; + } + public function __destruct() + { + foreach ($this->handles as $id => $handle) { + if (\PHP_VERSION_ID < 80000) { + \curl_close($handle); + } + unset($this->handles[$id]); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php new file mode 100644 index 00000000..d0ee0066 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php @@ -0,0 +1,23 @@ +shareHandleState = null; + $this->factory = $options['handle_factory']; + return; + } + $this->shareHandleState = $sharingMode !== TransportSharing::NONE ? CurlShareHandleState::fromOption($transportSharing) : null; + $this->factory = $this->shareHandleState !== null ? new CurlFactory(3, $this->shareHandleState->mode, $this->shareHandleState->handle) : new CurlFactory(3); + } + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + $easy = $this->factory->create($request, $options); + \curl_exec($easy->handle); + $easy->errno = \curl_errno($easy->handle); + return CurlFactory::finish($this, $easy, $this->factory); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php new file mode 100644 index 00000000..f8a83c0c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php @@ -0,0 +1,313 @@ + An array of delay times, indexed by handle id in `addRequest`. + * + * @see CurlMultiHandler::addRequest + */ + private $delays = []; + /** + * @var array An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt() + */ + private $options = []; + /** @var resource|\CurlMultiHandle */ + private $_mh; + /** + * @var bool + */ + private $executingMulti = \false; + /** + * @var array + */ + private $deferredCancels = []; + /** + * This handler accepts the following options: + * + * - handle_factory: An optional factory used to create curl handles + * - transport_sharing: Optional transport sharing mode. + * - select_timeout: Optional timeout (in seconds) to block before timing + * out while selecting curl handles. Defaults to 1 second. + * - options: An associative array of CURLMOPT_* options and + * corresponding values for curl_multi_setopt() + */ + public function __construct(array $options = []) + { + CurlShareHandleState::assertNoRequiredSharingCustomFactoryConflict($options, 'CurlMultiHandler'); + $transportSharing = $options['transport_sharing'] ?? null; + $sharingMode = CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing'); + if (\array_key_exists('handle_factory', $options) && $options['handle_factory'] !== null) { + $this->shareHandleState = null; + $this->factory = $options['handle_factory']; + } else { + $this->shareHandleState = $sharingMode !== TransportSharing::NONE ? CurlShareHandleState::fromOption($transportSharing) : null; + $this->factory = $this->shareHandleState !== null ? new CurlFactory(50, $this->shareHandleState->mode, $this->shareHandleState->handle) : new CurlFactory(50); + } + if (isset($options['select_timeout'])) { + $this->selectTimeout = $options['select_timeout']; + } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) { + \trigger_deprecation('guzzlehttp/guzzle', '7.2', 'The GUZZLE_CURL_SELECT_TIMEOUT environment variable is deprecated; use the "select_timeout" option instead.'); + $this->selectTimeout = (int) $selectTimeout; + } else { + $this->selectTimeout = 1; + } + $this->options = $options['options'] ?? []; + // unsetting the property forces the first access to go through + // __get(). + unset($this->_mh); + } + /** + * @param string $name + * + * @return resource|\CurlMultiHandle + * + * @throws \BadMethodCallException when another field as `_mh` will be gotten + * @throws \RuntimeException when curl can not initialize a multi handle + */ + public function __get($name) + { + if ($name !== '_mh') { + throw new \BadMethodCallException("Can not get other property as '_mh'."); + } + $multiHandle = \curl_multi_init(); + if (\false === $multiHandle) { + throw new \RuntimeException('Can not initialize curl multi handle.'); + } + $this->_mh = $multiHandle; + foreach ($this->options as $option => $value) { + // A warning is raised in case of a wrong option. + \curl_multi_setopt($this->_mh, $option, $value); + } + return $this->_mh; + } + public function __destruct() + { + if (isset($this->_mh)) { + try { + \curl_multi_close($this->_mh); + } catch (\Throwable $e) { + // Destructors must not throw. + } finally { + unset($this->_mh); + } + } + } + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + $easy = $this->factory->create($request, $options); + $id = (int) $easy->handle; + $promise = new Promise([$this, 'execute'], function () use($id) { + return $this->cancel($id); + }); + $this->addRequest(['easy' => $easy, 'deferred' => $promise]); + return $promise; + } + /** + * Ticks the curl event loop. + */ + public function tick() : void + { + // Add any delayed handles if needed. + if ($this->delays) { + $currentTime = Utils::currentTime(); + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + \curl_multi_add_handle($this->_mh, $this->handles[$id]['easy']->handle); + } + } + } + // Run curl_multi_exec in the queue to enable other async tasks to run + P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue'])); + // Step through the task queue which may add additional requests. + P\Utils::queue()->run(); + if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + \usleep(250); + } + do { + $this->executingMulti = \true; + try { + $exec = \curl_multi_exec($this->_mh, $this->active); + } finally { + $this->executingMulti = \false; + $this->cleanupDeferredCancels(); + } + // Prevent busy looping for slow HTTP requests. + if ($exec === \CURLM_CALL_MULTI_PERFORM) { + \curl_multi_select($this->_mh, $this->selectTimeout); + } + } while ($exec === \CURLM_CALL_MULTI_PERFORM); + $this->processMessages(); + } + /** + * Runs \curl_multi_exec() inside the event loop, to prevent busy looping + */ + private function tickInQueue() : void + { + $this->executingMulti = \true; + try { + $exec = \curl_multi_exec($this->_mh, $this->active); + } finally { + $this->executingMulti = \false; + $this->cleanupDeferredCancels(); + } + if ($exec === \CURLM_CALL_MULTI_PERFORM) { + \curl_multi_select($this->_mh, 0); + P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue'])); + } + } + /** + * Runs until all outstanding connections have completed. + */ + public function execute() : void + { + $queue = P\Utils::queue(); + while ($this->handles || !$queue->isEmpty()) { + // If there are no transfers, then sleep for the next delay + if (!$this->active && $this->delays) { + \usleep($this->timeToNext()); + } + $this->tick(); + } + } + private function addRequest(array $entry) : void + { + $easy = $entry['easy']; + $id = (int) $easy->handle; + $this->handles[$id] = $entry; + if (empty($easy->options['delay'])) { + \curl_multi_add_handle($this->_mh, $easy->handle); + } else { + $this->delays[$id] = Utils::currentTime() + $easy->options['delay'] / 1000; + } + } + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) : bool + { + if (!\is_int($id)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return \false; + } + $easy = $this->handles[$id]['easy']; + unset($this->delays[$id], $this->handles[$id]); + if ($this->executingMulti) { + $this->deferredCancels[$id] = $easy; + return \true; + } + $this->cleanupCancelledHandle($easy); + return \true; + } + private function cleanupDeferredCancels() : void + { + if ($this->deferredCancels === []) { + return; + } + $entries = $this->deferredCancels; + $this->deferredCancels = []; + foreach ($entries as $easy) { + $this->cleanupCancelledHandle($easy); + } + } + private function cleanupCancelledHandle(EasyHandle $easy) : void + { + $handle = $easy->handle; + \curl_multi_remove_handle($this->_mh, $handle); + if (\PHP_VERSION_ID < 80000) { + \curl_close($handle); + } + } + private function processMessages() : void + { + while ($done = \curl_multi_info_read($this->_mh)) { + if ($done['msg'] !== \CURLMSG_DONE) { + // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216 + continue; + } + if (!isset($done['handle'])) { + // Work around a PHP issue where cancelled transfers may omit the handle. + // Remove this once we no longer support PHP versions before the fix in + // https://github.com/php/php-src/pull/16302. + continue; + } + $id = (int) $done['handle']; + \curl_multi_remove_handle($this->_mh, $done['handle']); + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + $entry = $this->handles[$id]; + unset($this->handles[$id], $this->delays[$id]); + $entry['easy']->errno = $done['result']; + try { + $result = CurlFactory::finish($this, $entry['easy'], $this->factory); + } catch (\Throwable $e) { + $entry['deferred']->reject($e); + continue; + } + $entry['deferred']->resolve($result); + } + } + private function timeToNext() : int + { + $currentTime = Utils::currentTime(); + $nextTime = \PHP_INT_MAX; + foreach ($this->delays as $time) { + if ($time < $nextTime) { + $nextTime = $time; + } + } + return (int) \max(0, $nextTime - $currentTime) * 1000000; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php new file mode 100644 index 00000000..4cb52e9a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlShareHandleState.php @@ -0,0 +1,140 @@ +mode = $mode; + $this->handle = $handle; + } + /** + * @param mixed $sharing + */ + public static function fromOption($sharing) : ?self + { + if ($sharing instanceof self) { + return $sharing; + } + $mode = self::normalizeMode($sharing, 'transport_sharing'); + if ($mode === TransportSharing::NONE) { + return null; + } + if ($mode === TransportSharing::HANDLER_PREFER) { + return self::createHandlerShareOrNull($mode); + } + return self::createHandlerShare($mode); + } + /** + * @param mixed $sharing + */ + public static function normalizeMode($sharing, string $option) : string + { + if ($sharing instanceof self) { + return $sharing->mode; + } + if ($sharing === null || $sharing === TransportSharing::NONE) { + return TransportSharing::NONE; + } + if ($sharing === TransportSharing::HANDLER_PREFER || $sharing === TransportSharing::HANDLER_REQUIRE) { + return $sharing; + } + throw new \InvalidArgumentException(\sprintf('The "%s" option must be null or a GuzzleHttp\\TransportSharing::* constant; received %s.', $option, \get_debug_type($sharing))); + } + public static function assertNoRequiredSharingCustomFactoryConflict(array $options, string $handlerName) : void + { + if (!\array_key_exists('handle_factory', $options) || $options['handle_factory'] === null) { + return; + } + $mode = self::normalizeMode($options['transport_sharing'] ?? null, 'transport_sharing'); + if ($mode !== TransportSharing::HANDLER_REQUIRE) { + return; + } + throw new \InvalidArgumentException(\sprintf('The "transport_sharing" %s option cannot require sharing with a custom "handle_factory" because Guzzle cannot ensure that the custom factory applies CURLOPT_SHARE.', $handlerName)); + } + private static function createHandlerShareOrNull(string $mode) : ?self + { + try { + return self::createHandlerShare($mode); + } catch (\Throwable $e) { + return null; + } + } + private static function createHandlerShare(string $mode) : self + { + if (!\function_exists('curl_share_init') || !\function_exists('curl_share_setopt')) { + throw new \InvalidArgumentException('The "transport_sharing" option requires cURL share support.'); + } + self::requireCurlConstant('CURLOPT_SHARE'); + $shareOption = self::requireCurlConstant('CURLSHOPT_SHARE'); + $locks = self::handlerLocks($mode); + $handle = \curl_share_init(); + try { + foreach ($locks as $lock) { + try { + $success = \curl_share_setopt($handle, $shareOption, $lock); + } catch (\Throwable $e) { + throw new \InvalidArgumentException('Unable to configure cURL share handle: ' . $e->getMessage(), 0, $e); + } + if (!$success) { + throw new \InvalidArgumentException(\sprintf('Unable to configure cURL share handle with lock data %d.', $lock)); + } + } + } catch (\Throwable $e) { + self::closeHandlerShareHandleOnPhp7($handle); + throw $e; + } + return new self($mode, $handle); + } + /** + * @return int[] + */ + private static function handlerLocks(string $mode) : array + { + CurlVersion::ensureHandlerSharingSupported(); + if ($mode === TransportSharing::HANDLER_REQUIRE) { + CurlVersion::ensureSslSessionSharingSupported(); + } + $locks = [self::requireCurlConstant('CURL_LOCK_DATA_DNS')]; + if (CurlVersion::supportsSslSessionSharing()) { + $locks[] = self::requireCurlConstant('CURL_LOCK_DATA_SSL_SESSION'); + } + return $locks; + } + private static function requireCurlConstant(string $constant) : int + { + if (!\defined($constant)) { + throw new \InvalidArgumentException(\sprintf('The "transport_sharing" option requires %s, but it is not available in the installed PHP cURL extension.', $constant)); + } + $value = \constant($constant); + if (!\is_int($value)) { + throw new \InvalidArgumentException(\sprintf('The cURL constant %s must resolve to an integer.', $constant)); + } + return $value; + } + /** + * @param resource|\CurlShareHandle $handle + */ + private static function closeHandlerShareHandleOnPhp7($handle) : void + { + if (\PHP_VERSION_ID < 80000 && \is_resource($handle)) { + \curl_share_close($handle); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlVersion.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlVersion.php new file mode 100644 index 00000000..51cfedb9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlVersion.php @@ -0,0 +1,102 @@ +='); + } + public static function supportsTls12() : bool + { + $version = self::getVersion(); + return self::supportsSsl() && \defined('CURL_SSLVERSION_TLSv1_2') && $version !== null && \version_compare($version, self::TLS_12_VERSION, '>='); + } + public static function supportsTls13() : bool + { + $version = self::getVersion(); + return self::supportsSsl() && \defined('CURL_SSLVERSION_TLSv1_3') && $version !== null && \version_compare($version, self::TLS_13_VERSION, '>='); + } + public static function supportsHttp2() : bool + { + $versionInfo = self::getVersionInfo(); + return self::supportsTls12() && \defined('CURL_VERSION_HTTP2') && $versionInfo !== null && 0 !== (\CURL_VERSION_HTTP2 & $versionInfo['features']); + } + public static function supportsHttpsProxy() : bool + { + $versionInfo = self::getVersionInfo(); + // CURL_VERSION_HTTPS_PROXY is not defined on every supported PHP + // version; fall back to the curl.h bit value. + $httpsProxyFeature = \defined('CURL_VERSION_HTTPS_PROXY') ? \CURL_VERSION_HTTPS_PROXY : 1 << 21; + return $versionInfo !== null && \version_compare($versionInfo['version'], self::HTTPS_PROXY_VERSION, '>=') && 0 !== ($httpsProxyFeature & $versionInfo['features']); + } + public static function supportsHandlerSharing() : bool + { + $version = self::getVersion(); + return $version !== null && \version_compare($version, self::HANDLER_SHARING_VERSION, '>='); + } + public static function ensureHandlerSharingSupported() : void + { + if (!self::supportsHandlerSharing()) { + throw new \InvalidArgumentException(\sprintf('The "transport_sharing" option requires libcurl %s or higher for cURL share handles.', self::HANDLER_SHARING_VERSION)); + } + } + public static function supportsSslSessionSharing() : bool + { + $version = self::getVersion(); + return self::supportsSsl() && $version !== null && \version_compare($version, self::SSL_SESSION_SHARING_VERSION, '>='); + } + public static function ensureSslSessionSharingSupported() : void + { + if (!self::supportsSslSessionSharing()) { + throw new \InvalidArgumentException(\sprintf('The "transport_sharing" option requires libcurl %s or higher with SSL support for SSL session sharing.', self::SSL_SESSION_SHARING_VERSION)); + } + } + private static function supportsSsl() : bool + { + $versionInfo = self::getVersionInfo(); + return \defined('CURL_VERSION_SSL') && $versionInfo !== null && 0 !== (\CURL_VERSION_SSL & $versionInfo['features']); + } + public static function getVersion() : ?string + { + $versionInfo = self::getVersionInfo(); + return $versionInfo === null ? null : $versionInfo['version']; + } + /** + * @return array{version: string, features: int}|null + */ + private static function getVersionInfo() : ?array + { + if (self::$versionInfo === null) { + if (!\function_exists('curl_version')) { + self::$versionInfo = \false; + } else { + $versionInfo = \curl_version(); + self::$versionInfo = \is_array($versionInfo) && isset($versionInfo['version'], $versionInfo['features']) && \is_string($versionInfo['version']) && \is_int($versionInfo['features']) ? ['version' => $versionInfo['version'], 'features' => $versionInfo['features']] : \false; + } + } + return self::$versionInfo === \false ? null : self::$versionInfo; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/EasyHandle.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/EasyHandle.php new file mode 100644 index 00000000..13ec2b5f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/EasyHandle.php @@ -0,0 +1,96 @@ +response = null; + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($this->headers); + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (!empty($this->options['decode_content']) && isset($normalizedKeys['content-encoding'])) { + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + unset($headers[$normalizedKeys['content-encoding']]); + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + $bodyLength = (int) $this->sink->getSize(); + if ($bodyLength) { + $headers[$normalizedKeys['content-length']] = [(string) $bodyLength]; + } else { + unset($headers[$normalizedKeys['content-length']]); + } + } + } + // Attach a response to the easy handle with the parsed headers. + $this->response = new Response($status, $headers, $this->sink, $ver, $reason); + } + /** + * @param string $name + * + * @return void + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + $msg = $name === 'handle' ? 'The EasyHandle has been released' : 'Invalid property: ' . $name; + throw new \BadMethodCallException($msg); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php new file mode 100644 index 00000000..3c3b44ac --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php @@ -0,0 +1,64 @@ + $headers + * + * @return list + */ + private static function getLastHeaderBlock(array $headers) : array + { + $lastStatusLine = 0; + foreach ($headers as $index => $line) { + if (\preg_match('/^HTTP\\/\\S+\\s+/i', $line)) { + $lastStatusLine = $index; + } + } + return \array_slice($headers, $lastStatusLine); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/MockHandler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/MockHandler.php new file mode 100644 index 00000000..0bd7f098 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/MockHandler.php @@ -0,0 +1,173 @@ +|null $queue The parameters to be passed to the append function, as an indexed array. + * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled. + * @param callable|null $onRejected Callback to invoke when the return value is rejected. + */ + public function __construct(?array $queue = null, ?callable $onFulfilled = null, ?callable $onRejected = null) + { + $this->onFulfilled = $onFulfilled; + $this->onRejected = $onRejected; + if ($queue) { + // array_values included for BC + $this->append(...\array_values($queue)); + } + } + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + if (!$this->queue) { + throw new \OutOfBoundsException('Mock queue is empty'); + } + if (isset($options['delay']) && \is_numeric($options['delay'])) { + \usleep((int) $options['delay'] * 1000); + } + $this->lastRequest = $request; + $this->lastOptions = $options; + $response = \array_shift($this->queue); + if (isset($options['on_headers'])) { + if (!\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + try { + $options['on_headers']($response); + } catch (\Exception $e) { + $msg = 'An error was encountered during the on_headers event'; + $response = new RequestException($msg, $request, $response, $e); + } + } + if (\is_callable($response)) { + $response = $response($request, $options); + } + $response = $response instanceof \Throwable ? P\Create::rejectionFor($response) : P\Create::promiseFor($response); + return $response->then(function (?ResponseInterface $value) use($request, $options) { + $this->invokeStats($request, $options, $value); + if ($this->onFulfilled) { + ($this->onFulfilled)($value); + } + if ($value !== null && isset($options['sink'])) { + $contents = (string) $value->getBody(); + $sink = $options['sink']; + if (\is_resource($sink)) { + \fwrite($sink, $contents); + } elseif (\is_string($sink)) { + \file_put_contents($sink, $contents); + } elseif ($sink instanceof StreamInterface) { + $sink->write($contents); + } + } + return $value; + }, function ($reason) use($request, $options) { + $this->invokeStats($request, $options, null, $reason); + if ($this->onRejected) { + ($this->onRejected)($reason); + } + return P\Create::rejectionFor($reason); + }); + } + /** + * Adds one or more variadic requests, exceptions, callables, or promises + * to the queue. + * + * @param mixed ...$values + */ + public function append(...$values) : void + { + foreach ($values as $value) { + if ($value instanceof ResponseInterface || $value instanceof \Throwable || $value instanceof PromiseInterface || \is_callable($value)) { + $this->queue[] = $value; + } else { + throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . \get_debug_type($value)); + } + } + } + /** + * Get the last received request. + */ + public function getLastRequest() : ?RequestInterface + { + return $this->lastRequest; + } + /** + * Get the last received request options. + */ + public function getLastOptions() : array + { + return $this->lastOptions; + } + /** + * Returns the number of remaining items in the queue. + */ + public function count() : int + { + return \count($this->queue); + } + public function reset() : void + { + $this->queue = []; + } + /** + * @param mixed $reason Promise or reason. + */ + private function invokeStats(RequestInterface $request, array $options, ?ResponseInterface $response = null, $reason = null) : void + { + if (isset($options['on_stats'])) { + $transferTime = $options['transfer_time'] ?? 0; + $stats = new TransferStats($request, $response, $transferTime, $reason); + $options['on_stats']($stats); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/Proxy.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/Proxy.php new file mode 100644 index 00000000..20204399 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Handler/Proxy.php @@ -0,0 +1,49 @@ +transportSharingMode = CurlShareHandleState::normalizeMode($options['transport_sharing'] ?? null, 'transport_sharing'); + } + /** + * Sends an HTTP request. + * + * @param RequestInterface $request Request to send. + * @param array $options Request transfer options. + */ + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + // Sleep if there is a delay specified. + if (isset($options['delay'])) { + \usleep($options['delay'] * 1000); + } + $protocolVersion = $request->getProtocolVersion(); + if ('' === $protocolVersion) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Sending a request with an empty protocol version is deprecated; guzzlehttp/guzzle 8.0 will reject empty protocol versions.'); + $protocolVersion = '1.1'; + $request = Psr7\Utils::modifyRequest($request, ['version' => $protocolVersion]); + } + if ('1.0' !== $protocolVersion && '1.1' !== $protocolVersion) { + throw new ConnectException(\sprintf('HTTP/%s is not supported by the stream handler.', $protocolVersion), $request); + } + $startTime = isset($options['on_stats']) ? Utils::currentTime() : null; + self::triggerUnsupportedRequestOptionDeprecations($request, $options); + $this->assertTransportSharingSupported(); + try { + // Does not support the expect header. + $request = $request->withoutHeader('Expect'); + // Append a content-length header if body size is zero to match + // the behavior of `CurlHandler` + if ((0 === \strcasecmp('PUT', $request->getMethod()) || 0 === \strcasecmp('POST', $request->getMethod())) && 0 === $request->getBody()->getSize()) { + $request = $request->withHeader('Content-Length', '0'); + } + return $this->createResponse($request, $options, $this->createStream($request, $options), $startTime); + } catch (\InvalidArgumentException $e) { + throw $e; + } catch (\Exception $e) { + // Determine if the error was a networking error. + if (self::isConnectionError($e->getMessage())) { + $e = new ConnectException($e->getMessage(), $request, $e); + } else { + $e = $e instanceof RequestException ? $e : new RequestException($e->getMessage(), $request, null, $e); + } + $this->invokeStats($options, $request, $startTime, null, $e); + return P\Create::rejectionFor($e); + } + } + private static function isConnectionError(string $message) : bool + { + foreach (self::CONNECTION_ERRORS as $connectionError) { + if (\false !== \strpos($message, $connectionError)) { + return \true; + } + } + return \false; + } + private function invokeStats(array $options, RequestInterface $request, ?float $startTime, ?ResponseInterface $response = null, ?\Throwable $error = null) : void + { + if (isset($options['on_stats'])) { + $stats = new TransferStats($request, $response, Utils::currentTime() - $startTime, $error, []); + $options['on_stats']($stats); + } + } + /** + * @param resource $stream + */ + private function createResponse(RequestInterface $request, array $options, $stream, ?float $startTime) : PromiseInterface + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = []; + try { + [$ver, $status, $reason, $headers] = HeaderProcessor::parseHeaders($hdrs); + } catch (\Throwable $e) { + return $this->rejectResponseCreation($options, $request, $startTime, $e); + } + [$stream, $headers] = $this->checkDecode($options, $headers, $stream); + $stream = Psr7\Utils::streamFor($stream); + $sink = $stream; + if (\strcasecmp('HEAD', $request->getMethod())) { + $sink = $this->createSink($stream, $options); + } + try { + $response = new Psr7\Response($status, $headers, $sink, $ver, $reason); + } catch (\Throwable $e) { + return $this->rejectResponseCreation($options, $request, $startTime, $e); + } + if (isset($options['on_headers'])) { + try { + $options['on_headers']($response); + } catch (\Throwable $e) { + return P\Create::rejectionFor(new RequestException('An error was encountered during the on_headers event', $request, $response, $e)); + } + } + // Do not drain when the request is a HEAD request because they have + // no body. + if ($sink !== $stream) { + $this->drain($stream, $sink, $response->getHeaderLine('Content-Length')); + } + $this->invokeStats($options, $request, $startTime, $response, null); + return new FulfilledPromise($response); + } + private function rejectResponseCreation(array $options, RequestInterface $request, ?float $startTime, \Throwable $previous) : PromiseInterface + { + $reason = new RequestException('An error was encountered while creating the response', $request, null, $previous); + $this->invokeStats($options, $request, $startTime, null, $reason); + return P\Create::rejectionFor($reason); + } + private function createSink(StreamInterface $stream, array $options) : StreamInterface + { + if (!empty($options['stream'])) { + return $stream; + } + $sink = $options['sink'] ?? Psr7\Utils::tryFopen('php://temp', 'r+'); + return \is_string($sink) ? new Psr7\LazyOpenStream($sink, 'w+') : Psr7\Utils::streamFor($sink); + } + /** + * @param resource $stream + */ + private function checkDecode(array $options, array $headers, $stream) : array + { + // Automatically decode responses when instructed. + if (!empty($options['decode_content'])) { + $normalizedKeys = Utils::normalizeHeaderKeys($headers); + if (isset($normalizedKeys['content-encoding'])) { + $encoding = $headers[$normalizedKeys['content-encoding']]; + if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') { + $stream = new Psr7\InflateStream(Psr7\Utils::streamFor($stream)); + $headers['x-encoded-content-encoding'] = $headers[$normalizedKeys['content-encoding']]; + // Remove content-encoding header + unset($headers[$normalizedKeys['content-encoding']]); + // The decoded length cannot be known without inflating the + // stream, so keep the original length for inspection and + // drop the now-unknown Content-Length header. + if (isset($normalizedKeys['content-length'])) { + $headers['x-encoded-content-length'] = $headers[$normalizedKeys['content-length']]; + unset($headers[$normalizedKeys['content-length']]); + } + } + } + } + return [$stream, $headers]; + } + /** + * Drains the source stream into the "sink" client option. + * + * @param string $contentLength Header specifying the amount of + * data to read. + * + * @throws \RuntimeException when the sink option is invalid. + */ + private function drain(StreamInterface $source, StreamInterface $sink, string $contentLength) : StreamInterface + { + // If a content-length header is provided, then stop reading once + // that number of bytes has been read. This can prevent infinitely + // reading from a stream when dealing with servers that do not honor + // Connection: Close headers. + Psr7\Utils::copyToStream($source, $sink, \strlen($contentLength) > 0 && (int) $contentLength > 0 ? (int) $contentLength : -1); + $sink->seek(0); + $source->close(); + return $sink; + } + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = []; + \set_error_handler(static function ($_, $msg, $file, $line) use(&$errors) : bool { + $errors[] = ['message' => $msg, 'file' => $file, 'line' => $line]; + return \true; + }); + try { + $resource = $callback(); + } finally { + \restore_error_handler(); + } + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[{$key}] {$value}" . \PHP_EOL; + } + } + throw new \RuntimeException(\trim($message)); + } + return $resource; + } + /** + * @return resource + */ + private function createStream(RequestInterface $request, array $options) + { + static $methods; + if (!$methods) { + $methods = \array_flip(\get_class_methods(__CLASS__)); + } + $scheme = $request->getUri()->getScheme(); + if (!\in_array($scheme, ['http', 'https'], \true)) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", $scheme), $request); + } + $protocols = Utils::normalizeProtocols($options['protocols'] ?? ['http', 'https']); + if (!\in_array($scheme, $protocols, \true)) { + throw new RequestException(\sprintf('The scheme "%s" is not allowed by the protocols request option.', $scheme), $request); + } + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ($request->getProtocolVersion() === '1.1' && !$request->hasHeader('Connection')) { + $request = $request->withHeader('Connection', 'close'); + } + // Ensure SSL is verified by default + if (!isset($options['verify'])) { + $options['verify'] = \true; + } + $params = []; + $context = $this->getDefaultContext($request); + if (isset($options['on_headers']) && !\is_callable($options['on_headers'])) { + throw new \InvalidArgumentException('on_headers must be callable'); + } + if (!empty($options)) { + foreach ($options as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $context, $value, $params); + } + } + } + if (isset($options['stream_context'])) { + if (!\is_array($options['stream_context'])) { + throw new \InvalidArgumentException('stream_context must be an array'); + } + self::triggerConflictingStreamContextOptionDeprecations($options['stream_context']); + self::triggerUnsupportedStreamContextOptionDeprecations($options['stream_context']); + $context = \array_replace_recursive($context, $options['stream_context']); + } + // Microsoft NTLM authentication only supported with curl handler + if (isset($options['auth'][2]) && 'ntlm' === $options['auth'][2]) { + throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler'); + } + $uri = $this->resolveHost($request, $options); + $contextResource = $this->createResource(static function () use($context, $params) { + return \stream_context_create($context, $params); + }); + return $this->createResource(function () use($uri, $contextResource, $context, $options, $request) { + $resource = @\fopen((string) $uri, 'r', \false, $contextResource); + // See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable + if (\function_exists('WPMailSMTP\\Vendor\\http_get_last_response_headers')) { + $http_response_header = \WPMailSMTP\Vendor\http_get_last_response_headers(); + } + $this->lastHeaders = $http_response_header ?? []; + if (\false === $resource) { + throw new ConnectException(\sprintf('Connection refused for URI %s', $uri), $request, null, $context); + } + if (isset($options['read_timeout'])) { + $readTimeout = $options['read_timeout']; + $sec = (int) $readTimeout; + $usec = ($readTimeout - $sec) * 100000; + \stream_set_timeout($resource, $sec, $usec); + } + return $resource; + }); + } + private function resolveHost(RequestInterface $request, array $options) : UriInterface + { + $uri = $request->getUri(); + if (isset($options['force_ip_resolve']) && !\filter_var($uri->getHost(), \FILTER_VALIDATE_IP)) { + if ('v4' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_A); + if (\false === $records || !isset($records[0]['ip'])) { + throw new ConnectException(\sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost($records[0]['ip']); + } + if ('v6' === $options['force_ip_resolve']) { + $records = \dns_get_record($uri->getHost(), \DNS_AAAA); + if (\false === $records || !isset($records[0]['ipv6'])) { + throw new ConnectException(\sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request); + } + return $uri->withHost('[' . $records[0]['ipv6'] . ']'); + } + } + return $uri; + } + private function getDefaultContext(RequestInterface $request) : array + { + $headers = ''; + foreach ($request->getHeaders() as $name => $value) { + foreach ($value as $val) { + $headers .= "{$name}: {$val}\r\n"; + } + } + $context = ['http' => ['method' => $request->getMethod(), 'header' => $headers, 'protocol_version' => $request->getProtocolVersion(), 'ignore_errors' => \true, 'follow_location' => 0], 'ssl' => ['peer_name' => $request->getUri()->getHost()]]; + $body = (string) $request->getBody(); + if ('' !== $body) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!$request->hasHeader('Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + $context['http']['header'] = \rtrim($context['http']['header']); + return $context; + } + private static function triggerUnsupportedRequestOptionDeprecations(RequestInterface $request, array $options) : void + { + if (\array_key_exists('curl', $options) && $options['curl'] !== null && $options['curl'] !== [] && !self::isCurlOptionGeneratedByAuth($options)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "curl" request option to the stream handler is deprecated; guzzlehttp/guzzle 8.0 will reject this option because the stream handler ignores cURL options.'); + } + if (\array_key_exists('expect', $options) && $options['expect'] !== \false && $request->hasHeader('Expect')) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing the "expect" request option to the stream handler is deprecated when it adds an Expect header; guzzlehttp/guzzle 8.0 will reject this option because the stream handler does not support Expect: 100-Continue.'); + } + } + private static function triggerConflictingStreamContextOptionDeprecations(array $streamContext) : void + { + $conflictingOptions = self::conflictingStreamContextOptions(); + foreach ($streamContext as $wrapper => $contextOptions) { + if (!\is_string($wrapper) || !isset($conflictingOptions[$wrapper]) || !\is_array($contextOptions)) { + continue; + } + foreach ($contextOptions as $option => $_) { + if (!\is_string($option) || !\array_key_exists($option, $conflictingOptions[$wrapper])) { + continue; + } + \trigger_deprecation('guzzlehttp/guzzle', '7.12', \sprintf('Passing stream_context.%s.%s in the "stream_context" request option is deprecated; guzzlehttp/guzzle 8.0 will reject this option because it conflicts with Guzzle-managed request handling. Use %s instead.', $wrapper, $option, $conflictingOptions[$wrapper][$option])); + } + } + } + private static function triggerUnsupportedStreamContextOptionDeprecations(array $streamContext) : void + { + $unsupportedOptions = self::unsupportedStreamContextOptions($streamContext); + if ($unsupportedOptions === []) { + return; + } + \trigger_deprecation('guzzlehttp/guzzle', '7.12', \sprintf('Passing PHP stream context options outside the built-in stream handler allow-list to the "stream_context" request option is deprecated; guzzlehttp/guzzle 8.0 will reject stream context options outside the allow-list. Deprecated option%s: %s.', \count($unsupportedOptions) === 1 ? '' : 's', \implode(', ', $unsupportedOptions))); + } + /** + * @return string[] + */ + private static function unsupportedStreamContextOptions(array $streamContext) : array + { + $supportedOptions = self::supportedStreamContextOptions(); + $conflictingOptions = self::conflictingStreamContextOptions(); + $unsupportedOptions = []; + foreach ($streamContext as $wrapper => $contextOptions) { + if (!\is_string($wrapper) || !isset($supportedOptions[$wrapper])) { + if (\is_array($contextOptions)) { + foreach ($contextOptions as $option => $_) { + if (\is_string($wrapper) && \is_string($option) && isset($conflictingOptions[$wrapper]) && \array_key_exists($option, $conflictingOptions[$wrapper])) { + continue; + } + $unsupportedOptions[] = \sprintf('stream_context.%s.%s', (string) $wrapper, (string) $option); + } + } else { + $unsupportedOptions[] = \sprintf('stream_context.%s', (string) $wrapper); + } + continue; + } + if (!\is_array($contextOptions)) { + $unsupportedOptions[] = \sprintf('stream_context.%s', $wrapper); + continue; + } + foreach ($contextOptions as $option => $_) { + if (\is_string($option) && isset($conflictingOptions[$wrapper]) && \array_key_exists($option, $conflictingOptions[$wrapper])) { + continue; + } + if (!\is_string($option) || !\array_key_exists($option, $supportedOptions[$wrapper])) { + $unsupportedOptions[] = \sprintf('stream_context.%s.%s', $wrapper, (string) $option); + } + } + } + return $unsupportedOptions; + } + /** + * @return array> + */ + private static function supportedStreamContextOptions() : array + { + return ['http' => ['request_fulluri' => \true], 'socket' => ['bindto' => \true, 'tcp_nodelay' => \true], 'ssl' => ['SNI_enabled' => \true, 'capture_peer_cert' => \true, 'capture_peer_cert_chain' => \true, 'ciphers' => \true, 'disable_compression' => \true, 'no_ticket' => \true, 'peer_fingerprint' => \true, 'security_level' => \true, 'verify_depth' => \true]]; + } + /** + * @return array> + */ + private static function conflictingStreamContextOptions() : array + { + return ['http' => ['content' => 'the request body', 'follow_location' => 'the "allow_redirects" request option', 'header' => 'the request headers', 'max_redirects' => 'the "allow_redirects" request option', 'method' => 'the request method', 'protocol_version' => 'the request protocol version', 'proxy' => 'the "proxy" request option', 'timeout' => 'the "timeout" request option'], 'ssl' => ['allow_self_signed' => 'the "verify" request option', 'cafile' => 'the "verify" request option', 'capath' => 'the "verify" request option', 'crypto_method' => 'the "crypto_method" request option', 'local_cert' => 'the "cert" request option', 'local_pk' => 'the "ssl_key" request option', 'min_proto_version' => 'the "crypto_method" request option', 'passphrase' => 'the "cert" or "ssl_key" request option', 'peer_name' => 'the request URI', 'verify_peer' => 'the "verify" request option', 'verify_peer_name' => 'the "verify" request option']]; + } + private function assertTransportSharingSupported() : void + { + if ($this->transportSharingMode === TransportSharing::HANDLER_REQUIRE) { + throw new \InvalidArgumentException('The "transport_sharing" option requires transport sharing, but the stream handler does not support it.'); + } + } + private static function isCurlOptionGeneratedByAuth(array $options) : bool + { + if (!isset($options['curl']) || !\is_array($options['curl']) || !isset($options['auth'][2]) || !\is_string($options['auth'][2])) { + return \false; + } + if (!\defined('CURLOPT_HTTPAUTH') || !\defined('CURLOPT_USERPWD')) { + return \false; + } + $type = \strtolower($options['auth'][2]); + if ($type === 'digest') { + $httpAuth = \defined('CURLAUTH_DIGEST') ? \constant('CURLAUTH_DIGEST') : null; + } elseif ($type === 'ntlm') { + $httpAuth = \defined('CURLAUTH_NTLM') ? \constant('CURLAUTH_NTLM') : null; + } else { + return \false; + } + return $httpAuth !== null && \count($options['curl']) === 2 && isset($options['curl'][\CURLOPT_HTTPAUTH], $options['curl'][\CURLOPT_USERPWD]) && $options['curl'][\CURLOPT_HTTPAUTH] === $httpAuth; + } + /** + * @param mixed $value as passed via Request transfer options. + * + * @return array{0: string, 1: string|null} + */ + private static function normalizeTlsFileOption(string $option, $value) : array + { + $passphrase = null; + if (\is_array($value)) { + if (!isset($value[0]) || !\is_string($value[0])) { + throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option)); + } + if (isset($value[1])) { + if (!\is_string($value[1])) { + throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option)); + } + $passphrase = $value[1]; + } + $value = $value[0]; + } + if (!\is_string($value)) { + throw new \InvalidArgumentException(\sprintf('Invalid %s request option', $option)); + } + return [$value, $passphrase]; + } + private static function setTlsPassphrase(array &$options, ?string $passphrase, string $option) : void + { + if ($passphrase === null) { + return; + } + if (isset($options['ssl']['passphrase']) && $options['ssl']['passphrase'] !== $passphrase) { + throw new \InvalidArgumentException(\sprintf('Cannot use different passphrases for cert and ssl_key with the stream handler; %s conflicts with an existing TLS passphrase.', $option)); + } + $options['ssl']['passphrase'] = $passphrase; + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private static function assertStreamTlsType(string $option, $value) : void + { + if (!\is_string($value) || $value === '') { + throw new \InvalidArgumentException(\sprintf('%s must be a non-empty string', $option)); + } + if (\strtoupper($value) !== 'PEM') { + throw new \InvalidArgumentException(\sprintf('The stream handler only supports "PEM" for the %s request option.', $option)); + } + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_proxy(RequestInterface $request, array &$options, $value, array &$params) : void + { + $uri = null; + if (!\is_array($value)) { + $uri = $value; + } else { + $scheme = $request->getUri()->getScheme(); + if (isset($value[$scheme])) { + if (!isset($value['no']) || !Utils::isUriInNoProxy($request->getUri(), $value['no'])) { + $uri = $value[$scheme]; + } + } + } + if (!$uri) { + return; + } + $parsed = $this->parse_proxy($uri); + $options['http']['proxy'] = $parsed['proxy']; + if ($parsed['auth']) { + if (!isset($options['http']['header'])) { + $options['http']['header'] = ''; + } + $options['http']['header'] .= "\r\nProxy-Authorization: {$parsed['auth']}"; + } + } + /** + * Parses the given proxy URL to make it compatible with the format PHP's stream context expects. + */ + private function parse_proxy(string $url) : array + { + $parsed = \parse_url($url); + // parse_url() misreads scheme-less proxy authorities like + // "user:pass@host"; re-parse only those forms as HTTP. + $schemeLessAuthority = \strpos($url, '://') === \false && \strncmp($url, '//', 2) !== 0; + if ($schemeLessAuthority) { + if (\is_array($parsed) && !isset($parsed['scheme']) && isset($parsed['host'], $parsed['port'])) { + $parsed['scheme'] = 'http'; + } elseif ((!\is_array($parsed) || !isset($parsed['host'])) && (\strpos($url, '@') !== \false || \strncmp($url, '[', 1) === 0)) { + $parsed = \parse_url('http://' . $url); + } + } + if (\is_array($parsed) && isset($parsed['scheme']) && \strcasecmp($parsed['scheme'], 'http') === 0) { + if (isset($parsed['host'], $parsed['port'])) { + $user = $parsed['user'] ?? ''; + $pass = $parsed['pass'] ?? ''; + $auth = $user !== '' || $pass !== '' ? 'Basic ' . \base64_encode("{$user}:{$pass}") : null; + return ['proxy' => "tcp://{$parsed['host']}:{$parsed['port']}", 'auth' => $auth]; + } + } + // Return proxy as-is. + return ['proxy' => $url, 'auth' => null]; + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_timeout(RequestInterface $request, array &$options, $value, array &$params) : void + { + if ($value > 0) { + $options['http']['timeout'] = $value; + } + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_crypto_method(RequestInterface $request, array &$options, $value, array &$params) : void + { + if ($value === \STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT || $value === \STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT || $value === \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT || \defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && $value === \STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT) { + $options['http']['crypto_method'] = $value; + return; + } + throw new \InvalidArgumentException('Invalid crypto_method request option: unknown version provided'); + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_verify(RequestInterface $request, array &$options, $value, array &$params) : void + { + if ($value === \false) { + $options['ssl']['verify_peer'] = \false; + $options['ssl']['verify_peer_name'] = \false; + return; + } + if (\is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!\file_exists($value)) { + throw new \RuntimeException("SSL CA bundle not found: {$value}"); + } + } elseif ($value !== \true) { + throw new \InvalidArgumentException('Invalid verify request option'); + } + $options['ssl']['verify_peer'] = \true; + $options['ssl']['verify_peer_name'] = \true; + $options['ssl']['allow_self_signed'] = \false; + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert(RequestInterface $request, array &$options, $value, array &$params) : void + { + [$value, $passphrase] = self::normalizeTlsFileOption('cert', $value); + if (!\file_exists($value)) { + throw new \RuntimeException("SSL certificate not found: {$value}"); + } + self::setTlsPassphrase($options, $passphrase, 'cert'); + $options['ssl']['local_cert'] = $value; + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_cert_type(RequestInterface $request, array &$options, $value, array &$params) : void + { + self::assertStreamTlsType('cert_type', $value); + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_ssl_key(RequestInterface $request, array &$options, $value, array &$params) : void + { + [$value, $passphrase] = self::normalizeTlsFileOption('ssl_key', $value); + if (!\file_exists($value)) { + throw new \RuntimeException("SSL private key not found: {$value}"); + } + self::setTlsPassphrase($options, $passphrase, 'ssl_key'); + $options['ssl']['local_pk'] = $value; + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_ssl_key_type(RequestInterface $request, array &$options, $value, array &$params) : void + { + self::assertStreamTlsType('ssl_key_type', $value); + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_progress(RequestInterface $request, array &$options, $value, array &$params) : void + { + if (!\is_callable($value)) { + throw new \InvalidArgumentException('progress client option must be callable'); + } + self::addNotification($params, static function ($code, $a, $b, $c, $transferred, $total) use($value) { + if ($code == \STREAM_NOTIFY_PROGRESS) { + // The upload progress cannot be determined. Use 0 for cURL compatibility: + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + $value($total, $transferred, 0, 0); + } + }); + } + /** + * @param mixed $value as passed via Request transfer options. + */ + private function add_debug(RequestInterface $request, array &$options, $value, array &$params) : void + { + if ($value === \false) { + return; + } + static $map = [\STREAM_NOTIFY_CONNECT => 'CONNECT', \STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', \STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', \STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', \STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', \STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', \STREAM_NOTIFY_PROGRESS => 'PROGRESS', \STREAM_NOTIFY_FAILURE => 'FAILURE', \STREAM_NOTIFY_COMPLETED => 'COMPLETED', \STREAM_NOTIFY_RESOLVE => 'RESOLVE']; + static $args = ['severity', 'message', 'message_code', 'bytes_transferred', 'bytes_max']; + $value = Utils::debugResource($value); + $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment(''); + self::addNotification($params, static function (int $code, ...$passed) use($ident, $value, $map, $args) : void { + \fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (\array_filter($passed) as $i => $v) { + \fwrite($value, $args[$i] . ': "' . $v . '" '); + } + \fwrite($value, "\n"); + }); + } + private static function addNotification(array &$params, callable $notify) : void + { + // Wrap the existing function if needed. + if (!isset($params['notification'])) { + $params['notification'] = $notify; + } else { + $params['notification'] = self::callArray([$params['notification'], $notify]); + } + } + private static function callArray(array $functions) : callable + { + return static function (...$args) use($functions) { + foreach ($functions as $fn) { + $fn(...$args); + } + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/HandlerStack.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/HandlerStack.php new file mode 100644 index 00000000..b03462e1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/HandlerStack.php @@ -0,0 +1,246 @@ +push(Middleware::httpErrors(), 'http_errors'); + $stack->push(Middleware::redirect(), 'allow_redirects'); + $stack->push(Middleware::cookies(), 'cookies'); + $stack->push(Middleware::prepareBody(), 'prepare_body'); + return $stack; + } + /** + * @param (callable(RequestInterface, array): PromiseInterface)|null $handler Underlying HTTP handler. + */ + public function __construct(?callable $handler = null) + { + $this->handler = $handler; + } + /** + * Invokes the handler stack as a composed handler + * + * @return ResponseInterface|PromiseInterface + */ + public function __invoke(RequestInterface $request, array $options) + { + $handler = $this->resolve(); + return $handler($request, $options); + } + /** + * Dumps a string representation of the stack. + * + * @return string + */ + public function __toString() + { + $depth = 0; + $stack = []; + if ($this->handler !== null) { + $stack[] = '0) Handler: ' . $this->debugCallable($this->handler); + } + $result = ''; + foreach (\array_reverse($this->stack) as $tuple) { + ++$depth; + $str = "{$depth}) Name: '{$tuple[1]}', "; + $str .= 'Function: ' . $this->debugCallable($tuple[0]); + $result = "> {$str}\n{$result}"; + $stack[] = $str; + } + foreach (\array_keys($stack) as $k) { + $result .= "< {$stack[$k]}\n"; + } + return $result; + } + /** + * Set the HTTP handler that actually returns a promise. + * + * @param callable(RequestInterface, array): PromiseInterface $handler Accepts a request and array of options and + * returns a Promise. + */ + public function setHandler(callable $handler) : void + { + $this->handler = $handler; + $this->cached = null; + } + /** + * Returns true if the builder has a handler. + */ + public function hasHandler() : bool + { + return $this->handler !== null; + } + /** + * Unshift a middleware to the bottom of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function unshift(callable $middleware, ?string $name = null) : void + { + \array_unshift($this->stack, [$middleware, $name]); + $this->cached = null; + } + /** + * Push a middleware to the top of the stack. + * + * @param callable(callable): callable $middleware Middleware function + * @param string $name Name to register for this middleware. + */ + public function push(callable $middleware, string $name = '') : void + { + $this->stack[] = [$middleware, $name]; + $this->cached = null; + } + /** + * Add a middleware before another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function before(string $findName, callable $middleware, string $withName = '') : void + { + $this->splice($findName, $withName, $middleware, \true); + } + /** + * Add a middleware after another middleware by name. + * + * @param string $findName Middleware to find + * @param callable(callable): callable $middleware Middleware function + * @param string $withName Name to register for this middleware. + */ + public function after(string $findName, callable $middleware, string $withName = '') : void + { + $this->splice($findName, $withName, $middleware, \false); + } + /** + * Remove a middleware by instance or name from the stack. + * + * @param callable|string $remove Middleware to remove by instance or name. + */ + public function remove($remove) : void + { + if (!\is_string($remove) && !\is_callable($remove)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a callable or string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__); + } + $this->cached = null; + if (\is_string($remove)) { + $count = \count($this->stack); + $this->stack = \array_values(\array_filter($this->stack, static function ($tuple) use($remove) { + return $tuple[1] !== $remove; + })); + if ($count !== \count($this->stack) || !\is_callable($remove)) { + return; + } + } + $this->stack = \array_values(\array_filter($this->stack, static function ($tuple) use($remove) { + return $tuple[0] !== $remove; + })); + } + /** + * Compose the middleware and handler into a single callable function. + * + * @return callable(RequestInterface, array): PromiseInterface + */ + public function resolve() : callable + { + if ($this->cached === null) { + if (($prev = $this->handler) === null) { + throw new \LogicException('No handler has been specified'); + } + foreach (\array_reverse($this->stack) as $fn) { + /** @var callable(RequestInterface, array): PromiseInterface $prev */ + $prev = $fn[0]($prev); + } + $this->cached = $prev; + } + return $this->cached; + } + private function findByName(string $name) : int + { + foreach ($this->stack as $k => $v) { + if ($v[1] === $name) { + return $k; + } + } + throw new \InvalidArgumentException("Middleware not found: {$name}"); + } + /** + * Splices a function into the middleware list at a specific position. + */ + private function splice(string $findName, string $withName, callable $middleware, bool $before) : void + { + $this->cached = null; + $idx = $this->findByName($findName); + $tuple = [$middleware, $withName]; + if ($before) { + if ($idx === 0) { + \array_unshift($this->stack, $tuple); + } else { + $replacement = [$tuple, $this->stack[$idx]]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } elseif ($idx === \count($this->stack) - 1) { + $this->stack[] = $tuple; + } else { + $replacement = [$this->stack[$idx], $tuple]; + \array_splice($this->stack, $idx, 1, $replacement); + } + } + /** + * Provides a debug string for a given callable. + * + * @param callable|string $fn Function to write as a string. + */ + private function debugCallable($fn) : string + { + if (\is_string($fn)) { + return "callable({$fn})"; + } + if (\is_array($fn)) { + return \is_string($fn[0]) ? "callable({$fn[0]}::{$fn[1]})" : "callable(['" . \get_class($fn[0]) . "', '{$fn[1]}'])"; + } + /** @var object $fn */ + return 'callable(' . \spl_object_hash($fn) . ')'; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatter.php new file mode 100644 index 00000000..300238a1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatter.php @@ -0,0 +1,168 @@ +>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}"; + public const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}'; + /** + * @var string Template used to format log messages + */ + private $template; + /** + * @param string $template Log message template + */ + public function __construct(?string $template = self::CLF) + { + $this->template = $template ?: self::CLF; + } + /** + * Returns a formatted message string. + * + * @param RequestInterface $request Request that was sent + * @param ResponseInterface|null $response Response that was received + * @param \Throwable|null $error Exception that was received + */ + public function format(RequestInterface $request, ?ResponseInterface $response = null, ?\Throwable $error = null) : string + { + $cache = []; + /** @var string */ + return \preg_replace_callback('/{\\s*([A-Za-z_\\-\\.0-9]+)\\s*}/', function (array $matches) use($request, $response, $error, &$cache) { + if (isset($cache[$matches[1]])) { + return $cache[$matches[1]]; + } + $result = ''; + switch ($matches[1]) { + case 'request': + $result = Psr7\Message::toString($request); + break; + case 'response': + $result = $response ? Psr7\Message::toString($response) : ''; + break; + case 'req_headers': + $result = \trim($request->getMethod() . ' ' . $request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . "\r\n" . $this->headers($request); + break; + case 'res_headers': + $result = $response ? \sprintf('HTTP/%s %d %s', $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase()) . "\r\n" . $this->headers($response) : 'NULL'; + break; + case 'req_body': + $result = $request->getBody()->__toString(); + break; + case 'res_body': + if (!$response instanceof ResponseInterface) { + $result = 'NULL'; + break; + } + $body = $response->getBody(); + if (!$body->isSeekable()) { + $result = 'RESPONSE_NOT_LOGGEABLE'; + break; + } + $result = $response->getBody()->__toString(); + break; + case 'ts': + case 'date_iso_8601': + $result = \gmdate('c'); + break; + case 'date_common_log': + $result = \date('d/M/Y:H:i:s O'); + break; + case 'method': + $result = $request->getMethod(); + break; + case 'version': + $result = $request->getProtocolVersion(); + break; + case 'uri': + case 'url': + $result = $request->getUri()->__toString(); + break; + case 'target': + $result = $request->getRequestTarget(); + break; + case 'req_version': + $result = $request->getProtocolVersion(); + break; + case 'res_version': + $result = $response ? $response->getProtocolVersion() : 'NULL'; + break; + case 'host': + $result = $request->getHeaderLine('Host'); + break; + case 'hostname': + $result = \gethostname(); + break; + case 'code': + $result = $response ? $response->getStatusCode() : 'NULL'; + break; + case 'phrase': + $result = $response ? $response->getReasonPhrase() : 'NULL'; + break; + case 'error': + $result = $error ? $error->getMessage() : 'NULL'; + break; + default: + // handle prefixed dynamic headers + if (\strpos($matches[1], 'req_header_') === 0) { + $result = $request->getHeaderLine(\substr($matches[1], 11)); + } elseif (\strpos($matches[1], 'res_header_') === 0) { + $result = $response ? $response->getHeaderLine(\substr($matches[1], 11)) : 'NULL'; + } + } + $cache[$matches[1]] = $result; + return $result; + }, $this->template); + } + /** + * Get headers from message as string + */ + private function headers(MessageInterface $message) : string + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= $name . ': ' . \implode(', ', $values) . "\r\n"; + } + return \trim($result); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatterInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatterInterface.php new file mode 100644 index 00000000..70cb64fe --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/MessageFormatterInterface.php @@ -0,0 +1,17 @@ +withCookieHeader($request); + return $handler($request, $options)->then(static function (ResponseInterface $response) use($cookieJar, $request) : ResponseInterface { + $cookieJar->extractCookies($request, $response); + return $response; + }); + }; + }; + } + /** + * Middleware that throws exceptions for 4xx or 5xx responses when the + * "http_errors" request option is set to true. + * + * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages. + * + * @return callable(callable): callable Returns a function that accepts the next handler. + */ + public static function httpErrors(?BodySummarizerInterface $bodySummarizer = null) : callable + { + return static function (callable $handler) use($bodySummarizer) : callable { + return static function ($request, array $options) use($handler, $bodySummarizer) { + if (empty($options['http_errors'])) { + return $handler($request, $options); + } + return $handler($request, $options)->then(static function (ResponseInterface $response) use($request, $bodySummarizer) { + $code = $response->getStatusCode(); + if ($code < 400) { + return $response; + } + throw RequestException::create($request, $response, null, [], $bodySummarizer); + }); + }; + }; + } + /** + * Middleware that pushes history data to an ArrayAccess container. + * + * @param array|\ArrayAccess $container Container to hold the history (by reference). + * + * @return callable(callable): callable Returns a function that accepts the next handler. + * + * @throws \InvalidArgumentException if container is not an array or ArrayAccess. + */ + public static function history(&$container) : callable + { + if (!\is_array($container) && !$container instanceof \ArrayAccess) { + throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess'); + } + return static function (callable $handler) use(&$container) : callable { + return static function (RequestInterface $request, array $options) use($handler, &$container) { + return $handler($request, $options)->then(static function ($value) use($request, &$container, $options) { + $container[] = ['request' => $request, 'response' => $value, 'error' => null, 'options' => $options]; + return $value; + }, static function ($reason) use($request, &$container, $options) { + $container[] = ['request' => $request, 'response' => null, 'error' => $reason, 'options' => $options]; + return P\Create::rejectionFor($reason); + }); + }; + }; + } + /** + * Middleware that invokes a callback before and after sending a request. + * + * The provided listener cannot modify or alter the response. It simply + * "taps" into the chain to be notified before returning the promise. The + * before listener accepts a request and options array, and the after + * listener accepts a request, options array, and response promise. + * + * @param callable $before Function to invoke before forwarding the request. + * @param callable $after Function invoked after forwarding. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function tap(?callable $before = null, ?callable $after = null) : callable + { + return static function (callable $handler) use($before, $after) : callable { + return static function (RequestInterface $request, array $options) use($handler, $before, $after) { + if ($before) { + $before($request, $options); + } + $response = $handler($request, $options); + if ($after) { + $after($request, $options, $response); + } + return $response; + }; + }; + } + /** + * Middleware that handles request redirects. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function redirect() : callable + { + return static function (callable $handler) : RedirectMiddleware { + return new RedirectMiddleware($handler); + }; + } + /** + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function retry(callable $decider, ?callable $delay = null) : callable + { + return static function (callable $handler) use($decider, $delay) : RetryMiddleware { + return new RetryMiddleware($decider, $handler, $delay); + }; + } + /** + * Middleware that logs requests, responses, and errors using a message + * formatter. + * + * @param LoggerInterface $logger Logs messages. + * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings. + * @param string $logLevel Level at which to log requests. + * + * @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests. + * + * @return callable Returns a function that accepts the next handler. + */ + public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info') : callable + { + // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter + if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) { + throw new \LogicException(\sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class)); + } + return static function (callable $handler) use($logger, $formatter, $logLevel) : callable { + return static function (RequestInterface $request, array $options = []) use($handler, $logger, $formatter, $logLevel) { + return $handler($request, $options)->then(static function ($response) use($logger, $request, $formatter, $logLevel) : ResponseInterface { + $message = $formatter->format($request, $response); + $logger->log($logLevel, $message); + return $response; + }, static function ($reason) use($logger, $request, $formatter) : PromiseInterface { + $response = $reason instanceof RequestException ? $reason->getResponse() : null; + $message = $formatter->format($request, $response, P\Create::exceptionFor($reason)); + $logger->error($message); + return P\Create::rejectionFor($reason); + }); + }; + }; + } + /** + * This middleware adds a default content-type if possible, a default + * content-length or transfer-encoding header, and the expect header. + */ + public static function prepareBody() : callable + { + return static function (callable $handler) : PrepareBodyMiddleware { + return new PrepareBodyMiddleware($handler); + }; + } + /** + * Middleware that applies a map function to the request before passing to + * the next handler. + * + * @param callable $fn Function that accepts a RequestInterface and returns + * a RequestInterface. + */ + public static function mapRequest(callable $fn) : callable + { + return static function (callable $handler) use($fn) : callable { + return static function (RequestInterface $request, array $options) use($handler, $fn) { + return $handler($fn($request), $options); + }; + }; + } + /** + * Middleware that applies a map function to the resolved promise's + * response. + * + * @param callable $fn Function that accepts a ResponseInterface and + * returns a ResponseInterface. + */ + public static function mapResponse(callable $fn) : callable + { + return static function (callable $handler) use($fn) : callable { + return static function (RequestInterface $request, array $options) use($handler, $fn) { + return $handler($request, $options)->then($fn); + }; + }; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Pool.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 00000000..a1083961 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,120 @@ + $rfn) { + if ($rfn instanceof RequestInterface) { + (yield $key => $client->sendAsync($rfn, $opts)); + } elseif (\is_callable($rfn)) { + (yield $key => $rfn($opts)); + } else { + throw new \InvalidArgumentException('Each value yielded by the iterator must be a Psr7\\Http\\Message\\RequestInterface or a callable that returns a promise that fulfills with a Psr7\\Message\\Http\\ResponseInterface object.'); + } + } + }; + $this->each = new EachPromise($requests(), $config); + } + /** + * Get promise + */ + public function promise() : PromiseInterface + { + return $this->each->promise(); + } + /** + * Sends multiple requests concurrently and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send concurrently. + * @param array $options Passes through the options available in + * {@see Pool::__construct} + * + * @return array Returns an array containing the response or an exception + * in the same order that the requests were sent. + * + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch(ClientInterface $client, $requests, array $options = []) : array + { + $res = []; + self::cmpCallback($options, 'fulfilled', $res); + self::cmpCallback($options, 'rejected', $res); + $pool = new static($client, $requests, $options); + $pool->promise()->wait(); + \ksort($res); + return $res; + } + /** + * Execute callback(s) + */ + private static function cmpCallback(array &$options, string $name, array &$results) : void + { + if (!isset($options[$name])) { + $options[$name] = static function ($v, $k) use(&$results) { + $results[$k] = $v; + }; + } else { + $currentFn = $options[$name]; + $options[$name] = static function ($v, $k) use(&$results, $currentFn) { + $currentFn($v, $k); + $results[$k] = $v; + }; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php new file mode 100644 index 00000000..c000ff4a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php @@ -0,0 +1,86 @@ +nextHandler = $nextHandler; + } + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + $fn = $this->nextHandler; + // Don't do anything if the request has no body. + if ($request->getBody()->getSize() === 0) { + return $fn($request, $options); + } + $modify = []; + // Add a default content-type if possible. + if (!$request->hasHeader('Content-Type')) { + if ($uri = $request->getBody()->getMetadata('uri')) { + if (\is_string($uri) && ($type = Psr7\MimeType::fromFilename($uri))) { + $modify['set_headers']['Content-Type'] = $type; + } + } + } + // Add a default content-length or transfer-encoding header. + if (!$request->hasHeader('Content-Length') && !$request->hasHeader('Transfer-Encoding')) { + $size = $request->getBody()->getSize(); + if ($size !== null) { + $modify['set_headers']['Content-Length'] = (string) $size; + } else { + $modify['set_headers']['Transfer-Encoding'] = 'chunked'; + } + } + // Add the expect header if needed. + $this->addExpectHeader($request, $options, $modify); + return $fn(Psr7\Utils::modifyRequest($request, $modify), $options); + } + /** + * Add expect header + */ + private function addExpectHeader(RequestInterface $request, array $options, array &$modify) : void + { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + $expect = $options['expect'] ?? null; + // Return if disabled or using HTTP/1.0 + if ($expect === \false || $request->getProtocolVersion() === '1.0') { + return; + } + // The expect header is unconditionally enabled + if ($expect === \true) { + $modify['set_headers']['Expect'] = '100-Continue'; + return; + } + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $body = $request->getBody(); + $size = $body->getSize(); + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $modify['set_headers']['Expect'] = '100-Continue'; + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RedirectMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RedirectMiddleware.php new file mode 100644 index 00000000..ef570c88 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RedirectMiddleware.php @@ -0,0 +1,162 @@ + 5, 'protocols' => ['http', 'https'], 'strict' => \false, 'referer' => \false, 'track_redirects' => \false]; + /** + * @var callable(RequestInterface, array): PromiseInterface + */ + private $nextHandler; + /** + * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke. + */ + public function __construct(callable $nextHandler) + { + $this->nextHandler = $nextHandler; + } + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + $fn = $this->nextHandler; + if (empty($options['allow_redirects'])) { + return $fn($request, $options); + } + if ($options['allow_redirects'] === \true) { + $options['allow_redirects'] = self::$defaultSettings; + } elseif (!\is_array($options['allow_redirects'])) { + throw new \InvalidArgumentException('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $options['allow_redirects'] += self::$defaultSettings; + } + if (empty($options['allow_redirects']['max'])) { + return $fn($request, $options); + } + return $fn($request, $options)->then(function (ResponseInterface $response) use($request, $options) { + return $this->checkRedirect($request, $options, $response); + }); + } + /** + * @return ResponseInterface|PromiseInterface + */ + public function checkRedirect(RequestInterface $request, array $options, ResponseInterface $response) + { + if (\strpos((string) $response->getStatusCode(), '3') !== 0 || !$response->hasHeader('Location')) { + return $response; + } + $this->guardMax($request, $response, $options); + $nextRequest = $this->modifyRequest($request, $options, $response); + // If authorization is handled by curl, unset it if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $nextRequest->getUri()) && \defined('\\CURLOPT_HTTPAUTH')) { + unset($options['curl'][\CURLOPT_HTTPAUTH], $options['curl'][\CURLOPT_USERPWD]); + } + if (isset($options['allow_redirects']['on_redirect'])) { + $options['allow_redirects']['on_redirect']($request, $response, $nextRequest->getUri()); + } + $promise = $this($nextRequest, $options); + // Add headers to be able to track history of redirects. + if (!empty($options['allow_redirects']['track_redirects'])) { + return $this->withTracking($promise, (string) $nextRequest->getUri(), $response->getStatusCode()); + } + return $promise; + } + /** + * Enable tracking on promise. + */ + private function withTracking(PromiseInterface $promise, string $uri, int $statusCode) : PromiseInterface + { + return $promise->then(static function (ResponseInterface $response) use($uri, $statusCode) { + // Note that we are pushing to the front of the list as this + // would be an earlier response than what is currently present + // in the history header. + $historyHeader = $response->getHeader(self::HISTORY_HEADER); + $statusHeader = $response->getHeader(self::STATUS_HISTORY_HEADER); + \array_unshift($historyHeader, $uri); + \array_unshift($statusHeader, (string) $statusCode); + return $response->withHeader(self::HISTORY_HEADER, $historyHeader)->withHeader(self::STATUS_HISTORY_HEADER, $statusHeader); + }); + } + /** + * Check for too many redirects. + * + * @throws TooManyRedirectsException Too many redirects. + */ + private function guardMax(RequestInterface $request, ResponseInterface $response, array &$options) : void + { + $current = $options['__redirect_count'] ?? 0; + $options['__redirect_count'] = $current + 1; + $max = $options['allow_redirects']['max']; + if ($options['__redirect_count'] > $max) { + throw new TooManyRedirectsException("Will not follow more than {$max} redirects", $request, $response); + } + } + public function modifyRequest(RequestInterface $request, array $options, ResponseInterface $response) : RequestInterface + { + // Request modifications to apply. + $modify = []; + $protocols = $options['allow_redirects']['protocols']; + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || $statusCode <= 302 && !$options['allow_redirects']['strict']) { + $safeMethods = ['GET', 'HEAD', 'OPTIONS']; + $requestMethod = $request->getMethod(); + $modify['method'] = \in_array($requestMethod, $safeMethods) ? $requestMethod : 'GET'; + $modify['body'] = ''; + } + $uri = self::redirectUri($request, $response, $protocols); + $idnOptions = Utils::normalizeIdnConversionOption($options['idn_conversion'] ?? null); + if ($idnOptions !== null) { + $uri = Utils::idnUriConvert($uri, $idnOptions); + } + $modify['uri'] = $uri; + Psr7\Message::rewindBody($request); + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($options['allow_redirects']['referer'] && $modify['uri']->getScheme() === $request->getUri()->getScheme()) { + $uri = $request->getUri()->withUserInfo(''); + $modify['set_headers']['Referer'] = (string) $uri; + } else { + $modify['remove_headers'][] = 'Referer'; + } + // Remove Authorization and Cookie headers if URI is cross-origin. + if (Psr7\UriComparator::isCrossOrigin($request->getUri(), $modify['uri'])) { + $modify['remove_headers'][] = 'Authorization'; + $modify['remove_headers'][] = 'Cookie'; + } + return Psr7\Utils::modifyRequest($request, $modify); + } + /** + * Set the appropriate URL on the request based on the location header. + */ + private static function redirectUri(RequestInterface $request, ResponseInterface $response, array $protocols) : UriInterface + { + $location = Psr7\UriResolver::resolve($request->getUri(), new Psr7\Uri($response->getHeaderLine('Location'))); + // Ensure that the redirect URI is allowed based on the protocols. + if (!\in_array($location->getScheme(), $protocols)) { + throw new BadResponseException(\sprintf('Redirect URI, %s, does not use one of the allowed redirect protocols: %s', $location, \implode(', ', $protocols)), $request, $response); + } + return $location; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RequestOptions.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RequestOptions.php new file mode 100644 index 00000000..759ebd3d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RequestOptions.php @@ -0,0 +1,286 @@ +, default=['http', 'https']) + * Allowed redirect protocols. Redirect matching is case-sensitive; use + * "http" and "https". + * - on_redirect: (callable) PHP callable that is invoked when a redirect + * is encountered. The callable is invoked with the request, the redirect + * response that was received, and the effective URI. Any return value + * from the on_redirect function is ignored. + * - track_redirects: (bool, default=false) Track redirected URI and status + * history in response headers. + */ + public const ALLOW_REDIRECTS = 'allow_redirects'; + /** + * auth: (array{0: string, 1: string, 2?: string|null}|string|false|null) + * Pass an array of HTTP authentication parameters to use with the request. + * The array must contain the username in index [0], the password in index + * [1], and you can optionally provide a built-in authentication type in + * index [2]. Pass false or null to disable authentication for a request. + * String values are passed through for custom handlers. + */ + public const AUTH = 'auth'; + /** + * body: (resource|string|null|int|float|bool|\Psr\Http\Message\StreamInterface|(callable&object)|\Iterator|\Stringable) + * Body to send in the request. Callable arrays are arrays, and arrays are + * not valid body values in Guzzle. + */ + public const BODY = 'body'; + /** + * cert: (string|array{0: string, 1?: string|null}) Set to a string to + * specify the path to a client certificate file. PEM is the default + * certificate format. If a password is required, set cert to an array + * containing the certificate path in the first array element followed by + * the certificate password in the second array element. A null password is + * treated the same as omitting it. Use cert_type to specify another + * supported certificate format. + */ + public const CERT = 'cert'; + /** + * cert_type: (string) Specify the SSL client certificate file type. + */ + public const CERT_TYPE = 'cert_type'; + /** + * cookies: (false|GuzzleHttp\Cookie\CookieJarInterface, default=false) + * Specifies whether or not cookies are used in a request or what cookie + * jar to use or what cookies to send. This option only works if your + * handler has the `cookie` middleware. Valid values are `false` and + * an instance of {@see Cookie\CookieJarInterface}. + */ + public const COOKIES = 'cookies'; + /** + * connect_timeout: (int|float, default=0) Number of seconds to wait while + * trying to connect to a server. Use 0 to wait 300 seconds (the default + * behavior). + */ + public const CONNECT_TIMEOUT = 'connect_timeout'; + /** + * crypto_method: (int) A value describing the minimum TLS protocol + * version to use. + * + * This setting must be set to one of the + * ``STREAM_CRYPTO_METHOD_TLS*_CLIENT`` constants. PHP 7.4 or higher is + * required in order to use TLS 1.3, and cURL 7.34.0 or higher is required + * in order to specify a crypto method, with cURL 7.52.0 or higher being + * required to use TLS 1.3. + */ + public const CRYPTO_METHOD = 'crypto_method'; + /** + * curl: (array) Raw cURL options to apply when using a built-in cURL handler. + */ + public const CURL = 'curl'; + /** + * debug: (bool|resource) Set to true or set to a PHP stream returned by + * fopen() enable debug output with the HTTP handler used to send a + * request. + */ + public const DEBUG = 'debug'; + /** + * decode_content: (bool|string, default=true) Specify whether or not + * Content-Encoding responses (gzip, deflate, etc.) are automatically + * decoded. + */ + public const DECODE_CONTENT = 'decode_content'; + /** + * delay: (int|float) The amount of time to delay before sending in + * milliseconds. + */ + public const DELAY = 'delay'; + /** + * expect: (bool|integer) Controls the behavior of the + * "Expect: 100-Continue" header. + * + * Set to `true` to enable the "Expect: 100-Continue" header for all + * requests that sends a body. Set to `false` to disable the + * "Expect: 100-Continue" header for all requests. Set to a number so that + * the size of the payload must be greater than the number in order to send + * the Expect header. Setting to a number will send the Expect header for + * all requests in which the size of the payload cannot be determined or + * where the body is not rewindable. + * + * By default, Guzzle will add the "Expect: 100-Continue" header when the + * size of the body of a request is greater than 1 MB and a request is + * using HTTP/1.1. + */ + public const EXPECT = 'expect'; + /** + * form_params: (array) + * Associative array of form field names to scalar, null, or nested array + * values. Sets the Content-Type header to application/x-www-form-urlencoded + * when no Content-Type header is already present. + */ + public const FORM_PARAMS = 'form_params'; + /** + * headers: (array>|null) + * Associative array of HTTP headers. Each value MUST be a string or non-empty + * array of strings. + */ + public const HEADERS = 'headers'; + /** + * http_errors: (bool, default=true) Set to false to disable exceptions + * when a non- successful HTTP response is received. By default, + * exceptions will be thrown for 4xx and 5xx responses. This option only + * works if your handler has the `httpErrors` middleware. + */ + public const HTTP_ERRORS = 'http_errors'; + /** + * idn_conversion: (bool|int|null, default=false) A combination of IDNA_* + * constants for PHP's idn_to_ascii() function. Set to false or null to + * disable IDN support, or to true to use the default configuration + * (IDNA_DEFAULT constant). + */ + public const IDN_CONVERSION = 'idn_conversion'; + /** + * json: (mixed) Adds JSON data to a request. The provided value is JSON + * encoded and a Content-Type header of application/json will be added to + * the request if no Content-Type header is already present. + */ + public const JSON = 'json'; + /** + * multipart: (array) Array of part arrays, each containing a required + * "name" key mapping to the string or integer form field name, a required + * "contents" key mapping to any non-array value accepted by PSR-7 + * Utils::streamFor() or a nested array of field values, an optional + * "headers" array of string custom header values, and an optional + * "filename" key mapping to a string to send as the filename in the part. + * "headers" and "filename" cannot be used when "contents" is an array. + */ + public const MULTIPART = 'multipart'; + /** + * on_headers: (callable) A callable that is invoked when the HTTP headers + * of the response have been received but the body has not yet begun to + * download. + */ + public const ON_HEADERS = 'on_headers'; + /** + * on_stats: (callable) allows you to get access to transfer statistics of + * a request and access the lower level transfer details of the handler + * associated with your client. ``on_stats`` is a callable that is invoked + * when a handler has finished sending a request. The callback is invoked + * with transfer statistics about the request, the response received, or + * the error encountered. Included in the data is the total amount of time + * taken to send the request. + */ + public const ON_STATS = 'on_stats'; + /** + * progress: (callable) Defines a function to invoke when transfer + * progress is made. The function accepts the following positional + * arguments: the total number of bytes expected to be downloaded, the + * number of bytes downloaded so far, the number of bytes expected to be + * uploaded, the number of bytes uploaded so far. + */ + public const PROGRESS = 'progress'; + /** + * protocols: (non-empty-array, default=['http', 'https']) + * Allowed URI schemes. Built-in handlers accept only the case-sensitive + * values "http" and "https". + */ + public const PROTOCOLS = 'protocols'; + /** + * proxy: (string|array) Pass a string to specify an HTTP proxy, or an + * array to specify different proxies for different protocols (where the + * key is the protocol and the value is a proxy string or null). Provide a + * "no" key as a comma-delimited string, array of strings, or null to + * specify hosts or host-and-port pairs that should not be proxied. + */ + public const PROXY = 'proxy'; + /** + * query: (array|string) Associative array of query string + * values to add to the request. This option uses PHP's http_build_query() + * to create the string representation. Pass a string value if you need + * more control than what this method provides + */ + public const QUERY = 'query'; + /** + * sink: (resource|string|\Psr\Http\Message\StreamInterface) Where the data + * of the response is written to. Defaults to a PHP temp stream. Providing + * a string will write data to a file by the given name. + */ + public const SINK = 'sink'; + /** + * synchronous: (bool) Set to true to inform HTTP handlers that you intend + * on waiting on the response. This can be useful for optimizations. Note + * that a promise is still returned if you are using one of the async + * client methods. + */ + public const SYNCHRONOUS = 'synchronous'; + /** + * ssl_key: (array{0: string, 1?: string|null}|string) Specify the path to + * a private SSL key file. PEM is the default private key format. If a + * password is required, set ssl_key to an array containing the key path in + * the first array element followed by the key password in the second + * element. A null password is treated the same as omitting it. Use + * ssl_key_type to specify another supported key format. + */ + public const SSL_KEY = 'ssl_key'; + /** + * ssl_key_type: (string) Specify the SSL private key file type. + */ + public const SSL_KEY_TYPE = 'ssl_key_type'; + /** + * stream: (bool) Set to true to attempt to stream a response rather than + * download it all up-front. + */ + public const STREAM = 'stream'; + /** + * stream_context: (array) PHP stream context options to merge into the + * context used by the built-in stream handler. + */ + public const STREAM_CONTEXT = 'stream_context'; + /** + * verify: (bool|string, default=true) Describes the SSL certificate + * verification behavior of a request. Set to true to enable SSL + * certificate verification using the system CA bundle when available + * (the default). Set to false to disable certificate verification (this + * is insecure!). Set to a string to provide the path to a CA bundle on + * disk to enable verification using a custom certificate. + */ + public const VERIFY = 'verify'; + /** + * timeout: (int|float, default=0) Number describing the timeout of the + * request in seconds. Use 0 to wait indefinitely (the default behavior). + */ + public const TIMEOUT = 'timeout'; + /** + * read_timeout: (int|float, default=default_socket_timeout ini setting) + * Number describing the body read timeout, for stream requests. + */ + public const READ_TIMEOUT = 'read_timeout'; + /** + * retries: (int) Current retry count used by the retry middleware. + */ + public const RETRIES = 'retries'; + /** + * version: (string|int|float) Specifies the HTTP protocol version to attempt + * to use. + */ + public const VERSION = 'version'; + /** + * force_ip_resolve: (string) Set to "v4" to force IPv4 resolution or "v6" + * for IPv6 resolution when supported by the handler. + */ + public const FORCE_IP_RESOLVE = 'force_ip_resolve'; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RetryMiddleware.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RetryMiddleware.php new file mode 100644 index 00000000..8e53badc --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/RetryMiddleware.php @@ -0,0 +1,96 @@ +decider = $decider; + $this->nextHandler = $nextHandler; + $this->delay = $delay ?: static function (int $retries) : int { + return (int) 2 ** ($retries - 1) * 1000; + }; + } + /** + * Default exponential backoff delay function. + * + * @return int milliseconds. + * + * @deprecated since 7.11, will be removed in 8.0. + */ + public static function exponentialDelay(int $retries) : int + { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', '%s::%s() is deprecated and will be removed in 8.0.', __CLASS__, __FUNCTION__); + return (int) 2 ** ($retries - 1) * 1000; + } + public function __invoke(RequestInterface $request, array $options) : PromiseInterface + { + if (!isset($options['retries'])) { + $options['retries'] = 0; + } + $fn = $this->nextHandler; + return $fn($request, $options)->then($this->onFulfilled($request, $options), $this->onRejected($request, $options)); + } + /** + * Execute fulfilled closure + */ + private function onFulfilled(RequestInterface $request, array $options) : callable + { + return function ($value) use($request, $options) { + if (!($this->decider)($options['retries'], $request, $value, null)) { + return $value; + } + return $this->doRetry($request, $options, $value); + }; + } + /** + * Execute rejected closure + */ + private function onRejected(RequestInterface $req, array $options) : callable + { + return function ($reason) use($req, $options) { + if (!($this->decider)($options['retries'], $req, null, $reason)) { + return P\Create::rejectionFor($reason); + } + return $this->doRetry($req, $options); + }; + } + private function doRetry(RequestInterface $request, array $options, ?ResponseInterface $response = null) : PromiseInterface + { + $options['delay'] = ($this->delay)(++$options['retries'], $response, $request); + return $this($request, $options); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransferStats.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransferStats.php new file mode 100644 index 00000000..e1eca7a3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransferStats.php @@ -0,0 +1,114 @@ +request = $request; + $this->response = $response; + $this->transferTime = $transferTime; + $this->handlerErrorData = $handlerErrorData; + $this->handlerStats = $handlerStats; + } + public function getRequest() : RequestInterface + { + return $this->request; + } + /** + * Returns the response that was received (if any). + */ + public function getResponse() : ?ResponseInterface + { + return $this->response; + } + /** + * Returns true if a response was received. + */ + public function hasResponse() : bool + { + return $this->response !== null; + } + /** + * Gets handler specific error data. + * + * This might be an exception, a integer representing an error code, or + * anything else. Relying on this value assumes that you know what handler + * you are using. + * + * @return mixed + */ + public function getHandlerErrorData() + { + return $this->handlerErrorData; + } + /** + * Get the effective URI the request was sent to. + */ + public function getEffectiveUri() : UriInterface + { + return $this->request->getUri(); + } + /** + * Get the estimated time the request was being transferred by the handler. + * + * @return float|null Time in seconds. + */ + public function getTransferTime() : ?float + { + return $this->transferTime; + } + /** + * Gets an array of all of the handler specific transfer data. + */ + public function getHandlerStats() : array + { + return $this->handlerStats; + } + /** + * Get a specific handler statistic from the handler by name. + * + * @param string $stat Handler specific transfer stat to retrieve. + * + * @return mixed|null + */ + public function getHandlerStat(string $stat) + { + return $this->handlerStats[$stat] ?? null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransportSharing.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransportSharing.php new file mode 100644 index 00000000..149da98d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/TransportSharing.php @@ -0,0 +1,13 @@ + $sharingMode]); + $handler = $handler ? Proxy::wrapStreaming($handler, $streamHandler) : $streamHandler; + } elseif (!$handler) { + throw new \RuntimeException('GuzzleHttp requires cURL, the allow_url_fopen ini setting, or a custom HTTP handler.'); + } + return $handler; + } + /** + * Get the default User-Agent string to use with Guzzle. + */ + public static function defaultUserAgent() : string + { + return \sprintf('GuzzleHttp/%d', ClientInterface::MAJOR_VERSION); + } + /** + * Returns the default cacert bundle for the current system. + * + * First, the openssl.cafile and curl.cainfo php.ini settings are checked. + * If those settings are not configured, then the common locations for + * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X + * and Windows are checked. If any of these file locations are found on + * disk, they will be utilized. + * + * Note: the result of this function is cached for subsequent calls. + * + * @throws \RuntimeException if no bundle can be found. + * + * @deprecated Utils::defaultCaBundle will be removed in guzzlehttp/guzzle:8.0. This method is not needed in PHP 5.6+. + */ + public static function defaultCaBundle() : string + { + static $cached = null; + static $cafiles = [ + // Red Hat, CentOS, Fedora (provided by the ca-certificates package) + '/etc/pki/tls/certs/ca-bundle.crt', + // Ubuntu, Debian (provided by the ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', + // FreeBSD (provided by the ca_root_nss package) + '/usr/local/share/certs/ca-root-nss.crt', + // SLES 12 (provided by the ca-certificates package) + '/var/lib/ca-certificates/ca-bundle.pem', + // OS X provided by homebrew (using the default path) + '/usr/local/etc/openssl/cert.pem', + // Google app engine + '/etc/ca-certificates.crt', + // Windows? + 'C:\\windows\\system32\\curl-ca-bundle.crt', + 'C:\\windows\\curl-ca-bundle.crt', + ]; + if ($cached) { + return $cached; + } + if ($ca = \ini_get('openssl.cafile')) { + return $cached = $ca; + } + if ($ca = \ini_get('curl.cainfo')) { + return $cached = $ca; + } + foreach ($cafiles as $filename) { + if (\file_exists($filename)) { + return $cached = $filename; + } + } + throw new \RuntimeException(<< $noProxy + */ + private static function matchesNoProxyList(array $target, array $noProxy) : bool + { + foreach ($noProxy as $area) { + if (!\is_string($area)) { + continue; + } + $area = \trim($area); + // Always match on wildcards. + if ($area === '*') { + return \true; + } + $rule = self::parseNoProxyRule($area); + if ($rule !== null && self::noProxyRuleMatches($target, $rule)) { + return \true; + } + } + return \false; + } + /** + * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null + */ + private static function parseNoProxyTarget(UriInterface $uri) : ?array + { + $host = $uri->getHost(); + if ($host === '') { + return null; + } + return self::parseNoProxyHost($host, $uri->getPort() ?? self::getDefaultPort($uri->getScheme()), \true); + } + /** + * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null + */ + private static function parseNoProxyHostString(string $host) : ?array + { + $hostAndPort = self::splitNoProxyHostAndPort($host); + if ($hostAndPort === null) { + return null; + } + [$host] = $hostAndPort; + return self::parseNoProxyHost($host, null, \true); + } + /** + * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|array{type: string, value: string, prefix: int}|null + */ + private static function parseNoProxyRule(string $area) : ?array + { + $area = \trim($area); + if ($area === '' || $area === '*') { + return null; + } + if (\strpos($area, '/') !== \false) { + return self::parseNoProxyCidrRule($area); + } + $matchesRoot = \true; + if ($area[0] === '.') { + $matchesRoot = \false; + $area = \substr($area, 1); + } + $hostAndPort = self::splitNoProxyHostAndPort($area); + if ($hostAndPort === null) { + return null; + } + [$host, $port] = $hostAndPort; + if ($host === '*') { + if (!$matchesRoot) { + return null; + } + return ['type' => 'wildcard', 'value' => '*', 'port' => $port, 'matchesRoot' => \true]; + } + $rule = self::parseNoProxyHost($host, $port, $matchesRoot); + if ($rule !== null && !$matchesRoot && $rule['type'] === 'ip') { + return null; + } + return $rule; + } + /** + * @return array{type: string, value: string, port: int|null, matchesRoot: bool}|null + */ + private static function parseNoProxyHost(string $host, ?int $port, bool $matchesRoot) : ?array + { + if ($host !== '' && $host[0] === '[') { + if (\substr($host, -1) !== ']') { + return null; + } + $address = \substr($host, 1, -1); + if (!\filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) { + return null; + } + $host = $address; + } + $packedIp = self::packIpAddress($host); + if ($packedIp !== \false) { + return ['type' => 'ip', 'value' => $packedIp, 'port' => $port, 'matchesRoot' => $matchesRoot]; + } + if ($host === '' || \strpos($host, ':') !== \false) { + return null; + } + // Normalize a single DNS root dot for no-proxy domain matching. + if (\substr($host, -1) === '.') { + $host = \substr($host, 0, -1); + if ($host === '') { + return null; + } + } + return ['type' => 'domain', 'value' => \strtolower($host), 'port' => $port, 'matchesRoot' => $matchesRoot]; + } + /** + * @return array{0: string, 1: int|null}|null + */ + private static function splitNoProxyHostAndPort(string $area) : ?array + { + if ($area !== '' && $area[0] === '[') { + $closingBracket = \strpos($area, ']'); + if ($closingBracket === \false) { + return null; + } + $host = \substr($area, 0, $closingBracket + 1); + $tail = \substr($area, $closingBracket + 1); + if ($tail === '') { + return [$host, null]; + } + if ($tail[0] !== ':') { + return null; + } + $port = self::parseNoProxyPort(\substr($tail, 1)); + return $port === null ? null : [$host, $port]; + } + if (self::packIpAddress($area) !== \false) { + return [$area, null]; + } + $colon = \strrpos($area, ':'); + if ($colon === \false) { + return [$area, null]; + } + $port = self::parseNoProxyPort(\substr($area, $colon + 1)); + if ($port === null) { + return null; + } + return [\substr($area, 0, $colon), $port]; + } + private static function parseNoProxyPort(string $port) : ?int + { + return self::parseBoundedUnsignedInteger($port, 65535); + } + /** + * @return array{type: string, value: string, prefix: int}|null + */ + private static function parseNoProxyCidrRule(string $area) : ?array + { + $slash = \strpos($area, '/'); + if ($slash === \false) { + return null; + } + $prefix = \substr($area, $slash + 1); + $network = \substr($area, 0, $slash); + if ($network !== '' && $network[0] === '[' && \substr($network, -1) === ']') { + $network = \substr($network, 1, -1); + } + $network = self::packIpAddress($network); + if ($network === \false) { + return null; + } + $prefix = self::parseBoundedUnsignedInteger($prefix, \strlen($network) * 8); + if ($prefix === null) { + return null; + } + return ['type' => 'cidr', 'value' => $network, 'prefix' => $prefix]; + } + private static function parseBoundedUnsignedInteger(string $value, int $max) : ?int + { + if ($value === '' || !\ctype_digit($value)) { + return null; + } + $normalized = \ltrim($value, '0'); + $normalized = $normalized === '' ? '0' : $normalized; + $limit = (string) $max; + if (\strlen($normalized) > \strlen($limit) || \strlen($normalized) === \strlen($limit) && \strcmp($normalized, $limit) > 0) { + return null; + } + return (int) $normalized; + } + /** + * @param array{type: string, value: string, port: int|null, matchesRoot: bool} $target + * @param array{type: string, value: string, port?: int|null, matchesRoot?: bool, prefix?: int|null} $rule + */ + private static function noProxyRuleMatches(array $target, array $rule) : bool + { + if ($rule['type'] === 'wildcard') { + return ($rule['port'] ?? null) === null || $rule['port'] === $target['port']; + } + if ($rule['type'] === 'cidr') { + if ($target['type'] !== 'ip' || !isset($rule['prefix'])) { + return \false; + } + if (\strlen($target['value']) !== \strlen($rule['value'])) { + return \false; + } + return self::ipMatchesPrefix($target['value'], $rule['value'], $rule['prefix']); + } + if (($rule['port'] ?? null) !== null && $rule['port'] !== $target['port']) { + return \false; + } + if ($rule['type'] !== $target['type']) { + return \false; + } + if ($rule['type'] === 'ip') { + return $rule['value'] === $target['value']; + } + if (($rule['matchesRoot'] ?? \false) && $target['value'] === $rule['value']) { + return \true; + } + $suffix = '.' . $rule['value']; + return \substr($target['value'], -\strlen($suffix)) === $suffix; + } + /** + * @return string|false + */ + private static function packIpAddress(string $ip) + { + if (!\filter_var($ip, \FILTER_VALIDATE_IP)) { + return \false; + } + return \inet_pton($ip); + } + private static function ipMatchesPrefix(string $address, string $network, int $prefix) : bool + { + $fullBytes = \intdiv($prefix, 8); + $remainingBits = $prefix % 8; + if ($fullBytes > 0 && \substr($address, 0, $fullBytes) !== \substr($network, 0, $fullBytes)) { + return \false; + } + if ($remainingBits === 0) { + return \true; + } + $mask = 0xff << 8 - $remainingBits & 0xff; + return (\ord($address[$fullBytes]) & $mask) === (\ord($network[$fullBytes]) & $mask); + } + private static function getDefaultPort(string $scheme) : ?int + { + if ($scheme === 'http') { + return 80; + } + if ($scheme === 'https') { + return 443; + } + return null; + } + /** + * Wrapper for json_decode that throws when an error occurs. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return object|array|string|int|float|bool|null + * + * @throws InvalidArgumentException if the JSON cannot be decoded. + * + * @see https://www.php.net/manual/en/function.json-decode.php + */ + public static function jsonDecode(string $json, bool $assoc = \false, int $depth = 512, int $options = 0) + { + if ($depth < 1) { + throw new InvalidArgumentException('json_decode error: Maximum stack depth exceeded'); + } + $data = \json_decode($json, $assoc, $depth, $options); + if (\JSON_ERROR_NONE !== \json_last_error()) { + throw new InvalidArgumentException('json_decode error: ' . \json_last_error_msg()); + } + return $data; + } + /** + * Wrapper for JSON encoding that throws when an error occurs. + * + * @param mixed $value The value being encoded + * @param int $options JSON encode option bitmask + * @param int $depth Set the maximum depth. Must be greater than zero. + * + * @throws InvalidArgumentException if the JSON cannot be encoded. + * + * @see https://www.php.net/manual/en/function.json-encode.php + */ + public static function jsonEncode($value, int $options = 0, int $depth = 512) : string + { + $json = \json_encode($value, $options, $depth); + if (\JSON_ERROR_NONE !== \json_last_error()) { + throw new InvalidArgumentException('json_encode error: ' . \json_last_error_msg()); + } + /** @var string */ + return $json; + } + /** + * Wrapper for the hrtime() or microtime() functions + * (depending on the PHP version, one of the two is used) + * + * @return float UNIX timestamp + * + * @internal + */ + public static function currentTime() : float + { + return (float) \function_exists('hrtime') ? \hrtime(\true) / 1000000000.0 : \microtime(\true); + } + /** + * @param mixed $value + * + * @internal + */ + public static function normalizeIdnConversionOption($value) : ?int + { + if ($value === null || $value === \false) { + return null; + } + if ($value === \true) { + return \IDNA_DEFAULT; + } + if (\is_int($value)) { + return $value; + } + if (\is_string($value) && \is_numeric($value) || \is_float($value) && \is_finite($value)) { + \trigger_deprecation('guzzlehttp/guzzle', '7.11', 'Passing %s as the "idn_conversion" request option is deprecated; guzzlehttp/guzzle 8.0 will reject values that are not true, false, null, or an integer IDNA_* bitmask.', \get_debug_type($value)); + return (int) $value; + } + throw new InvalidArgumentException('idn_conversion must be true, false, null, or an integer IDNA_* bitmask'); + } + /** + * @throws InvalidArgumentException + * + * @internal + */ + public static function idnUriConvert(UriInterface $uri, int $options = 0) : UriInterface + { + if ($uri->getHost()) { + $asciiHost = self::idnToAsci($uri->getHost(), $options, $info); + if ($asciiHost === \false) { + $errorBitSet = $info['errors'] ?? 0; + $errorConstants = \array_filter(\array_keys(\get_defined_constants()), static function (string $name) : bool { + return \substr($name, 0, 11) === 'IDNA_ERROR_'; + }); + $errors = []; + foreach ($errorConstants as $errorConstant) { + if ($errorBitSet & \constant($errorConstant)) { + $errors[] = $errorConstant; + } + } + $errorMessage = 'IDN conversion failed'; + if ($errors) { + $errorMessage .= ' (errors: ' . \implode(', ', $errors) . ')'; + } + throw new InvalidArgumentException($errorMessage); + } + if ($uri->getHost() !== $asciiHost) { + // Replace URI only if the ASCII version is different + $uri = $uri->withHost($asciiHost); + } + } + return $uri; + } + /** + * @internal + */ + public static function getenv(string $name) : ?string + { + if (isset($_SERVER[$name])) { + return (string) $_SERVER[$name]; + } + if (\PHP_SAPI === 'cli' && ($value = \getenv($name)) !== \false && $value !== null) { + return (string) $value; + } + return null; + } + /** + * @return string|false + */ + private static function idnToAsci(string $domain, int $options, ?array &$info = []) + { + if (\function_exists('idn_to_ascii') && \defined('INTL_IDNA_VARIANT_UTS46')) { + return \idn_to_ascii($domain, $options, \INTL_IDNA_VARIANT_UTS46, $info); + } + throw new \Error('ext-idn or symfony/polyfill-intl-idn not loaded or too old'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/functions.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/functions.php new file mode 100644 index 00000000..85ef0ef0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/guzzle/src/functions.php @@ -0,0 +1,158 @@ + +Copyright (c) 2015 Graham Campbell +Copyright (c) 2017 Tobias Schultze +Copyright (c) 2020 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/AggregateException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/AggregateException.php new file mode 100644 index 00000000..0d0770c8 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/AggregateException.php @@ -0,0 +1,15 @@ +then(function ($v) { echo $v; }); + * + * @param callable $generatorFn Generator function to wrap into a promise. + * + * @return Promise + * + * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration + */ +final class Coroutine implements PromiseInterface +{ + /** + * @var PromiseInterface|null + */ + private $currentPromise; + /** + * @var Generator + */ + private $generator; + /** + * @var Promise + */ + private $result; + public function __construct(callable $generatorFn) + { + $this->generator = $generatorFn(); + $this->result = new Promise(function () : void { + while (isset($this->currentPromise)) { + $this->currentPromise->wait(); + } + }); + try { + $this->nextCoroutine($this->generator->current()); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + /** + * Create a new coroutine. + */ + public static function of(callable $generatorFn) : self + { + return new self($generatorFn); + } + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface + { + return $this->result->then($onFulfilled, $onRejected); + } + public function otherwise(callable $onRejected) : PromiseInterface + { + return $this->result->otherwise($onRejected); + } + public function wait(bool $unwrap = \true) + { + return $this->result->wait($unwrap); + } + public function getState() : string + { + return $this->result->getState(); + } + public function resolve($value) : void + { + $this->result->resolve($value); + } + public function reject($reason) : void + { + $this->result->reject($reason); + } + public function cancel() : void + { + if (isset($this->currentPromise)) { + $this->currentPromise->cancel(); + } + $this->result->cancel(); + } + private function nextCoroutine($yielded) : void + { + $this->currentPromise = Create::promiseFor($yielded)->then([$this, '_handleSuccess'], [$this, '_handleFailure']); + } + /** + * @internal + */ + public function _handleSuccess($value) : void + { + unset($this->currentPromise); + try { + $next = $this->generator->send($value); + if ($this->generator->valid()) { + $this->nextCoroutine($next); + } else { + $this->result->resolve($value); + } + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } + /** + * @internal + */ + public function _handleFailure($reason) : void + { + unset($this->currentPromise); + try { + $nextYield = $this->generator->throw(Create::exceptionFor($reason)); + // The throw was caught, so keep iterating on the coroutine + $this->nextCoroutine($nextYield); + } catch (Throwable $throwable) { + $this->result->reject($throwable); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Create.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Create.php new file mode 100644 index 00000000..0f4b44c4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Create.php @@ -0,0 +1,71 @@ +then([$promise, 'resolve'], [$promise, 'reject']); + return $promise; + } + return new FulfilledPromise($value); + } + /** + * Creates a rejected promise for a reason if the reason is not a promise. + * If the provided reason is a promise, then it is returned as-is. + * + * @param mixed $reason Promise or reason. + */ + public static function rejectionFor($reason) : PromiseInterface + { + if ($reason instanceof PromiseInterface) { + return $reason; + } + return new RejectedPromise($reason); + } + /** + * Create an exception for a rejected promise value. + * + * @param mixed $reason + */ + public static function exceptionFor($reason) : \Throwable + { + if ($reason instanceof \Throwable) { + return $reason; + } + return new RejectionException($reason); + } + /** + * Returns an iterator for the given value. + * + * @param mixed $value + */ + public static function iterFor($value) : \Iterator + { + if ($value instanceof \Iterator) { + return $value; + } + if (\is_array($value)) { + return new \ArrayIterator($value); + } + if (!\is_iterable($value)) { + \trigger_deprecation('guzzlehttp/promises', '2.5', 'Passing a non-iterable to %s::%s() is deprecated; guzzlehttp/promises 3.0 will require an iterable.', __CLASS__, __FUNCTION__); + } + return new \ArrayIterator([$value]); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Each.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Each.php new file mode 100644 index 00000000..ddc9eaa9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Each.php @@ -0,0 +1,67 @@ + $onFulfilled, 'rejected' => $onRejected]))->promise(); + } + /** + * Like of, but only allows a certain number of outstanding promises at any + * given time. + * + * $concurrency may be an integer or a function that accepts the number of + * pending promises and returns a numeric concurrency limit value to allow + * for dynamic a concurrency size. + * + * @param mixed $iterable + * @param int|callable $concurrency + */ + public static function ofLimit($iterable, $concurrency, ?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface + { + $iterable = self::prepareIterable($iterable, __FUNCTION__); + return (new EachPromise($iterable, ['fulfilled' => $onFulfilled, 'rejected' => $onRejected, 'concurrency' => $concurrency]))->promise(); + } + /** + * Like limit, but ensures that no promise in the given $iterable argument + * is rejected. If any promise is rejected, then the aggregate promise is + * rejected with the encountered rejection. + * + * @param mixed $iterable + * @param int|callable $concurrency + */ + public static function ofLimitAll($iterable, $concurrency, ?callable $onFulfilled = null) : PromiseInterface + { + $iterable = self::prepareIterable($iterable, __FUNCTION__); + return self::ofLimit($iterable, $concurrency, $onFulfilled, function ($reason, $idx, PromiseInterface $aggregate) : void { + $aggregate->reject($reason); + }); + } + private static function prepareIterable($iterable, string $method) : iterable + { + if (\is_iterable($iterable)) { + return $iterable; + } + \trigger_deprecation('guzzlehttp/promises', '2.5', 'Passing a non-iterable to %s::%s() is deprecated; guzzlehttp/promises 3.0 will require an iterable.', self::class, $method); + return [$iterable]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/EachPromise.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/EachPromise.php new file mode 100644 index 00000000..7b9f33d7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/EachPromise.php @@ -0,0 +1,212 @@ +iterable = Create::iterFor($iterable); + if (isset($config['concurrency'])) { + $this->concurrency = $config['concurrency']; + } + if (isset($config['fulfilled'])) { + $this->onFulfilled = $config['fulfilled']; + } + if (isset($config['rejected'])) { + $this->onRejected = $config['rejected']; + } + } + /** @psalm-suppress InvalidNullableReturnType */ + public function promise() : PromiseInterface + { + if ($this->aggregate) { + return $this->aggregate; + } + try { + $this->createPromise(); + /** @psalm-assert Promise $this->aggregate */ + $this->iterable->rewind(); + $this->refillPending(); + if (!$this->pending) { + Utils::queue()->add(function () : void { + if (!$this->aggregate || Is::settled($this->aggregate)) { + return; + } + try { + $this->checkIfFinished(); + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } + }); + } + } catch (\Throwable $e) { + $this->aggregate->reject($e); + } + /** + * @psalm-suppress NullableReturnStatement + */ + return $this->aggregate; + } + private function createPromise() : void + { + $this->mutex = \false; + $this->aggregate = new Promise(function () : void { + if ($this->checkIfFinished()) { + return; + } + \reset($this->pending); + // Consume a potentially fluctuating list of promises while + // ensuring that indexes are maintained (precluding array_shift). + while ($promise = \current($this->pending)) { + \next($this->pending); + $promise->wait(); + if (Is::settled($this->aggregate)) { + return; + } + } + }); + // Clear the references when the promise is resolved. + $clearFn = function () : void { + $this->iterable = $this->concurrency = $this->pending = null; + $this->onFulfilled = $this->onRejected = null; + $this->nextPendingIndex = 0; + }; + $this->aggregate->then($clearFn, $clearFn); + } + private function refillPending() : void + { + if (!$this->concurrency) { + // Add all pending promises. + while ($this->addPending() && $this->advanceIterator()) { + } + return; + } + // Add only up to N pending promises. + $concurrency = \is_callable($this->concurrency) ? ($this->concurrency)(\count($this->pending)) : $this->concurrency; + $concurrency = \max($concurrency - \count($this->pending), 0); + // Concurrency may be set to 0 to disallow new promises. + if (!$concurrency) { + return; + } + // Add the first pending promise. + $this->addPending(); + // Note this is special handling for concurrency=1 so that we do + // not advance the iterator after adding the first promise. This + // helps work around issues with generators that might not have the + // next value to yield until promise callbacks are called. + while (--$concurrency && $this->advanceIterator() && $this->addPending()) { + } + } + private function addPending() : bool + { + if (!$this->iterable || !$this->iterable->valid()) { + return \false; + } + $promise = Create::promiseFor($this->iterable->current()); + $key = $this->iterable->key(); + // Iterable keys may not be unique, so we use a counter to + // guarantee uniqueness + $idx = $this->nextPendingIndex++; + $this->pending[$idx] = $promise->then(function ($value) use($idx, $key) : void { + if ($this->onFulfilled) { + ($this->onFulfilled)($value, $key, $this->aggregate); + } + $this->step($idx); + }, function ($reason) use($idx, $key) : void { + if ($this->onRejected) { + ($this->onRejected)($reason, $key, $this->aggregate); + } + $this->step($idx); + }); + return \true; + } + private function advanceIterator() : bool + { + // Place a lock on the iterator so that we ensure to not recurse, + // preventing fatal generator errors. + if ($this->mutex) { + return \false; + } + $this->mutex = \true; + try { + $this->iterable->next(); + $this->mutex = \false; + return \true; + } catch (\Throwable $e) { + $this->aggregate->reject($e); + $this->mutex = \false; + return \false; + } + } + private function step(int $idx) : void + { + // If the promise was already resolved, then ignore this step. + if (Is::settled($this->aggregate)) { + return; + } + unset($this->pending[$idx]); + // Only refill pending promises if we are not locked, preventing the + // EachPromise to recursively invoke the provided iterator, which + // cause a fatal error: "Cannot resume an already running generator" + if ($this->advanceIterator() && !$this->checkIfFinished()) { + // Add more pending promises if possible. + $this->refillPending(); + } + } + private function checkIfFinished() : bool + { + if (!$this->pending && !$this->iterable->valid()) { + // Resolve the promise if there's nothing left to do. + $this->aggregate->resolve(null); + return \true; + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/FulfilledPromise.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/FulfilledPromise.php new file mode 100644 index 00000000..7cf85af3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/FulfilledPromise.php @@ -0,0 +1,73 @@ +value = $value; + } + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface + { + // Return itself if there is no onFulfilled function. + if (!$onFulfilled) { + return $this; + } + $queue = Utils::queue(); + $p = new Promise([$queue, 'run']); + $value = $this->value; + $queue->add(static function () use($p, $value, $onFulfilled) : void { + if (Is::pending($p)) { + try { + $p->resolve($onFulfilled($value)); + } catch (\Throwable $e) { + $p->reject($e); + } + } + }); + return $p; + } + public function otherwise(callable $onRejected) : PromiseInterface + { + return $this->then(null, $onRejected); + } + public function wait(bool $unwrap = \true) + { + return $unwrap ? $this->value : null; + } + public function getState() : string + { + return self::FULFILLED; + } + public function resolve($value) : void + { + if ($value !== $this->value) { + throw new \LogicException('Cannot resolve a fulfilled promise'); + } + } + public function reject($reason) : void + { + throw new \LogicException('Cannot reject a fulfilled promise'); + } + public function cancel() : void + { + // pass + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Is.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Is.php new file mode 100644 index 00000000..c086a9a7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Is.php @@ -0,0 +1,36 @@ +getState() === PromiseInterface::PENDING; + } + /** + * Returns true if a promise is fulfilled or rejected. + */ + public static function settled(PromiseInterface $promise) : bool + { + return $promise->getState() !== PromiseInterface::PENDING; + } + /** + * Returns true if a promise is fulfilled. + */ + public static function fulfilled(PromiseInterface $promise) : bool + { + return $promise->getState() === PromiseInterface::FULFILLED; + } + /** + * Returns true if a promise is rejected. + */ + public static function rejected(PromiseInterface $promise) : bool + { + return $promise->getState() === PromiseInterface::REJECTED; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Promise.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Promise.php new file mode 100644 index 00000000..f090ead0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/Promise.php @@ -0,0 +1,236 @@ +waitFn = $waitFn; + $this->cancelFn = $cancelFn; + } + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface + { + if ($this->state === self::PENDING) { + $p = new Promise(null, [$this, 'cancel']); + $this->handlers[] = [$p, $onFulfilled, $onRejected]; + $p->waitList = $this->waitList; + $p->waitList[] = $this; + return $p; + } + // Return a fulfilled promise and immediately invoke any callbacks. + if ($this->state === self::FULFILLED) { + $promise = Create::promiseFor($this->result); + return $onFulfilled ? $promise->then($onFulfilled) : $promise; + } + // It's either cancelled or rejected, so return a rejected promise + // and immediately invoke any callbacks. + $rejection = Create::rejectionFor($this->result); + return $onRejected ? $rejection->then(null, $onRejected) : $rejection; + } + public function otherwise(callable $onRejected) : PromiseInterface + { + return $this->then(null, $onRejected); + } + public function wait(bool $unwrap = \true) + { + $this->waitIfPending(); + if ($this->result instanceof PromiseInterface) { + return $this->result->wait($unwrap); + } + if ($unwrap) { + if ($this->state === self::FULFILLED) { + return $this->result; + } + // It's rejected so "unwrap" and throw an exception. + throw Create::exceptionFor($this->result); + } + } + public function getState() : string + { + return $this->state; + } + public function cancel() : void + { + if ($this->state !== self::PENDING) { + return; + } + $this->waitFn = $this->waitList = null; + if ($this->cancelFn) { + $fn = $this->cancelFn; + $this->cancelFn = null; + try { + $fn(); + } catch (\Throwable $e) { + $this->reject($e); + } + } + // Reject the promise only if it wasn't rejected in a then callback. + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject(new CancellationException('Promise has been cancelled')); + } + } + public function resolve($value) : void + { + $this->settle(self::FULFILLED, $value); + } + public function reject($reason) : void + { + $this->settle(self::REJECTED, $reason); + } + private function settle(string $state, $value) : void + { + if ($this->state !== self::PENDING) { + // Ignore calls with the same resolution. + if ($state === $this->state && $value === $this->result) { + return; + } + throw $this->state === $state ? new \LogicException("The promise is already {$state}.") : new \LogicException("Cannot change a {$this->state} promise to {$state}"); + } + if ($value === $this) { + throw new \LogicException('Cannot fulfill or reject a promise with itself'); + } + // Clear out the state of the promise but stash the handlers. + $this->state = $state; + $this->result = $value; + $handlers = $this->handlers; + $this->handlers = null; + $this->waitList = $this->waitFn = null; + $this->cancelFn = null; + if (!$handlers) { + return; + } + // If the value was not a settled promise or a thenable, then resolve + // it in the task queue using the correct ID. + if (!\is_object($value) || !\method_exists($value, 'then')) { + $id = $state === self::FULFILLED ? 1 : 2; + // It's a success, so resolve the handlers in the queue. + Utils::queue()->add(static function () use($id, $value, $handlers) : void { + foreach ($handlers as $handler) { + self::callHandler($id, $value, $handler); + } + }); + } elseif ($value instanceof Promise && Is::pending($value)) { + // We can just merge our handlers onto the next promise. + $value->handlers = \array_merge($value->handlers, $handlers); + } else { + // Resolve the handlers when the forwarded promise is resolved. + $value->then(static function ($value) use($handlers) : void { + foreach ($handlers as $handler) { + self::callHandler(1, $value, $handler); + } + }, static function ($reason) use($handlers) : void { + foreach ($handlers as $handler) { + self::callHandler(2, $reason, $handler); + } + }); + } + } + /** + * Call a stack of handlers using a specific callback index and value. + * + * @param int $index 1 (resolve) or 2 (reject). + * @param mixed $value Value to pass to the callback. + * @param array $handler Array of handler data (promise and callbacks). + */ + private static function callHandler(int $index, $value, array $handler) : void + { + /** @var PromiseInterface $promise */ + $promise = $handler[0]; + // The promise may have been cancelled or resolved before placing + // this thunk in the queue. + if (Is::settled($promise)) { + return; + } + try { + if (isset($handler[$index])) { + /* + * If $f throws an exception, then $handler will be in the exception + * stack trace. Since $handler contains a reference to the callable + * itself we get a circular reference. We clear the $handler + * here to avoid that memory leak. + */ + $f = $handler[$index]; + unset($handler); + $promise->resolve($f($value)); + } elseif ($index === 1) { + // Forward resolution values as-is. + $promise->resolve($value); + } else { + // Forward rejections down the chain. + $promise->reject($value); + } + } catch (\Throwable $reason) { + $promise->reject($reason); + } + } + private function waitIfPending() : void + { + if ($this->state !== self::PENDING) { + return; + } elseif ($this->waitFn) { + $this->invokeWaitFn(); + } elseif ($this->waitList) { + $this->invokeWaitList(); + } else { + // If there's no wait function, then reject the promise. + $this->reject('Cannot wait on a promise that has ' . 'no internal wait function. You must provide a wait ' . 'function when constructing the promise to be able to ' . 'wait on a promise.'); + } + Utils::queue()->run(); + /** @psalm-suppress RedundantCondition */ + if ($this->state === self::PENDING) { + $this->reject('Invoking the wait callback did not resolve the promise'); + } + } + private function invokeWaitFn() : void + { + try { + $wfn = $this->waitFn; + $this->waitFn = null; + $wfn(\true); + } catch (\Throwable $reason) { + if ($this->state === self::PENDING) { + // The promise has not been resolved yet, so reject the promise + // with the exception. + $this->reject($reason); + } else { + // The promise was already resolved, so there's a problem in + // the application. + throw $reason; + } + } + } + private function invokeWaitList() : void + { + $waitList = $this->waitList; + $this->waitList = null; + foreach ($waitList as $result) { + do { + $result->waitIfPending(); + $result = $result->result; + } while ($result instanceof Promise); + if ($result instanceof PromiseInterface) { + $result->wait(\false); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/PromiseInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/PromiseInterface.php new file mode 100644 index 00000000..f06df42a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/PromiseInterface.php @@ -0,0 +1,80 @@ +reason = $reason; + } + public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface + { + // If there's no onRejected callback then just return self. + if (!$onRejected) { + return $this; + } + $queue = Utils::queue(); + $reason = $this->reason; + $p = new Promise([$queue, 'run']); + $queue->add(static function () use($p, $reason, $onRejected) : void { + if (Is::pending($p)) { + try { + // Return a resolved promise if onRejected does not throw. + $p->resolve($onRejected($reason)); + } catch (\Throwable $e) { + // onRejected threw, so return a rejected promise. + $p->reject($e); + } + } + }); + return $p; + } + public function otherwise(callable $onRejected) : PromiseInterface + { + return $this->then(null, $onRejected); + } + public function wait(bool $unwrap = \true) + { + if ($unwrap) { + throw Create::exceptionFor($this->reason); + } + return null; + } + public function getState() : string + { + return self::REJECTED; + } + public function resolve($value) : void + { + throw new \LogicException('Cannot resolve a rejected promise'); + } + public function reject($reason) : void + { + if ($reason !== $this->reason) { + throw new \LogicException('Cannot reject a rejected promise'); + } + } + public function cancel() : void + { + // pass + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/RejectionException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/RejectionException.php new file mode 100644 index 00000000..41cb9a77 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/RejectionException.php @@ -0,0 +1,41 @@ +reason = $reason; + $message = 'The promise was rejected'; + if ($description) { + $message .= ' with reason: ' . $description; + } elseif (\is_string($reason) || \is_object($reason) && \method_exists($reason, '__toString')) { + $message .= ' with reason: ' . $this->reason; + } elseif ($reason instanceof \JsonSerializable) { + $message .= ' with reason: ' . \json_encode($this->reason, \JSON_PRETTY_PRINT); + } + parent::__construct($message); + } + /** + * Returns the rejection reason. + * + * @return mixed + */ + public function getReason() + { + return $this->reason; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueue.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueue.php new file mode 100644 index 00000000..197e8b13 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueue.php @@ -0,0 +1,65 @@ +run(); + * + * @final + */ +class TaskQueue implements TaskQueueInterface +{ + private $enableShutdown = \true; + private $queue = []; + public function __construct(bool $withShutdown = \true) + { + if ($withShutdown) { + \register_shutdown_function(function () : void { + if ($this->enableShutdown) { + // Only run the tasks if an E_ERROR didn't occur. + $err = \error_get_last(); + if (!$err || $err['type'] ^ \E_ERROR) { + $this->run(); + } + } + }); + } + } + public function isEmpty() : bool + { + return !$this->queue; + } + public function add(callable $task) : void + { + $this->queue[] = $task; + } + public function run() : void + { + while ($task = \array_shift($this->queue)) { + /** @var callable $task */ + $task(); + } + } + /** + * The task queue will be run and exhausted by default when the process + * exits IFF the exit is not the result of a PHP E_ERROR error. + * + * You can disable running the automatic shutdown of the queue by calling + * this function. If you disable the task queue shutdown process, then you + * MUST either run the task queue (as a result of running your event loop + * or manually using the run() method) or wait on each outstanding promise. + * + * Note: This shutdown will occur before any destructors are triggered. + */ + public function disableShutdown() : void + { + $this->enableShutdown = \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueueInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueueInterface.php new file mode 100644 index 00000000..1861713f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/promises/src/TaskQueueInterface.php @@ -0,0 +1,21 @@ + + * while ($eventLoop->isRunning()) { + * GuzzleHttp\Promise\Utils::queue()->run(); + * } + * + * + * @param TaskQueueInterface|null $assign Optionally specify a new queue instance. + */ + public static function queue(?TaskQueueInterface $assign = null) : TaskQueueInterface + { + static $queue; + if ($assign) { + $queue = $assign; + } elseif (!$queue) { + $queue = new TaskQueue(); + } + return $queue; + } + /** + * Adds a function to run in the task queue when it is next `run()` and + * returns a promise that is fulfilled or rejected with the result. + * + * @param callable $task Task function to run. + */ + public static function task(callable $task) : PromiseInterface + { + $queue = self::queue(); + $promise = new Promise([$queue, 'run']); + $queue->add(function () use($task, $promise) : void { + try { + if (Is::pending($promise)) { + $promise->resolve($task()); + } + } catch (\Throwable $e) { + $promise->reject($e); + } + }); + return $promise; + } + /** + * Synchronously waits on a promise to resolve and returns an inspection + * state array. + * + * Returns a state associative array containing a "state" key mapping to a + * valid promise state. If the state of the promise is "fulfilled", the + * array will contain a "value" key mapping to the fulfilled value of the + * promise. If the promise is rejected, the array will contain a "reason" + * key mapping to the rejection reason of the promise. + * + * @param PromiseInterface $promise Promise or value. + */ + public static function inspect(PromiseInterface $promise) : array + { + try { + return ['state' => PromiseInterface::FULFILLED, 'value' => $promise->wait()]; + } catch (\Throwable $e) { + if ($e instanceof AggregateException) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + if ($e instanceof RejectionException) { + return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()]; + } + return ['state' => PromiseInterface::REJECTED, 'reason' => $e]; + } + } + /** + * Waits on all of the provided promises, but does not unwrap rejected + * promises as thrown exception. + * + * Returns an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param PromiseInterface[] $promises Traversable of promises to wait upon. + */ + public static function inspectAll($promises) : array + { + $promises = self::prepareIterable($promises, __FUNCTION__); + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = self::inspect($promise); + } + return $results; + } + /** + * Waits on all of the provided promises and returns the fulfilled values. + * + * Returns an array that contains the value of each promise (in the same + * order the promises were provided). An exception is thrown if any of the + * promises are rejected. + * + * @param iterable $promises Iterable of PromiseInterface objects to wait on. + * + * @throws \Throwable on error + */ + public static function unwrap($promises) : array + { + $promises = self::prepareIterable($promises, __FUNCTION__); + $results = []; + foreach ($promises as $key => $promise) { + $results[$key] = $promise->wait(); + } + return $results; + } + /** + * Given an array of promises, return a promise that is fulfilled when all + * the items in the array are fulfilled. + * + * The promise's fulfillment value is an array with fulfillment values at + * respective positions to the original array. If any promise in the array + * rejects, the returned promise is rejected with the rejection reason. + * + * @param mixed $promises Promises or values. + * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution. + */ + public static function all($promises, bool $recursive = \false) : PromiseInterface + { + $promises = self::prepareIterable($promises, __FUNCTION__); + $results = []; + $promise = Each::of($promises, function ($value, $idx) use(&$results) : void { + $results[$idx] = $value; + }, function ($reason, $idx, Promise $aggregate) : void { + if (Is::pending($aggregate)) { + $aggregate->reject($reason); + } + })->then(function () use(&$results) { + \ksort($results); + return $results; + }); + if (\true === $recursive) { + $promise = $promise->then(function ($results) use($recursive, &$promises) { + foreach ($promises as $promise) { + if (Is::pending($promise)) { + return self::all($promises, $recursive); + } + } + return $results; + }); + } + return $promise; + } + /** + * Initiate a competitive race between multiple promises or values (values + * will become immediately fulfilled promises). + * + * When count amount of promises have been fulfilled, the returned promise + * is fulfilled with an array that contains the fulfillment values of the + * winners in order of resolution. + * + * This promise is rejected with a {@see AggregateException} if the number + * of fulfilled promises is less than the desired $count. + * + * @param int $count Total number of promises. + * @param mixed $promises Promises or values. + */ + public static function some(int $count, $promises) : PromiseInterface + { + $promises = self::prepareIterable($promises, __FUNCTION__); + $results = []; + $rejections = []; + return Each::of($promises, function ($value, $idx, PromiseInterface $p) use(&$results, $count) : void { + if (Is::settled($p)) { + return; + } + $results[$idx] = $value; + if (\count($results) >= $count) { + $p->resolve(null); + } + }, function ($reason) use(&$rejections) : void { + $rejections[] = $reason; + })->then(function () use(&$results, &$rejections, $count) { + if (\count($results) !== $count) { + throw new AggregateException('Not enough promises to fulfill count', $rejections); + } + \ksort($results); + return \array_values($results); + }); + } + /** + * Like some(), with 1 as count. However, if the promise fulfills, the + * fulfillment value is not an array of 1 but the value directly. + * + * @param mixed $promises Promises or values. + */ + public static function any($promises) : PromiseInterface + { + $promises = self::prepareIterable($promises, __FUNCTION__); + return self::some(1, $promises)->then(function ($values) { + return $values[0]; + }); + } + /** + * Returns a promise that is fulfilled when all of the provided promises have + * been fulfilled or rejected. + * + * The returned promise is fulfilled with an array of inspection state arrays. + * + * @see inspect for the inspection state array format. + * + * @param mixed $promises Promises or values. + */ + public static function settle($promises) : PromiseInterface + { + $promises = self::prepareIterable($promises, __FUNCTION__); + $results = []; + return Each::of($promises, function ($value, $idx) use(&$results) : void { + $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value]; + }, function ($reason, $idx) use(&$results) : void { + $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason]; + })->then(function () use(&$results) { + \ksort($results); + return $results; + }); + } + private static function prepareIterable($promises, string $method) : iterable + { + if (\is_iterable($promises)) { + return $promises; + } + self::triggerNonIterableDeprecation($promises, $method); + return [$promises]; + } + private static function triggerNonIterableDeprecation($promises, string $method) : void + { + if (\is_iterable($promises)) { + return; + } + \trigger_deprecation('guzzlehttp/promises', '2.5', 'Passing a non-iterable to %s::%s() is deprecated; guzzlehttp/promises 3.0 will require an iterable.', self::class, $method); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/LICENSE new file mode 100644 index 00000000..51c7ec81 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/LICENSE @@ -0,0 +1,26 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michael Dowling +Copyright (c) 2015 Márk Sági-Kazár +Copyright (c) 2015 Graham Campbell +Copyright (c) 2016 Tobias Schultze +Copyright (c) 2016 George Mponos +Copyright (c) 2018 Tobias Nyholm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/AppendStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/AppendStream.php new file mode 100644 index 00000000..8506df78 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/AppendStream.php @@ -0,0 +1,221 @@ +addStream($stream); + } + } + public function __toString() : string + { + try { + $this->rewind(); + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + \trigger_error(\sprintf('%s::__toString exception: %s', self::class, (string) $e), \E_USER_ERROR); + return ''; + } + } + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) : void + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = \false; + } + $this->streams[] = $stream; + } + public function getContents() : string + { + return Utils::copyToString($this); + } + /** + * Closes each attached stream. + */ + public function close() : void + { + $this->pos = $this->current = 0; + $this->seekable = \true; + foreach ($this->streams as $stream) { + $stream->close(); + } + $this->streams = []; + } + /** + * Detaches each attached stream. + * + * Returns null as it's not clear which underlying stream resource to return. + */ + public function detach() + { + $this->pos = $this->current = 0; + $this->seekable = \true; + foreach ($this->streams as $stream) { + $stream->detach(); + } + $this->streams = []; + return null; + } + public function tell() : int + { + return $this->pos; + } + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + */ + public function getSize() : ?int + { + $size = 0; + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + return $size; + } + public function eof() : bool + { + return !$this->streams || $this->current >= \count($this->streams) - 1 && $this->streams[$this->current]->eof(); + } + public function rewind() : void + { + $this->seek(0); + } + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + */ + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + if (!$this->seekable) { + throw new \RuntimeException('This AppendStream is not seekable'); + } elseif ($whence !== \SEEK_SET) { + throw new \RuntimeException('The AppendStream can only seek with SEEK_SET'); + } + $this->pos = $this->current = 0; + // Rewind each stream + foreach ($this->streams as $i => $stream) { + try { + $stream->rewind(); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to seek stream ' . $i . ' of the AppendStream', 0, $e); + } + } + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $result = $this->read(\min(8096, $offset - $this->pos)); + if ($result === '') { + break; + } + } + } + /** + * Reads from all of the appended streams until the length is met or EOF. + */ + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + if ($this->streams === []) { + return ''; + } + $buffer = ''; + $total = \count($this->streams) - 1; + $remaining = $length; + $progressToNext = \false; + while ($remaining > 0) { + // Progress to the next stream if needed. + if ($progressToNext || $this->streams[$this->current]->eof()) { + $progressToNext = \false; + if ($this->current === $total) { + break; + } + ++$this->current; + } + $result = $this->streams[$this->current]->read($remaining); + if ($result === '') { + $progressToNext = \true; + continue; + } + $buffer .= $result; + $remaining = $length - \strlen($buffer); + } + $this->pos += \strlen($buffer); + return $buffer; + } + public function isReadable() : bool + { + return \true; + } + public function isWritable() : bool + { + return \false; + } + public function isSeekable() : bool + { + return $this->seekable; + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + throw new \RuntimeException('Cannot write to an AppendStream'); + } + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key !== null && !\is_string($key)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', \get_debug_type($key)); + } + return $key ? null : []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/BufferStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/BufferStream.php new file mode 100644 index 00000000..54252178 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/BufferStream.php @@ -0,0 +1,136 @@ +hwm = $hwm; + } + public function __toString() : string + { + return $this->getContents(); + } + public function getContents() : string + { + $buffer = $this->buffer; + $this->buffer = ''; + return $buffer; + } + public function close() : void + { + $this->buffer = ''; + } + public function detach() + { + $this->close(); + return null; + } + public function getSize() : ?int + { + return \strlen($this->buffer); + } + public function isReadable() : bool + { + return \true; + } + public function isWritable() : bool + { + return \true; + } + public function isSeekable() : bool + { + return \false; + } + public function rewind() : void + { + $this->seek(0); + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + throw new \RuntimeException('Cannot seek a BufferStream'); + } + public function eof() : bool + { + return \strlen($this->buffer) === 0; + } + public function tell() : int + { + throw new \RuntimeException('Cannot determine the position of a BufferStream'); + } + /** + * Reads data from the buffer. + */ + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + $currentLength = \strlen($this->buffer); + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = \substr($this->buffer, 0, $length); + $this->buffer = \substr($this->buffer, $length); + } + return $result; + } + /** + * Writes data to the buffer. + */ + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + $this->buffer .= $string; + if (\strlen($this->buffer) >= $this->hwm) { + return 0; + } + return \strlen($string); + } + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key !== null && !\is_string($key)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', \get_debug_type($key)); + } + if ($key === 'hwm') { + return $this->hwm; + } + return $key ? null : []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/CachingStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/CachingStream.php new file mode 100644 index 00000000..f69f8119 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/CachingStream.php @@ -0,0 +1,164 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+')); + } + public function getSize() : ?int + { + if ($this->detached) { + return null; + } + $remoteSize = $this->remoteStream->getSize(); + if (null === $remoteSize) { + return null; + } + return \max($this->stream->getSize(), $remoteSize); + } + public function rewind() : void + { + $this->seek(0); + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + if ($whence === \SEEK_SET) { + $byte = $offset; + } elseif ($whence === \SEEK_CUR) { + $byte = $offset + $this->tell(); + } elseif ($whence === \SEEK_END) { + $size = $this->remoteStream->getSize(); + if ($size === null) { + $size = $this->cacheEntireStream(); + } + $byte = $size + $offset; + } else { + throw new \InvalidArgumentException('Invalid whence'); + } + $diff = $byte - $this->stream->getSize(); + if ($diff > 0) { + // Read the remoteStream until we have read in at least the amount + // of bytes requested, or we reach the end of the file. + while ($diff > 0 && !$this->remoteStream->eof()) { + $previousSize = $this->stream->getSize(); + $previousSkipReadBytes = $this->skipReadBytes; + $data = $this->read($diff); + $currentSize = $this->stream->getSize(); + if ($data === '' && $currentSize === $previousSize && $this->skipReadBytes === $previousSkipReadBytes) { + break; + } + $diff = $byte - $currentSize; + } + } else { + // We can just do a normal seek since we've already seen this byte. + $this->stream->seek($byte); + } + } + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - \strlen($data); + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read($remaining + $this->skipReadBytes); + if ($this->skipReadBytes) { + $len = \strlen($remoteData); + $remoteData = \substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = \max(0, $this->skipReadBytes - $len); + } + $data .= $remoteData; + // A short cache write would silently corrupt later replays, so fail loudly. + if ($this->stream->write($remoteData) !== \strlen($remoteData)) { + throw new \RuntimeException('Unable to cache the entire read from the remote stream'); + } + } + return $data; + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = \strlen($string) + $this->tell() - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + return $this->stream->write($string); + } + public function eof() : bool + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + public function detach() + { + if ($this->detached) { + return null; + } + $position = $this->tell(); + $this->cacheEntireStream(); + $this->stream->seek($position); + $resource = $this->stream->detach(); + $this->detached = \true; + return $resource; + } + /** + * Close both the remote stream and buffer stream + */ + public function close() : void + { + $this->remoteStream->close(); + $this->stream->close(); + $this->detached = \true; + } + private function cacheEntireStream() : int + { + $target = new FnStream(['write' => 'strlen']); + Utils::copyToStream($this, $target); + return $this->tell(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/DroppingStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/DroppingStream.php new file mode 100644 index 00000000..a1c5b7f7 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/DroppingStream.php @@ -0,0 +1,43 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + $diff = $this->maxLength - $this->stream->getSize(); + // Begin returning 0 when the underlying stream is too large. + if ($diff <= 0) { + return 0; + } + // Write the stream or a subset of the stream if needed. + if (\strlen($string) < $diff) { + return $this->stream->write($string); + } + return $this->stream->write(\substr($string, 0, $diff)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Exception/MalformedUriException.php new file mode 100644 index 00000000..e0131d35 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Exception/MalformedUriException.php @@ -0,0 +1,12 @@ + */ + private $methods; + /** + * @param array $methods Hash of method name to a callable. + */ + public function __construct(array $methods) + { + $this->methods = $methods; + // Create the callables on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + /** + * Lazily determine which methods are not implemented. + * + * @throws \BadMethodCallException + */ + public function __get(string $name) : void + { + throw new \BadMethodCallException(\str_replace('_fn_', '', $name) . '() is not implemented in the FnStream'); + } + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + ($this->_fn_close)(); + } + } + /** + * An unserialize would allow the __destruct to run when the unserialized value goes out of scope. + * + * @throws \LogicException + */ + public function __wakeup() : void + { + throw new \LogicException('FnStream should never be unserialized'); + } + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a callable + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (\array_diff(self::SLOTS, \array_keys($methods)) as $diff) { + /** @var callable $callable */ + $callable = [$stream, $diff]; + $methods[$diff] = $callable; + } + return new self($methods); + } + public function __toString() : string + { + try { + /** @var string */ + return ($this->_fn___toString)(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + \trigger_error(\sprintf('%s::__toString exception: %s', self::class, (string) $e), \E_USER_ERROR); + return ''; + } + } + public function close() : void + { + ($this->_fn_close)(); + } + public function detach() + { + return ($this->_fn_detach)(); + } + public function getSize() : ?int + { + return ($this->_fn_getSize)(); + } + public function tell() : int + { + return ($this->_fn_tell)(); + } + public function eof() : bool + { + return ($this->_fn_eof)(); + } + public function isSeekable() : bool + { + return ($this->_fn_isSeekable)(); + } + public function rewind() : void + { + ($this->_fn_rewind)(); + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + ($this->_fn_seek)($offset, $whence); + } + public function isWritable() : bool + { + return ($this->_fn_isWritable)(); + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + return ($this->_fn_write)($string); + } + public function isReadable() : bool + { + return ($this->_fn_isReadable)(); + } + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + return ($this->_fn_read)($length); + } + public function getContents() : string + { + return ($this->_fn_getContents)(); + } + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key !== null && !\is_string($key)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', \get_debug_type($key)); + } + return ($this->_fn_getMetadata)($key); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Header.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Header.php new file mode 100644 index 00000000..aa710e86 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Header.php @@ -0,0 +1,150 @@ +]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[\trim($m[0], $trimmed)] = \trim($m[1], $trimmed); + } else { + $part[] = \trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + } + return $params; + } + /** + * Split a header value into semicolon-separated parameters. + * + * @return string[] + */ + private static function splitParameters(string $value) : array + { + $values = []; + $start = 0; + $isQuoted = \false; + $isEscaped = \false; + for ($i = 0, $max = \strlen($value); $i < $max; ++$i) { + $char = $value[$i]; + if ($isEscaped) { + $isEscaped = \false; + continue; + } + if ($isQuoted && $char === '\\') { + $isEscaped = \true; + continue; + } + if ($char === '"') { + $isQuoted = !$isQuoted; + continue; + } + if (!$isQuoted && $char === ';') { + $values[] = \substr($value, $start, $i - $start); + $start = $i + 1; + } + } + $values[] = \substr($value, $start); + return $values; + } + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param string|array $header Header to normalize. + * + * @deprecated Use self::splitList() instead. + */ + public static function normalize($header) : array + { + $result = []; + foreach ((array) $header as $value) { + foreach (self::splitList($value) as $parsed) { + $result[] = $parsed; + } + } + return $result; + } + /** + * Splits a HTTP header defined to contain a comma-separated list into + * each individual value. Empty values will be removed. + * + * Example headers include 'accept', 'cache-control' and 'if-none-match'. + * + * This method must not be used to parse headers that are not defined as + * a list, such as 'user-agent' or 'set-cookie'. + * + * @param string|string[] $values Header value as returned by MessageInterface::getHeader() + * + * @return string[] + */ + public static function splitList($values) : array + { + if (!\is_array($values)) { + $values = [$values]; + } + $result = []; + foreach ($values as $value) { + if (!\is_string($value)) { + throw new \TypeError('$header must either be a string or an array containing strings.'); + } + $v = ''; + $isQuoted = \false; + $isEscaped = \false; + for ($i = 0, $max = \strlen($value); $i < $max; ++$i) { + if ($isEscaped) { + $v .= $value[$i]; + $isEscaped = \false; + continue; + } + if (!$isQuoted && $value[$i] === ',') { + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + $v = ''; + continue; + } + if ($isQuoted && $value[$i] === '\\') { + $isEscaped = \true; + $v .= $value[$i]; + continue; + } + if ($value[$i] === '"') { + $isQuoted = !$isQuoted; + $v .= $value[$i]; + continue; + } + $v .= $value[$i]; + } + $v = \trim($v); + if ($v !== '') { + $result[] = $v; + } + } + return $result; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/HttpFactory.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/HttpFactory.php new file mode 100644 index 00000000..8f552bbf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/HttpFactory.php @@ -0,0 +1,76 @@ +getSize(); + } + return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); + } + public function createStream(string $content = '') : StreamInterface + { + return Utils::streamFor($content); + } + public function createStreamFromFile(string $file, string $mode = 'r') : StreamInterface + { + try { + $resource = Utils::tryFopen($file, $mode); + } catch (\RuntimeException $e) { + if ('' === $mode || \false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], \true)) { + throw new \InvalidArgumentException(\sprintf('Invalid file opening mode "%s"', $mode), 0, $e); + } + throw $e; + } + return Utils::streamFor($resource); + } + public function createStreamFromResource($resource) : StreamInterface + { + return Utils::streamFor($resource); + } + public function createServerRequest(string $method, $uri, array $serverParams = []) : ServerRequestInterface + { + if (empty($method)) { + if (!empty($serverParams['REQUEST_METHOD'])) { + $method = $serverParams['REQUEST_METHOD']; + } else { + throw new \InvalidArgumentException('Cannot determine HTTP method'); + } + } + return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); + } + public function createResponse(int $code = 200, string $reasonPhrase = '') : ResponseInterface + { + return new Response($code, [], null, '1.1', $reasonPhrase); + } + public function createRequest(string $method, $uri) : RequestInterface + { + return new Request($method, $uri); + } + public function createUri(string $uri = '') : UriInterface + { + return new Uri($uri); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/InflateStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/InflateStream.php new file mode 100644 index 00000000..ee972434 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/InflateStream.php @@ -0,0 +1,33 @@ + 15 + 32]); + $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LazyOpenStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LazyOpenStream.php new file mode 100644 index 00000000..45579a05 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LazyOpenStream.php @@ -0,0 +1,41 @@ +filename = $filename; + $this->mode = $mode; + // unsetting the property forces the first access to go through + // __get(). + unset($this->stream); + } + /** + * Creates the underlying stream lazily when required. + */ + protected function createStream() : StreamInterface + { + return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode)); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LimitStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LimitStream.php new file mode 100644 index 00000000..88318ae3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/LimitStream.php @@ -0,0 +1,138 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + public function eof() : bool + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return \true; + } + // No limit and the underlying stream is not at EOF + if ($this->limit === -1) { + return \false; + } + return $this->stream->tell() >= $this->offset + $this->limit; + } + /** + * Returns the size of the limited subset of data + */ + public function getSize() : ?int + { + if (null === ($length = $this->stream->getSize())) { + return null; + } + $size = $length - $this->offset; + if ($this->limit !== -1) { + $size = \min($this->limit, $size); + } + return \max(0, $size); + } + /** + * Allow for a bounded seek on the read limited stream + */ + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + if ($whence !== \SEEK_SET || $offset < 0) { + throw new \RuntimeException(\sprintf('Cannot seek to offset %s with whence %s', $offset, $whence)); + } + $offset += $this->offset; + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + $this->stream->seek($offset); + } + /** + * Give a relative tell() + */ + public function tell() : int + { + return $this->stream->tell() - $this->offset; + } + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @throws \RuntimeException if the stream cannot be seeked. + */ + public function setOffset(int $offset) : void + { + $current = $this->stream->tell(); + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if ($this->stream->isSeekable()) { + $this->stream->seek($offset); + } elseif ($current > $offset) { + throw new \RuntimeException("Could not seek to stream offset {$offset}"); + } else { + $this->stream->read($offset - $current); + } + } + $this->offset = $offset; + } + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + */ + public function setLimit(int $limit) : void + { + $this->limit = $limit; + } + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + if ($this->limit === -1) { + return $this->stream->read($length); + } + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = $this->offset + $this->limit - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(\min($remaining, $length)); + } + return ''; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Message.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Message.php new file mode 100644 index 00000000..c9a46db6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Message.php @@ -0,0 +1,252 @@ +getMethod() . ' ' . $message->getRequestTarget()) . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' . $message->getStatusCode() . ' ' . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + foreach ($message->getHeaders() as $name => $values) { + if (\is_string($name) && \strtolower($name) === 'set-cookie') { + foreach ($values as $value) { + $msg .= "\r\n{$name}: " . $value; + } + } else { + $msg .= "\r\n{$name}: " . \implode(', ', $values); + } + } + return "{$msg}\r\n\r\n" . $message->getBody(); + } + /** + * Get a short summary of the message body. + * + * Will return `null` if the response is not printable. + * + * @param MessageInterface $message The message to get the body summary + * @param int $truncateAt The maximum allowed size of the summary + */ + public static function bodySummary(MessageInterface $message, int $truncateAt = 120) : ?string + { + $body = $message->getBody(); + if (!$body->isSeekable() || !$body->isReadable()) { + return null; + } + $size = $body->getSize(); + if ($size === 0) { + return null; + } + $body->rewind(); + $summary = $body->read($truncateAt); + if ($size > $truncateAt) { + if (\preg_match('//u', $summary) !== 1) { + $summary = self::trimTrailingIncompleteUtf8Character($summary, $body->read(3)); + } + $summary .= ' (truncated...)'; + } + $body->rewind(); + // Matches any printable character, including unicode characters: + // letters, marks, numbers, punctuation, spacing, and separators. + if (\preg_match('/[^\\pL\\pM\\pN\\pP\\pS\\pZ\\n\\r\\t]/u', $summary) !== 0) { + return null; + } + return $summary; + } + /** + * Trims a partial UTF-8 character from the end of a truncated string. + */ + private static function trimTrailingIncompleteUtf8Character(string $summary, string $lookahead) : string + { + $length = \strlen($summary); + if ($length === 0) { + return $summary; + } + $start = $length - 1; + while ($start >= 0) { + $byte = \ord($summary[$start]); + if ($byte < 0x80 || $byte > 0xbf) { + break; + } + --$start; + } + if ($start < 0) { + return $summary; + } + $lead = \ord($summary[$start]); + if ($lead >= 0xc2 && $lead <= 0xdf) { + $expectedLength = 2; + } elseif ($lead >= 0xe0 && $lead <= 0xef) { + $expectedLength = 3; + } elseif ($lead >= 0xf0 && $lead <= 0xf4) { + $expectedLength = 4; + } else { + return $summary; + } + $availableLength = $length - $start; + if ($availableLength >= $expectedLength) { + return $summary; + } + $sequence = \substr($summary, $start) . \substr($lookahead, 0, $expectedLength - $availableLength); + if (\strlen($sequence) !== $expectedLength || \preg_match('//u', $sequence) !== 1) { + return $summary; + } + return \substr($summary, 0, $start); + } + /** + * Attempts to rewind a message body and throws an exception on failure. + * + * The body of the message will only be rewound if a call to `tell()` + * returns a value other than `0`. + * + * @param MessageInterface $message Message to rewind + * + * @throws \RuntimeException + */ + public static function rewindBody(MessageInterface $message) : void + { + $body = $message->getBody(); + if ($body->tell()) { + $body->rewind(); + } + } + /** + * Parses an HTTP message into an associative array. + * + * The array contains the "start-line" key containing the start line of + * the message, "headers" key containing an associative array of header + * array values, and a "body" key containing the body of the message. + * + * @param string $message HTTP request or response to parse. + */ + public static function parseMessage(string $message) : array + { + if (!$message) { + throw new \InvalidArgumentException('Invalid message'); + } + $message = \ltrim($message, "\r\n"); + $messageParts = \preg_split("/\r?\n\r?\n/", $message, 2); + if ($messageParts === \false || \count($messageParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing header delimiter'); + } + [$rawHeaders, $body] = $messageParts; + $rawHeaders .= "\r\n"; + // Put back the delimiter we split previously + $headerParts = \preg_split("/\r?\n/", $rawHeaders, 2); + if ($headerParts === \false || \count($headerParts) !== 2) { + throw new \InvalidArgumentException('Invalid message: Missing status line'); + } + [$startLine, $rawHeaders] = $headerParts; + if (\preg_match("/(?:^HTTP\\/|^[A-Z]+ \\S+ HTTP\\/)(\\d+(?:\\.\\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') { + // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0 + $rawHeaders = \preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders); + } + /** @var array[] $headerLines */ + $count = \preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, \PREG_SET_ORDER); + // If these aren't the same, then one line didn't match and there's an invalid header. + if ($count !== \substr_count($rawHeaders, "\n")) { + // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 + if (\preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) { + throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding'); + } + throw new \InvalidArgumentException('Invalid header syntax'); + } + $headers = []; + foreach ($headerLines as $headerLine) { + $headers[$headerLine[1]][] = $headerLine[2]; + } + return ['start-line' => $startLine, 'headers' => $headers, 'body' => $body]; + } + /** + * Constructs a URI for an HTTP request message. + * + * @param string $path Path from the start-line + * @param array $headers Array of headers (each value an array). + */ + public static function parseRequestUri(string $path, array $headers) : string + { + $host = self::getHostFromHeaders($headers); + // If no host is found, then a full URI cannot be constructed. + if ($host === null) { + return $path; + } + $scheme = \substr($host, -4) === ':443' ? 'https' : 'http'; + return $scheme . '://' . $host . '/' . \ltrim($path, '/'); + } + /** + * @param array $headers Array of headers (each value an array). + */ + private static function getHostFromHeaders(array $headers) : ?string + { + $hostKey = \array_filter(\array_keys($headers), function ($k) { + // Numeric array keys are converted to int by PHP. + $k = (string) $k; + return \strtolower($k) === 'host'; + }); + if (!$hostKey) { + return null; + } + $host = $headers[\reset($hostKey)][0]; + if (!\is_string($host) || Rfc7230::parseHostHeader($host) === null) { + throw new \InvalidArgumentException('Invalid request string'); + } + return $host; + } + /** + * Parses a request message string into a request object. + * + * @param string $message Request message string. + */ + public static function parseRequest(string $message) : RequestInterface + { + $data = self::parseMessage($message); + if (\strpbrk($data['start-line'], "\r\n") !== \false) { + throw new \InvalidArgumentException('Invalid request string'); + } + $matches = []; + if (!\preg_match('/^[\\S]+\\s+([a-zA-Z]+:\\/\\/|\\/).*/', $data['start-line'], $matches)) { + throw new \InvalidArgumentException('Invalid request string'); + } + $parts = \explode(' ', $data['start-line'], 3); + $version = isset($parts[2]) ? \explode('/', $parts[2])[1] : '1.1'; + $request = new Request($parts[0], $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1], $data['headers'], $data['body'], $version); + return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]); + } + /** + * Parses a response message string into a response object. + * + * @param string $message Response message string. + */ + public static function parseResponse(string $message) : ResponseInterface + { + $data = self::parseMessage($message); + if (\strpbrk($data['start-line'], "\r\n") !== \false) { + throw new \InvalidArgumentException('Invalid response string'); + } + // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + // the space between status-code and reason-phrase is required. But + // browsers accept responses without space and reason as well. + if (!\preg_match('/^HTTP\\/.* [0-9]{3}( .*|$)/', $data['start-line'])) { + throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']); + } + $parts = \explode(' ', $data['start-line'], 3); + return new Response((int) $parts[1], $data['headers'], $data['body'], \explode('/', $parts[0])[1], $parts[2] ?? null); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MessageTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MessageTrait.php new file mode 100644 index 00000000..8e62951c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MessageTrait.php @@ -0,0 +1,272 @@ + array of values */ + private $headers = []; + /** @var string[] Map of lowercase header name => original name at registration */ + private $headerNames = []; + /** @var string */ + private $protocol = '1.1'; + /** @var StreamInterface|null */ + private $stream; + public function getProtocolVersion() : string + { + return $this->protocol; + } + /** + * @return static + */ + public function withProtocolVersion($version) : MessageInterface + { + if (!\is_string($version)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to MessageInterface::withProtocolVersion() is deprecated; guzzlehttp/psr7 3.0 requires string.', \get_debug_type($version)); + } + $this->assertProtocolVersion($version); + if ($this->protocol === $version) { + return $this; + } + $new = clone $this; + $new->protocol = $version; + return $new; + } + public function getHeaders() : array + { + return $this->headers; + } + public function hasHeader($header) : bool + { + return isset($this->headerNames[\strtolower($header)]); + } + public function getHeader($header) : array + { + $header = \strtolower($header); + if (!isset($this->headerNames[$header])) { + return []; + } + $header = $this->headerNames[$header]; + return $this->headers[$header]; + } + public function getHeaderLine($header) : string + { + return \implode(', ', $this->getHeader($header)); + } + /** + * @return static + */ + public function withHeader($header, $value) : MessageInterface + { + $this->assertHeader($header); + $values = \is_array($value) ? $value : [$value]; + foreach ($values as $item) { + if (!\is_string($item) && (\is_scalar($item) || $item === null)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to MessageInterface::withHeader() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].', \get_debug_type($item)); + break; + } + } + $value = $this->normalizeHeaderValue($value); + $normalized = \strtolower($header); + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + unset($new->headers[$new->headerNames[$normalized]]); + } + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + return $new; + } + /** + * @return static + */ + public function withAddedHeader($header, $value) : MessageInterface + { + $this->assertHeader($header); + $values = \is_array($value) ? $value : [$value]; + foreach ($values as $item) { + if (!\is_string($item) && (\is_scalar($item) || $item === null)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to MessageInterface::withAddedHeader() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].', \get_debug_type($item)); + break; + } + } + $value = $this->normalizeHeaderValue($value); + $normalized = \strtolower($header); + $new = clone $this; + if (isset($new->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $new->headers[$header] = \array_merge($this->headers[$header], $value); + } else { + $new->headerNames[$normalized] = $header; + $new->headers[$header] = $value; + } + return $new; + } + /** + * @return static + */ + public function withoutHeader($header) : MessageInterface + { + $normalized = \strtolower($header); + if (!isset($this->headerNames[$normalized])) { + return $this; + } + $header = $this->headerNames[$normalized]; + $new = clone $this; + unset($new->headers[$header], $new->headerNames[$normalized]); + return $new; + } + public function getBody() : StreamInterface + { + if (!$this->stream) { + $this->stream = Utils::streamFor(''); + } + return $this->stream; + } + /** + * @return static + */ + public function withBody(StreamInterface $body) : MessageInterface + { + if ($body === $this->stream) { + return $this; + } + $new = clone $this; + $new->stream = $body; + return $new; + } + /** + * @param (string|string[])[] $headers + */ + private function setHeaders(array $headers) : void + { + $this->headerNames = $this->headers = []; + foreach ($headers as $header => $value) { + // Numeric array keys are converted to int by PHP. + $header = (string) $header; + $this->assertHeader($header); + $values = \is_array($value) ? $value : [$value]; + foreach ($values as $item) { + if (!\is_string($item) && (\is_scalar($item) || $item === null)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to %s::__construct() is deprecated; guzzlehttp/psr7 3.0 requires string|string[].', \get_debug_type($item), static::class); + break; + } + } + $value = $this->normalizeHeaderValue($value); + $normalized = \strtolower($header); + if (isset($this->headerNames[$normalized])) { + $header = $this->headerNames[$normalized]; + $this->headers[$header] = \array_merge($this->headers[$header], $value); + } else { + $this->headerNames[$normalized] = $header; + $this->headers[$header] = $value; + } + } + } + /** + * @param mixed $value + * + * @return string[] + */ + private function normalizeHeaderValue($value) : array + { + if (\is_array($value) && $value === []) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing an empty array as a header value is deprecated; guzzlehttp/psr7 3.0 rejects empty header value arrays.'); + } + if (!\is_array($value)) { + return $this->trimAndValidateHeaderValues([$value]); + } + return $this->trimAndValidateHeaderValues($value); + } + /** + * Trims whitespace from the header values. + * + * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field. + * + * header-field = field-name ":" OWS field-value OWS + * OWS = *( SP / HTAB ) + * + * @param mixed[] $values Header values + * + * @return string[] Trimmed header values + * + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4 + */ + private function trimAndValidateHeaderValues(array $values) : array + { + return \array_map(function ($value) { + if (!\is_scalar($value) && null !== $value) { + throw new \InvalidArgumentException(\sprintf('Header value must be scalar or null but %s provided.', \is_object($value) ? \get_class($value) : \gettype($value))); + } + // Convert non-finite floats explicitly, as implicit coercion of + // NAN emits a warning on PHP 8.5. + if (\is_float($value) && !\is_finite($value)) { + $value = \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + } + $trimmed = \trim((string) $value, " \t"); + $this->assertValue($trimmed); + return $trimmed; + }, \array_values($values)); + } + /** + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * + * @param mixed $header + */ + private function assertHeader($header) : void + { + if (!\is_string($header)) { + throw new \InvalidArgumentException(\sprintf('Header name must be a string but %s provided.', \is_object($header) ? \get_class($header) : \gettype($header))); + } + if (!\preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { + throw new \InvalidArgumentException(\sprintf('"%s" is not valid header name.', $header)); + } + } + /** + * @param mixed $version + */ + private function assertProtocolVersion($version) : void + { + if (\is_string($version)) { + $this->assertNoLineSeparators($version, 'Protocol version'); + } + } + private function assertNoLineSeparators(string $value, string $field) : void + { + if (\strpbrk($value, "\r\n") !== \false) { + throw new \InvalidArgumentException($field . ' must not contain CR or LF characters.'); + } + } + /** + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2 + * + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + * VCHAR = %x21-7E + * obs-text = %x80-FF + * obs-fold = CRLF 1*( SP / HTAB ) + */ + private function assertValue(string $value) : void + { + // The regular expression intentionally does not support the obs-fold production, because as + // per RFC 7230#3.2.4: + // + // A sender MUST NOT generate a message that includes + // line folding (i.e., that has any field-value that contains a match to + // the obs-fold rule) unless the message is intended for packaging + // within the message/http media type. + // + // Clients must not send a request with line folding and a server sending folded headers is + // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting + // folding is not likely to break any legitimate use case. + if (!\preg_match('/^[\\x20\\x09\\x21-\\x7E\\x80-\\xFF]*$/D', $value)) { + throw new \InvalidArgumentException(\sprintf('"%s" is not valid header value.', $value)); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MimeType.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MimeType.php new file mode 100644 index 00000000..90faf069 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MimeType.php @@ -0,0 +1,27 @@ + 'application/vnd.lotus-1-2-3', '1km' => 'application/vnd.1000minds.decision-model+xml', '210' => 'model/step', '3dml' => 'text/vnd.in3d.3dml', '3ds' => 'image/x-3ds', '3g2' => 'video/3gpp2', '3gp' => 'video/3gpp', '3gpp' => 'video/3gpp', '3mf' => 'model/3mf', '7z' => 'application/x-7z-compressed', '7zip' => 'application/x-7z-compressed', 'aab' => 'application/x-authorware-bin', 'aac' => 'audio/aac', 'aam' => 'application/x-authorware-map', 'aas' => 'application/x-authorware-seg', 'abw' => 'application/x-abiword', 'ac' => 'application/pkix-attr-cert', 'ac3' => 'audio/ac3', 'acc' => 'application/vnd.americandynamics.acc', 'ace' => 'application/x-ace-compressed', 'acu' => 'application/vnd.acucobol', 'acutc' => 'application/vnd.acucorp', 'adp' => 'audio/adpcm', 'adts' => 'audio/aac', 'aep' => 'application/vnd.audiograph', 'afm' => 'application/x-font-type1', 'afp' => 'application/vnd.ibm.modcap', 'age' => 'application/vnd.age', 'ahead' => 'application/vnd.ahead.space', 'ai' => 'application/postscript', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'air' => 'application/vnd.adobe.air-application-installer-package+zip', 'ait' => 'application/vnd.dvb.ait', 'ami' => 'application/vnd.amiga.ami', 'aml' => 'application/automationml-aml+xml', 'amlx' => 'application/automationml-amlx+zip', 'amr' => 'audio/amr', 'apk' => 'application/vnd.android.package-archive', 'apng' => 'image/apng', 'appcache' => 'text/cache-manifest', 'appinstaller' => 'application/appinstaller', 'application' => 'application/x-ms-application', 'appx' => 'application/appx', 'appxbundle' => 'application/appxbundle', 'apr' => 'application/vnd.lotus-approach', 'arc' => 'application/x-freearc', 'arj' => 'application/x-arj', 'asc' => 'application/pgp-keys', 'asf' => 'video/x-ms-asf', 'asm' => 'text/x-asm', 'aso' => 'application/vnd.accpac.simply.aso', 'asx' => 'video/x-ms-asf', 'atc' => 'application/vnd.acucorp', 'atom' => 'application/atom+xml', 'atomcat' => 'application/atomcat+xml', 'atomdeleted' => 'application/atomdeleted+xml', 'atomsvc' => 'application/atomsvc+xml', 'atx' => 'application/vnd.antix.game-component', 'au' => 'audio/basic', 'avci' => 'image/avci', 'avcs' => 'image/avcs', 'avi' => 'video/x-msvideo', 'avif' => 'image/avif', 'aw' => 'application/applixware', 'azf' => 'application/vnd.airzip.filesecure.azf', 'azs' => 'application/vnd.airzip.filesecure.azs', 'azv' => 'image/vnd.airzip.accelerator.azv', 'azw' => 'application/vnd.amazon.ebook', 'b16' => 'image/vnd.pco.b16', 'bary' => 'model/vnd.bary', 'bat' => 'application/x-msdownload', 'bcpio' => 'application/x-bcpio', 'bdf' => 'application/x-font-bdf', 'bdm' => 'application/vnd.syncml.dm+wbxml', 'bdo' => 'application/vnd.nato.bindingdataobject+xml', 'bdoc' => 'application/bdoc', 'bed' => 'application/vnd.realvnc.bed', 'bh2' => 'application/vnd.fujitsu.oasysprs', 'bin' => 'application/octet-stream', 'blb' => 'application/x-blorb', 'blend' => 'application/x-blender', 'blorb' => 'application/x-blorb', 'bmi' => 'application/vnd.bmi', 'bmml' => 'application/vnd.balsamiq.bmml+xml', 'bmp' => 'image/bmp', 'book' => 'application/vnd.framemaker', 'box' => 'application/vnd.previewsystems.box', 'boz' => 'application/x-bzip2', 'bpk' => 'application/octet-stream', 'bpmn' => 'application/octet-stream', 'brush' => 'application/vnd.procreate.brush', 'brushset' => 'application/vnd.procreate.brushset', 'bsp' => 'model/vnd.valve.source.compiled-map', 'btf' => 'image/prs.btif', 'btif' => 'image/prs.btif', 'buffer' => 'application/octet-stream', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip2', 'c' => 'text/x-c', 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', 'c4d' => 'application/vnd.clonk.c4group', 'c4f' => 'application/vnd.clonk.c4group', 'c4g' => 'application/vnd.clonk.c4group', 'c4p' => 'application/vnd.clonk.c4group', 'c4u' => 'application/vnd.clonk.c4group', 'cab' => 'application/vnd.ms-cab-compressed', 'caf' => 'audio/x-caf', 'cap' => 'application/vnd.tcpdump.pcap', 'car' => 'application/vnd.curl.car', 'cat' => 'application/vnd.ms-pki.seccat', 'cb7' => 'application/x-cbr', 'cba' => 'application/x-cbr', 'cbr' => 'application/x-cbr', 'cbt' => 'application/x-cbr', 'cbz' => 'application/x-cbr', 'cc' => 'text/x-c', 'cco' => 'application/x-cocoa', 'cct' => 'application/x-director', 'ccxml' => 'application/ccxml+xml', 'cdbcmsg' => 'application/vnd.contact.cmsg', 'cdf' => 'application/x-netcdf', 'cdfx' => 'application/cdfx+xml', 'cdkey' => 'application/vnd.mediastation.cdkey', 'cdmia' => 'application/cdmi-capability', 'cdmic' => 'application/cdmi-container', 'cdmid' => 'application/cdmi-domain', 'cdmio' => 'application/cdmi-object', 'cdmiq' => 'application/cdmi-queue', 'cdx' => 'chemical/x-cdx', 'cdxml' => 'application/vnd.chemdraw+xml', 'cdy' => 'application/vnd.cinderella', 'cer' => 'application/pkix-cert', 'cfs' => 'application/x-cfs-compressed', 'cgm' => 'image/cgm', 'chat' => 'application/x-chat', 'chm' => 'application/vnd.ms-htmlhelp', 'chrt' => 'application/vnd.kde.kchart', 'cif' => 'chemical/x-cif', 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', 'cil' => 'application/vnd.ms-artgalry', 'cjs' => 'application/node', 'cla' => 'application/vnd.claymore', 'class' => 'application/java-vm', 'cld' => 'model/vnd.cld', 'clkk' => 'application/vnd.crick.clicker.keyboard', 'clkp' => 'application/vnd.crick.clicker.palette', 'clkt' => 'application/vnd.crick.clicker.template', 'clkw' => 'application/vnd.crick.clicker.wordbank', 'clkx' => 'application/vnd.crick.clicker', 'clp' => 'application/x-msclip', 'cmc' => 'application/vnd.cosmocaller', 'cmdf' => 'chemical/x-cmdf', 'cml' => 'chemical/x-cml', 'cmp' => 'application/vnd.yellowriver-custom-menu', 'cmx' => 'image/x-cmx', 'cod' => 'application/vnd.rim.cod', 'coffee' => 'text/coffeescript', 'com' => 'application/x-msdownload', 'conf' => 'text/plain', 'cpio' => 'application/x-cpio', 'cpl' => 'application/cpl+xml', 'cpp' => 'text/x-c', 'cpt' => 'application/mac-compactpro', 'crd' => 'application/x-mscardfile', 'crl' => 'application/pkix-crl', 'crt' => 'application/x-x509-ca-cert', 'crx' => 'application/x-chrome-extension', 'cryptonote' => 'application/vnd.rig.cryptonote', 'csh' => 'application/x-csh', 'csl' => 'application/vnd.citationstyles.style+xml', 'csml' => 'chemical/x-csml', 'csp' => 'application/vnd.commonspace', 'csr' => 'application/octet-stream', 'css' => 'text/css', 'cst' => 'application/x-director', 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'curl' => 'text/vnd.curl', 'cwl' => 'application/cwl', 'cww' => 'application/prs.cww', 'cxt' => 'application/x-director', 'cxx' => 'text/x-c', 'dae' => 'model/vnd.collada+xml', 'daf' => 'application/vnd.mobius.daf', 'dart' => 'application/vnd.dart', 'dataless' => 'application/vnd.fdsn.seed', 'davmount' => 'application/davmount+xml', 'dbf' => 'application/vnd.dbf', 'dbk' => 'application/docbook+xml', 'dcm' => 'application/dicom', 'dcmp' => 'application/vnd.dcmp+xml', 'dcr' => 'application/x-director', 'dcurl' => 'text/vnd.curl.dcurl', 'dd2' => 'application/vnd.oma.dd2+xml', 'ddd' => 'application/vnd.fujixerox.ddd', 'ddf' => 'application/vnd.syncml.dmddf+xml', 'dds' => 'image/vnd.ms-dds', 'deb' => 'application/x-debian-package', 'def' => 'text/plain', 'deploy' => 'application/octet-stream', 'der' => 'application/x-x509-ca-cert', 'dfac' => 'application/vnd.dreamfactory', 'dgc' => 'application/x-dgc-compressed', 'dib' => 'image/bmp', 'dic' => 'text/x-c', 'dir' => 'application/x-director', 'dis' => 'application/vnd.mobius.dis', 'disposition-notification' => 'message/disposition-notification', 'dist' => 'application/octet-stream', 'distz' => 'application/octet-stream', 'djv' => 'image/vnd.djvu', 'djvu' => 'image/vnd.djvu', 'dll' => 'application/octet-stream', 'dmg' => 'application/x-apple-diskimage', 'dmn' => 'application/octet-stream', 'dmp' => 'application/vnd.tcpdump.pcap', 'dms' => 'application/octet-stream', 'dna' => 'application/vnd.dna', 'dng' => 'image/x-adobe-dng', 'doc' => 'application/msword', 'docm' => 'application/vnd.ms-word.document.macroenabled.12', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dot' => 'application/msword', 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'dp' => 'application/vnd.osgi.dp', 'dpg' => 'application/vnd.dpgraph', 'dpx' => 'image/dpx', 'dra' => 'audio/vnd.dra', 'drle' => 'image/dicom-rle', 'drm' => 'application/vnd.procreate.dream', 'dsc' => 'text/prs.lines.tag', 'dssc' => 'application/dssc+der', 'dst' => 'application/octet-stream', 'dtb' => 'application/x-dtbook+xml', 'dtd' => 'application/xml-dtd', 'dts' => 'audio/vnd.dts', 'dtshd' => 'audio/vnd.dts.hd', 'dump' => 'application/octet-stream', 'dvb' => 'video/vnd.dvb.file', 'dvi' => 'application/x-dvi', 'dwd' => 'application/atsc-dwd+xml', 'dwf' => 'model/vnd.dwf', 'dwg' => 'image/vnd.dwg', 'dxf' => 'image/vnd.dxf', 'dxp' => 'application/vnd.spotfire.dxp', 'dxr' => 'application/x-director', 'ear' => 'application/java-archive', 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', 'ecma' => 'application/ecmascript', 'edm' => 'application/vnd.novadigm.edm', 'edx' => 'application/vnd.novadigm.edx', 'efif' => 'application/vnd.picsel', 'ei6' => 'application/vnd.pg.osasli', 'elc' => 'application/octet-stream', 'emf' => 'image/emf', 'eml' => 'message/rfc822', 'emma' => 'application/emma+xml', 'emotionml' => 'application/emotionml+xml', 'emz' => 'application/x-msmetafile', 'eol' => 'audio/vnd.digital-winds', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'es3' => 'application/vnd.eszigno3+xml', 'esa' => 'application/vnd.osgi.subsystem', 'esf' => 'application/vnd.epson.esf', 'et3' => 'application/vnd.eszigno3+xml', 'etx' => 'text/x-setext', 'eva' => 'application/x-eva', 'evy' => 'application/x-envoy', 'exe' => 'application/octet-stream', 'exi' => 'application/exi', 'exp' => 'application/express', 'exr' => 'image/aces', 'ext' => 'application/vnd.novadigm.ext', 'ez' => 'application/andrew-inset', 'ez2' => 'application/vnd.ezpix-album', 'ez3' => 'application/vnd.ezpix-package', 'f' => 'text/x-fortran', 'f4v' => 'video/mp4', 'f77' => 'text/x-fortran', 'f90' => 'text/x-fortran', 'facti' => 'image/vnd.blockfact.facti', 'fbs' => 'image/vnd.fastbidsheet', 'fbx' => 'application/vnd.autodesk.fbx', 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', 'fcs' => 'application/vnd.isac.fcs', 'fdf' => 'application/fdf', 'fdt' => 'application/fdt+xml', 'fe_launch' => 'application/vnd.denovo.fcselayout-link', 'fg5' => 'application/vnd.fujitsu.oasysgp', 'fgd' => 'application/x-director', 'fh' => 'image/x-freehand', 'fh4' => 'image/x-freehand', 'fh5' => 'image/x-freehand', 'fh7' => 'image/x-freehand', 'fhc' => 'image/x-freehand', 'fig' => 'application/x-xfig', 'fits' => 'image/fits', 'flac' => 'audio/x-flac', 'fli' => 'video/x-fli', 'flo' => 'application/vnd.micrografx.flo', 'flv' => 'video/x-flv', 'flw' => 'application/vnd.kde.kivio', 'flx' => 'text/vnd.fmi.flexstor', 'fly' => 'text/vnd.fly', 'fm' => 'application/vnd.framemaker', 'fnc' => 'application/vnd.frogans.fnc', 'fo' => 'application/vnd.software602.filler.form+xml', 'for' => 'text/x-fortran', 'fpx' => 'image/vnd.fpx', 'frame' => 'application/vnd.framemaker', 'fsc' => 'application/vnd.fsc.weblaunch', 'fst' => 'image/vnd.fst', 'ftc' => 'application/vnd.fluxtime.clip', 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', 'fvt' => 'video/vnd.fvt', 'fxp' => 'application/vnd.adobe.fxp', 'fxpl' => 'application/vnd.adobe.fxp', 'fzs' => 'application/vnd.fuzzysheet', 'g2w' => 'application/vnd.geoplan', 'g3' => 'image/g3fax', 'g3w' => 'application/vnd.geospace', 'gac' => 'application/vnd.groove-account', 'gam' => 'application/x-tads', 'gbr' => 'application/rpki-ghostbusters', 'gca' => 'application/x-gca-compressed', 'gdl' => 'model/vnd.gdl', 'gdoc' => 'application/vnd.google-apps.document', 'gdraw' => 'application/vnd.google-apps.drawing', 'ged' => 'text/vnd.familysearch.gedcom', 'geo' => 'application/vnd.dynageo', 'geojson' => 'application/geo+json', 'gex' => 'application/vnd.geometry-explorer', 'gform' => 'application/vnd.google-apps.form', 'ggb' => 'application/vnd.geogebra.file', 'ggs' => 'application/vnd.geogebra.slides', 'ggt' => 'application/vnd.geogebra.tool', 'ghf' => 'application/vnd.groove-help', 'gif' => 'image/gif', 'gim' => 'application/vnd.groove-identity-message', 'gjam' => 'application/vnd.google-apps.jam', 'glb' => 'model/gltf-binary', 'gltf' => 'model/gltf+json', 'gmap' => 'application/vnd.google-apps.map', 'gml' => 'application/gml+xml', 'gmx' => 'application/vnd.gmx', 'gnumeric' => 'application/x-gnumeric', 'gph' => 'application/vnd.flographit', 'gpx' => 'application/gpx+xml', 'gqf' => 'application/vnd.grafeq', 'gqs' => 'application/vnd.grafeq', 'gram' => 'application/srgs', 'gramps' => 'application/x-gramps-xml', 'gre' => 'application/vnd.geometry-explorer', 'grv' => 'application/vnd.groove-injector', 'grxml' => 'application/srgs+xml', 'gscript' => 'application/vnd.google-apps.script', 'gsf' => 'application/x-font-ghostscript', 'gsheet' => 'application/vnd.google-apps.spreadsheet', 'gsite' => 'application/vnd.google-apps.site', 'gslides' => 'application/vnd.google-apps.presentation', 'gtar' => 'application/x-gtar', 'gtm' => 'application/vnd.groove-tool-message', 'gtw' => 'model/vnd.gtw', 'gv' => 'text/vnd.graphviz', 'gxf' => 'application/gxf', 'gxt' => 'application/vnd.geonext', 'gz' => 'application/gzip', 'gzip' => 'application/gzip', 'h' => 'text/x-c', 'h261' => 'video/h261', 'h263' => 'video/h263', 'h264' => 'video/h264', 'hal' => 'application/vnd.hal+xml', 'hbci' => 'application/vnd.hbci', 'hbs' => 'text/x-handlebars-template', 'hdd' => 'application/x-virtualbox-hdd', 'hdf' => 'application/x-hdf', 'heic' => 'image/heic', 'heics' => 'image/heic-sequence', 'heif' => 'image/heif', 'heifs' => 'image/heif-sequence', 'hej2' => 'image/hej2k', 'held' => 'application/atsc-held+xml', 'hh' => 'text/x-c', 'hjson' => 'application/hjson', 'hlp' => 'application/winhlp', 'hpgl' => 'application/vnd.hp-hpgl', 'hpid' => 'application/vnd.hp-hpid', 'hps' => 'application/vnd.hp-hps', 'hqx' => 'application/mac-binhex40', 'htc' => 'text/x-component', 'htke' => 'application/vnd.kenameaapp', 'htm' => 'text/html', 'html' => 'text/html', 'hvd' => 'application/vnd.yamaha.hv-dic', 'hvp' => 'application/vnd.yamaha.hv-voice', 'hvs' => 'application/vnd.yamaha.hv-script', 'i2g' => 'application/vnd.intergeo', 'icc' => 'application/vnd.iccprofile', 'ice' => 'x-conference/x-cooltalk', 'icm' => 'application/vnd.iccprofile', 'ico' => 'image/vnd.microsoft.icon', 'ics' => 'text/calendar', 'ief' => 'image/ief', 'ifb' => 'text/calendar', 'ifm' => 'application/vnd.shana.informed.formdata', 'iges' => 'model/iges', 'igl' => 'application/vnd.igloader', 'igm' => 'application/vnd.insors.igm', 'igs' => 'model/iges', 'igx' => 'application/vnd.micrografx.igx', 'iif' => 'application/vnd.shana.informed.interchange', 'img' => 'application/octet-stream', 'imp' => 'application/vnd.accpac.simply.imp', 'ims' => 'application/vnd.ms-ims', 'in' => 'text/plain', 'indd' => 'application/x-indesign', 'ini' => 'text/plain', 'ink' => 'application/inkml+xml', 'inkml' => 'application/inkml+xml', 'install' => 'application/x-install-instructions', 'iota' => 'application/vnd.astraea-software.iota', 'ipfix' => 'application/ipfix', 'ipk' => 'application/vnd.shana.informed.package', 'ipynb' => 'application/x-ipynb+json', 'irm' => 'application/vnd.ibm.rights-management', 'irp' => 'application/vnd.irepository.package+xml', 'iso' => 'application/x-iso9660-image', 'itp' => 'application/vnd.shana.informed.formtemplate', 'its' => 'application/its+xml', 'ivp' => 'application/vnd.immervision-ivp', 'ivu' => 'application/vnd.immervision-ivu', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'jade' => 'text/jade', 'jaii' => 'image/jaii', 'jais' => 'image/jais', 'jam' => 'application/vnd.jam', 'jar' => 'application/java-archive', 'jardiff' => 'application/x-java-archive-diff', 'java' => 'text/x-java-source', 'jfif' => 'image/jpeg', 'jhc' => 'image/jphc', 'jisp' => 'application/vnd.jisp', 'jls' => 'image/jls', 'jlt' => 'application/vnd.hp-jlyt', 'jng' => 'image/x-jng', 'jnlp' => 'application/x-java-jnlp-file', 'joda' => 'application/vnd.joost.joda-archive', 'jp2' => 'image/jp2', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpf' => 'image/jpx', 'jpg' => 'image/jpeg', 'jpg2' => 'image/jp2', 'jpgm' => 'image/jpm', 'jpgv' => 'video/jpeg', 'jph' => 'image/jph', 'jpm' => 'image/jpm', 'jpx' => 'image/jpx', 'js' => 'text/javascript', 'json' => 'application/json', 'json5' => 'application/json5', 'jsonld' => 'application/ld+json', 'jsonml' => 'application/jsonml+json', 'jsx' => 'text/jsx', 'jt' => 'model/jt', 'jxl' => 'image/jxl', 'jxr' => 'image/jxr', 'jxra' => 'image/jxra', 'jxrs' => 'image/jxrs', 'jxs' => 'image/jxs', 'jxsc' => 'image/jxsc', 'jxsi' => 'image/jxsi', 'jxss' => 'image/jxss', 'kar' => 'audio/midi', 'karbon' => 'application/vnd.kde.karbon', 'kbl' => 'application/kbl+xml', 'kdb' => 'application/octet-stream', 'kdbx' => 'application/x-keepass2', 'key' => 'application/vnd.apple.keynote', 'kfo' => 'application/vnd.kde.kformula', 'kia' => 'application/vnd.kidspiration', 'kml' => 'application/vnd.google-earth.kml+xml', 'kmz' => 'application/vnd.google-earth.kmz', 'kne' => 'application/vnd.kinar', 'knp' => 'application/vnd.kinar', 'kon' => 'application/vnd.kde.kontour', 'kpr' => 'application/vnd.kde.kpresenter', 'kpt' => 'application/vnd.kde.kpresenter', 'kpxx' => 'application/vnd.ds-keypoint', 'ksp' => 'application/vnd.kde.kspread', 'ktr' => 'application/vnd.kahootz', 'ktx' => 'image/ktx', 'ktx2' => 'image/ktx2', 'ktz' => 'application/vnd.kahootz', 'kwd' => 'application/vnd.kde.kword', 'kwt' => 'application/vnd.kde.kword', 'lasxml' => 'application/vnd.las.las+xml', 'latex' => 'application/x-latex', 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', 'les' => 'application/vnd.hhe.lesson-player', 'less' => 'text/less', 'lgr' => 'application/lgr+xml', 'lha' => 'application/x-lzh-compressed', 'link66' => 'application/vnd.route66.link66+xml', 'list' => 'text/plain', 'list3820' => 'application/vnd.ibm.modcap', 'listafp' => 'application/vnd.ibm.modcap', 'litcoffee' => 'text/coffeescript', 'lnk' => 'application/x-ms-shortcut', 'log' => 'text/plain', 'lostxml' => 'application/lost+xml', 'lottie' => 'application/zip+dotlottie', 'lrf' => 'application/octet-stream', 'lrm' => 'application/vnd.ms-lrm', 'ltf' => 'application/vnd.frogans.ltf', 'lua' => 'text/x-lua', 'luac' => 'application/x-lua-bytecode', 'lvp' => 'audio/vnd.lucent.voice', 'lwp' => 'application/vnd.lotus-wordpro', 'lzh' => 'application/x-lzh-compressed', 'm13' => 'application/x-msmediaview', 'm14' => 'application/x-msmediaview', 'm1v' => 'video/mpeg', 'm21' => 'application/mp21', 'm2a' => 'audio/mpeg', 'm2t' => 'video/mp2t', 'm2ts' => 'video/mp2t', 'm2v' => 'video/mpeg', 'm3a' => 'audio/mpeg', 'm3u' => 'audio/x-mpegurl', 'm3u8' => 'application/vnd.apple.mpegurl', 'm4a' => 'audio/mp4', 'm4b' => 'audio/mp4', 'm4p' => 'application/mp4', 'm4s' => 'video/iso.segment', 'm4u' => 'video/vnd.mpegurl', 'm4v' => 'video/x-m4v', 'ma' => 'application/mathematica', 'mads' => 'application/mads+xml', 'maei' => 'application/mmt-aei+xml', 'mag' => 'application/vnd.ecowin.chart', 'maker' => 'application/vnd.framemaker', 'man' => 'text/troff', 'manifest' => 'text/cache-manifest', 'map' => 'application/json', 'mar' => 'application/octet-stream', 'markdown' => 'text/markdown', 'mathml' => 'application/mathml+xml', 'mb' => 'application/mathematica', 'mbk' => 'application/vnd.mobius.mbk', 'mbox' => 'application/mbox', 'mc1' => 'application/vnd.medcalcdata', 'mcd' => 'application/vnd.mcd', 'mcurl' => 'text/vnd.curl.mcurl', 'md' => 'text/markdown', 'mdb' => 'application/x-msaccess', 'mdi' => 'image/vnd.ms-modi', 'mdx' => 'text/mdx', 'me' => 'text/troff', 'mesh' => 'model/mesh', 'meta4' => 'application/metalink4+xml', 'metalink' => 'application/metalink+xml', 'mets' => 'application/mets+xml', 'mfm' => 'application/vnd.mfmp', 'mft' => 'application/rpki-manifest', 'mgp' => 'application/vnd.osgeo.mapguide.package', 'mgz' => 'application/vnd.proteus.magazine', 'mht' => 'message/rfc822', 'mhtml' => 'message/rfc822', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mie' => 'application/x-mie', 'mif' => 'application/vnd.mif', 'mime' => 'message/rfc822', 'mj2' => 'video/mj2', 'mjp2' => 'video/mj2', 'mjs' => 'text/javascript', 'mk3d' => 'video/matroska-3d', 'mka' => 'audio/matroska', 'mkd' => 'text/x-markdown', 'mks' => 'video/x-matroska', 'mkv' => 'video/matroska', 'mlp' => 'application/vnd.dolby.mlp', 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', 'mmf' => 'application/vnd.smaf', 'mml' => 'text/mathml', 'mmr' => 'image/vnd.fujixerox.edmics-mmr', 'mng' => 'video/x-mng', 'mny' => 'application/x-msmoney', 'mobi' => 'application/x-mobipocket-ebook', 'mods' => 'application/mods+xml', 'mov' => 'video/quicktime', 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', 'mp21' => 'application/mp21', 'mp2a' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4s' => 'application/mp4', 'mp4v' => 'video/mp4', 'mpc' => 'application/vnd.mophun.certificate', 'mpd' => 'application/dash+xml', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpf' => 'application/media-policy-dataset+xml', 'mpg' => 'video/mpeg', 'mpg4' => 'video/mp4', 'mpga' => 'audio/mpeg', 'mpkg' => 'application/vnd.apple.installer+xml', 'mpm' => 'application/vnd.blueice.multipass', 'mpn' => 'application/vnd.mophun.application', 'mpp' => 'application/vnd.ms-project', 'mpt' => 'application/vnd.ms-project', 'mpy' => 'application/vnd.ibm.minipay', 'mqy' => 'application/vnd.mobius.mqy', 'mrc' => 'application/marc', 'mrcx' => 'application/marcxml+xml', 'ms' => 'text/troff', 'mscml' => 'application/mediaservercontrol+xml', 'mseed' => 'application/vnd.fdsn.mseed', 'mseq' => 'application/vnd.mseq', 'msf' => 'application/vnd.epson.msf', 'msg' => 'application/vnd.ms-outlook', 'msh' => 'model/mesh', 'msi' => 'application/octet-stream', 'msix' => 'application/msix', 'msixbundle' => 'application/msixbundle', 'msl' => 'application/vnd.mobius.msl', 'msm' => 'application/octet-stream', 'msp' => 'application/octet-stream', 'msty' => 'application/vnd.muvee.style', 'mtl' => 'model/mtl', 'mts' => 'video/mp2t', 'mus' => 'application/vnd.musician', 'musd' => 'application/mmt-usd+xml', 'musicxml' => 'application/vnd.recordare.musicxml+xml', 'mvb' => 'application/x-msmediaview', 'mvt' => 'application/vnd.mapbox-vector-tile', 'mwf' => 'application/vnd.mfer', 'mxf' => 'application/mxf', 'mxl' => 'application/vnd.recordare.musicxml', 'mxmf' => 'audio/mobile-xmf', 'mxml' => 'application/xv+xml', 'mxs' => 'application/vnd.triscape.mxs', 'mxu' => 'video/vnd.mpegurl', 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', 'n3' => 'text/n3', 'nb' => 'application/mathematica', 'nbp' => 'application/vnd.wolfram.player', 'nc' => 'application/x-netcdf', 'ncx' => 'application/x-dtbncx+xml', 'ndjson' => 'application/x-ndjson', 'nfo' => 'text/x-nfo', 'ngdat' => 'application/vnd.nokia.n-gage.data', 'nitf' => 'application/vnd.nitf', 'nlu' => 'application/vnd.neurolanguage.nlu', 'nml' => 'application/vnd.enliven', 'nnd' => 'application/vnd.noblenet-directory', 'nns' => 'application/vnd.noblenet-sealer', 'nnw' => 'application/vnd.noblenet-web', 'npx' => 'image/vnd.net-fpx', 'nq' => 'application/n-quads', 'nsc' => 'application/x-conference', 'nsf' => 'application/vnd.lotus-notes', 'nt' => 'application/n-triples', 'ntf' => 'application/vnd.nitf', 'numbers' => 'application/vnd.apple.numbers', 'nzb' => 'application/x-nzb', 'oa2' => 'application/vnd.fujitsu.oasys2', 'oa3' => 'application/vnd.fujitsu.oasys3', 'oas' => 'application/vnd.fujitsu.oasys', 'obd' => 'application/x-msbinder', 'obgx' => 'application/vnd.openblox.game+xml', 'obj' => 'model/obj', 'oda' => 'application/oda', 'odb' => 'application/vnd.oasis.opendocument.database', 'odc' => 'application/vnd.oasis.opendocument.chart', 'odf' => 'application/vnd.oasis.opendocument.formula', 'odft' => 'application/vnd.oasis.opendocument.formula-template', 'odg' => 'application/vnd.oasis.opendocument.graphics', 'odi' => 'application/vnd.oasis.opendocument.image', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'odt' => 'application/vnd.oasis.opendocument.text', 'oga' => 'audio/ogg', 'ogex' => 'model/vnd.opengex', 'ogg' => 'audio/ogg', 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'omdoc' => 'application/omdoc+xml', 'one' => 'application/onenote', 'onea' => 'application/onenote', 'onepkg' => 'application/onenote', 'onetmp' => 'application/onenote', 'onetoc' => 'application/onenote', 'onetoc2' => 'application/onenote', 'opf' => 'application/oebps-package+xml', 'opml' => 'text/x-opml', 'oprc' => 'application/vnd.palm', 'opus' => 'audio/ogg', 'org' => 'application/vnd.lotus-organizer', 'osf' => 'application/vnd.yamaha.openscoreformat', 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'osm' => 'application/vnd.openstreetmap.data+xml', 'otc' => 'application/vnd.oasis.opendocument.chart-template', 'otf' => 'font/otf', 'otg' => 'application/vnd.oasis.opendocument.graphics-template', 'oth' => 'application/vnd.oasis.opendocument.text-web', 'oti' => 'application/vnd.oasis.opendocument.image-template', 'otp' => 'application/vnd.oasis.opendocument.presentation-template', 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', 'ott' => 'application/vnd.oasis.opendocument.text-template', 'ova' => 'application/x-virtualbox-ova', 'ovf' => 'application/x-virtualbox-ovf', 'owl' => 'application/rdf+xml', 'oxps' => 'application/oxps', 'oxt' => 'application/vnd.openofficeorg.extension', 'p' => 'text/x-pascal', 'p10' => 'application/pkcs10', 'p12' => 'application/x-pkcs12', 'p21' => 'model/step', 'p7a' => 'application/x-pkcs7-signature', 'p7b' => 'application/x-pkcs7-certificates', 'p7c' => 'application/pkcs7-mime', 'p7e' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7r' => 'application/x-pkcs7-certreqresp', 'p7s' => 'application/pkcs7-signature', 'p8' => 'application/pkcs8', 'pac' => 'application/x-ns-proxy-autoconfig', 'pages' => 'application/vnd.apple.pages', 'parquet' => 'application/vnd.apache.parquet', 'pas' => 'text/x-pascal', 'paw' => 'application/vnd.pawaafile', 'pbd' => 'application/vnd.powerbuilder6', 'pbm' => 'image/x-portable-bitmap', 'pcap' => 'application/vnd.tcpdump.pcap', 'pcf' => 'application/x-font-pcf', 'pcl' => 'application/vnd.hp-pcl', 'pclxl' => 'application/vnd.hp-pclxl', 'pct' => 'image/x-pict', 'pcurl' => 'application/vnd.curl.pcurl', 'pcx' => 'image/vnd.zbrush.pcx', 'pdb' => 'application/vnd.palm', 'pde' => 'text/x-processing', 'pdf' => 'application/pdf', 'pem' => 'application/x-x509-user-cert', 'pfa' => 'application/x-font-type1', 'pfb' => 'application/x-font-type1', 'pfm' => 'application/x-font-type1', 'pfr' => 'application/font-tdpfr', 'pfx' => 'application/x-pkcs12', 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', 'pgp' => 'application/pgp-encrypted', 'phar' => 'application/octet-stream', 'php' => 'application/x-httpd-php', 'php3' => 'application/x-httpd-php', 'php4' => 'application/x-httpd-php', 'phps' => 'application/x-httpd-php-source', 'phtml' => 'application/x-httpd-php', 'pic' => 'image/x-pict', 'pkg' => 'application/octet-stream', 'pki' => 'application/pkixcmp', 'pkipath' => 'application/pkix-pkipath', 'pkpass' => 'application/vnd.apple.pkpass', 'pl' => 'application/x-perl', 'plb' => 'application/vnd.3gpp.pic-bw-large', 'plc' => 'application/vnd.mobius.plc', 'plf' => 'application/vnd.pocketlearn', 'pls' => 'application/pls+xml', 'pm' => 'application/x-perl', 'pml' => 'application/vnd.ctc-posml', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'portpkg' => 'application/vnd.macports.portpkg', 'pot' => 'application/vnd.ms-powerpoint', 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppa' => 'application/vnd.ms-powerpoint', 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', 'ppd' => 'application/vnd.cups-ppd', 'ppm' => 'image/x-portable-pixmap', 'pps' => 'application/vnd.ms-powerpoint', 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'ppt' => 'application/vnd.ms-powerpoint', 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pqa' => 'application/vnd.palm', 'prc' => 'model/prc', 'pre' => 'application/vnd.lotus-freelance', 'prf' => 'application/pics-rules', 'provx' => 'application/provenance+xml', 'ps' => 'application/postscript', 'psb' => 'application/vnd.3gpp.pic-bw-small', 'psd' => 'image/vnd.adobe.photoshop', 'psf' => 'application/x-font-linux-psf', 'pskcxml' => 'application/pskc+xml', 'pti' => 'image/prs.pti', 'ptid' => 'application/vnd.pvi.ptid1', 'pub' => 'application/x-mspublisher', 'pv' => 'application/octet-stream', 'pvb' => 'application/vnd.3gpp.pic-bw-var', 'pwn' => 'application/vnd.3m.post-it-notes', 'pxf' => 'application/octet-stream', 'pya' => 'audio/vnd.ms-playready.media.pya', 'pyo' => 'model/vnd.pytha.pyox', 'pyox' => 'model/vnd.pytha.pyox', 'pyv' => 'video/vnd.ms-playready.media.pyv', 'qam' => 'application/vnd.epson.quickanime', 'qbo' => 'application/vnd.intu.qbo', 'qfx' => 'application/vnd.intu.qfx', 'qps' => 'application/vnd.publishare-delta-tree', 'qt' => 'video/quicktime', 'qwd' => 'application/vnd.quark.quarkxpress', 'qwt' => 'application/vnd.quark.quarkxpress', 'qxb' => 'application/vnd.quark.quarkxpress', 'qxd' => 'application/vnd.quark.quarkxpress', 'qxl' => 'application/vnd.quark.quarkxpress', 'qxt' => 'application/vnd.quark.quarkxpress', 'ra' => 'audio/x-realaudio', 'ram' => 'audio/x-pn-realaudio', 'raml' => 'application/raml+yaml', 'rapd' => 'application/route-apd+xml', 'rar' => 'application/vnd.rar', 'ras' => 'image/x-cmu-raster', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', 'rdf' => 'application/rdf+xml', 'rdz' => 'application/vnd.data-vision.rdz', 'relo' => 'application/p2p-overlay+xml', 'rep' => 'application/vnd.businessobjects', 'res' => 'application/x-dtbresource+xml', 'rgb' => 'image/x-rgb', 'rif' => 'application/reginfo+xml', 'rip' => 'audio/vnd.rip', 'ris' => 'application/x-research-info-systems', 'rl' => 'application/resource-lists+xml', 'rlc' => 'image/vnd.fujixerox.edmics-rlc', 'rld' => 'application/resource-lists-diff+xml', 'rm' => 'application/vnd.rn-realmedia', 'rmi' => 'audio/midi', 'rmp' => 'audio/x-pn-realaudio-plugin', 'rms' => 'application/vnd.jcp.javame.midlet-rms', 'rmvb' => 'application/vnd.rn-realmedia-vbr', 'rnc' => 'application/relax-ng-compact-syntax', 'rng' => 'application/xml', 'roa' => 'application/rpki-roa', 'roff' => 'text/troff', 'rp9' => 'application/vnd.cloanto.rp9', 'rpm' => 'application/x-redhat-package-manager', 'rpss' => 'application/vnd.nokia.radio-presets', 'rpst' => 'application/vnd.nokia.radio-preset', 'rq' => 'application/sparql-query', 'rs' => 'application/rls-services+xml', 'rsa' => 'application/x-pkcs7', 'rsat' => 'application/atsc-rsat+xml', 'rsd' => 'application/rsd+xml', 'rsheet' => 'application/urc-ressheet+xml', 'rss' => 'application/rss+xml', 'rtf' => 'text/rtf', 'rtx' => 'text/richtext', 'run' => 'application/x-makeself', 'rusd' => 'application/route-usd+xml', 'rv' => 'video/vnd.rn-realvideo', 's' => 'text/x-asm', 's3m' => 'audio/s3m', 'saf' => 'application/vnd.yamaha.smaf-audio', 'sass' => 'text/x-sass', 'sbml' => 'application/sbml+xml', 'sc' => 'application/vnd.ibm.secure-container', 'scd' => 'application/x-msschedule', 'scm' => 'application/vnd.lotus-screencam', 'scq' => 'application/scvp-cv-request', 'scs' => 'application/scvp-cv-response', 'scss' => 'text/x-scss', 'scurl' => 'text/vnd.curl.scurl', 'sda' => 'application/vnd.stardivision.draw', 'sdc' => 'application/vnd.stardivision.calc', 'sdd' => 'application/vnd.stardivision.impress', 'sdkd' => 'application/vnd.solent.sdkm+xml', 'sdkm' => 'application/vnd.solent.sdkm+xml', 'sdp' => 'application/sdp', 'sdw' => 'application/vnd.stardivision.writer', 'sea' => 'application/x-sea', 'see' => 'application/vnd.seemail', 'seed' => 'application/vnd.fdsn.seed', 'sema' => 'application/vnd.sema', 'semd' => 'application/vnd.semd', 'semf' => 'application/vnd.semf', 'senmlx' => 'application/senml+xml', 'sensmlx' => 'application/sensml+xml', 'ser' => 'application/java-serialized-object', 'setpay' => 'application/set-payment-initiation', 'setreg' => 'application/set-registration-initiation', 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', 'sfs' => 'application/vnd.spotfire.sfs', 'sfv' => 'text/x-sfv', 'sgi' => 'image/sgi', 'sgl' => 'application/vnd.stardivision.writer-global', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'sh' => 'application/x-sh', 'shar' => 'application/x-shar', 'shex' => 'text/shex', 'shf' => 'application/shf+xml', 'shtml' => 'text/html', 'sid' => 'image/x-mrsid-image', 'sieve' => 'application/sieve', 'sig' => 'application/pgp-signature', 'sil' => 'audio/silk', 'silo' => 'model/mesh', 'sis' => 'application/vnd.symbian.install', 'sisx' => 'application/vnd.symbian.install', 'sit' => 'application/x-stuffit', 'sitx' => 'application/x-stuffitx', 'siv' => 'application/sieve', 'skd' => 'application/vnd.koan', 'skm' => 'application/vnd.koan', 'skp' => 'application/vnd.koan', 'skt' => 'application/vnd.koan', 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 'slim' => 'text/slim', 'slm' => 'text/slim', 'sls' => 'application/route-s-tsid+xml', 'slt' => 'application/vnd.epson.salt', 'sm' => 'application/vnd.stepmania.stepchart', 'smf' => 'application/vnd.stardivision.math', 'smi' => 'application/smil+xml', 'smil' => 'application/smil+xml', 'smv' => 'video/x-smv', 'smzip' => 'application/vnd.stepmania.package', 'snd' => 'audio/basic', 'snf' => 'application/x-font-snf', 'so' => 'application/octet-stream', 'spc' => 'application/x-pkcs7-certificates', 'spdx' => 'text/spdx', 'spf' => 'application/vnd.yamaha.smaf-phrase', 'spl' => 'application/x-futuresplash', 'spot' => 'text/vnd.in3d.spot', 'spp' => 'application/scvp-vp-response', 'spq' => 'application/scvp-vp-request', 'spx' => 'audio/ogg', 'sql' => 'application/sql', 'sqlite' => 'application/vnd.sqlite3', 'sqlite3' => 'application/vnd.sqlite3', 'src' => 'application/x-wais-source', 'srt' => 'application/x-subrip', 'sru' => 'application/sru+xml', 'srx' => 'application/sparql-results+xml', 'ssdl' => 'application/ssdl+xml', 'sse' => 'application/vnd.kodak-descriptor', 'ssf' => 'application/vnd.epson.ssf', 'ssml' => 'application/ssml+xml', 'sst' => 'application/octet-stream', 'st' => 'application/vnd.sailingtracker.track', 'stc' => 'application/vnd.sun.xml.calc.template', 'std' => 'application/vnd.sun.xml.draw.template', 'step' => 'model/step', 'stf' => 'application/vnd.wt.stf', 'sti' => 'application/vnd.sun.xml.impress.template', 'stk' => 'application/hyperstudio', 'stl' => 'model/stl', 'stp' => 'model/step', 'stpnc' => 'model/step', 'stpx' => 'model/step+xml', 'stpxz' => 'model/step-xml+zip', 'stpz' => 'model/step+zip', 'str' => 'application/vnd.pg.format', 'stw' => 'application/vnd.sun.xml.writer.template', 'styl' => 'text/stylus', 'stylus' => 'text/stylus', 'sub' => 'image/vnd.dvb.subtitle', 'sus' => 'application/vnd.sus-calendar', 'susp' => 'application/vnd.sus-calendar', 'sv4cpio' => 'application/x-sv4cpio', 'sv4crc' => 'application/x-sv4crc', 'svc' => 'application/vnd.dvb.service', 'svd' => 'application/vnd.svd', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'swa' => 'application/x-director', 'swf' => 'application/x-shockwave-flash', 'swi' => 'application/vnd.aristanetworks.swi', 'swidtag' => 'application/swid+xml', 'sxc' => 'application/vnd.sun.xml.calc', 'sxd' => 'application/vnd.sun.xml.draw', 'sxg' => 'application/vnd.sun.xml.writer.global', 'sxi' => 'application/vnd.sun.xml.impress', 'sxm' => 'application/vnd.sun.xml.math', 'sxw' => 'application/vnd.sun.xml.writer', 'systemverify' => 'application/vnd.pp.systemverify+xml', 't' => 'text/troff', 't3' => 'application/x-t3vm-image', 't38' => 'image/t38', 'taglet' => 'application/vnd.mynfc', 'tao' => 'application/vnd.tao.intent-module-archive', 'tap' => 'image/vnd.tencent.tap', 'tar' => 'application/x-tar', 'tcap' => 'application/vnd.3gpp2.tcap', 'tcl' => 'application/x-tcl', 'td' => 'application/urc-targetdesc+xml', 'teacher' => 'application/vnd.smart.teacher', 'tei' => 'application/tei+xml', 'teicorpus' => 'application/tei+xml', 'tex' => 'application/x-tex', 'texi' => 'application/x-texinfo', 'texinfo' => 'application/x-texinfo', 'text' => 'text/plain', 'tfi' => 'application/thraud+xml', 'tfm' => 'application/x-tex-tfm', 'tfx' => 'image/tiff-fx', 'tga' => 'image/x-tga', 'tgz' => 'application/gzip', 'thmx' => 'application/vnd.ms-officetheme', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'tk' => 'application/x-tcl', 'tmo' => 'application/vnd.tmobile-livetv', 'toml' => 'application/toml', 'torrent' => 'application/x-bittorrent', 'tpl' => 'application/vnd.groove-tool-template', 'tpt' => 'application/vnd.trid.tpt', 'tr' => 'text/troff', 'tra' => 'application/vnd.trueapp', 'trig' => 'application/trig', 'trm' => 'application/x-msterminal', 'ts' => 'video/mp2t', 'tsd' => 'application/timestamped-data', 'tsv' => 'text/tab-separated-values', 'ttc' => 'font/collection', 'ttf' => 'font/ttf', 'ttl' => 'text/turtle', 'ttml' => 'application/ttml+xml', 'twd' => 'application/vnd.simtech-mindmapper', 'twds' => 'application/vnd.simtech-mindmapper', 'txd' => 'application/vnd.genomatix.tuxedo', 'txf' => 'application/vnd.mobius.txf', 'txt' => 'text/plain', 'u32' => 'application/x-authorware-bin', 'u3d' => 'model/u3d', 'u8dsn' => 'message/global-delivery-status', 'u8hdr' => 'message/global-headers', 'u8mdn' => 'message/global-disposition-notification', 'u8msg' => 'message/global', 'ubj' => 'application/ubjson', 'udeb' => 'application/x-debian-package', 'ufd' => 'application/vnd.ufdl', 'ufdl' => 'application/vnd.ufdl', 'ulx' => 'application/x-glulx', 'umj' => 'application/vnd.umajin', 'unityweb' => 'application/vnd.unity', 'uo' => 'application/vnd.uoml+xml', 'uoml' => 'application/vnd.uoml+xml', 'uri' => 'text/uri-list', 'uris' => 'text/uri-list', 'urls' => 'text/uri-list', 'usda' => 'model/vnd.usda', 'usdz' => 'model/vnd.usdz+zip', 'ustar' => 'application/x-ustar', 'utz' => 'application/vnd.uiq.theme', 'uu' => 'text/x-uuencode', 'uva' => 'audio/vnd.dece.audio', 'uvd' => 'application/vnd.dece.data', 'uvf' => 'application/vnd.dece.data', 'uvg' => 'image/vnd.dece.graphic', 'uvh' => 'video/vnd.dece.hd', 'uvi' => 'image/vnd.dece.graphic', 'uvm' => 'video/vnd.dece.mobile', 'uvp' => 'video/vnd.dece.pd', 'uvs' => 'video/vnd.dece.sd', 'uvt' => 'application/vnd.dece.ttml+xml', 'uvu' => 'video/vnd.uvvu.mp4', 'uvv' => 'video/vnd.dece.video', 'uvva' => 'audio/vnd.dece.audio', 'uvvd' => 'application/vnd.dece.data', 'uvvf' => 'application/vnd.dece.data', 'uvvg' => 'image/vnd.dece.graphic', 'uvvh' => 'video/vnd.dece.hd', 'uvvi' => 'image/vnd.dece.graphic', 'uvvm' => 'video/vnd.dece.mobile', 'uvvp' => 'video/vnd.dece.pd', 'uvvs' => 'video/vnd.dece.sd', 'uvvt' => 'application/vnd.dece.ttml+xml', 'uvvu' => 'video/vnd.uvvu.mp4', 'uvvv' => 'video/vnd.dece.video', 'uvvx' => 'application/vnd.dece.unspecified', 'uvvz' => 'application/vnd.dece.zip', 'uvx' => 'application/vnd.dece.unspecified', 'uvz' => 'application/vnd.dece.zip', 'vbox' => 'application/x-virtualbox-vbox', 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', 'vcard' => 'text/vcard', 'vcd' => 'application/x-cdlink', 'vcf' => 'text/x-vcard', 'vcg' => 'application/vnd.groove-vcard', 'vcs' => 'text/x-vcalendar', 'vcx' => 'application/vnd.vcx', 'vdi' => 'application/x-virtualbox-vdi', 'vds' => 'model/vnd.sap.vds', 'vdx' => 'application/vnd.ms-visio.viewer', 'vec' => 'application/vec+xml', 'vhd' => 'application/x-virtualbox-vhd', 'vis' => 'application/vnd.visionary', 'viv' => 'video/vnd.vivo', 'vmdk' => 'application/x-virtualbox-vmdk', 'vob' => 'video/x-ms-vob', 'vor' => 'application/vnd.stardivision.writer', 'vox' => 'application/x-authorware-bin', 'vrml' => 'model/vrml', 'vsd' => 'application/vnd.visio', 'vsdx' => 'application/vnd.visio', 'vsf' => 'application/vnd.vsf', 'vss' => 'application/vnd.visio', 'vst' => 'application/vnd.visio', 'vsw' => 'application/vnd.visio', 'vtf' => 'image/vnd.valve.source.texture', 'vtt' => 'text/vtt', 'vtu' => 'model/vnd.vtu', 'vtx' => 'application/vnd.visio', 'vxml' => 'application/voicexml+xml', 'w3d' => 'application/x-director', 'wad' => 'application/x-doom', 'wadl' => 'application/vnd.sun.wadl+xml', 'war' => 'application/java-archive', 'wasm' => 'application/wasm', 'wav' => 'audio/wav', 'wax' => 'audio/x-ms-wax', 'wbmp' => 'image/vnd.wap.wbmp', 'wbs' => 'application/vnd.criticaltools.wbs+xml', 'wbxml' => 'application/vnd.wap.wbxml', 'wcm' => 'application/vnd.ms-works', 'wdb' => 'application/vnd.ms-works', 'wdp' => 'image/vnd.ms-photo', 'weba' => 'audio/webm', 'webapp' => 'application/x-web-app-manifest+json', 'webm' => 'video/webm', 'webmanifest' => 'application/manifest+json', 'webp' => 'image/webp', 'wg' => 'application/vnd.pmi.widget', 'wgsl' => 'text/wgsl', 'wgt' => 'application/widget', 'wif' => 'application/watcherinfo+xml', 'wks' => 'application/vnd.ms-works', 'wm' => 'video/x-ms-wm', 'wma' => 'audio/x-ms-wma', 'wmd' => 'application/x-ms-wmd', 'wmf' => 'image/wmf', 'wml' => 'text/vnd.wap.wml', 'wmlc' => 'application/vnd.wap.wmlc', 'wmls' => 'text/vnd.wap.wmlscript', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wmv' => 'video/x-ms-wmv', 'wmx' => 'video/x-ms-wmx', 'wmz' => 'application/x-ms-wmz', 'woff' => 'font/woff', 'woff2' => 'font/woff2', 'word' => 'application/msword', 'wpd' => 'application/vnd.wordperfect', 'wpl' => 'application/vnd.ms-wpl', 'wps' => 'application/vnd.ms-works', 'wqd' => 'application/vnd.wqd', 'wri' => 'application/x-mswrite', 'wrl' => 'model/vrml', 'wsc' => 'message/vnd.wfa.wsc', 'wsdl' => 'application/wsdl+xml', 'wspolicy' => 'application/wspolicy+xml', 'wtb' => 'application/vnd.webturbo', 'wvx' => 'video/x-ms-wvx', 'x32' => 'application/x-authorware-bin', 'x3d' => 'model/x3d+xml', 'x3db' => 'model/x3d+fastinfoset', 'x3dbz' => 'model/x3d+binary', 'x3dv' => 'model/x3d-vrml', 'x3dvz' => 'model/x3d+vrml', 'x3dz' => 'model/x3d+xml', 'x_b' => 'model/vnd.parasolid.transmit.binary', 'x_t' => 'model/vnd.parasolid.transmit.text', 'xaml' => 'application/xaml+xml', 'xap' => 'application/x-silverlight-app', 'xar' => 'application/vnd.xara', 'xav' => 'application/xcap-att+xml', 'xbap' => 'application/x-ms-xbap', 'xbd' => 'application/vnd.fujixerox.docuworks.binder', 'xbm' => 'image/x-xbitmap', 'xca' => 'application/xcap-caps+xml', 'xcs' => 'application/calendar+xml', 'xdcf' => 'application/vnd.gov.sk.xmldatacontainer+xml', 'xdf' => 'application/xcap-diff+xml', 'xdm' => 'application/vnd.syncml.dm+xml', 'xdp' => 'application/vnd.adobe.xdp+xml', 'xdssc' => 'application/dssc+xml', 'xdw' => 'application/vnd.fujixerox.docuworks', 'xel' => 'application/xcap-el+xml', 'xenc' => 'application/xenc+xml', 'xer' => 'application/patch-ops-error+xml', 'xfdf' => 'application/xfdf', 'xfdl' => 'application/vnd.xfdl', 'xht' => 'application/xhtml+xml', 'xhtm' => 'application/vnd.pwg-xhtml-print+xml', 'xhtml' => 'application/xhtml+xml', 'xhvml' => 'application/xv+xml', 'xif' => 'image/vnd.xiff', 'xl' => 'application/vnd.ms-excel', 'xla' => 'application/vnd.ms-excel', 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', 'xlc' => 'application/vnd.ms-excel', 'xlf' => 'application/xliff+xml', 'xlm' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlt' => 'application/vnd.ms-excel', 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlw' => 'application/vnd.ms-excel', 'xm' => 'audio/xm', 'xml' => 'application/xml', 'xns' => 'application/xcap-ns+xml', 'xo' => 'application/vnd.olpc-sugar', 'xop' => 'application/xop+xml', 'xpi' => 'application/x-xpinstall', 'xpl' => 'application/xproc+xml', 'xpm' => 'image/x-xpixmap', 'xpr' => 'application/vnd.is-xpr', 'xps' => 'application/vnd.ms-xpsdocument', 'xpw' => 'application/vnd.intercon.formnet', 'xpx' => 'application/vnd.intercon.formnet', 'xsd' => 'application/xml', 'xsf' => 'application/prs.xsf+xml', 'xsl' => 'application/xslt+xml', 'xslt' => 'application/xslt+xml', 'xsm' => 'application/vnd.syncml+xml', 'xspf' => 'application/xspf+xml', 'xul' => 'application/vnd.mozilla.xul+xml', 'xvm' => 'application/xv+xml', 'xvml' => 'application/xv+xml', 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz', 'xz' => 'application/x-xz', 'yaml' => 'text/yaml', 'yang' => 'application/yang', 'yin' => 'application/yin+xml', 'yml' => 'text/yaml', 'ymp' => 'text/x-suse-ymp', 'z' => 'application/x-compress', 'z1' => 'application/x-zmachine', 'z2' => 'application/x-zmachine', 'z3' => 'application/x-zmachine', 'z4' => 'application/x-zmachine', 'z5' => 'application/x-zmachine', 'z6' => 'application/x-zmachine', 'z7' => 'application/x-zmachine', 'z8' => 'application/x-zmachine', 'zaz' => 'application/vnd.zzazz.deck+xml', 'zip' => 'application/zip', 'zir' => 'application/vnd.zul', 'zirz' => 'application/vnd.zul', 'zmm' => 'application/vnd.handheld-entertainment+xml', 'zsh' => 'text/x-scriptzsh']; + /** + * Determines the mimetype of a file by looking at its extension. + * + * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromFilename(string $filename) : ?string + { + return self::fromExtension(\pathinfo($filename, \PATHINFO_EXTENSION)); + } + /** + * Maps a file extensions to a mimetype. + * + * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json + */ + public static function fromExtension(string $extension) : ?string + { + return self::MIME_TYPES[\strtolower($extension)] ?? null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MultipartStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MultipartStream.php new file mode 100644 index 00000000..215213cb --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/MultipartStream.php @@ -0,0 +1,225 @@ +boundary = $boundary ?: \bin2hex(\random_bytes(20)); + $this->stream = $this->createStream($elements); + } + public function getBoundary() : string + { + return $this->boundary; + } + public function isWritable() : bool + { + return \false; + } + /** + * Get the headers needed before transferring the content of a POST file + * + * @param array $headers + */ + private function getHeaders(array $headers) : string + { + $str = ''; + foreach ($headers as $key => $value) { + $key = (string) $key; + $str .= "{$key}: {$value}\r\n"; + } + return "--{$this->boundary}\r\n" . \trim($str) . "\r\n\r\n"; + } + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $elements = []) : StreamInterface + { + $stream = new AppendStream(); + foreach ($elements as $element) { + if (!\is_array($element)) { + throw new \UnexpectedValueException('An array is expected'); + } + $this->addElement($stream, $element); + } + // Add the trailing boundary with CRLF + $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n")); + return $stream; + } + private function addElement(AppendStream $stream, array $element) : void + { + foreach (['contents', 'name'] as $key) { + if (!\array_key_exists($key, $element)) { + throw new \InvalidArgumentException("A '{$key}' key is required"); + } + } + if (!\is_string($element['name']) && !\is_int($element['name'])) { + throw new \InvalidArgumentException("The 'name' key must be a string or integer"); + } + if (\is_array($element['contents'])) { + if (\array_key_exists('filename', $element) || \array_key_exists('headers', $element)) { + throw new \InvalidArgumentException("The 'filename' and 'headers' options cannot be used when 'contents' is an array"); + } + $this->addNestedElements($stream, $element['contents'], (string) $element['name']); + return; + } + $contents = $element['contents']; + if (\is_scalar($contents) && !\is_string($contents)) { + // Multipart field values are byte strings on the wire, so finite + // numeric and boolean field values are cast to string here rather + // than tripping streamFor()'s non-string-scalar deprecation. Non-finite + // floats are deprecated and normalized here too, so the deprecation is + // reported against MultipartStream instead of transitively through + // streamFor(). + if (\is_float($contents) && !\is_finite($contents)) { + \trigger_deprecation('guzzlehttp/psr7', '2.12', 'Passing a non-finite float as multipart contents is deprecated; guzzlehttp/psr7 3.0 rejects non-finite floats.'); + $contents = \is_nan($contents) ? 'NAN' : ($contents > 0 ? 'INF' : '-INF'); + } + $contents = (string) $contents; + } + $element['contents'] = Utils::streamFor($contents); + if (empty($element['filename'])) { + $uri = $element['contents']->getMetadata('uri'); + if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') { + $element['filename'] = $uri; + } + } + [$body, $headers] = $this->createElement((string) $element['name'], $element['contents'], $element['filename'] ?? null, $element['headers'] ?? []); + $stream->addStream(Utils::streamFor($this->getHeaders($headers))); + $stream->addStream($body); + $stream->addStream(Utils::streamFor("\r\n")); + } + /** + * Recursively expand array contents into multiple form fields. + * + * @param array $contents + */ + private function addNestedElements(AppendStream $stream, array $contents, string $root) : void + { + foreach ($contents as $key => $value) { + $fieldName = $root === '' ? \sprintf('[%s]', (string) $key) : \sprintf('%s[%s]', $root, (string) $key); + if (\is_array($value)) { + $this->addNestedElements($stream, $value, $fieldName); + } else { + $this->addElement($stream, ['name' => $fieldName, 'contents' => $value]); + } + } + } + /** + * @param array $headers + * + * @return array{0: StreamInterface, 1: array} + */ + private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers) : array + { + $headers = self::normalizePartHeaders($headers); + // Set a default content-disposition header if one was no provided + $disposition = self::getHeader($headers, 'content-disposition'); + if (!$disposition) { + $headers['Content-Disposition'] = $filename === '0' || $filename ? \sprintf('form-data; name="%s"; filename="%s"', $name, \basename($filename)) : "form-data; name=\"{$name}\""; + } + // Set a default content-length header if one was no provided + $length = self::getHeader($headers, 'content-length'); + if (!$length) { + if ($length = $stream->getSize()) { + $headers['Content-Length'] = (string) $length; + } + } + // Set a default Content-Type if one was not supplied + $type = self::getHeader($headers, 'content-type'); + if (!$type && ($filename === '0' || $filename)) { + $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream'; + } + return [$stream, $headers]; + } + /** + * @param array $headers + */ + private static function getHeader(array $headers, string $key) : ?string + { + $lowercaseHeader = \strtolower($key); + foreach ($headers as $k => $v) { + if (\strtolower((string) $k) === $lowercaseHeader) { + return $v; + } + } + return null; + } + private static function isValidBoundary(string $boundary) : bool + { + $length = \strlen($boundary); + if ($length < 1 || $length > 70 || $boundary[$length - 1] === ' ') { + return \false; + } + return \strspn($boundary, self::BOUNDARY_CHARS) === $length; + } + /** + * @param array $headers + * + * @return array + */ + private static function normalizePartHeaders(array $headers) : array + { + $normalized = []; + foreach ($headers as $key => $value) { + self::deprecateInvalidPartHeaderName((string) $key); + if (!\is_string($value)) { + if (!\is_scalar($value) && $value !== null && !(\is_object($value) && \method_exists($value, '__toString'))) { + throw new \InvalidArgumentException(\sprintf('Multipart part header value must be a string or stringable value but %s provided.', \get_debug_type($value))); + } + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s as a multipart part header value is deprecated; guzzlehttp/psr7 3.0 requires string multipart part header values.', \get_debug_type($value)); + } + $value = (string) $value; + self::deprecateInvalidPartHeaderValue($value); + $normalized[$key] = $value; + } + return $normalized; + } + private static function deprecateInvalidPartHeaderName(string $name) : void + { + if (!\preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $name)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing an invalid multipart part header name to MultipartStream is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart part header names.'); + } + } + private static function deprecateInvalidPartHeaderValue(string $value) : void + { + if (!\preg_match('/^[\\x20\\x09\\x21-\\x7E\\x80-\\xFF]*$/D', $value)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing an invalid multipart part header value to MultipartStream is deprecated; guzzlehttp/psr7 3.0 rejects invalid multipart part header values.'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/NoSeekStream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/NoSeekStream.php new file mode 100644 index 00000000..c69ab7d5 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/NoSeekStream.php @@ -0,0 +1,29 @@ +source = $source; + $this->size = $options['size'] ?? null; + $this->metadata = $options['metadata'] ?? []; + $this->buffer = new BufferStream(); + } + public function __toString() : string + { + try { + return Utils::copyToString($this); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + \trigger_error(\sprintf('%s::__toString exception: %s', self::class, (string) $e), \E_USER_ERROR); + return ''; + } + } + public function close() : void + { + $this->detach(); + } + public function detach() + { + $this->tellPos = 0; + $this->source = null; + return null; + } + public function getSize() : ?int + { + return $this->size; + } + public function tell() : int + { + return $this->tellPos; + } + public function eof() : bool + { + return $this->source === null; + } + public function isSeekable() : bool + { + return \false; + } + public function rewind() : void + { + $this->seek(0); + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + throw new \RuntimeException('Cannot seek a PumpStream'); + } + public function isWritable() : bool + { + return \false; + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + throw new \RuntimeException('Cannot write to a PumpStream'); + } + public function isReadable() : bool + { + return \true; + } + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + $data = $this->buffer->read($length); + $readLen = \strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += \strlen($data) - $readLen; + } + return $data; + } + public function getContents() : string + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + return $result; + } + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key !== null && !\is_string($key)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', \get_debug_type($key)); + } + if (!$key) { + return $this->metadata; + } + return $this->metadata[$key] ?? null; + } + private function pump(int $length) : void + { + if ($this->source !== null) { + do { + $data = ($this->source)($length); + if ($data === \false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= \strlen($data); + } while ($length > 0); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Query.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Query.php new file mode 100644 index 00000000..f1b866d1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Query.php @@ -0,0 +1,128 @@ + '1', 'foo[b]' => '2'])`. + * + * @param string $str Query string to parse + * @param int|bool $urlEncoding How the query string is encoded + */ + public static function parse(string $str, $urlEncoding = \true) : array + { + $result = []; + if ($str === '') { + return $result; + } + if ($urlEncoding === \true) { + $decoder = function ($value) { + return \rawurldecode(\str_replace('+', ' ', (string) $value)); + }; + } elseif ($urlEncoding === \PHP_QUERY_RFC3986) { + $decoder = 'rawurldecode'; + } elseif ($urlEncoding === \PHP_QUERY_RFC1738) { + $decoder = 'urldecode'; + } else { + $decoder = function ($str) { + return $str; + }; + } + foreach (\explode('&', $str) as $kvp) { + $parts = \explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + if (!\array_key_exists($key, $result)) { + $result[$key] = $value; + } else { + if (!\is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + return $result; + } + /** + * Build a query string from an array of key value pairs. + * + * This function can use the return value of `parse()` to build a query + * string. This function does not modify the provided keys when an array is + * encountered (like `http_build_query()` would). + * + * @param array $params Query string parameters. + * @param int|false $encoding Set to false to not encode, + * PHP_QUERY_RFC3986 to encode using + * RFC3986, or PHP_QUERY_RFC1738 to + * encode using RFC1738. + * @param bool $treatBoolsAsInts Set to true to encode as 0/1, and + * false as false/true. + */ + public static function build(array $params, $encoding = \PHP_QUERY_RFC3986, bool $treatBoolsAsInts = \true) : string + { + if (!$params) { + return ''; + } + if ($encoding === \false) { + $encoder = function (string $str) : string { + return $str; + }; + } elseif ($encoding === \PHP_QUERY_RFC3986) { + $encoder = 'rawurlencode'; + } elseif ($encoding === \PHP_QUERY_RFC1738) { + $encoder = 'urlencode'; + } else { + throw new \InvalidArgumentException('Invalid type'); + } + $castBool = $treatBoolsAsInts ? static function ($v) { + return (int) $v; + } : static function ($v) { + return $v ? 'true' : 'false'; + }; + $qs = ''; + foreach ($params as $k => $v) { + $k = $encoder((string) $k); + if (!\is_array($v)) { + $qs .= $k; + $v = \is_bool($v) ? $castBool($v) : self::normalizeNonFiniteFloat($v); + if ($v !== null) { + $qs .= '=' . $encoder((string) $v); + } + $qs .= '&'; + } else { + foreach ($v as $vv) { + $qs .= $k; + $vv = \is_bool($vv) ? $castBool($vv) : self::normalizeNonFiniteFloat($vv); + if ($vv !== null) { + $qs .= '=' . $encoder((string) $vv); + } + $qs .= '&'; + } + } + } + return $qs ? (string) \substr($qs, 0, -1) : ''; + } + /** + * Converts non-finite floats to the strings PHP coerces them to, as + * implicit coercion of NAN emits a warning on PHP 8.5. + * + * @param mixed $value + * + * @return mixed + */ + private static function normalizeNonFiniteFloat($value) + { + if (\is_float($value) && !\is_finite($value)) { + \trigger_deprecation('guzzlehttp/psr7', '2.12', 'Passing a non-finite float to Query::build() is deprecated; guzzlehttp/psr7 3.0 rejects non-finite floats.'); + return \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + } + return $value; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Request.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Request.php new file mode 100644 index 00000000..9439ba82 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Request.php @@ -0,0 +1,139 @@ +assertMethod($method); + $this->assertProtocolVersion($version); + if (!$uri instanceof UriInterface) { + $uri = new Uri($uri); + } + self::warnOnMethodCasingChange($method); + $this->method = \strtoupper($method); + $this->uri = $uri; + $this->setHeaders($headers); + $this->protocol = $version; + if (!isset($this->headerNames['host'])) { + $this->updateHostFromUri(); + } + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + } + public function getRequestTarget() : string + { + if ($this->requestTarget !== null) { + return $this->requestTarget; + } + $target = $this->uri->getPath(); + if ($target === '') { + $target = '/'; + } + if ($this->uri->getQuery() != '') { + $target .= '?' . $this->uri->getQuery(); + } + return $target; + } + public function withRequestTarget($requestTarget) : RequestInterface + { + if (\preg_match('#\\s#', $requestTarget)) { + throw new InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); + } + $new = clone $this; + $new->requestTarget = $requestTarget; + return $new; + } + public function getMethod() : string + { + return $this->method; + } + public function withMethod($method) : RequestInterface + { + $this->assertMethod($method); + self::warnOnMethodCasingChange($method); + $new = clone $this; + $new->method = \strtoupper($method); + return $new; + } + public function getUri() : UriInterface + { + return $this->uri; + } + public function withUri(UriInterface $uri, $preserveHost = \false) : RequestInterface + { + if (!\is_bool($preserveHost)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to RequestInterface::withUri() is deprecated; guzzlehttp/psr7 3.0 requires bool for $preserveHost.', \get_debug_type($preserveHost)); + } + if ($uri === $this->uri) { + return $this; + } + $new = clone $this; + $new->uri = $uri; + if (!$preserveHost || !isset($this->headerNames['host'])) { + $new->updateHostFromUri(); + } + return $new; + } + private function updateHostFromUri() : void + { + $host = $this->uri->getHost(); + if ($host == '') { + return; + } + Uri::assertValidHost($host); + if (($port = $this->uri->getPort()) !== null) { + $host .= ':' . $port; + } + $this->assertValue($host); + if (isset($this->headerNames['host'])) { + $header = $this->headerNames['host']; + } else { + $header = 'Host'; + $this->headerNames['host'] = 'Host'; + } + // Ensure Host is the first header. + // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4 + $this->headers = [$header => [$host]] + $this->headers; + } + /** + * @param mixed $method + */ + private function assertMethod($method) : void + { + if (!\is_string($method) || $method === '') { + throw new InvalidArgumentException('Method must be a non-empty string.'); + } + $this->assertNoLineSeparators($method, 'Method'); + } + private static function warnOnMethodCasingChange(string $method) : void + { + if ($method !== \strtoupper($method)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing a non-uppercase HTTP method is deprecated; guzzlehttp/psr7 3.0 preserves method casing and will no longer uppercase it. Normalize the method before constructing or modifying requests if uppercase is required.'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Response.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Response.php new file mode 100644 index 00000000..c6bd7d33 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Response.php @@ -0,0 +1,89 @@ + 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required']; + /** @var string */ + private $reasonPhrase; + /** @var int */ + private $statusCode; + /** + * @param int $status Status code + * @param (string|string[])[] $headers Response headers + * @param string|resource|StreamInterface|null $body Response body + * @param string $version Protocol version + * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) + */ + public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null) + { + $this->assertStatusCodeRange($status); + $this->assertProtocolVersion($version); + $this->statusCode = $status; + if ($body !== '' && $body !== null) { + $this->stream = Utils::streamFor($body); + } + $this->setHeaders($headers); + if ($reason == '' && isset(self::PHRASES[$this->statusCode])) { + $reasonPhrase = self::PHRASES[$this->statusCode]; + } else { + $reasonPhrase = (string) $reason; + } + $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase'); + $this->reasonPhrase = $reasonPhrase; + $this->protocol = $version; + } + public function getStatusCode() : int + { + return $this->statusCode; + } + public function getReasonPhrase() : string + { + return $this->reasonPhrase; + } + public function withStatus($code, $reasonPhrase = '') : ResponseInterface + { + if (!\is_int($code) && \filter_var($code, \FILTER_VALIDATE_INT) !== \false) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to ResponseInterface::withStatus() is deprecated; guzzlehttp/psr7 3.0 requires int for $code.', \get_debug_type($code)); + } + if (!\is_string($reasonPhrase)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to ResponseInterface::withStatus() is deprecated; guzzlehttp/psr7 3.0 requires string for $reasonPhrase.', \get_debug_type($reasonPhrase)); + } + $this->assertStatusCodeIsInteger($code); + $code = (int) $code; + $this->assertStatusCodeRange($code); + $new = clone $this; + $new->statusCode = $code; + if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) { + $reasonPhrase = self::PHRASES[$new->statusCode]; + } + $reasonPhrase = (string) $reasonPhrase; + $this->assertNoLineSeparators($reasonPhrase, 'Reason phrase'); + $new->reasonPhrase = $reasonPhrase; + return $new; + } + /** + * @param mixed $statusCode + */ + private function assertStatusCodeIsInteger($statusCode) : void + { + if (\filter_var($statusCode, \FILTER_VALIDATE_INT) === \false) { + throw new \InvalidArgumentException('Status code must be an integer value.'); + } + } + private function assertStatusCodeRange(int $statusCode) : void + { + if ($statusCode < 100 || $statusCode >= 600) { + throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.'); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Rfc3986.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Rfc3986.php new file mode 100644 index 00000000..157c7408 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Rfc3986.php @@ -0,0 +1,23 @@ +@,;:\\\"/[\\]?={}\x01- ]++):[ \t]*+((?:[ \t]*+[!-~\x80-\xff]++)*+)[ \t]*+\r?\n)m"; + public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; + /** + * @return array{0: string, 1: int|null}|null + */ + public static function parseHostHeader(string $authority) : ?array + { + if ($authority === '') { + return null; + } + $host = $authority; + $port = null; + if ($authority[0] === '[') { + $closingBracket = \strpos($authority, ']'); + if ($closingBracket === \false) { + return null; + } + $host = \substr($authority, 0, $closingBracket + 1); + $remainder = \substr($authority, $closingBracket + 1); + if ($remainder !== '') { + if ($remainder[0] !== ':') { + return null; + } + $port = self::parseAuthorityPort(\substr($remainder, 1)); + if ($port === null) { + return null; + } + } + } elseif (\false !== ($colon = \strpos($authority, ':'))) { + $host = \substr($authority, 0, $colon); + $port = self::parseAuthorityPort(\substr($authority, $colon + 1)); + if ($port === null) { + return null; + } + } + if ($host === '' || !self::isValidHostHeaderHost($host)) { + return null; + } + return [$host, $port]; + } + private static function isValidHostHeaderHost(string $host) : bool + { + if (\preg_match('/[\\x00-\\x20\\x7F\\/\\?#@\\\\]/', $host)) { + return \false; + } + if (\strpos($host, '[') !== \false || \strpos($host, ']') !== \false) { + if ($host[0] !== '[' || \substr($host, -1) !== ']') { + return \false; + } + $address = \substr($host, 1, -1); + return \filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== \false || \preg_match('/^v[0-9a-f]+\\.[' . Rfc3986::CHAR_UNRESERVED . Rfc3986::CHAR_SUB_DELIMS . ':]+$/iD', $address) === 1; + } + return \strpos($host, ':') === \false; + } + private static function parseAuthorityPort(string $port) : ?int + { + if ($port === '' || !\ctype_digit($port)) { + return null; + } + $normalized = \ltrim($port, '0'); + if ($normalized === '') { + return 0; + } + if (\strlen($normalized) > 5 || (int) $normalized > 0xffff) { + return null; + } + return (int) $normalized; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/ServerRequest.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/ServerRequest.php new file mode 100644 index 00000000..cd1b5836 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/ServerRequest.php @@ -0,0 +1,343 @@ +serverParams = $serverParams; + parent::__construct($method, $uri, $headers, $body, $version); + } + /** + * Return an UploadedFile instance array. + * + * @param array $files An array which respect $_FILES structure + * + * @throws InvalidArgumentException for unrecognized values + */ + public static function normalizeFiles(array $files) : array + { + $normalized = []; + foreach ($files as $key => $value) { + if ($value instanceof UploadedFileInterface) { + $normalized[$key] = $value; + } elseif (\is_array($value) && isset($value['tmp_name'])) { + $normalized[$key] = self::createUploadedFileFromSpec($value); + } elseif (\is_array($value)) { + $normalized[$key] = self::normalizeFiles($value); + continue; + } else { + throw new InvalidArgumentException('Invalid value in files specification'); + } + } + return $normalized; + } + /** + * Create and return an UploadedFile instance from a $_FILES specification. + * + * If the specification represents an array of values, this method will + * delegate to normalizeNestedFileSpec() and return that return value. + * + * @param array $value $_FILES struct + * + * @return UploadedFileInterface|UploadedFileInterface[] + */ + private static function createUploadedFileFromSpec(array $value) + { + if (\is_array($value['tmp_name'])) { + return self::normalizeNestedFileSpec($value); + } + return new UploadedFile($value['tmp_name'], (int) $value['size'], (int) $value['error'], $value['name'], $value['type']); + } + /** + * Normalize an array of file specifications. + * + * Loops through all nested files and returns a normalized array of + * UploadedFileInterface instances. + * + * @return UploadedFileInterface[] + */ + private static function normalizeNestedFileSpec(array $files = []) : array + { + $normalizedFiles = []; + foreach (\array_keys($files['tmp_name']) as $key) { + $spec = ['tmp_name' => $files['tmp_name'][$key], 'size' => $files['size'][$key] ?? null, 'error' => $files['error'][$key] ?? null, 'name' => $files['name'][$key] ?? null, 'type' => $files['type'][$key] ?? null]; + $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); + } + return $normalizedFiles; + } + /** + * Return a ServerRequest populated with superglobals: + * $_GET + * $_POST + * $_COOKIE + * $_FILES + * $_SERVER + */ + public static function fromGlobals() : ServerRequestInterface + { + $method = \strtoupper(self::getServerParam('REQUEST_METHOD') ?? 'GET'); + $headers = self::removeInvalidHostHeader(self::getAllHeaders()); + $uri = self::getUriFromGlobals(); + $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); + $serverProtocol = self::getServerParam('SERVER_PROTOCOL'); + $protocol = $serverProtocol !== null ? \str_replace('HTTP/', '', $serverProtocol) : '1.1'; + $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); + return $serverRequest->withCookieParams($_COOKIE)->withQueryParams($_GET)->withParsedBody($_POST)->withUploadedFiles(self::normalizeFiles($_FILES)); + } + /** + * @return array + */ + private static function getAllHeaders() : array + { + return self::normalizeHeaderValues(\getallheaders()); + } + /** + * @param array $headers + * + * @return array + */ + private static function normalizeHeaderValues(array $headers) : array + { + $normalized = []; + foreach ($headers as $name => $value) { + if (\is_scalar($value) || \is_object($value) && \method_exists($value, '__toString')) { + $normalized[$name] = (string) $value; + } + } + return $normalized; + } + private static function getServerParam(string $key) : ?string + { + return isset($_SERVER[$key]) && \is_string($_SERVER[$key]) ? $_SERVER[$key] : null; + } + /** + * @param array $headers + * + * @return array + */ + private static function removeInvalidHostHeader(array $headers) : array + { + foreach ($headers as $name => $value) { + if (\strtolower((string) $name) !== 'host') { + continue; + } + if (Rfc7230::parseHostHeader($value) === null) { + unset($headers[$name]); + } + } + return $headers; + } + /** + * @return array{0: string|null, 1: int|null} + */ + private static function extractHostAndPortFromAuthority(string $authority) : array + { + return Rfc7230::parseHostHeader($authority) ?? [null, null]; + } + /** + * Get a Uri populated with values from $_SERVER. + */ + public static function getUriFromGlobals() : UriInterface + { + $uri = new Uri(''); + $https = self::getServerParam('HTTPS'); + $uri = $uri->withScheme(!empty($https) && $https !== 'off' ? 'https' : 'http'); + $hasPort = \false; + $authority = self::getServerParam('HTTP_HOST'); + if ($authority !== null) { + [$host, $port] = self::extractHostAndPortFromAuthority($authority); + if ($host !== null) { + $uri = $uri->withHost($host); + } + if ($port !== null) { + $hasPort = \true; + $uri = $uri->withPort($port); + } + } elseif (($serverName = self::getServerParam('SERVER_NAME')) !== null) { + $uri = $uri->withHost($serverName); + } elseif (($serverAddr = self::getServerParam('SERVER_ADDR')) !== null) { + $uri = $uri->withHost($serverAddr); + } + $serverPort = self::getServerParam('SERVER_PORT'); + if (!$hasPort && $serverPort !== null && \preg_match('/^[+-]?\\d+$/', $serverPort) === 1) { + $uri = $uri->withPort((int) $serverPort); + } + $hasQuery = \false; + $requestUri = self::getServerParam('REQUEST_URI'); + if ($requestUri !== null) { + $requestUriParts = \explode('?', $requestUri, 2); + $uri = $uri->withPath($requestUriParts[0]); + if (isset($requestUriParts[1])) { + $hasQuery = \true; + $uri = $uri->withQuery($requestUriParts[1]); + } + } + $queryString = self::getServerParam('QUERY_STRING'); + if (!$hasQuery && $queryString !== null) { + $uri = $uri->withQuery($queryString); + } + return $uri; + } + public function getServerParams() : array + { + return $this->serverParams; + } + public function getUploadedFiles() : array + { + return $this->uploadedFiles; + } + public function withUploadedFiles(array $uploadedFiles) : ServerRequestInterface + { + $invalidUploadedFileFound = \false; + $invalidUploadedFile = null; + $stack = [$uploadedFiles]; + while ($stack !== []) { + foreach (\array_pop($stack) as $uploadedFile) { + if ($uploadedFile instanceof UploadedFileInterface) { + continue; + } + if (\is_array($uploadedFile)) { + $stack[] = $uploadedFile; + continue; + } + $invalidUploadedFileFound = \true; + $invalidUploadedFile = $uploadedFile; + break 2; + } + } + if ($invalidUploadedFileFound) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s inside ServerRequestInterface::withUploadedFiles() is deprecated; guzzlehttp/psr7 3.0 requires an UploadedFileInterface[] tree.', \get_debug_type($invalidUploadedFile)); + } + $new = clone $this; + $new->uploadedFiles = $uploadedFiles; + return $new; + } + public function getCookieParams() : array + { + return $this->cookieParams; + } + public function withCookieParams(array $cookies) : ServerRequestInterface + { + $new = clone $this; + $new->cookieParams = $cookies; + return $new; + } + public function getQueryParams() : array + { + return $this->queryParams; + } + public function withQueryParams(array $query) : ServerRequestInterface + { + $new = clone $this; + $new->queryParams = $query; + return $new; + } + /** + * @return array|object|null + */ + public function getParsedBody() + { + return $this->parsedBody; + } + public function withParsedBody($data) : ServerRequestInterface + { + if ($data !== null && !\is_array($data) && !\is_object($data)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to ServerRequestInterface::withParsedBody() is deprecated; guzzlehttp/psr7 3.0 requires array|object|null.', \get_debug_type($data)); + } + $new = clone $this; + $new->parsedBody = $data; + return $new; + } + public function getAttributes() : array + { + return $this->attributes; + } + /** + * @return mixed + */ + public function getAttribute($attribute, $default = null) + { + if (!\is_string($attribute)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to ServerRequestInterface::getAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.', \get_debug_type($attribute)); + } + if (\false === \array_key_exists($attribute, $this->attributes)) { + return $default; + } + return $this->attributes[$attribute]; + } + public function withAttribute($attribute, $value) : ServerRequestInterface + { + if (!\is_string($attribute)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to ServerRequestInterface::withAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.', \get_debug_type($attribute)); + } + $new = clone $this; + $new->attributes[$attribute] = $value; + return $new; + } + public function withoutAttribute($attribute) : ServerRequestInterface + { + if (!\is_string($attribute)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to ServerRequestInterface::withoutAttribute() is deprecated; guzzlehttp/psr7 3.0 requires string for $attribute.', \get_debug_type($attribute)); + } + if (\false === \array_key_exists($attribute, $this->attributes)) { + return $this; + } + $new = clone $this; + unset($new->attributes[$attribute]); + return $new; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Stream.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Stream.php new file mode 100644 index 00000000..bc6e20db --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Stream.php @@ -0,0 +1,250 @@ +size = $options['size']; + } + $this->customMetadata = $options['metadata'] ?? []; + $this->stream = $stream; + $meta = \stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = (bool) \preg_match(self::READABLE_MODES, $meta['mode']); + $this->writable = (bool) \preg_match(self::WRITABLE_MODES, $meta['mode']); + $this->uri = $meta['uri'] ?? null; + } + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + public function __toString() : string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + \trigger_error(\sprintf('%s::__toString exception: %s', self::class, (string) $e), \E_USER_ERROR); + return ''; + } + } + public function getContents() : string + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + return Utils::tryGetContents($this->stream); + } + public function close() : void + { + if (isset($this->stream)) { + if (\is_resource($this->stream)) { + \fclose($this->stream); + } + $this->detach(); + } + } + public function detach() + { + if (!isset($this->stream)) { + return null; + } + $result = $this->stream; + unset($this->stream); + $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = \false; + return $result; + } + public function getSize() : ?int + { + if ($this->size !== null) { + return $this->size; + } + if (!isset($this->stream)) { + return null; + } + // Clear the stat cache if the stream has a URI + if ($this->uri) { + \clearstatcache(\true, $this->uri); + } + $stats = \fstat($this->stream); + if (\is_array($stats) && isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + return null; + } + public function isReadable() : bool + { + return $this->readable; + } + public function isWritable() : bool + { + return $this->writable; + } + public function isSeekable() : bool + { + return $this->seekable; + } + public function eof() : bool + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + return \feof($this->stream); + } + public function tell() : int + { + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + $result = \ftell($this->stream); + if ($result === \false) { + throw new \RuntimeException('Unable to determine stream position'); + } + return $result; + } + public function rewind() : void + { + $this->seek(0); + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + $whence = (int) $whence; + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->seekable) { + throw new \RuntimeException('Stream is not seekable'); + } + if (\fseek($this->stream, $offset, $whence) === -1) { + throw new \RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . \var_export($whence, \true)); + } + } + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->readable) { + throw new \RuntimeException('Cannot read from non-readable stream'); + } + if ($length < 0) { + throw new \RuntimeException('Length parameter cannot be negative'); + } + if (0 === $length) { + return ''; + } + try { + $string = \fread($this->stream, $length); + } catch (\Exception $e) { + throw new \RuntimeException('Unable to read from stream', 0, $e); + } + if (\false === $string) { + throw new \RuntimeException('Unable to read from stream'); + } + return $string; + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + if (!isset($this->stream)) { + throw new \RuntimeException('Stream is detached'); + } + if (!$this->writable) { + throw new \RuntimeException('Cannot write to a non-writable stream'); + } + // We can't know the size after writing anything + $this->size = null; + $result = \fwrite($this->stream, $string); + if ($result === \false) { + throw new \RuntimeException('Unable to write to stream'); + } + return $result; + } + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key !== null && !\is_string($key)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', \get_debug_type($key)); + } + if (!isset($this->stream)) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + \stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + $meta = \stream_get_meta_data($this->stream); + return $meta[$key] ?? null; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamDecoratorTrait.php new file mode 100644 index 00000000..84da4228 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamDecoratorTrait.php @@ -0,0 +1,146 @@ +stream = $stream; + } + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + * + * @return StreamInterface + */ + public function __get(string $name) + { + if ($name === 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + throw new \UnexpectedValueException("{$name} not found on class"); + } + public function __toString() : string + { + try { + if ($this->isSeekable()) { + $this->seek(0); + } + return $this->getContents(); + } catch (\Throwable $e) { + if (\PHP_VERSION_ID >= 70400) { + throw $e; + } + \trigger_error(\sprintf('%s::__toString exception: %s', self::class, (string) $e), \E_USER_ERROR); + return ''; + } + } + public function getContents() : string + { + return Utils::copyToString($this); + } + /** + * Allow decorators to implement custom methods + * + * @return mixed + */ + public function __call(string $method, array $args) + { + /** @var callable $callable */ + $callable = [$this->stream, $method]; + $result = $callable(...$args); + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + public function close() : void + { + $this->stream->close(); + } + /** + * @return mixed + */ + public function getMetadata($key = null) + { + if ($key !== null && !\is_string($key)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::getMetadata() is deprecated; guzzlehttp/psr7 3.0 requires string|null for $key.', \get_debug_type($key)); + } + return $this->stream->getMetadata($key); + } + public function detach() + { + return $this->stream->detach(); + } + public function getSize() : ?int + { + return $this->stream->getSize(); + } + public function eof() : bool + { + return $this->stream->eof(); + } + public function tell() : int + { + return $this->stream->tell(); + } + public function isReadable() : bool + { + return $this->stream->isReadable(); + } + public function isWritable() : bool + { + return $this->stream->isWritable(); + } + public function isSeekable() : bool + { + return $this->stream->isSeekable(); + } + public function rewind() : void + { + $this->seek(0); + } + public function seek($offset, $whence = \SEEK_SET) : void + { + if (!\is_int($offset)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $offset.', \get_debug_type($offset)); + } + if (!\is_int($whence)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::seek() is deprecated; guzzlehttp/psr7 3.0 requires int for $whence.', \get_debug_type($whence)); + } + $this->stream->seek($offset, $whence); + } + public function read($length) : string + { + if (!\is_int($length)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::read() is deprecated; guzzlehttp/psr7 3.0 requires int for $length.', \get_debug_type($length)); + } + return $this->stream->read($length); + } + public function write($string) : int + { + if (!\is_string($string)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to StreamInterface::write() is deprecated; guzzlehttp/psr7 3.0 requires string for $string.', \get_debug_type($string)); + } + return $this->stream->write($string); + } + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @throws \BadMethodCallException + */ + protected function createStream() : StreamInterface + { + throw new \BadMethodCallException('Not implemented'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamWrapper.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamWrapper.php new file mode 100644 index 00000000..77582ca1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/StreamWrapper.php @@ -0,0 +1,149 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' . 'writable, or both.'); + } + $resource = @\fopen('guzzle://stream', $mode, \false, self::createStreamContext($stream)); + if ($resource === \false) { + throw new \RuntimeException('Unable to create stream resource'); + } + return $resource; + } + /** + * Creates a stream context that can be used to open a stream as a php stream resource. + * + * @return resource + */ + public static function createStreamContext(StreamInterface $stream) + { + return \stream_context_create(['guzzle' => ['stream' => $stream]]); + } + /** + * Registers the stream wrapper if needed + */ + public static function register() : void + { + if (!\in_array('guzzle', \stream_get_wrappers())) { + \stream_wrapper_register('guzzle', __CLASS__); + } + } + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null) : bool + { + $options = \stream_context_get_options($this->context); + if (!isset($options['guzzle']['stream'])) { + return \false; + } + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + return \true; + } + public function stream_read(int $count) : string + { + return $this->stream->read($count); + } + public function stream_write(string $data) : int + { + return $this->stream->write($data); + } + public function stream_tell() : int + { + return $this->stream->tell(); + } + public function stream_eof() : bool + { + return $this->stream->eof(); + } + public function stream_seek(int $offset, int $whence) : bool + { + $this->stream->seek($offset, $whence); + return \true; + } + /** + * @return resource|false + */ + public function stream_cast(int $cast_as) + { + $stream = clone $this->stream; + $resource = $stream->detach(); + return $resource ?? \false; + } + /** + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * }|false + */ + public function stream_stat() + { + if ($this->stream->getSize() === null) { + return \false; + } + static $modeMap = ['r' => 33060, 'rb' => 33060, 'r+' => 33206, 'w' => 33188, 'wb' => 33188]; + return ['dev' => 0, 'ino' => 0, 'mode' => $modeMap[$this->mode], 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => $this->stream->getSize() ?: 0, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0]; + } + /** + * @return array{ + * dev: int, + * ino: int, + * mode: int, + * nlink: int, + * uid: int, + * gid: int, + * rdev: int, + * size: int, + * atime: int, + * mtime: int, + * ctime: int, + * blksize: int, + * blocks: int + * } + */ + public function url_stat(string $path, int $flags) : array + { + return ['dev' => 0, 'ino' => 0, 'mode' => 0, 'nlink' => 0, 'uid' => 0, 'gid' => 0, 'rdev' => 0, 'size' => 0, 'atime' => 0, 'mtime' => 0, 'ctime' => 0, 'blksize' => 0, 'blocks' => 0]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UploadedFile.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UploadedFile.php new file mode 100644 index 00000000..069ea20e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UploadedFile.php @@ -0,0 +1,152 @@ + 'UPLOAD_ERR_OK', \UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE', \UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE', \UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL', \UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE', \UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR', \UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE', \UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION']; + /** + * @var string|null + */ + private $clientFilename; + /** + * @var string|null + */ + private $clientMediaType; + /** + * @var int + */ + private $error; + /** + * @var string|null + */ + private $file; + /** + * @var bool + */ + private $moved = \false; + /** + * @var int|null + */ + private $size; + /** + * @var StreamInterface|null + */ + private $stream; + /** + * @param StreamInterface|string|resource $streamOrFile + */ + public function __construct($streamOrFile, ?int $size, int $errorStatus, ?string $clientFilename = null, ?string $clientMediaType = null) + { + $this->setError($errorStatus); + $this->size = $size; + $this->clientFilename = $clientFilename; + $this->clientMediaType = $clientMediaType; + if ($this->isOk()) { + $this->setStreamOrFile($streamOrFile); + } + } + /** + * Depending on the value set file or stream variable + * + * @param StreamInterface|string|resource $streamOrFile + * + * @throws InvalidArgumentException + */ + private function setStreamOrFile($streamOrFile) : void + { + if (\is_string($streamOrFile)) { + $this->file = $streamOrFile; + } elseif (\is_resource($streamOrFile)) { + $this->stream = new Stream($streamOrFile); + } elseif ($streamOrFile instanceof StreamInterface) { + $this->stream = $streamOrFile; + } else { + throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile'); + } + } + /** + * @throws InvalidArgumentException + */ + private function setError(int $error) : void + { + if (!isset(UploadedFile::ERROR_MAP[$error])) { + throw new InvalidArgumentException('Invalid error status for UploadedFile'); + } + $this->error = $error; + } + private static function isStringNotEmpty($param) : bool + { + return \is_string($param) && \false === empty($param); + } + /** + * Return true if there is no upload error + */ + private function isOk() : bool + { + return $this->error === \UPLOAD_ERR_OK; + } + public function isMoved() : bool + { + return $this->moved; + } + /** + * @throws RuntimeException if is moved or not ok + */ + private function validateActive() : void + { + if (\false === $this->isOk()) { + throw new RuntimeException(\sprintf('Cannot retrieve stream due to upload error (%s)', self::ERROR_MAP[$this->error])); + } + if ($this->isMoved()) { + throw new RuntimeException('Cannot retrieve stream after it has already been moved'); + } + } + public function getStream() : StreamInterface + { + $this->validateActive(); + if ($this->stream instanceof StreamInterface) { + return $this->stream; + } + /** @var string $file */ + $file = $this->file; + return new LazyOpenStream($file, 'r+'); + } + public function moveTo($targetPath) : void + { + $this->validateActive(); + if (\false === self::isStringNotEmpty($targetPath)) { + throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); + } + if ($this->file) { + $this->moved = \PHP_SAPI === 'cli' ? \rename($this->file, $targetPath) : \move_uploaded_file($this->file, $targetPath); + } else { + Utils::copyToStream($this->getStream(), new LazyOpenStream($targetPath, 'w')); + $this->moved = \true; + } + if (\false === $this->moved) { + throw new RuntimeException(\sprintf('Uploaded file could not be moved to %s', $targetPath)); + } + } + public function getSize() : ?int + { + return $this->size; + } + public function getError() : int + { + return $this->error; + } + public function getClientFilename() : ?string + { + return $this->clientFilename; + } + public function getClientMediaType() : ?string + { + return $this->clientMediaType; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Uri.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Uri.php new file mode 100644 index 00000000..2e2c7ca0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Uri.php @@ -0,0 +1,633 @@ + 80, 'https' => 443, 'ftp' => 21, 'gopher' => 70, 'nntp' => 119, 'news' => 119, 'telnet' => 23, 'tn3270' => 23, 'imap' => 143, 'pop' => 110, 'ldap' => 389]; + private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B']; + /** @var string Uri scheme. */ + private $scheme = ''; + /** @var string Uri user info. */ + private $userInfo = ''; + /** @var string Uri host. */ + private $host = ''; + /** @var int|null Uri port. */ + private $port; + /** @var string Uri path. */ + private $path = ''; + /** @var string Uri query string. */ + private $query = ''; + /** @var string Uri fragment. */ + private $fragment = ''; + public function __construct(string $uri = '') + { + if ($uri !== '') { + $parts = self::parse($uri); + if ($parts === \false) { + throw new MalformedUriException("Unable to parse URI: {$uri}"); + } + try { + $this->applyParts($parts); + } catch (MalformedUriException $e) { + throw $e; + } catch (\InvalidArgumentException $e) { + throw new MalformedUriException($e->getMessage(), 0, $e); + } + } + } + /** + * UTF-8 aware \parse_url() replacement. + * + * The internal function produces broken output for non ASCII domain names + * (IDN) when used with locales other than "C". + * + * On the other hand, cURL understands IDN correctly only when UTF-8 locale + * is configured ("C.UTF-8", "en_US.UTF-8", etc.). + * + * @see https://bugs.php.net/bug.php?id=52923 + * @see https://www.php.net/manual/en/function.parse-url.php#114817 + * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING + * + * @return array|false + */ + private static function parse(string $url) + { + if (self::isPathNoSchemeReference($url)) { + return self::parsePathNoSchemeReference($url); + } + // Preserve bracketed IPv6 literals before encoding, including dotted IPv4 tails. + $prefix = ''; + if (\preg_match('%^([0-9A-Za-z+.-]+://\\[[0-9:.a-fA-F]+\\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + /** @var string|null */ + $encodedUrl = \preg_replace_callback('%[^:/@?&=#]+%usD', static function ($matches) { + return \urlencode($matches[0]); + }, $url); + if ($encodedUrl === null) { + return \false; + } + $result = \parse_url($prefix . $encodedUrl); + if ($result === \false) { + return \false; + } + return \array_map('urldecode', $result); + } + private static function isPathNoSchemeReference(string $url) : bool + { + if ($url === '' || $url[0] === '/' || $url[0] === '?' || $url[0] === '#') { + return \false; + } + $firstSegment = \substr($url, 0, \strcspn($url, '/?#')); + return \strpos($firstSegment, ':') === \false; + } + /** + * @return array{path: string, query?: string, fragment?: string} + */ + private static function parsePathNoSchemeReference(string $url) : array + { + $parts = []; + if (\false !== ($fragmentPosition = \strpos($url, '#'))) { + $parts['fragment'] = \substr($url, $fragmentPosition + 1); + $url = \substr($url, 0, $fragmentPosition); + } + if (\false !== ($queryPosition = \strpos($url, '?'))) { + $parts['query'] = \substr($url, $queryPosition + 1); + $url = \substr($url, 0, $queryPosition); + } + $parts['path'] = $url; + return $parts; + } + public function __toString() : string + { + return self::composeComponents($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment); + } + /** + * Composes a URI reference string from its various components. + * + * Usually this method does not need to be called manually but instead is used indirectly via + * `Psr\Http\Message\UriInterface::__toString`. + * + * PSR-7 UriInterface treats an empty component the same as a missing component as + * getQuery(), getFragment() etc. always return a string. This explains the slight + * difference to RFC 3986 Section 5.3. + * + * Another adjustment is that the authority separator is added even when the authority is missing/empty + * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with + * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But + * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to + * that format). + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3 + */ + public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment) : string + { + $uri = ''; + // weak type checks to also accept null until we can add scalar type hints + if ($scheme != '') { + $uri .= $scheme . ':'; + } + if ($authority != '' || $scheme === 'file') { + $uri .= '//' . $authority; + } + if ($authority != '' && $path != '' && $path[0] != '/') { + $path = '/' . $path; + } + $uri .= $path; + if ($query != '') { + $uri .= '?' . $query; + } + if ($fragment != '') { + $uri .= '#' . $fragment; + } + return $uri; + } + /** + * Whether the URI has the default port of the current scheme. + * + * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used + * independently of the implementation. + */ + public static function isDefaultPort(UriInterface $uri) : bool + { + return $uri->getPort() === null || isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]; + } + /** + * Whether the URI is absolute, i.e. it has a scheme. + * + * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true + * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative + * to another URI, the base URI. Relative references can be divided into several forms: + * - network-path references, e.g. '//example.com/path' + * - absolute-path references, e.g. '/path' + * - relative-path references, e.g. 'subpath' + * + * @see Uri::isNetworkPathReference + * @see Uri::isAbsolutePathReference + * @see Uri::isRelativePathReference + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4 + */ + public static function isAbsolute(UriInterface $uri) : bool + { + return $uri->getScheme() !== ''; + } + /** + * Whether the URI is a network-path reference. + * + * A relative reference that begins with two slash characters is termed an network-path reference. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + */ + public static function isNetworkPathReference(UriInterface $uri) : bool + { + return $uri->getScheme() === '' && $uri->getAuthority() !== ''; + } + /** + * Whether the URI is a absolute-path reference. + * + * A relative reference that begins with a single slash character is termed an absolute-path reference. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + */ + public static function isAbsolutePathReference(UriInterface $uri) : bool + { + return $uri->getScheme() === '' && $uri->getAuthority() === '' && isset($uri->getPath()[0]) && $uri->getPath()[0] === '/'; + } + /** + * Whether the URI is a relative-path reference. + * + * A relative reference that does not begin with a slash character is termed a relative-path reference. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2 + */ + public static function isRelativePathReference(UriInterface $uri) : bool + { + return $uri->getScheme() === '' && $uri->getAuthority() === '' && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/'); + } + /** + * Whether the URI is a same-document reference. + * + * A same-document reference refers to a URI that is, aside from its fragment + * component, identical to the base URI. When no base URI is given, only an empty + * URI reference (apart from its fragment) is considered a same-document reference. + * + * @param UriInterface $uri The URI to check + * @param UriInterface|null $base An optional base URI to compare against + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4 + */ + public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null) : bool + { + if ($base !== null) { + $uri = UriResolver::resolve($base, $uri); + return $uri->getScheme() === $base->getScheme() && $uri->getAuthority() === $base->getAuthority() && $uri->getPath() === $base->getPath() && $uri->getQuery() === $base->getQuery(); + } + return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === ''; + } + /** + * Creates a new URI with a specific query string value removed. + * + * Any existing query string values that exactly match the provided key are + * removed. + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Query string key to remove. + */ + public static function withoutQueryValue(UriInterface $uri, string $key) : UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + return $uri->withQuery(\implode('&', $result)); + } + /** + * Creates a new URI with a specific query string value. + * + * Any existing query string values that exactly match the provided key are + * removed and replaced with the given key value pair. + * + * A value of null will set the query string key without a value, e.g. "key" + * instead of "key=value". + * + * @param UriInterface $uri URI to use as a base. + * @param string $key Key to set. + * @param string|null $value Value to set + */ + public static function withQueryValue(UriInterface $uri, string $key, ?string $value) : UriInterface + { + $result = self::getFilteredQueryString($uri, [$key]); + $result[] = self::generateQueryString($key, $value); + return $uri->withQuery(\implode('&', $result)); + } + /** + * Creates a new URI with multiple specific query string values. + * + * It has the same behavior as withQueryValue() but for an associative array of key => value. + * + * @param UriInterface $uri URI to use as a base. + * @param (string|null)[] $keyValueArray Associative array of key and values + */ + public static function withQueryValues(UriInterface $uri, array $keyValueArray) : UriInterface + { + $result = self::getFilteredQueryString($uri, \array_keys($keyValueArray)); + foreach ($keyValueArray as $key => $value) { + $result[] = self::generateQueryString((string) $key, $value !== null ? self::stringifyQueryValue($value) : null); + } + return $uri->withQuery(\implode('&', $result)); + } + /** + * Stringifies a non-null query value, deprecating non-string values that + * guzzlehttp/psr7 3.0 will reject. Non-finite floats are normalized to the + * strings PHP coerces them to, as implicit coercion of NAN emits a warning + * on PHP 8.5. + * + * @param mixed $value + */ + private static function stringifyQueryValue($value) : string + { + if (!\is_string($value)) { + \trigger_deprecation('guzzlehttp/psr7', '2.12', 'Passing %s to Uri::withQueryValues() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string or null query values.', \gettype($value)); + if (\is_float($value) && !\is_finite($value)) { + return \is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF'); + } + } + return (string) $value; + } + /** + * Creates a URI from a hash of `parse_url` components. + * + * @see https://www.php.net/manual/en/function.parse-url.php + * + * @throws MalformedUriException If the components do not form a valid URI. + */ + public static function fromParts(array $parts) : UriInterface + { + $uri = new self(); + try { + $uri->applyParts($parts); + $uri->validateState(); + } catch (MalformedUriException $e) { + throw $e; + } catch (\InvalidArgumentException $e) { + throw new MalformedUriException($e->getMessage(), 0, $e); + } + return $uri; + } + /** + * @throws \InvalidArgumentException If the host is invalid. + * + * @internal + */ + public static function assertValidHost(string $host) : void + { + if ($host === '') { + return; + } + if (\preg_match('/[\\x00-\\x20\\x7F]/', $host)) { + throw new \InvalidArgumentException(\sprintf('Invalid host: "%s"', $host)); + } + } + public function getScheme() : string + { + return $this->scheme; + } + public function getAuthority() : string + { + $authority = $this->host; + if ($this->userInfo !== '') { + $authority = $this->userInfo . '@' . $authority; + } + if ($this->port !== null) { + $authority .= ':' . $this->port; + } + return $authority; + } + public function getUserInfo() : string + { + return $this->userInfo; + } + public function getHost() : string + { + return $this->host; + } + public function getPort() : ?int + { + return $this->port; + } + public function getPath() : string + { + return $this->path; + } + public function getQuery() : string + { + return $this->query; + } + public function getFragment() : string + { + return $this->fragment; + } + public function withScheme($scheme) : UriInterface + { + $scheme = $this->filterScheme($scheme); + if ($this->scheme === $scheme) { + return $this; + } + $new = clone $this; + $new->scheme = $scheme; + $new->removeDefaultPort(); + $new->validateState(); + return $new; + } + public function withUserInfo($user, $password = null) : UriInterface + { + $info = $this->filterUserInfoComponent($user); + if ($password !== null) { + $info .= ':' . $this->filterUserInfoComponent($password); + } + if ($this->userInfo === $info) { + return $this; + } + $new = clone $this; + $new->userInfo = $info; + $new->validateState(); + return $new; + } + public function withHost($host) : UriInterface + { + $host = $this->filterHost($host); + if ($this->host === $host) { + return $this; + } + $new = clone $this; + $new->host = $host; + $new->validateState(); + return $new; + } + public function withPort($port) : UriInterface + { + if ($port !== null && !\is_int($port)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to UriInterface::withPort() is deprecated; guzzlehttp/psr7 3.0 requires int|null.', \get_debug_type($port)); + } + $port = $this->filterPort($port); + if ($this->port === $port) { + return $this; + } + $new = clone $this; + $new->port = $port; + $new->removeDefaultPort(); + $new->validateState(); + return $new; + } + public function withPath($path) : UriInterface + { + $path = $this->filterPath($path); + if ($this->path === $path) { + return $this; + } + $new = clone $this; + $new->path = $path; + $new->validateState(); + return $new; + } + public function withQuery($query) : UriInterface + { + $query = $this->filterQueryAndFragment($query); + if ($this->query === $query) { + return $this; + } + $new = clone $this; + $new->query = $query; + return $new; + } + public function withFragment($fragment) : UriInterface + { + $fragment = $this->filterQueryAndFragment($fragment); + if ($this->fragment === $fragment) { + return $this; + } + $new = clone $this; + $new->fragment = $fragment; + return $new; + } + public function jsonSerialize() : string + { + return $this->__toString(); + } + /** + * Apply parse_url parts to a URI. + * + * @param array $parts Array of parse_url parts to apply. + */ + private function applyParts(array $parts) : void + { + $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : ''; + $this->userInfo = isset($parts['user']) ? $this->filterUserInfoComponent($parts['user']) : ''; + $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : ''; + $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; + $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; + $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; + $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; + if (isset($parts['pass'])) { + $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']); + } + $this->removeDefaultPort(); + } + /** + * @param mixed $scheme + * + * @throws \InvalidArgumentException If the scheme is invalid. + */ + private function filterScheme($scheme) : string + { + if (!\is_string($scheme)) { + throw new \InvalidArgumentException('Scheme must be a string'); + } + $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + if ($scheme !== '' && !\preg_match('/^[a-z][a-z0-9.+-]*$/D', $scheme)) { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing "%s" as a URI scheme is deprecated; guzzlehttp/psr7 3.0 requires URI schemes to match RFC 3986 syntax and begin with a letter.', $scheme); + } + return $scheme; + } + /** + * @param mixed $component + * + * @throws \InvalidArgumentException If the user info is invalid. + */ + private function filterUserInfoComponent($component) : string + { + if (!\is_string($component)) { + throw new \InvalidArgumentException('User info must be a string'); + } + return \preg_replace_callback('/(?:[^%' . Rfc3986::CHAR_UNRESERVED . Rfc3986::CHAR_SUB_DELIMS . ']+|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $component); + } + /** + * @param mixed $host + * + * @throws \InvalidArgumentException If the host is invalid. + */ + private function filterHost($host) : string + { + if (!\is_string($host)) { + throw new \InvalidArgumentException('Host must be a string'); + } + $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + self::assertValidHost($host); + return $host; + } + /** + * @param mixed $port + * + * @throws \InvalidArgumentException If the port is invalid. + */ + private function filterPort($port) : ?int + { + if ($port === null) { + return null; + } + $port = (int) $port; + if (0 > $port || 0xffff < $port) { + throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port)); + } + return $port; + } + /** + * @param (string|int)[] $keys + * + * @return string[] + */ + private static function getFilteredQueryString(UriInterface $uri, array $keys) : array + { + $current = $uri->getQuery(); + if ($current === '') { + return []; + } + $decodedKeys = \array_map(function ($k) : string { + return \rawurldecode((string) $k); + }, $keys); + return \array_filter(\explode('&', $current), function ($part) use($decodedKeys) { + return !\in_array(\rawurldecode(\explode('=', $part)[0]), $decodedKeys, \true); + }); + } + private static function generateQueryString(string $key, ?string $value) : string + { + // Query string separators ("=", "&") and literal plus signs ("+") within the + // key or value need to be encoded + // (while preventing double-encoding) before setting the query string. All other + // chars that need percent-encoding will be encoded by withQuery(). + $queryString = \strtr($key, self::QUERY_SEPARATORS_REPLACEMENT); + if ($value !== null) { + $queryString .= '=' . \strtr($value, self::QUERY_SEPARATORS_REPLACEMENT); + } + return $queryString; + } + private function removeDefaultPort() : void + { + if ($this->port !== null && self::isDefaultPort($this)) { + $this->port = null; + } + } + /** + * Filters the path of a URI + * + * @param mixed $path + * + * @throws \InvalidArgumentException If the path is invalid. + */ + private function filterPath($path) : string + { + if (!\is_string($path)) { + throw new \InvalidArgumentException('Path must be a string'); + } + return \preg_replace_callback('/(?:[^' . Rfc3986::CHAR_UNRESERVED . Rfc3986::CHAR_SUB_DELIMS . '%:@\\/]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $path); + } + /** + * Filters the query string or fragment of a URI. + * + * @param mixed $str + * + * @throws \InvalidArgumentException If the query or fragment is invalid. + */ + private function filterQueryAndFragment($str) : string + { + if (!\is_string($str)) { + throw new \InvalidArgumentException('Query and fragment must be a string'); + } + return \preg_replace_callback('/(?:[^' . Rfc3986::CHAR_UNRESERVED . Rfc3986::CHAR_SUB_DELIMS . '%:@\\/\\?]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $str); + } + private function rawurlencodeMatchZero(array $match) : string + { + return \rawurlencode($match[0]); + } + private function validateState() : void + { + if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) { + $this->host = self::HTTP_DEFAULT_HOST; + } + if ($this->getAuthority() === '') { + if (0 === \strpos($this->path, '//')) { + throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"'); + } + if ($this->scheme === '' && \false !== \strpos(\explode('/', $this->path, 2)[0], ':')) { + throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon'); + } + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriComparator.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriComparator.php new file mode 100644 index 00000000..067f3476 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriComparator.php @@ -0,0 +1,49 @@ +getHost(), $modified->getHost()) !== 0) { + return \true; + } + if ($original->getScheme() !== $modified->getScheme()) { + return \true; + } + if (self::computePort($original) !== self::computePort($modified)) { + return \true; + } + return \false; + } + private static function computePort(UriInterface $uri) : ?int + { + $port = $uri->getPort(); + if (null !== $port) { + return $port; + } + if ('http' === $uri->getScheme()) { + return 80; + } + if ('https' === $uri->getScheme()) { + return 443; + } + return null; + } + private function __construct() + { + // cannot be instantiated + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriNormalizer.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriNormalizer.php new file mode 100644 index 00000000..052401b4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriNormalizer.php @@ -0,0 +1,186 @@ +getPath() === '' && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')) { + $uri = $uri->withPath('/'); + } + if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') { + $uri = $uri->withHost(''); + } + if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) { + $uri = $uri->withPort(null); + } + if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) { + $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath())); + } + if ($flags & self::REMOVE_DUPLICATE_SLASHES) { + $uri = $uri->withPath(\preg_replace('#//++#', '/', $uri->getPath())); + } + if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') { + $queryKeyValues = \explode('&', $uri->getQuery()); + \sort($queryKeyValues); + $uri = $uri->withQuery(\implode('&', $queryKeyValues)); + } + return $uri; + } + /** + * Whether two URIs can be considered equivalent. + * + * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also + * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be + * resolved against the same base URI. If this is not the case, determination of equivalence or difference of + * relative references does not mean anything. + * + * @param UriInterface $uri1 An URI to compare + * @param UriInterface $uri2 An URI to compare + * @param int $normalizations A bitmask of normalizations to apply, see constants + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1 + */ + public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS) : bool + { + return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations); + } + private static function capitalizePercentEncoding(UriInterface $uri) : UriInterface + { + $regex = '/(?:%[A-Fa-f0-9]{2})++/'; + $callback = function (array $match) : string { + return \strtoupper($match[0]); + }; + return $uri->withPath(self::normalizePercentEncodingInComponent($uri->getPath(), $regex, $callback))->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback))->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback)); + } + private static function decodeUnreservedCharacters(UriInterface $uri) : UriInterface + { + $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i'; + $callback = function (array $match) : string { + return \rawurldecode($match[0]); + }; + return $uri->withPath(self::normalizePercentEncodingInComponent($uri->getPath(), $regex, $callback))->withQuery(self::normalizePercentEncodingInComponent($uri->getQuery(), $regex, $callback))->withFragment(self::normalizePercentEncodingInComponent($uri->getFragment(), $regex, $callback)); + } + /** + * @param callable(array): string $callback + */ + private static function normalizePercentEncodingInComponent(string $component, string $regex, callable $callback) : string + { + $normalized = \preg_replace_callback($regex, $callback, $component); + if ($normalized === null) { + throw new \RuntimeException('Unable to normalize URI component percent-encoding'); + } + return $normalized; + } + private function __construct() + { + // cannot be instantiated + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriResolver.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriResolver.php new file mode 100644 index 00000000..67585796 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/UriResolver.php @@ -0,0 +1,176 @@ +getScheme() != '') { + return $rel->withPath(self::removeDotSegments($rel->getPath())); + } + if ($rel->getAuthority() != '') { + return $rel->withScheme($base->getScheme())->withPath(self::removeDotSegments($rel->getPath())); + } + if ($rel->getPath() === '') { + $targetPath = $base->getPath(); + $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery(); + } else { + if ($rel->getPath()[0] === '/') { + $targetPath = $rel->getPath(); + } else { + if ($base->getAuthority() != '' && $base->getPath() === '') { + $targetPath = '/' . $rel->getPath(); + } else { + $lastSlashPos = \strrpos($base->getPath(), '/'); + if ($lastSlashPos === \false) { + $targetPath = $rel->getPath(); + } else { + $targetPath = \substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath(); + } + } + } + $targetPath = self::removeDotSegments($targetPath); + $targetQuery = $rel->getQuery(); + } + return $base->withPath($targetPath)->withQuery($targetQuery)->withFragment($rel->getFragment()); + } + /** + * Returns the target URI as a relative reference from the base URI. + * + * This method is the counterpart to resolve(): + * + * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target)) + * + * One use-case is to use the current request URI as base URI and then generate relative links in your documents + * to reduce the document size or offer self-contained downloadable document archives. + * + * $base = new Uri('http://example.com/a/b/'); + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'. + * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'. + * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'. + * + * This method also accepts a target that is already relative and will try to relativize it further. Only a + * relative-path reference will be returned as-is. + * + * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well + */ + public static function relativize(UriInterface $base, UriInterface $target) : UriInterface + { + if ($target->getScheme() !== '' && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')) { + return $target; + } + if (Uri::isRelativePathReference($target)) { + // As the target is already highly relative we return it as-is. It would be possible to resolve + // the target with `$target = self::resolve($base, $target);` and then try make it more relative + // by removing a duplicate query. But let's not do that automatically. + return $target; + } + if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) { + return $target->withScheme(''); + } + // We must remove the path before removing the authority because if the path starts with two slashes, the URI + // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also + // invalid. + $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost(''); + if ($base->getPath() !== $target->getPath()) { + return $emptyPathUri->withPath(self::getRelativePath($base, $target)); + } + if ($base->getQuery() === $target->getQuery()) { + // Only the target fragment is left. And it must be returned even if base and target fragment are the same. + return $emptyPathUri->withQuery(''); + } + // If the base URI has a query but the target has none, we cannot return an empty path reference as it would + // inherit the base query component when resolving. + if ($target->getQuery() === '') { + $segments = \explode('/', $target->getPath()); + /** @var string $lastSegment */ + $lastSegment = \end($segments); + return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment); + } + return $emptyPathUri; + } + private static function getRelativePath(UriInterface $base, UriInterface $target) : string + { + $sourceSegments = \explode('/', $base->getPath()); + $targetSegments = \explode('/', $target->getPath()); + \array_pop($sourceSegments); + $targetLastSegment = \array_pop($targetSegments); + foreach ($sourceSegments as $i => $segment) { + if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) { + unset($sourceSegments[$i], $targetSegments[$i]); + } else { + break; + } + } + $targetSegments[] = $targetLastSegment; + $relativePath = \str_repeat('../', \count($sourceSegments)) . \implode('/', $targetSegments); + // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name. + if ('' === $relativePath || \false !== \strpos(\explode('/', $relativePath, 2)[0], ':')) { + $relativePath = "./{$relativePath}"; + } elseif ('/' === $relativePath[0]) { + if ($base->getAuthority() != '' && $base->getPath() === '') { + // In this case an extra slash is added by resolve() automatically. So we must not add one here. + $relativePath = ".{$relativePath}"; + } else { + $relativePath = "./{$relativePath}"; + } + } + return $relativePath; + } + private function __construct() + { + // cannot be instantiated + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Utils.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Utils.php new file mode 100644 index 00000000..da5436c0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/guzzlehttp/psr7/src/Utils.php @@ -0,0 +1,544 @@ + $v) { + if (!\in_array(\strtolower((string) $k), $keys)) { + $result[$k] = $v; + } + } + return $result; + } + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * The copy stops if the destination write returns 0, for example a + * BufferStream at its high water mark or a full DroppingStream. For a + * guaranteed full copy use a normal writable stream such as a file or + * php://temp stream. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1) : void + { + $bufferSize = 8192; + if ($maxLen === -1) { + while (!$source->eof()) { + $buf = $source->read($bufferSize); + if ($buf === '') { + break; + } + if (!self::writeAll($dest, $buf)) { + break; + } + } + } else { + $remaining = $maxLen; + while ($remaining > 0 && !$source->eof()) { + $buf = $source->read(\min($bufferSize, $remaining)); + $len = \strlen($buf); + if (!$len) { + break; + } + $remaining -= $len; + if (!self::writeAll($dest, $buf)) { + break; + } + } + } + } + /** + * Writes the full buffer to the destination, retrying short writes. + * + * Returns false when the destination write returns 0 or less. + */ + private static function writeAll(StreamInterface $dest, string $buf) : bool + { + $written = 0; + $len = \strlen($buf); + while ($written < $len) { + $result = $dest->write(\substr($buf, $written)); + if ($result <= 0) { + return \false; + } + $written += $result; + } + return \true; + } + /** + * Copy the contents of a stream into a string until the given number of + * bytes have been read. + * + * @param StreamInterface $stream Stream to read + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + * + * @throws \RuntimeException on error. + */ + public static function copyToString(StreamInterface $stream, int $maxLen = -1) : string + { + $buffer = ''; + if ($maxLen === -1) { + while (!$stream->eof()) { + $buf = $stream->read(1048576); + if ($buf === '') { + break; + } + $buffer .= $buf; + } + return $buffer; + } + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === '') { + break; + } + $buffer .= $buf; + $len = \strlen($buffer); + } + return $buffer; + } + /** + * Calculate a hash of a stream. + * + * This method reads the entire stream to calculate a rolling hash, based + * on PHP's `hash_init` functions. + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @throws \RuntimeException on error. + */ + public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = \false) : string + { + $pos = $stream->tell(); + if ($pos > 0) { + $stream->rewind(); + } + $ctx = \hash_init($algo); + while (!$stream->eof()) { + \hash_update($ctx, $stream->read(1048576)); + } + $out = \hash_final($ctx, $rawOutput); + $stream->seek($pos); + return $out; + } + /** + * Clone and modify a request with the given changes. + * + * This method is useful for reducing the number of clones needed to mutate + * a message. + * + * The changes can be one of: + * - method: (string) Changes the HTTP method. + * - set_headers: (array) Sets the given headers. Values must be strings + * or non-empty arrays of strings. + * - remove_headers: (array) Remove the given headers. Values may be + * strings or integers. + * - body: (mixed) Sets the given body. Present non-null values are converted + * with self::streamFor(), including scalar values, resources, streams, + * iterators, callable arrays, closures, invokable objects, and objects + * with __toString(). String inputs remain literal bodies. + * - uri: (UriInterface) Set the URI. + * - query: (string) Set the query string value of the URI. + * - version: (string) Set the protocol version. + * + * @param RequestInterface $request Request to clone and modify. + * @param array $changes Changes to apply. + */ + public static function modifyRequest(RequestInterface $request, array $changes) : RequestInterface + { + if (!$changes) { + return $request; + } + self::warnOnInvalidModifyRequestChanges($changes); + $headers = $request->getHeaders(); + if (!isset($changes['uri'])) { + $uri = $request->getUri(); + } else { + // Remove the host header if one is on the URI + $host = $changes['uri']->getHost(); + if ($host !== '') { + if (isset($changes['set_headers']) && \is_array($changes['set_headers'])) { + foreach (\array_keys($changes['set_headers']) as $header) { + if (\strtolower((string) $header) === 'host') { + throw new \InvalidArgumentException('Cannot modify request with both a URI containing a host and an explicit Host header.'); + } + } + } + $changes['set_headers']['Host'] = $host; + if ($port = $changes['uri']->getPort()) { + $standardPorts = ['http' => 80, 'https' => 443]; + $scheme = $changes['uri']->getScheme(); + if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) { + $changes['set_headers']['Host'] .= ':' . $port; + } + } + } + $uri = $changes['uri']; + } + if (!empty($changes['remove_headers'])) { + $headers = self::caselessRemove($changes['remove_headers'], $headers); + } + if (!empty($changes['set_headers'])) { + $headers = self::caselessRemove(\array_keys($changes['set_headers']), $headers); + $headers = $changes['set_headers'] + $headers; + } + if (isset($changes['query'])) { + $uri = $uri->withQuery($changes['query']); + } + $hasHost = \false; + foreach (\array_keys($headers) as $header) { + if (\strtolower((string) $header) === 'host') { + $hasHost = \true; + break; + } + } + // Match Request::__construct() by adding a Host header when one is not provided. + if (!$hasHost && $uri->getHost() !== '') { + $host = $uri->getHost(); + if (($port = $uri->getPort()) !== null) { + $host .= ':' . $port; + } + $headers = ['Host' => [$host]] + $headers; + } + $new = $request; + if (isset($changes['method'])) { + $new = $new->withMethod($changes['method']); + } + if (isset($changes['uri']) || isset($changes['query'])) { + $new = $new->withUri($uri, \true); + } + if ($headers !== $new->getHeaders()) { + foreach (\array_keys($new->getHeaders()) as $header) { + /** @var RequestInterface */ + $new = $new->withoutHeader((string) $header); + } + $addedHeaders = []; + foreach ($headers as $header => $value) { + $header = (string) $header; + $normalized = \strtolower($header); + if (isset($addedHeaders[$normalized])) { + /** @var RequestInterface */ + $new = $new->withAddedHeader($addedHeaders[$normalized], $value); + } else { + /** @var RequestInterface */ + $new = $new->withHeader($header, $value); + $addedHeaders[$normalized] = $header; + } + } + } + if (isset($changes['body'])) { + /** @var RequestInterface */ + $new = $new->withBody(self::streamFor($changes['body'])); + } + if (isset($changes['version'])) { + /** @var RequestInterface */ + $new = $new->withProtocolVersion($changes['version']); + } + return $new; + } + /** + * @param array $changes + */ + private static function warnOnInvalidModifyRequestChanges(array $changes) : void + { + foreach (['method', 'query', 'version'] as $key) { + if (\array_key_exists($key, $changes) && !\is_string($changes[$key])) { + self::warnOnInvalidModifyRequestChange($key, 'string', $changes[$key]); + } + } + if (\array_key_exists('uri', $changes) && !$changes['uri'] instanceof UriInterface) { + self::warnOnInvalidModifyRequestChange('uri', 'UriInterface', $changes['uri']); + } + if (\array_key_exists('body', $changes) && $changes['body'] === null) { + self::warnOnInvalidModifyRequestChange('body', 'resource|string|int|float|bool|StreamInterface|callable|\\Iterator|\\Stringable', $changes['body']); + } + if (\array_key_exists('set_headers', $changes)) { + if (!\is_array($changes['set_headers'])) { + self::warnOnInvalidModifyRequestChange('set_headers', 'array>', $changes['set_headers']); + } else { + foreach ($changes['set_headers'] as $header => $value) { + $headerPath = \sprintf('set_headers.%s', (string) $header); + if (\is_array($value)) { + if ($value === []) { + self::warnOnInvalidModifyRequestChange($headerPath, 'string|non-empty-array', $value); + break; + } + foreach ($value as $index => $item) { + if (!\is_string($item)) { + self::warnOnInvalidModifyRequestChange(\sprintf('%s.%s', $headerPath, (string) $index), 'string', $item); + break 2; + } + } + } elseif (!\is_string($value)) { + self::warnOnInvalidModifyRequestChange($headerPath, 'string|non-empty-array', $value); + break; + } + } + } + } + if (!\array_key_exists('remove_headers', $changes)) { + return; + } + if (!\is_array($changes['remove_headers'])) { + self::warnOnInvalidModifyRequestChange('remove_headers', 'array', $changes['remove_headers']); + return; + } + foreach ($changes['remove_headers'] as $index => $header) { + if (!\is_string($header) && !\is_int($header)) { + self::warnOnInvalidModifyRequestChange(\sprintf('remove_headers.%s', (string) $index), 'string|int', $header); + return; + } + } + } + /** + * @param mixed $value + */ + private static function warnOnInvalidModifyRequestChange(string $key, string $expected, $value) : void + { + \trigger_deprecation('guzzlehttp/psr7', '2.11', 'Passing %s to Utils::modifyRequest() change "%s" is deprecated; guzzlehttp/psr7 3.0 requires %s.', \get_debug_type($value), $key, $expected); + } + /** + * Read a line from the stream up to the maximum allowed buffer length. + * + * @param StreamInterface $stream Stream to read from + * @param int|null $maxLength Maximum buffer length + */ + public static function readLine(StreamInterface $stream, ?int $maxLength = null) : string + { + $buffer = ''; + $size = 0; + while (!$stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte === "\n" || ++$size === $maxLength - 1) { + break; + } + } + return $buffer; + } + /** + * Redact the password in the user info part of a URI. + */ + public static function redactUserInfo(UriInterface $uri) : UriInterface + { + $userInfo = $uri->getUserInfo(); + if (\false !== ($pos = \strpos($userInfo, ':'))) { + return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***'); + } + return $uri; + } + /** + * Create a new stream based on the input type. + * + * Options is an associative array that can contain the following keys: + * - metadata: Array of custom metadata. + * - size: Size of the stream. + * + * This method accepts the following `$resource` types: + * - `Psr\Http\Message\StreamInterface`: Returns the value as-is. + * - `string`: Creates a stream object that uses the given string as the contents. + * - `resource`: Creates a stream object that wraps the given PHP stream resource. + * - `Iterator`: If the provided value implements `Iterator`, then a read-only + * stream object will be created that wraps the given iterable. Each time the + * stream is read from, data from the iterator will fill a buffer and will be + * continuously called until the buffer is equal to the requested read size. + * Subsequent read calls will first read from the buffer and then call `next` + * on the underlying iterator until it is exhausted. + * - `object` with `__toString()`: If the object has the `__toString()` method, + * the object will be cast to a string and then a stream will be returned that + * uses the string value. + * - `NULL`: When `null` is passed, an empty stream object is returned. + * - `callable`: When a callable array, closure, or invokable object is passed + * and no earlier resource or object rule applies, a read-only stream object + * will be created that invokes the given callable. The callable is invoked + * with the suggested number of bytes to read. The callable can return fewer + * or more bytes than requested, but MUST return `false` or `null` when there + * is no more data to return. Any additional bytes will be buffered and used + * in subsequent reads. String inputs are always treated as string bodies, + * even when they name callable functions. + * + * Passing a non-string scalar (`int`, `float`, or `bool`) is deprecated; cast + * it to a string instead. guzzlehttp/psr7 3.0 will reject non-string scalars. + * + * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data + * @param array{size?: int, metadata?: array} $options Additional options + * + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function streamFor($resource = '', array $options = []) : StreamInterface + { + if (\is_scalar($resource)) { + if (!\is_string($resource)) { + \trigger_deprecation('guzzlehttp/psr7', '2.12', 'Passing %s to Utils::streamFor() is deprecated; cast it to a string. guzzlehttp/psr7 3.0 will only accept string, resource, StreamInterface, Stringable, Iterator, callable, or null.', \gettype($resource)); + if (\is_float($resource) && !\is_finite($resource)) { + // Normalized only to avoid PHP 8.5's (string) NAN warning + // while deprecated; 3.0 rejects non-finite floats with every + // other non-string scalar. + $resource = \is_nan($resource) ? 'NAN' : ($resource > 0 ? 'INF' : '-INF'); + } + } + $stream = self::tryFopen('php://temp', 'r+'); + if ($resource !== '') { + \fwrite($stream, (string) $resource); + \fseek($stream, 0); + } + return new Stream($stream, $options); + } + switch (\gettype($resource)) { + case 'resource': + /* + * The 'php://input' is a special stream with quirks and inconsistencies. + * We avoid using that stream by reading it into php://temp + */ + /** @var resource $resource */ + if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') { + $stream = self::tryFopen('php://temp', 'w+'); + \stream_copy_to_stream($resource, $stream); + \fseek($stream, 0); + $resource = $stream; + } + return new Stream($resource, $options); + case 'object': + /** @var object $resource */ + if ($resource instanceof StreamInterface) { + return $resource; + } elseif ($resource instanceof \Iterator) { + return new PumpStream(function () use($resource) { + if (!$resource->valid()) { + return \false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } elseif (\method_exists($resource, '__toString')) { + return self::streamFor((string) $resource, $options); + } + break; + case 'NULL': + return new Stream(self::tryFopen('php://temp', 'r+'), $options); + } + if (\is_callable($resource)) { + return new PumpStream($resource, $options); + } + throw new \InvalidArgumentException('Invalid resource type: ' . \gettype($resource)); + } + /** + * Safely opens a PHP stream resource using a filename. + * + * When fopen fails, PHP normally raises a warning. This function adds an + * error handler that checks for errors and throws an exception instead. + * + * @param string $filename File to open + * @param string $mode Mode used to open the file + * + * @return resource + * + * @throws \RuntimeException if the file cannot be opened + */ + public static function tryFopen(string $filename, string $mode) + { + $ex = null; + \set_error_handler(static function (int $errno, string $errstr) use($filename, $mode, &$ex) : bool { + $ex = new \RuntimeException(\sprintf('Unable to open "%s" using mode "%s": %s', $filename, $mode, $errstr)); + return \true; + }); + try { + /** @var resource $handle */ + $handle = \fopen($filename, $mode); + } catch (\Throwable $e) { + $ex = new \RuntimeException(\sprintf('Unable to open "%s" using mode "%s": %s', $filename, $mode, $e->getMessage()), 0, $e); + } + \restore_error_handler(); + if ($ex) { + /** @var \RuntimeException $ex */ + throw $ex; + } + return $handle; + } + /** + * Safely gets the contents of a given stream. + * + * When stream_get_contents fails, PHP normally raises a warning. This + * function adds an error handler that checks for errors and throws an + * exception instead. + * + * @param resource $stream + * + * @throws \RuntimeException if the stream cannot be read + */ + public static function tryGetContents($stream) : string + { + $ex = null; + \set_error_handler(static function (int $errno, string $errstr) use(&$ex) : bool { + $ex = new \RuntimeException(\sprintf('Unable to read stream contents: %s', $errstr)); + return \true; + }); + try { + /** @var string|false $contents */ + $contents = \stream_get_contents($stream); + if ($contents === \false) { + $ex = new \RuntimeException('Unable to read stream contents'); + } + } catch (\Throwable $e) { + $ex = new \RuntimeException(\sprintf('Unable to read stream contents: %s', $e->getMessage()), 0, $e); + } + \restore_error_handler(); + if ($ex) { + /** @var \RuntimeException $ex */ + throw $ex; + } + return $contents; + } + /** + * Returns a UriInterface for the given value. + * + * This function accepts a string or UriInterface and returns a + * UriInterface for the given value. If the value is already a + * UriInterface, it is returned as-is. + * + * @param string|UriInterface $uri + * + * @throws \InvalidArgumentException + */ + public static function uriFor($uri) : UriInterface + { + if ($uri instanceof UriInterface) { + return $uri; + } + if (\is_string($uri)) { + return new Uri($uri); + } + throw new \InvalidArgumentException('URI must be a string or UriInterface'); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/LICENSE new file mode 100644 index 00000000..9c8958af --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2023 Alex Bilbie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AbstractGrant.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AbstractGrant.php new file mode 100644 index 00000000..4159ad0c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AbstractGrant.php @@ -0,0 +1,72 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant; + +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\RequiredParameterTrait; +/** + * Represents a type of authorization grant. + * + * An authorization grant is a credential representing the resource + * owner's authorization (to access its protected resources) used by the + * client to obtain an access token. OAuth 2.0 defines four + * grant types -- authorization code, implicit, resource owner password + * credentials, and client credentials -- as well as an extensibility + * mechanism for defining additional types. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3 Authorization Grant (RFC 6749, §1.3) + */ +abstract class AbstractGrant +{ + use RequiredParameterTrait; + /** + * Returns the name of this grant, eg. 'grant_name', which is used as the + * grant type when encoding URL query parameters. + * + * @return string + */ + protected abstract function getName(); + /** + * Returns a list of all required request parameters. + * + * @return array + */ + protected abstract function getRequiredRequestParameters(); + /** + * Returns this grant's name as its string representation. This allows for + * string interpolation when building URL query parameters. + * + * @return string + */ + public function __toString() + { + return $this->getName(); + } + /** + * Prepares an access token request's parameters by checking that all + * required parameters are set, then merging with any given defaults. + * + * @param array $defaults + * @param array $options + * @return array + */ + public function prepareRequestParameters(array $defaults, array $options) + { + $defaults['grant_type'] = $this->getName(); + $required = $this->getRequiredRequestParameters(); + $provided = \array_merge($defaults, $options); + $this->checkRequiredParameters($required, $provided); + return $provided; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AuthorizationCode.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AuthorizationCode.php new file mode 100644 index 00000000..6c5c1109 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/AuthorizationCode.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant; + +/** + * Represents an authorization code grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.1 Authorization Code (RFC 6749, §1.3.1) + */ +class AuthorizationCode extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'authorization_code'; + } + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return ['code']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/ClientCredentials.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/ClientCredentials.php new file mode 100644 index 00000000..a7823ba2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/ClientCredentials.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant; + +/** + * Represents a client credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.4 Client Credentials (RFC 6749, §1.3.4) + */ +class ClientCredentials extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'client_credentials'; + } + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return []; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php new file mode 100644 index 00000000..9235440e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Exception/InvalidGrantException.php @@ -0,0 +1,25 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant\Exception; + +use InvalidArgumentException; +/** + * Exception thrown if the grant does not extend from AbstractGrant. + * + * @see League\OAuth2\Client\Grant\AbstractGrant + */ +class InvalidGrantException extends InvalidArgumentException +{ +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/GrantFactory.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/GrantFactory.php new file mode 100644 index 00000000..ed331770 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/GrantFactory.php @@ -0,0 +1,91 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant; + +use WPMailSMTP\Vendor\League\OAuth2\Client\Grant\Exception\InvalidGrantException; +/** + * Represents a factory used when retrieving an authorization grant type. + */ +class GrantFactory +{ + /** + * @var array + */ + protected $registry = []; + /** + * Defines a grant singleton in the registry. + * + * @param string $name + * @param AbstractGrant $grant + * @return self + */ + public function setGrant($name, AbstractGrant $grant) + { + $this->registry[$name] = $grant; + return $this; + } + /** + * Returns a grant singleton by name. + * + * If the grant has not be registered, a default grant will be loaded. + * + * @param string $name + * @return AbstractGrant + */ + public function getGrant($name) + { + if (empty($this->registry[$name])) { + $this->registerDefaultGrant($name); + } + return $this->registry[$name]; + } + /** + * Registers a default grant singleton by name. + * + * @param string $name + * @return self + */ + protected function registerDefaultGrant($name) + { + // PascalCase the grant. E.g: 'authorization_code' becomes 'AuthorizationCode' + $class = \str_replace(' ', '', \ucwords(\str_replace(['-', '_'], ' ', $name))); + $class = 'WPMailSMTP\\Vendor\\League\\OAuth2\\Client\\Grant\\' . $class; + $this->checkGrant($class); + return $this->setGrant($name, new $class()); + } + /** + * Determines if a variable is a valid grant. + * + * @param mixed $class + * @return boolean + */ + public function isGrant($class) + { + return \is_subclass_of($class, AbstractGrant::class); + } + /** + * Checks if a variable is a valid grant. + * + * @throws InvalidGrantException + * @param mixed $class + * @return void + */ + public function checkGrant($class) + { + if (!$this->isGrant($class)) { + throw new InvalidGrantException(\sprintf('Grant "%s" must extend AbstractGrant', \is_object($class) ? \get_class($class) : $class)); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Password.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Password.php new file mode 100644 index 00000000..bc882953 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/Password.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant; + +/** + * Represents a resource owner password credentials grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.3.3 Resource Owner Password Credentials (RFC 6749, §1.3.3) + */ +class Password extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'password'; + } + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return ['username', 'password']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/RefreshToken.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/RefreshToken.php new file mode 100644 index 00000000..65f33e41 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Grant/RefreshToken.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Grant; + +/** + * Represents a refresh token grant. + * + * @link http://tools.ietf.org/html/rfc6749#section-6 Refreshing an Access Token (RFC 6749, §6) + */ +class RefreshToken extends AbstractGrant +{ + /** + * @inheritdoc + */ + protected function getName() + { + return 'refresh_token'; + } + /** + * @inheritdoc + */ + protected function getRequiredRequestParameters() + { + return ['refresh_token']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php new file mode 100644 index 00000000..603c4a64 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/HttpBasicAuthOptionProvider.php @@ -0,0 +1,38 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\OptionProvider; + +use InvalidArgumentException; +/** + * Add http basic auth into access token request options + * @link https://tools.ietf.org/html/rfc6749#section-2.3.1 + */ +class HttpBasicAuthOptionProvider extends PostAuthOptionProvider +{ + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + if (empty($params['client_id']) || empty($params['client_secret'])) { + throw new InvalidArgumentException('clientId and clientSecret are required for http basic auth'); + } + $encodedCredentials = \base64_encode(\sprintf('%s:%s', $params['client_id'], $params['client_secret'])); + unset($params['client_id'], $params['client_secret']); + $options = parent::getAccessTokenOptions($method, $params); + $options['headers']['Authorization'] = 'Basic ' . $encodedCredentials; + return $options; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php new file mode 100644 index 00000000..7bb48d80 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/OptionProviderInterface.php @@ -0,0 +1,30 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\OptionProvider; + +/** + * Interface for access token options provider + */ +interface OptionProviderInterface +{ + /** + * Builds request options used for requesting an access token. + * + * @param string $method + * @param array $params + * @return array + */ + public function getAccessTokenOptions($method, array $params); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php new file mode 100644 index 00000000..b0bb47f0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/OptionProvider/PostAuthOptionProvider.php @@ -0,0 +1,46 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\OptionProvider; + +use WPMailSMTP\Vendor\League\OAuth2\Client\Provider\AbstractProvider; +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\QueryBuilderTrait; +/** + * Provide options for access token + */ +class PostAuthOptionProvider implements OptionProviderInterface +{ + use QueryBuilderTrait; + /** + * @inheritdoc + */ + public function getAccessTokenOptions($method, array $params) + { + $options = ['headers' => ['content-type' => 'application/x-www-form-urlencoded']]; + if ($method === AbstractProvider::METHOD_POST) { + $options['body'] = $this->getAccessTokenBody($params); + } + return $options; + } + /** + * Returns the request body for requesting an access token. + * + * @param array $params + * @return string + */ + protected function getAccessTokenBody(array $params) + { + return $this->buildQueryString($params); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/AbstractProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/AbstractProvider.php new file mode 100644 index 00000000..3151d2c4 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/AbstractProvider.php @@ -0,0 +1,802 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Provider; + +use WPMailSMTP\Vendor\GuzzleHttp\Client as HttpClient; +use WPMailSMTP\Vendor\GuzzleHttp\ClientInterface as HttpClientInterface; +use WPMailSMTP\Vendor\GuzzleHttp\Exception\BadResponseException; +use WPMailSMTP\Vendor\GuzzleHttp\Exception\GuzzleException; +use InvalidArgumentException; +use WPMailSMTP\Vendor\League\OAuth2\Client\Grant\AbstractGrant; +use WPMailSMTP\Vendor\League\OAuth2\Client\Grant\GrantFactory; +use WPMailSMTP\Vendor\League\OAuth2\Client\OptionProvider\OptionProviderInterface; +use WPMailSMTP\Vendor\League\OAuth2\Client\OptionProvider\PostAuthOptionProvider; +use WPMailSMTP\Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessToken; +use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessTokenInterface; +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\ArrayAccessorTrait; +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\GuardedPropertyTrait; +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\QueryBuilderTrait; +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\RequestFactory; +use WPMailSMTP\Vendor\Psr\Http\Message\RequestInterface; +use WPMailSMTP\Vendor\Psr\Http\Message\ResponseInterface; +use UnexpectedValueException; +/** + * Represents a service provider (authorization server). + * + * @link http://tools.ietf.org/html/rfc6749#section-1.1 Roles (RFC 6749, §1.1) + */ +abstract class AbstractProvider +{ + use ArrayAccessorTrait; + use GuardedPropertyTrait; + use QueryBuilderTrait; + /** + * @var string|null Key used in a token response to identify the resource owner. + */ + const ACCESS_TOKEN_RESOURCE_OWNER_ID = null; + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_GET = 'GET'; + /** + * @var string HTTP method used to fetch access tokens. + */ + const METHOD_POST = 'POST'; + /** + * @var string PKCE method used to fetch authorization token. + * The PKCE code challenge will be hashed with sha256 (recommended). + */ + const PKCE_METHOD_S256 = 'S256'; + /** + * @var string PKCE method used to fetch authorization token. + * The PKCE code challenge will be sent as plain text, this is NOT recommended. + * Only use `plain` if no other option is possible. + */ + const PKCE_METHOD_PLAIN = 'plain'; + /** + * @var string + */ + protected $clientId; + /** + * @var string + */ + protected $clientSecret; + /** + * @var string + */ + protected $redirectUri; + /** + * @var string + */ + protected $state; + /** + * @var string|null + */ + protected $pkceCode = null; + /** + * @var GrantFactory + */ + protected $grantFactory; + /** + * @var RequestFactory + */ + protected $requestFactory; + /** + * @var HttpClientInterface + */ + protected $httpClient; + /** + * @var OptionProviderInterface + */ + protected $optionProvider; + /** + * Constructs an OAuth 2.0 service provider. + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @param array $collaborators An array of collaborators that may be used to + * override this provider's default behavior. Collaborators include + * `grantFactory`, `requestFactory`, and `httpClient`. + * Individual providers may introduce more collaborators, as needed. + */ + public function __construct(array $options = [], array $collaborators = []) + { + // We'll let the GuardedPropertyTrait handle mass assignment of incoming + // options, skipping any blacklisted properties defined in the provider + $this->fillProperties($options); + if (empty($collaborators['grantFactory'])) { + $collaborators['grantFactory'] = new GrantFactory(); + } + $this->setGrantFactory($collaborators['grantFactory']); + if (empty($collaborators['requestFactory'])) { + $collaborators['requestFactory'] = new RequestFactory(); + } + $this->setRequestFactory($collaborators['requestFactory']); + if (empty($collaborators['httpClient'])) { + $client_options = $this->getAllowedClientOptions($options); + $collaborators['httpClient'] = new HttpClient(\array_intersect_key($options, \array_flip($client_options))); + } + $this->setHttpClient($collaborators['httpClient']); + if (empty($collaborators['optionProvider'])) { + $collaborators['optionProvider'] = new PostAuthOptionProvider(); + } + $this->setOptionProvider($collaborators['optionProvider']); + } + /** + * Returns the list of options that can be passed to the HttpClient + * + * @param array $options An array of options to set on this provider. + * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`. + * Individual providers may introduce more options, as needed. + * @return array The options to pass to the HttpClient constructor + */ + protected function getAllowedClientOptions(array $options) + { + $client_options = ['timeout', 'proxy']; + // Only allow turning off ssl verification if it's for a proxy + if (!empty($options['proxy'])) { + $client_options[] = 'verify'; + } + return $client_options; + } + /** + * Sets the grant factory instance. + * + * @param GrantFactory $factory + * @return self + */ + public function setGrantFactory(GrantFactory $factory) + { + $this->grantFactory = $factory; + return $this; + } + /** + * Returns the current grant factory instance. + * + * @return GrantFactory + */ + public function getGrantFactory() + { + return $this->grantFactory; + } + /** + * Sets the request factory instance. + * + * @param RequestFactory $factory + * @return self + */ + public function setRequestFactory(RequestFactory $factory) + { + $this->requestFactory = $factory; + return $this; + } + /** + * Returns the request factory instance. + * + * @return RequestFactory + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + /** + * Sets the HTTP client instance. + * + * @param HttpClientInterface $client + * @return self + */ + public function setHttpClient(HttpClientInterface $client) + { + $this->httpClient = $client; + return $this; + } + /** + * Returns the HTTP client instance. + * + * @return HttpClientInterface + */ + public function getHttpClient() + { + return $this->httpClient; + } + /** + * Sets the option provider instance. + * + * @param OptionProviderInterface $provider + * @return self + */ + public function setOptionProvider(OptionProviderInterface $provider) + { + $this->optionProvider = $provider; + return $this; + } + /** + * Returns the option provider instance. + * + * @return OptionProviderInterface + */ + public function getOptionProvider() + { + return $this->optionProvider; + } + /** + * Returns the current value of the state parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string + */ + public function getState() + { + return $this->state; + } + /** + * Set the value of the pkceCode parameter. + * + * When using PKCE this should be set before requesting an access token. + * + * @param string $pkceCode + * @return self + */ + public function setPkceCode($pkceCode) + { + $this->pkceCode = $pkceCode; + return $this; + } + /** + * Returns the current value of the pkceCode parameter. + * + * This can be accessed by the redirect handler during authorization. + * + * @return string|null + */ + public function getPkceCode() + { + return $this->pkceCode; + } + /** + * Returns the base URL for authorizing a client. + * + * Eg. https://oauth.service.com/authorize + * + * @return string + */ + public abstract function getBaseAuthorizationUrl(); + /** + * Returns the base URL for requesting an access token. + * + * Eg. https://oauth.service.com/token + * + * @param array $params + * @return string + */ + public abstract function getBaseAccessTokenUrl(array $params); + /** + * Returns the URL for requesting the resource owner's details. + * + * @param AccessToken $token + * @return string + */ + public abstract function getResourceOwnerDetailsUrl(AccessToken $token); + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomState($length = 32) + { + // Converting bytes to hex will always double length. Hence, we can reduce + // the amount of bytes by half to produce the correct length. + return \bin2hex(\random_bytes($length / 2)); + } + /** + * Returns a new random string to use as PKCE code_verifier and + * hashed as code_challenge parameters in an authorization flow. + * Must be between 43 and 128 characters long. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected function getRandomPkceCode($length = 64) + { + return \substr(\strtr(\base64_encode(\random_bytes($length)), '+/', '-_'), 0, $length); + } + /** + * 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. + * + * @return array + */ + protected abstract function getDefaultScopes(); + /** + * Returns the string that should be used to separate scopes when building + * the URL for requesting an access token. + * + * @return string Scope separator, defaults to ',' + */ + protected function getScopeSeparator() + { + return ','; + } + /** + * @return string|null + */ + protected function getPkceMethod() + { + return null; + } + /** + * Returns authorization parameters based on provided options. + * + * @param array $options + * @return array Authorization parameters + * @throws InvalidArgumentException + */ + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + $options += ['response_type' => 'code', 'approval_prompt' => 'auto']; + 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']; + $pkceMethod = $this->getPkceMethod(); + if (!empty($pkceMethod)) { + $this->pkceCode = $this->getRandomPkceCode(); + if ($pkceMethod === static::PKCE_METHOD_S256) { + $options['code_challenge'] = \trim(\strtr(\base64_encode(\hash('sha256', $this->pkceCode, \true)), '+/', '-_'), '='); + } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) { + $options['code_challenge'] = $this->pkceCode; + } else { + throw new InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".'); + } + $options['code_challenge_method'] = $pkceMethod; + } + // 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; + } + $options['client_id'] = $this->clientId; + return $options; + } + /** + * Builds the authorization URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAuthorizationQuery(array $params) + { + return $this->buildQueryString($params); + } + /** + * Builds the authorization URL. + * + * @param array $options + * @return string Authorization URL + * @throws InvalidArgumentException + */ + public function getAuthorizationUrl(array $options = []) + { + $base = $this->getBaseAuthorizationUrl(); + $params = $this->getAuthorizationParameters($options); + $query = $this->getAuthorizationQuery($params); + return $this->appendQuery($base, $query); + } + /** + * Redirects the client for authorization. + * + * @param array $options + * @param callable|null $redirectHandler + * @return mixed + * @throws InvalidArgumentException + */ + public function authorize(array $options = [], ?callable $redirectHandler = null) + { + $url = $this->getAuthorizationUrl($options); + if ($redirectHandler) { + return $redirectHandler($url, $this); + } + // @codeCoverageIgnoreStart + \header('Location: ' . $url); + exit; + // @codeCoverageIgnoreEnd + } + /** + * Appends a query string to a URL. + * + * @param string $url The URL to append the query to + * @param string $query The HTTP query string + * @return string The resulting URL + */ + protected function appendQuery($url, $query) + { + $query = \trim($query, '?&'); + if ($query) { + $glue = \strstr($url, '?') === \false ? '?' : '&'; + return $url . $glue . $query; + } + return $url; + } + /** + * Returns the method to use when requesting an access token. + * + * @return string HTTP method + */ + protected function getAccessTokenMethod() + { + return self::METHOD_POST; + } + /** + * Returns the key used in the access token response to identify the resource owner. + * + * @return string|null Resource owner identifier key + */ + protected function getAccessTokenResourceOwnerId() + { + return static::ACCESS_TOKEN_RESOURCE_OWNER_ID; + } + /** + * Builds the access token URL's query string. + * + * @param array $params Query parameters + * @return string Query string + */ + protected function getAccessTokenQuery(array $params) + { + return $this->buildQueryString($params); + } + /** + * Checks that a provided grant is valid, or attempts to produce one if the + * provided grant is a string. + * + * @param AbstractGrant|string $grant + * @return AbstractGrant + */ + protected function verifyGrant($grant) + { + if (\is_string($grant)) { + return $this->grantFactory->getGrant($grant); + } + $this->grantFactory->checkGrant($grant); + return $grant; + } + /** + * Returns the full URL to use when requesting an access token. + * + * @param array $params Query parameters + * @return string + */ + protected function getAccessTokenUrl(array $params) + { + $url = $this->getBaseAccessTokenUrl($params); + if ($this->getAccessTokenMethod() === self::METHOD_GET) { + $query = $this->getAccessTokenQuery($params); + return $this->appendQuery($url, $query); + } + return $url; + } + /** + * Returns a prepared request for requesting an access token. + * + * @param array $params Query string parameters + * @return RequestInterface + */ + protected function getAccessTokenRequest(array $params) + { + $method = $this->getAccessTokenMethod(); + $url = $this->getAccessTokenUrl($params); + $options = $this->optionProvider->getAccessTokenOptions($this->getAccessTokenMethod(), $params); + return $this->getRequest($method, $url, $options); + } + /** + * Requests an access token using a specified grant and option set. + * + * @param mixed $grant + * @param array $options + * @return AccessTokenInterface + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException + */ + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + if (isset($options['scope']) && \is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = \implode($separator, $options['scope']); + } + $params = ['client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'redirect_uri' => $this->redirectUri]; + if (!empty($this->pkceCode)) { + $params['code_verifier'] = $this->pkceCode; + } + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + if (\false === \is_array($response)) { + throw new UnexpectedValueException('Invalid response received from Authorization Server. Expected JSON.'); + } + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + return $token; + } + /** + * Returns a PSR-7 request instance that is not authenticated. + * + * @param string $method + * @param string $url + * @param array $options + * @return RequestInterface + */ + public function getRequest($method, $url, array $options = []) + { + return $this->createRequest($method, $url, null, $options); + } + /** + * Returns an authenticated PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options Any of "headers", "body", and "protocolVersion". + * @return RequestInterface + */ + public function getAuthenticatedRequest($method, $url, $token, array $options = []) + { + return $this->createRequest($method, $url, $token, $options); + } + /** + * Creates a PSR-7 request instance. + * + * @param string $method + * @param string $url + * @param AccessTokenInterface|string|null $token + * @param array $options + * @return RequestInterface + */ + protected function createRequest($method, $url, $token, array $options) + { + $defaults = ['headers' => $this->getHeaders($token)]; + $options = \array_merge_recursive($defaults, $options); + $factory = $this->getRequestFactory(); + return $factory->getRequestWithOptions($method, $url, $options); + } + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + * @throws GuzzleException + */ + public function getResponse(RequestInterface $request) + { + return $this->getHttpClient()->send($request); + } + /** + * Sends a request and returns the parsed response. + * + * @param RequestInterface $request + * @return mixed + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException + */ + public function getParsedResponse(RequestInterface $request) + { + try { + $response = $this->getResponse($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + $parsed = $this->parseResponse($response); + $this->checkResponse($response, $parsed); + return $parsed; + } + /** + * Attempts to parse a JSON response. + * + * @param string $content JSON content from response body + * @return array Parsed JSON data + * @throws UnexpectedValueException if the content could not be parsed + */ + protected function parseJson($content) + { + $content = \json_decode($content, \true); + if (\json_last_error() !== \JSON_ERROR_NONE) { + throw new UnexpectedValueException(\sprintf("Failed to parse JSON response: %s", \json_last_error_msg())); + } + return $content; + } + /** + * Returns the content type header of a response. + * + * @param ResponseInterface $response + * @return string Semi-colon separated join of content-type headers. + */ + protected function getContentType(ResponseInterface $response) + { + return \implode(';', $response->getHeader('content-type')); + } + /** + * Parses the response according to its content-type header. + * + * @throws UnexpectedValueException + * @param ResponseInterface $response + * @return array + */ + protected function parseResponse(ResponseInterface $response) + { + $content = (string) $response->getBody(); + $type = $this->getContentType($response); + if (\strpos($type, 'urlencoded') !== \false) { + \parse_str($content, $parsed); + return $parsed; + } + // Attempt to parse the string as JSON regardless of content type, + // since some providers use non-standard content types. Only throw an + // exception if the JSON could not be parsed when it was expected to. + try { + return $this->parseJson($content); + } catch (UnexpectedValueException $e) { + if (\strpos($type, 'json') !== \false) { + throw $e; + } + if ($response->getStatusCode() == 500) { + throw new UnexpectedValueException('An OAuth server error was encountered that did not contain a JSON body', 0, $e); + } + return $content; + } + } + /** + * Checks a provider response for errors. + * + * @throws IdentityProviderException + * @param ResponseInterface $response + * @param array|string $data Parsed response data + * @return void + */ + protected abstract function checkResponse(ResponseInterface $response, $data); + /** + * Prepares an parsed access token response for a grant. + * + * Custom mapping of expiration, etc should be done here. Always call the + * parent method when overloading this method. + * + * @param array $result + * @return array + */ + protected function prepareAccessTokenResponse(array $result) + { + if ($this->getAccessTokenResourceOwnerId() !== null) { + $result['resource_owner_id'] = $this->getValueByKey($result, $this->getAccessTokenResourceOwnerId()); + } + return $result; + } + /** + * Creates an access token from a response. + * + * The grant that was used to fetch the response can be used to provide + * additional context. + * + * @param array $response + * @param AbstractGrant $grant + * @return AccessTokenInterface + */ + protected function createAccessToken(array $response, AbstractGrant $grant) + { + return new AccessToken($response); + } + /** + * Generates a resource owner object from a successful resource owner + * details request. + * + * @param array $response + * @param AccessToken $token + * @return ResourceOwnerInterface + */ + protected abstract function createResourceOwner(array $response, AccessToken $token); + /** + * Requests and returns the resource owner of given access token. + * + * @param AccessToken $token + * @return ResourceOwnerInterface + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException + */ + public function getResourceOwner(AccessToken $token) + { + $response = $this->fetchResourceOwnerDetails($token); + return $this->createResourceOwner($response, $token); + } + /** + * Requests resource owner details. + * + * @param AccessToken $token + * @return mixed + * @throws IdentityProviderException + * @throws UnexpectedValueException + * @throws GuzzleException + */ + protected function fetchResourceOwnerDetails(AccessToken $token) + { + $url = $this->getResourceOwnerDetailsUrl($token); + $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token); + $response = $this->getParsedResponse($request); + if (\false === \is_array($response)) { + throw new UnexpectedValueException('Invalid response received from Authorization Server. Expected JSON.'); + } + return $response; + } + /** + * Returns the default headers used by this provider. + * + * Typically this is used to set 'Accept' or 'Content-Type' headers. + * + * @return array + */ + protected function getDefaultHeaders() + { + return []; + } + /** + * Returns the authorization headers used by this provider. + * + * Typically this is "Bearer" or "MAC". For more information see: + * http://tools.ietf.org/html/rfc6749#section-7.1 + * + * No default is provided, providers must overload this method to activate + * authorization headers. + * + * @param mixed|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return []; + } + /** + * Returns all headers used by this provider for a request. + * + * The request will be authenticated if an access token is provided. + * + * @param mixed|null $token object or string + * @return array + */ + public function getHeaders($token = null) + { + if ($token) { + return \array_merge($this->getDefaultHeaders(), $this->getAuthorizationHeaders($token)); + } + return $this->getDefaultHeaders(); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php new file mode 100644 index 00000000..16f15d09 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/Exception/IdentityProviderException.php @@ -0,0 +1,45 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Provider\Exception; + +/** + * Exception thrown if the provider response contains errors. + */ +class IdentityProviderException extends \Exception +{ + /** + * @var mixed + */ + protected $response; + /** + * @param string $message + * @param int $code + * @param mixed $response The response body + */ + public function __construct($message, $code, $response) + { + $this->response = $response; + parent::__construct($message, $code); + } + /** + * Returns the exception's response body. + * + * @return mixed + */ + public function getResponseBody() + { + return $this->response; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericProvider.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericProvider.php new file mode 100644 index 00000000..8b7bbdfa --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericProvider.php @@ -0,0 +1,201 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Provider; + +use InvalidArgumentException; +use WPMailSMTP\Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException; +use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessToken; +use WPMailSMTP\Vendor\League\OAuth2\Client\Tool\BearerAuthorizationTrait; +use WPMailSMTP\Vendor\Psr\Http\Message\ResponseInterface; +/** + * Represents a generic service provider that may be used to interact with any + * OAuth 2.0 service provider, using Bearer token authentication. + */ +class GenericProvider extends AbstractProvider +{ + use BearerAuthorizationTrait; + /** + * @var string + */ + private $urlAuthorize; + /** + * @var string + */ + private $urlAccessToken; + /** + * @var string + */ + private $urlResourceOwnerDetails; + /** + * @var string + */ + private $accessTokenMethod; + /** + * @var string + */ + private $accessTokenResourceOwnerId; + /** + * @var array|null + */ + private $scopes = null; + /** + * @var string + */ + private $scopeSeparator; + /** + * @var string + */ + private $responseError = 'error'; + /** + * @var string + */ + private $responseCode; + /** + * @var string + */ + private $responseResourceOwnerId = 'id'; + /** + * @var string|null + */ + private $pkceMethod = null; + /** + * @param array $options + * @param array $collaborators + */ + public function __construct(array $options = [], array $collaborators = []) + { + $this->assertRequiredOptions($options); + $possible = $this->getConfigurableOptions(); + $configured = \array_intersect_key($options, \array_flip($possible)); + foreach ($configured as $key => $value) { + $this->{$key} = $value; + } + // Remove all options that are only used locally + $options = \array_diff_key($options, $configured); + parent::__construct($options, $collaborators); + } + /** + * Returns all options that can be configured. + * + * @return array + */ + protected function getConfigurableOptions() + { + return \array_merge($this->getRequiredOptions(), ['accessTokenMethod', 'accessTokenResourceOwnerId', 'scopeSeparator', 'responseError', 'responseCode', 'responseResourceOwnerId', 'scopes', 'pkceMethod']); + } + /** + * Returns all options that are required. + * + * @return array + */ + protected function getRequiredOptions() + { + return ['urlAuthorize', 'urlAccessToken', 'urlResourceOwnerDetails']; + } + /** + * Verifies that all required options have been passed. + * + * @param array $options + * @return void + * @throws InvalidArgumentException + */ + private function assertRequiredOptions(array $options) + { + $missing = \array_diff_key(\array_flip($this->getRequiredOptions()), $options); + if (!empty($missing)) { + throw new InvalidArgumentException('Required options not defined: ' . \implode(', ', \array_keys($missing))); + } + } + /** + * @inheritdoc + */ + public function getBaseAuthorizationUrl() + { + return $this->urlAuthorize; + } + /** + * @inheritdoc + */ + public function getBaseAccessTokenUrl(array $params) + { + return $this->urlAccessToken; + } + /** + * @inheritdoc + */ + public function getResourceOwnerDetailsUrl(AccessToken $token) + { + return $this->urlResourceOwnerDetails; + } + /** + * @inheritdoc + */ + public function getDefaultScopes() + { + return $this->scopes; + } + /** + * @inheritdoc + */ + protected function getAccessTokenMethod() + { + return $this->accessTokenMethod ?: parent::getAccessTokenMethod(); + } + /** + * @inheritdoc + */ + protected function getAccessTokenResourceOwnerId() + { + return $this->accessTokenResourceOwnerId ?: parent::getAccessTokenResourceOwnerId(); + } + /** + * @inheritdoc + */ + protected function getScopeSeparator() + { + return $this->scopeSeparator ?: parent::getScopeSeparator(); + } + /** + * @inheritdoc + */ + protected function getPkceMethod() + { + return $this->pkceMethod ?: parent::getPkceMethod(); + } + /** + * @inheritdoc + */ + protected function checkResponse(ResponseInterface $response, $data) + { + if (!empty($data[$this->responseError])) { + $error = $data[$this->responseError]; + if (!\is_string($error)) { + $error = \var_export($error, \true); + } + $code = $this->responseCode && !empty($data[$this->responseCode]) ? $data[$this->responseCode] : 0; + if (!\is_int($code)) { + $code = \intval($code); + } + throw new IdentityProviderException($error, $code, $data); + } + } + /** + * @inheritdoc + */ + protected function createResourceOwner(array $response, AccessToken $token) + { + return new GenericResourceOwner($response, $this->responseResourceOwnerId); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericResourceOwner.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericResourceOwner.php new file mode 100644 index 00000000..2d990e35 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/GenericResourceOwner.php @@ -0,0 +1,57 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Provider; + +/** + * Represents a generic resource owner for use with the GenericProvider. + */ +class GenericResourceOwner implements ResourceOwnerInterface +{ + /** + * @var array + */ + protected $response; + /** + * @var string + */ + protected $resourceOwnerId; + /** + * @param array $response + * @param string $resourceOwnerId + */ + public function __construct(array $response, $resourceOwnerId) + { + $this->response = $response; + $this->resourceOwnerId = $resourceOwnerId; + } + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId() + { + return $this->response[$this->resourceOwnerId]; + } + /** + * Returns the raw resource owner response. + * + * @return array + */ + public function toArray() + { + return $this->response; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/ResourceOwnerInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/ResourceOwnerInterface.php new file mode 100644 index 00000000..6ada43ca --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Provider/ResourceOwnerInterface.php @@ -0,0 +1,35 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Provider; + +/** + * Classes implementing `ResourceOwnerInterface` may be used to represent + * the resource owner authenticated with a service provider. + */ +interface ResourceOwnerInterface +{ + /** + * Returns the identifier of the authorized resource owner. + * + * @return mixed + */ + public function getId(); + /** + * Return all of the owner details available as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessToken.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessToken.php new file mode 100644 index 00000000..c4f4b987 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessToken.php @@ -0,0 +1,211 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Token; + +use InvalidArgumentException; +use RuntimeException; +/** + * Represents an access token. + * + * @link http://tools.ietf.org/html/rfc6749#section-1.4 Access Token (RFC 6749, §1.4) + */ +class AccessToken implements AccessTokenInterface, ResourceOwnerAccessTokenInterface, SettableRefreshTokenInterface +{ + /** + * @var string + */ + protected $accessToken; + /** + * @var int + */ + protected $expires; + /** + * @var string + */ + protected $refreshToken; + /** + * @var string + */ + protected $resourceOwnerId; + /** + * @var array + */ + protected $values = []; + /** + * @var int + */ + private static $timeNow; + /** + * Set the time now. This should only be used for testing purposes. + * + * @param int $timeNow the time in seconds since epoch + * @return void + */ + public static function setTimeNow($timeNow) + { + self::$timeNow = $timeNow; + } + /** + * Reset the time now if it was set for test purposes. + * + * @return void + */ + public static function resetTimeNow() + { + self::$timeNow = null; + } + /** + * @return int + */ + public function getTimeNow() + { + return self::$timeNow ? self::$timeNow : \time(); + } + /** + * Constructs an access token. + * + * @param array $options An array of options returned by the service provider + * in the access token request. The `access_token` option is required. + * @throws InvalidArgumentException if `access_token` is not provided in `$options`. + */ + public function __construct(array $options = []) + { + if (empty($options['access_token'])) { + throw new InvalidArgumentException('Required option not passed: "access_token"'); + } + $this->accessToken = $options['access_token']; + if (!empty($options['resource_owner_id'])) { + $this->resourceOwnerId = $options['resource_owner_id']; + } + if (!empty($options['refresh_token'])) { + $this->refreshToken = $options['refresh_token']; + } + // We need to know when the token expires. Show preference to + // 'expires_in' since it is defined in RFC6749 Section 5.1. + // Defer to 'expires' if it is provided instead. + if (isset($options['expires_in'])) { + if (!\is_numeric($options['expires_in'])) { + throw new \InvalidArgumentException('expires_in value must be an integer'); + } + $this->expires = $options['expires_in'] != 0 ? $this->getTimeNow() + $options['expires_in'] : 0; + } elseif (!empty($options['expires'])) { + // Some providers supply the seconds until expiration rather than + // the exact timestamp. Take a best guess at which we received. + $expires = (int) $options['expires']; + if (!$this->isExpirationTimestamp($expires)) { + $expires += $this->getTimeNow(); + } + $this->expires = $expires; + } + // Capture any additional values that might exist in the token but are + // not part of the standard response. Vendors will sometimes pass + // additional user data this way. + $this->values = \array_diff_key($options, \array_flip(['access_token', 'resource_owner_id', 'refresh_token', 'expires_in', 'expires'])); + } + /** + * Check if a value is an expiration timestamp or second value. + * + * @param integer $value + * @return bool + */ + protected function isExpirationTimestamp($value) + { + // If the given value is larger than the original OAuth 2 draft date, + // assume that it is meant to be a (possible expired) timestamp. + $oauth2InceptionDate = 1349067600; + // 2012-10-01 + return $value > $oauth2InceptionDate; + } + /** + * @inheritdoc + */ + public function getToken() + { + return $this->accessToken; + } + /** + * @inheritdoc + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + /** + * @inheritdoc + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + } + /** + * @inheritdoc + */ + public function getExpires() + { + return $this->expires; + } + /** + * @inheritdoc + */ + public function getResourceOwnerId() + { + return $this->resourceOwnerId; + } + /** + * @inheritdoc + */ + public function hasExpired() + { + $expires = $this->getExpires(); + if (empty($expires)) { + throw new RuntimeException('"expires" is not set on the token'); + } + return $expires < $this->getTimeNow(); + } + /** + * @inheritdoc + */ + public function getValues() + { + return $this->values; + } + /** + * @inheritdoc + */ + public function __toString() + { + return (string) $this->getToken(); + } + /** + * @inheritdoc + */ + public function jsonSerialize() + { + $parameters = $this->values; + if ($this->accessToken) { + $parameters['access_token'] = $this->accessToken; + } + if ($this->refreshToken) { + $parameters['refresh_token'] = $this->refreshToken; + } + if ($this->expires) { + $parameters['expires'] = $this->expires; + } + if ($this->resourceOwnerId) { + $parameters['resource_owner_id'] = $this->resourceOwnerId; + } + return $parameters; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessTokenInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessTokenInterface.php new file mode 100644 index 00000000..8c9f6fd6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/AccessTokenInterface.php @@ -0,0 +1,67 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Token; + +use JsonSerializable; +use ReturnTypeWillChange; +use RuntimeException; +interface AccessTokenInterface extends JsonSerializable +{ + /** + * Returns the access token string of this instance. + * + * @return string + */ + public function getToken(); + /** + * Returns the refresh token, if defined. + * + * @return string|null + */ + public function getRefreshToken(); + /** + * Returns the expiration timestamp in seconds, if defined. + * + * @return integer|null + */ + public function getExpires(); + /** + * Checks if this token has expired. + * + * @return boolean true if the token has expired, false otherwise. + * @throws RuntimeException if 'expires' is not set on the token. + */ + public function hasExpired(); + /** + * Returns additional vendor values stored in the token. + * + * @return array + */ + public function getValues(); + /** + * Returns a string representation of the access token + * + * @return string + */ + public function __toString(); + /** + * Returns an array of parameters to serialize when this is serialized with + * json_encode(). + * + * @return array + */ + #[ReturnTypeWillChange] + public function jsonSerialize(); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php new file mode 100644 index 00000000..36be42cf --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/ResourceOwnerAccessTokenInterface.php @@ -0,0 +1,25 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Token; + +interface ResourceOwnerAccessTokenInterface extends AccessTokenInterface +{ + /** + * Returns the resource owner identifier, if defined. + * + * @return string|null + */ + public function getResourceOwnerId(); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php new file mode 100644 index 00000000..45e10666 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Token/SettableRefreshTokenInterface.php @@ -0,0 +1,26 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Token; + +interface SettableRefreshTokenInterface +{ + /** + * Sets or replaces the refresh token with the provided refresh token. + * + * @param string $refreshToken + * @return void + */ + public function setRefreshToken($refreshToken); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ArrayAccessorTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ArrayAccessorTrait.php new file mode 100644 index 00000000..c690167f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ArrayAccessorTrait.php @@ -0,0 +1,47 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +/** + * Provides generic array navigation tools. + */ +trait ArrayAccessorTrait +{ + /** + * Returns a value by key using dot notation. + * + * @param array $data + * @param string $key + * @param mixed|null $default + * @return mixed + */ + private function getValueByKey(array $data, $key, $default = null) + { + if (!\is_string($key) || empty($key) || !\count($data)) { + return $default; + } + if (\strpos($key, '.') !== \false) { + $keys = \explode('.', $key); + foreach ($keys as $innerKey) { + if (!\is_array($data) || !\array_key_exists($innerKey, $data)) { + return $default; + } + $data = $data[$innerKey]; + } + return $data; + } + return \array_key_exists($key, $data) ? $data[$key] : $default; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php new file mode 100644 index 00000000..31b7b6d1 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/BearerAuthorizationTrait.php @@ -0,0 +1,35 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessTokenInterface; +/** + * Enables `Bearer` header authorization for providers. + * + * @link http://tools.ietf.org/html/rfc6750 Bearer Token Usage (RFC 6750) + */ +trait BearerAuthorizationTrait +{ + /** + * Returns authorization headers for the 'bearer' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + */ + protected function getAuthorizationHeaders($token = null) + { + return ['Authorization' => 'Bearer ' . $token]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/GuardedPropertyTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/GuardedPropertyTrait.php new file mode 100644 index 00000000..4d54a528 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/GuardedPropertyTrait.php @@ -0,0 +1,66 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +/** + * Provides support for blacklisting explicit properties from the + * mass assignment behavior. + */ +trait GuardedPropertyTrait +{ + /** + * The properties that aren't mass assignable. + * + * @var array + */ + protected $guarded = []; + /** + * Attempts to mass assign the given options to explicitly defined properties, + * skipping over any properties that are defined in the guarded array. + * + * @param array $options + * @return mixed + */ + protected function fillProperties(array $options = []) + { + if (isset($options['guarded'])) { + unset($options['guarded']); + } + foreach ($options as $option => $value) { + if (\property_exists($this, $option) && !$this->isGuarded($option)) { + $this->{$option} = $value; + } + } + } + /** + * Returns current guarded properties. + * + * @return array + */ + public function getGuarded() + { + return $this->guarded; + } + /** + * Determines if the given property is guarded. + * + * @param string $property + * @return bool + */ + public function isGuarded($property) + { + return \in_array($property, $this->getGuarded()); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/MacAuthorizationTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/MacAuthorizationTrait.php new file mode 100644 index 00000000..942dc542 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/MacAuthorizationTrait.php @@ -0,0 +1,76 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessToken; +use WPMailSMTP\Vendor\League\OAuth2\Client\Token\AccessTokenInterface; +/** + * Enables `MAC` header authorization for providers. + * + * @link http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-05 Message Authentication Code (MAC) Tokens + */ +trait MacAuthorizationTrait +{ + /** + * Returns the id of this token for MAC generation. + * + * @param AccessToken $token + * @return string + */ + protected abstract function getTokenId(AccessToken $token); + /** + * Returns the MAC signature for the current request. + * + * @param string $id + * @param integer $ts + * @param string $nonce + * @return string + */ + protected abstract function getMacSignature($id, $ts, $nonce); + /** + * Returns a new random string to use as the state parameter in an + * authorization flow. + * + * @param int $length Length of the random string to be generated. + * @return string + */ + protected abstract function getRandomState($length = 32); + /** + * Returns the authorization headers for the 'mac' grant. + * + * @param AccessTokenInterface|string|null $token Either a string or an access token instance + * @return array + * @codeCoverageIgnore + * + * @todo This is currently untested and provided only as an example. If you + * complete the implementation, please create a pull request for + * https://github.com/thephpleague/oauth2-client + */ + protected function getAuthorizationHeaders($token = null) + { + if ($token === null) { + return []; + } + $ts = \time(); + $id = $this->getTokenId($token); + $nonce = $this->getRandomState(16); + $mac = $this->getMacSignature($id, $ts, $nonce); + $parts = []; + foreach (\compact('id', 'ts', 'nonce', 'mac') as $key => $value) { + $parts[] = \sprintf('%s="%s"', $key, $value); + } + return ['Authorization' => 'MAC ' . \implode(', ', $parts)]; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ProviderRedirectTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ProviderRedirectTrait.php new file mode 100644 index 00000000..d8b2254e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/ProviderRedirectTrait.php @@ -0,0 +1,105 @@ +redirectLimit) { + $attempts++; + $response = $this->getHttpClient()->send($request, ['allow_redirects' => \false]); + if ($this->isRedirect($response)) { + $redirectUrl = new Uri($response->getHeader('Location')[0]); + $request = $request->withUri($redirectUrl); + } else { + break; + } + } + return $response; + } + /** + * Returns the HTTP client instance. + * + * @return GuzzleHttp\ClientInterface + */ + public abstract function getHttpClient(); + /** + * Retrieves current redirect limit. + * + * @return integer + */ + public function getRedirectLimit() + { + return $this->redirectLimit; + } + /** + * Determines if a given response is a redirect. + * + * @param ResponseInterface $response + * + * @return boolean + */ + protected function isRedirect(ResponseInterface $response) + { + $statusCode = $response->getStatusCode(); + return $statusCode > 300 && $statusCode < 400 && $response->hasHeader('Location'); + } + /** + * Sends a request instance and returns a response instance. + * + * WARNING: This method does not attempt to catch exceptions caused by HTTP + * errors! It is recommended to wrap this method in a try/catch block. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function getResponse(RequestInterface $request) + { + try { + $response = $this->followRequestRedirects($request); + } catch (BadResponseException $e) { + $response = $e->getResponse(); + } + return $response; + } + /** + * Updates the redirect limit. + * + * @param integer $limit + * @return League\OAuth2\Client\Provider\AbstractProvider + * @throws InvalidArgumentException + */ + public function setRedirectLimit($limit) + { + if (!\is_int($limit)) { + throw new InvalidArgumentException('redirectLimit must be an integer.'); + } + if ($limit < 1) { + throw new InvalidArgumentException('redirectLimit must be greater than or equal to one.'); + } + $this->redirectLimit = $limit; + return $this; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/QueryBuilderTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/QueryBuilderTrait.php new file mode 100644 index 00000000..c8b28658 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/QueryBuilderTrait.php @@ -0,0 +1,33 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +/** + * Provides a standard way to generate query strings. + */ +trait QueryBuilderTrait +{ + /** + * Build a query string from an array. + * + * @param array $params + * + * @return string + */ + protected function buildQueryString(array $params) + { + return \http_build_query($params, '', '&', \PHP_QUERY_RFC3986); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequestFactory.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequestFactory.php new file mode 100644 index 00000000..1b9c8d71 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequestFactory.php @@ -0,0 +1,67 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +use WPMailSMTP\Vendor\GuzzleHttp\Psr7\Request; +/** + * Used to produce PSR-7 Request instances. + * + * @link https://github.com/guzzle/guzzle/pull/1101 + */ +class RequestFactory +{ + /** + * Creates a PSR-7 Request instance. + * + * @param null|string $method HTTP method for the request. + * @param null|string $uri URI for the request. + * @param array $headers Headers for the message. + * @param string|resource|StreamInterface $body Message body. + * @param string $version HTTP protocol version. + * + * @return Request + */ + public function getRequest($method, $uri, array $headers = [], $body = null, $version = '1.1') + { + return new Request($method, $uri, $headers, $body, $version); + } + /** + * Parses simplified options. + * + * @param array $options Simplified options. + * + * @return array Extended options for use with getRequest. + */ + protected function parseOptions(array $options) + { + // Should match default values for getRequest + $defaults = ['headers' => [], 'body' => null, 'version' => '1.1']; + return \array_merge($defaults, $options); + } + /** + * Creates a request using a simplified array of options. + * + * @param null|string $method + * @param null|string $uri + * @param array $options + * + * @return Request + */ + public function getRequestWithOptions($method, $uri, array $options = []) + { + $options = $this->parseOptions($options); + return $this->getRequest($method, $uri, $options['headers'], $options['body'], $options['version']); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequiredParameterTrait.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequiredParameterTrait.php new file mode 100644 index 00000000..bc439629 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/league/oauth2-client/src/Tool/RequiredParameterTrait.php @@ -0,0 +1,51 @@ + + * @license http://opensource.org/licenses/MIT MIT + * @link http://thephpleague.com/oauth2-client/ Documentation + * @link https://packagist.org/packages/league/oauth2-client Packagist + * @link https://github.com/thephpleague/oauth2-client GitHub + */ +namespace WPMailSMTP\Vendor\League\OAuth2\Client\Tool; + +use BadMethodCallException; +/** + * Provides functionality to check for required parameters. + */ +trait RequiredParameterTrait +{ + /** + * Checks for a required parameter in a hash. + * + * @throws BadMethodCallException + * @param string $name + * @param array $params + * @return void + */ + private function checkRequiredParameter($name, array $params) + { + if (!isset($params[$name])) { + throw new BadMethodCallException(\sprintf('Required parameter not passed: "%s"', $name)); + } + } + /** + * Checks for multiple required parameters in a hash. + * + * @throws InvalidArgumentException + * @param array $names + * @param array $params + * @return void + */ + private function checkRequiredParameters(array $names, array $params) + { + foreach ($names as $name) { + $this->checkRequiredParameter($name, $params); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/LICENSE new file mode 100644 index 00000000..ea7b7f2e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mark Jones + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/xlsxwriter.class.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/xlsxwriter.class.php new file mode 100644 index 00000000..4ad14657 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mk-j/php_xlsxwriter/xlsxwriter.class.php @@ -0,0 +1,1028 @@ +tempFilename()) or self::log("Warning: tempdir " . \sys_get_temp_dir() . " not writeable, use ->setTempDir()"); + \class_exists('ZipArchive') or self::log("Error: ZipArchive class does not exist"); + $this->addCellStyle($number_format = 'GENERAL', $style_string = null); + } + public function setTitle($title = '') + { + $this->title = $title; + } + public function setSubject($subject = '') + { + $this->subject = $subject; + } + public function setAuthor($author = '') + { + $this->author = $author; + } + public function setCompany($company = '') + { + $this->company = $company; + } + public function setKeywords($keywords = '') + { + $this->keywords = $keywords; + } + public function setDescription($description = '') + { + $this->description = $description; + } + public function setTempDir($tempdir = '') + { + $this->tempdir = $tempdir; + } + public function setRightToLeft($isRightToLeft = \false) + { + $this->isRightToLeft = $isRightToLeft; + } + public function __destruct() + { + if (!empty($this->temp_files)) { + foreach ($this->temp_files as $temp_file) { + @\unlink($temp_file); + } + } + } + protected function tempFilename() + { + $tempdir = !empty($this->tempdir) ? $this->tempdir : \sys_get_temp_dir(); + $filename = \tempnam($tempdir, "xlsx_writer_"); + if (!$filename) { + // If you are seeing this error, it's possible you may have too many open + // file handles. If you're creating a spreadsheet with many small inserts, + // it is possible to exceed the default 1024 open file handles. Run 'ulimit -a' + // and try increasing the 'open files' number with 'ulimit -n 8192' + throw new \Exception("Unable to create tempfile - check file handle limits?"); + } + $this->temp_files[] = $filename; + return $filename; + } + public function writeToStdOut() + { + $temp_file = $this->tempFilename(); + self::writeToFile($temp_file); + \readfile($temp_file); + } + public function writeToString() + { + $temp_file = $this->tempFilename(); + self::writeToFile($temp_file); + $string = \file_get_contents($temp_file); + return $string; + } + public function writeToFile($filename) + { + foreach ($this->sheets as $sheet_name => $sheet) { + self::finalizeSheet($sheet_name); + //making sure all footers have been written + } + if (\file_exists($filename)) { + if (\is_writable($filename)) { + @\unlink($filename); + //if the zip already exists, remove it + } else { + self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", file is not writeable."); + return; + } + } + $zip = new \ZipArchive(); + if (empty($this->sheets)) { + self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", no worksheets defined."); + return; + } + if (!$zip->open($filename, \ZipArchive::CREATE)) { + self::log("Error in " . __CLASS__ . "::" . __FUNCTION__ . ", unable to create zip."); + return; + } + $zip->addEmptyDir("docProps/"); + $zip->addFromString("docProps/app.xml", self::buildAppXML()); + $zip->addFromString("docProps/core.xml", self::buildCoreXML()); + $zip->addEmptyDir("_rels/"); + $zip->addFromString("_rels/.rels", self::buildRelationshipsXML()); + $zip->addEmptyDir("xl/worksheets/"); + foreach ($this->sheets as $sheet) { + $zip->addFile($sheet->filename, "xl/worksheets/" . $sheet->xmlname); + } + $zip->addFromString("xl/workbook.xml", self::buildWorkbookXML()); + $zip->addFile($this->writeStylesXML(), "xl/styles.xml"); + //$zip->addFromString("xl/styles.xml" , self::buildStylesXML() ); + $zip->addFromString("[Content_Types].xml", self::buildContentTypesXML()); + $zip->addEmptyDir("xl/_rels/"); + $zip->addFromString("xl/_rels/workbook.xml.rels", self::buildWorkbookRelsXML()); + $zip->close(); + } + protected function initializeSheet($sheet_name, $col_widths = array(), $auto_filter = \false, $freeze_rows = \false, $freeze_columns = \false) + { + //if already initialized + if ($this->current_sheet == $sheet_name || isset($this->sheets[$sheet_name])) { + return; + } + $sheet_filename = $this->tempFilename(); + $sheet_xmlname = 'sheet' . (\count($this->sheets) + 1) . ".xml"; + $this->sheets[$sheet_name] = (object) array('filename' => $sheet_filename, 'sheetname' => $sheet_name, 'xmlname' => $sheet_xmlname, 'row_count' => 0, 'file_writer' => new XLSXWriter_BuffererWriter($sheet_filename), 'columns' => array(), 'merge_cells' => array(), 'max_cell_tag_start' => 0, 'max_cell_tag_end' => 0, 'auto_filter' => $auto_filter, 'freeze_rows' => $freeze_rows, 'freeze_columns' => $freeze_columns, 'finalized' => \false); + $rightToLeftValue = $this->isRightToLeft ? 'true' : 'false'; + $sheet =& $this->sheets[$sheet_name]; + $tabselected = \count($this->sheets) == 1 ? 'true' : 'false'; + //only first sheet is selected + $max_cell = XLSXWriter::xlsCell(self::EXCEL_2007_MAX_ROW, self::EXCEL_2007_MAX_COL); + //XFE1048577 + $sheet->file_writer->write('' . "\n"); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->max_cell_tag_start = $sheet->file_writer->ftell(); + $sheet->file_writer->write(''); + $sheet->max_cell_tag_end = $sheet->file_writer->ftell(); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + if ($sheet->freeze_rows && $sheet->freeze_columns) { + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + } elseif ($sheet->freeze_rows) { + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + } elseif ($sheet->freeze_columns) { + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + } else { + // not frozen + $sheet->file_writer->write(''); + } + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $i = 0; + if (!empty($col_widths)) { + foreach ($col_widths as $column_width) { + $sheet->file_writer->write(''); + $i++; + } + } + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + } + private function addCellStyle($number_format, $cell_style_string) + { + $number_format_idx = self::add_to_list_get_index($this->number_formats, $number_format); + $lookup_string = $number_format_idx . ";" . $cell_style_string; + $cell_style_idx = self::add_to_list_get_index($this->cell_styles, $lookup_string); + return $cell_style_idx; + } + private function initializeColumnTypes($header_types) + { + $column_types = array(); + foreach ($header_types as $v) { + $number_format = self::numberFormatStandardized($v); + $number_format_type = self::determineNumberFormatType($number_format); + $cell_style_idx = $this->addCellStyle($number_format, $style_string = null); + $column_types[] = array( + 'number_format' => $number_format, + //contains excel format like 'YYYY-MM-DD HH:MM:SS' + 'number_format_type' => $number_format_type, + //contains friendly format like 'datetime' + 'default_cell_style' => $cell_style_idx, + ); + } + return $column_types; + } + public function writeSheetHeader($sheet_name, array $header_types, $col_options = null) + { + if (empty($sheet_name) || empty($header_types)) { + return; + } + $suppress_row = isset($col_options['suppress_row']) ? \intval($col_options['suppress_row']) : \false; + if (\is_bool($col_options)) { + self::log("Warning! passing {$suppress_row}=false|true to writeSheetHeader() is deprecated, this will be removed in a future version."); + $suppress_row = \intval($col_options); + } + $style =& $col_options; + $col_widths = isset($col_options['widths']) ? (array) $col_options['widths'] : array(); + $auto_filter = isset($col_options['auto_filter']) ? \intval($col_options['auto_filter']) : \false; + $freeze_rows = isset($col_options['freeze_rows']) ? \intval($col_options['freeze_rows']) : \false; + $freeze_columns = isset($col_options['freeze_columns']) ? \intval($col_options['freeze_columns']) : \false; + self::initializeSheet($sheet_name, $col_widths, $auto_filter, $freeze_rows, $freeze_columns); + $sheet =& $this->sheets[$sheet_name]; + $sheet->columns = $this->initializeColumnTypes($header_types); + if (!$suppress_row) { + $header_row = \array_keys($header_types); + $sheet->file_writer->write(''); + $sheet->row_count++; + } + $this->current_sheet = $sheet_name; + } + public function writeSheetRow($sheet_name, array $row, $row_options = null) + { + if (empty($sheet_name)) { + return; + } + $this->initializeSheet($sheet_name); + $sheet =& $this->sheets[$sheet_name]; + if (\count($sheet->columns) < \count($row)) { + $default_column_types = $this->initializeColumnTypes(\array_fill($from = 0, $until = \count($row), 'GENERAL')); + //will map to n_auto + $sheet->columns = \array_merge((array) $sheet->columns, $default_column_types); + } + if (!empty($row_options)) { + $ht = isset($row_options['height']) ? \floatval($row_options['height']) : 12.1; + $customHt = isset($row_options['height']) ? \true : \false; + $hidden = isset($row_options['hidden']) ? (bool) $row_options['hidden'] : \false; + $collapsed = isset($row_options['collapsed']) ? (bool) $row_options['collapsed'] : \false; + $sheet->file_writer->write(''); + if (!empty($sheet->merge_cells)) { + $sheet->file_writer->write(''); + foreach ($sheet->merge_cells as $range) { + $sheet->file_writer->write(''); + } + $sheet->file_writer->write(''); + } + $max_cell = self::xlsCell($sheet->row_count - 1, \count($sheet->columns) - 1); + if ($sheet->auto_filter) { + $sheet->file_writer->write(''); + } + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $sheet->file_writer->write('&C&"Times New Roman,Regular"&12&A'); + $sheet->file_writer->write('&C&"Times New Roman,Regular"&12Page &P'); + $sheet->file_writer->write(''); + $sheet->file_writer->write(''); + $max_cell_tag = ''; + $padding_length = $sheet->max_cell_tag_end - $sheet->max_cell_tag_start - \strlen($max_cell_tag); + $sheet->file_writer->fseek($sheet->max_cell_tag_start); + $sheet->file_writer->write($max_cell_tag . \str_repeat(" ", $padding_length)); + $sheet->file_writer->close(); + $sheet->finalized = \true; + } + public function markMergedCell($sheet_name, $start_cell_row, $start_cell_column, $end_cell_row, $end_cell_column) + { + if (empty($sheet_name) || $this->sheets[$sheet_name]->finalized) { + return; + } + self::initializeSheet($sheet_name); + $sheet =& $this->sheets[$sheet_name]; + $startCell = self::xlsCell($start_cell_row, $start_cell_column); + $endCell = self::xlsCell($end_cell_row, $end_cell_column); + $sheet->merge_cells[] = $startCell . ":" . $endCell; + } + public function writeSheet(array $data, $sheet_name = '', array $header_types = array()) + { + $sheet_name = empty($sheet_name) ? 'Sheet1' : $sheet_name; + $data = empty($data) ? array(array('')) : $data; + if (!empty($header_types)) { + $this->writeSheetHeader($sheet_name, $header_types); + } + foreach ($data as $i => $row) { + $this->writeSheetRow($sheet_name, $row); + } + $this->finalizeSheet($sheet_name); + } + protected function writeCell(XLSXWriter_BuffererWriter &$file, $row_number, $column_number, $value, $num_format_type, $cell_style_idx) + { + $cell_name = self::xlsCell($row_number, $column_number); + if (!\is_scalar($value) || $value === '') { + //objects, array, empty + $file->write(''); + } elseif (\is_string($value) && $value[0] == '=') { + $file->write('' . self::xmlspecialchars(\ltrim($value, '=')) . ''); + } elseif ($num_format_type == 'n_date') { + $file->write('' . \intval(self::convert_date_time($value)) . ''); + } elseif ($num_format_type == 'n_datetime') { + $file->write('' . self::convert_date_time($value) . ''); + } elseif ($num_format_type == 'n_numeric') { + $file->write('' . self::xmlspecialchars($value) . ''); + //int,float,currency + } elseif ($num_format_type == 'n_string') { + $file->write('' . self::xmlspecialchars($value) . ''); + } elseif ($num_format_type == 'n_auto' || 1) { + //auto-detect unknown column types + if (!\is_string($value) || $value == '0' || $value[0] != '0' && \ctype_digit($value) || \preg_match("/^\\-?(0|[1-9][0-9]*)(\\.[0-9]+)?\$/", $value)) { + $file->write('' . self::xmlspecialchars($value) . ''); + //int,float,currency + } else { + //implied: ($cell_format=='string') + $file->write('' . self::xmlspecialchars($value) . ''); + } + } + } + protected function styleFontIndexes() + { + static $border_allowed = array('left', 'right', 'top', 'bottom'); + static $border_style_allowed = array('thin', 'medium', 'thick', 'dashDot', 'dashDotDot', 'dashed', 'dotted', 'double', 'hair', 'mediumDashDot', 'mediumDashDotDot', 'mediumDashed', 'slantDashDot'); + static $horizontal_allowed = array('general', 'left', 'right', 'justify', 'center'); + static $vertical_allowed = array('bottom', 'center', 'distributed', 'top'); + $default_font = array('size' => '10', 'name' => 'Arial', 'family' => '2'); + $fills = array('', ''); + //2 placeholders for static xml later + $fonts = array('', '', '', ''); + //4 placeholders for static xml later + $borders = array(''); + //1 placeholder for static xml later + $style_indexes = array(); + foreach ($this->cell_styles as $i => $cell_style_string) { + $semi_colon_pos = \strpos($cell_style_string, ";"); + $number_format_idx = \substr($cell_style_string, 0, $semi_colon_pos); + $style_json_string = \substr($cell_style_string, $semi_colon_pos + 1); + $style = @\json_decode($style_json_string, $as_assoc = \true); + $style_indexes[$i] = array('num_fmt_idx' => $number_format_idx); + //initialize entry + if (isset($style['border']) && \is_string($style['border'])) { + //border is a comma delimited str + $border_value['side'] = \array_intersect(\explode(",", $style['border']), $border_allowed); + if (isset($style['border-style']) && \in_array($style['border-style'], $border_style_allowed)) { + $border_value['style'] = $style['border-style']; + } + if (isset($style['border-color']) && \is_string($style['border-color']) && $style['border-color'][0] == '#') { + $v = \substr($style['border-color'], 1, 6); + $v = \strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v; + // expand cf0 => ccff00 + $border_value['color'] = "FF" . \strtoupper($v); + } + $style_indexes[$i]['border_idx'] = self::add_to_list_get_index($borders, \json_encode($border_value)); + } + if (isset($style['fill']) && \is_string($style['fill']) && $style['fill'][0] == '#') { + $v = \substr($style['fill'], 1, 6); + $v = \strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v; + // expand cf0 => ccff00 + $style_indexes[$i]['fill_idx'] = self::add_to_list_get_index($fills, "FF" . \strtoupper($v)); + } + if (isset($style['halign']) && \in_array($style['halign'], $horizontal_allowed)) { + $style_indexes[$i]['alignment'] = \true; + $style_indexes[$i]['halign'] = $style['halign']; + } + if (isset($style['valign']) && \in_array($style['valign'], $vertical_allowed)) { + $style_indexes[$i]['alignment'] = \true; + $style_indexes[$i]['valign'] = $style['valign']; + } + if (isset($style['wrap_text'])) { + $style_indexes[$i]['alignment'] = \true; + $style_indexes[$i]['wrap_text'] = (bool) $style['wrap_text']; + } + $font = $default_font; + if (isset($style['font-size'])) { + $font['size'] = \floatval($style['font-size']); + //floatval to allow "10.5" etc + } + if (isset($style['font']) && \is_string($style['font'])) { + if ($style['font'] == 'Comic Sans MS') { + $font['family'] = 4; + } + if ($style['font'] == 'Times New Roman') { + $font['family'] = 1; + } + if ($style['font'] == 'Courier New') { + $font['family'] = 3; + } + $font['name'] = \strval($style['font']); + } + if (isset($style['font-style']) && \is_string($style['font-style'])) { + if (\strpos($style['font-style'], 'bold') !== \false) { + $font['bold'] = \true; + } + if (\strpos($style['font-style'], 'italic') !== \false) { + $font['italic'] = \true; + } + if (\strpos($style['font-style'], 'strike') !== \false) { + $font['strike'] = \true; + } + if (\strpos($style['font-style'], 'underline') !== \false) { + $font['underline'] = \true; + } + } + if (isset($style['color']) && \is_string($style['color']) && $style['color'][0] == '#') { + $v = \substr($style['color'], 1, 6); + $v = \strlen($v) == 3 ? $v[0] . $v[0] . $v[1] . $v[1] . $v[2] . $v[2] : $v; + // expand cf0 => ccff00 + $font['color'] = "FF" . \strtoupper($v); + } + if ($font != $default_font) { + $style_indexes[$i]['font_idx'] = self::add_to_list_get_index($fonts, \json_encode($font)); + } + } + return array('fills' => $fills, 'fonts' => $fonts, 'borders' => $borders, 'styles' => $style_indexes); + } + protected function writeStylesXML() + { + $r = self::styleFontIndexes(); + $fills = $r['fills']; + $fonts = $r['fonts']; + $borders = $r['borders']; + $style_indexes = $r['styles']; + $temporary_filename = $this->tempFilename(); + $file = new XLSXWriter_BuffererWriter($temporary_filename); + $file->write('' . "\n"); + $file->write(''); + $file->write(''); + foreach ($this->number_formats as $i => $v) { + $file->write(''); + } + //$file->write( ''); + //$file->write( ''); + //$file->write( ''); + //$file->write( ''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + foreach ($fonts as $font) { + if (!empty($font)) { + //fonts have 4 empty placeholders in array to offset the 4 static xml entries above + $f = \json_decode($font, \true); + $file->write(''); + $file->write(''); + $file->write(''); + if (!empty($f['color'])) { + $file->write(''); + } + if (!empty($f['bold'])) { + $file->write(''); + } + if (!empty($f['italic'])) { + $file->write(''); + } + if (!empty($f['underline'])) { + $file->write(''); + } + if (!empty($f['strike'])) { + $file->write(''); + } + $file->write(''); + } + } + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + foreach ($fills as $fill) { + if (!empty($fill)) { + //fills have 2 empty placeholders in array to offset the 2 static xml entries above + $file->write(''); + } + } + $file->write(''); + $file->write(''); + $file->write(''); + foreach ($borders as $border) { + if (!empty($border)) { + //fonts have an empty placeholder in the array to offset the static xml entry above + $pieces = \json_decode($border, \true); + $border_style = !empty($pieces['style']) ? $pieces['style'] : 'hair'; + $border_color = !empty($pieces['color']) ? '' : ''; + $file->write(''); + foreach (array('left', 'right', 'top', 'bottom') as $side) { + $show_side = \in_array($side, $pieces['side']) ? \true : \false; + $file->write($show_side ? "<{$side} style=\"{$border_style}\">{$border_color}" : "<{$side}/>"); + } + $file->write(''); + $file->write(''); + } + } + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + //$file->write( ''); + //$file->write( ''); + //$file->write( ''); + //$file->write( ''); + foreach ($style_indexes as $v) { + $applyAlignment = isset($v['alignment']) ? 'true' : 'false'; + $wrapText = !empty($v['wrap_text']) ? 'true' : 'false'; + $horizAlignment = isset($v['halign']) ? $v['halign'] : 'general'; + $vertAlignment = isset($v['valign']) ? $v['valign'] : 'bottom'; + $applyBorder = isset($v['border_idx']) ? 'true' : 'false'; + $applyFont = 'true'; + $borderIdx = isset($v['border_idx']) ? \intval($v['border_idx']) : 0; + $fillIdx = isset($v['fill_idx']) ? \intval($v['fill_idx']) : 0; + $fontIdx = isset($v['font_idx']) ? \intval($v['font_idx']) : 0; + //$file->write(''); + $file->write(''); + $file->write(' '); + $file->write(' '); + } + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->write(''); + $file->close(); + return $temporary_filename; + } + protected function buildAppXML() + { + $app_xml = ""; + $app_xml .= '' . "\n"; + $app_xml .= ''; + $app_xml .= '0'; + $app_xml .= '' . self::xmlspecialchars($this->company) . ''; + $app_xml .= ''; + return $app_xml; + } + protected function buildCoreXML() + { + $core_xml = ""; + $core_xml .= '' . "\n"; + $core_xml .= ''; + $core_xml .= '' . \date("Y-m-d\\TH:i:s.00\\Z") . ''; + //$date_time = '2014-10-25T15:54:37.00Z'; + $core_xml .= '' . self::xmlspecialchars($this->title) . ''; + $core_xml .= '' . self::xmlspecialchars($this->subject) . ''; + $core_xml .= '' . self::xmlspecialchars($this->author) . ''; + if (!empty($this->keywords)) { + $core_xml .= '' . self::xmlspecialchars(\implode(", ", (array) $this->keywords)) . ''; + } + $core_xml .= '' . self::xmlspecialchars($this->description) . ''; + $core_xml .= '0'; + $core_xml .= ''; + return $core_xml; + } + protected function buildRelationshipsXML() + { + $rels_xml = ""; + $rels_xml .= '' . "\n"; + $rels_xml .= ''; + $rels_xml .= ''; + $rels_xml .= ''; + $rels_xml .= ''; + $rels_xml .= "\n"; + $rels_xml .= ''; + return $rels_xml; + } + protected function buildWorkbookXML() + { + $i = 0; + $workbook_xml = ""; + $workbook_xml .= '' . "\n"; + $workbook_xml .= ''; + $workbook_xml .= ''; + $workbook_xml .= ''; + $workbook_xml .= ''; + foreach ($this->sheets as $sheet_name => $sheet) { + $sheetname = self::sanitize_sheetname($sheet->sheetname); + $workbook_xml .= ''; + $i++; + } + $workbook_xml .= ''; + $workbook_xml .= ''; + foreach ($this->sheets as $sheet_name => $sheet) { + if ($sheet->auto_filter) { + $sheetname = self::sanitize_sheetname($sheet->sheetname); + $workbook_xml .= ''; + $i++; + } + } + $workbook_xml .= ''; + $workbook_xml .= ''; + return $workbook_xml; + } + protected function buildWorkbookRelsXML() + { + $i = 0; + $wkbkrels_xml = ""; + $wkbkrels_xml .= '' . "\n"; + $wkbkrels_xml .= ''; + $wkbkrels_xml .= ''; + foreach ($this->sheets as $sheet_name => $sheet) { + $wkbkrels_xml .= ''; + $i++; + } + $wkbkrels_xml .= "\n"; + $wkbkrels_xml .= ''; + return $wkbkrels_xml; + } + protected function buildContentTypesXML() + { + $content_types_xml = ""; + $content_types_xml .= '' . "\n"; + $content_types_xml .= ''; + $content_types_xml .= ''; + $content_types_xml .= ''; + foreach ($this->sheets as $sheet_name => $sheet) { + $content_types_xml .= ''; + } + $content_types_xml .= ''; + $content_types_xml .= ''; + $content_types_xml .= ''; + $content_types_xml .= ''; + $content_types_xml .= "\n"; + $content_types_xml .= ''; + return $content_types_xml; + } + //------------------------------------------------------------------ + /* + * @param $row_number int, zero based + * @param $column_number int, zero based + * @param $absolute bool + * @return Cell label/coordinates, ex: A1, C3, AA42 (or if $absolute==true: $A$1, $C$3, $AA$42) + * */ + public static function xlsCell($row_number, $column_number, $absolute = \false) + { + $n = $column_number; + for ($r = ""; $n >= 0; $n = \intval($n / 26) - 1) { + $r = \chr($n % 26 + 0x41) . $r; + } + if ($absolute) { + return '$' . $r . '$' . ($row_number + 1); + } + return $r . ($row_number + 1); + } + //------------------------------------------------------------------ + public static function log($string) + { + //file_put_contents("php://stderr", date("Y-m-d H:i:s:").rtrim(is_array($string) ? json_encode($string) : $string)."\n"); + \error_log(\date("Y-m-d H:i:s:") . \rtrim(\is_array($string) ? \json_encode($string) : $string) . "\n"); + } + //------------------------------------------------------------------ + public static function sanitize_filename($filename) + { + $nonprinting = \array_map('chr', \range(0, 31)); + $invalid_chars = array('<', '>', '?', '"', ':', '|', '\\', '/', '*', '&'); + $all_invalids = \array_merge($nonprinting, $invalid_chars); + return \str_replace($all_invalids, "", $filename); + } + //------------------------------------------------------------------ + public static function sanitize_sheetname($sheetname) + { + static $badchars = '\\/?*:[]'; + static $goodchars = ' '; + $sheetname = \strtr($sheetname, $badchars, $goodchars); + $sheetname = \function_exists('mb_substr') ? \mb_substr($sheetname, 0, 31) : \substr($sheetname, 0, 31); + $sheetname = \trim(\trim(\trim($sheetname), "'")); + //trim before and after trimming single quotes + return !empty($sheetname) ? $sheetname : 'Sheet' . (\rand() % 900 + 100); + } + //------------------------------------------------------------------ + public static function xmlspecialchars($val) + { + //note, badchars does not include \t\n\r (\x09\x0a\x0d) + static $badchars = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\v\f\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"; + static $goodchars = " "; + return \strtr(\htmlspecialchars((string) $val, \ENT_QUOTES | \ENT_XML1 | \ENT_SUBSTITUTE), $badchars, $goodchars); + //strtr appears to be faster than str_replace + } + //------------------------------------------------------------------ + public static function array_first_key(array $arr) + { + \reset($arr); + $first_key = \key($arr); + return $first_key; + } + //------------------------------------------------------------------ + private static function determineNumberFormatType($num_format) + { + $num_format = \preg_replace("/\\[(Black|Blue|Cyan|Green|Magenta|Red|White|Yellow)\\]/i", "", $num_format); + if ($num_format == 'GENERAL') { + return 'n_auto'; + } + if ($num_format == '@') { + return 'n_string'; + } + if ($num_format == '0') { + return 'n_numeric'; + } + if (\preg_match('/[H]{1,2}:[M]{1,2}(?![^"]*+")/i', $num_format)) { + return 'n_datetime'; + } + if (\preg_match('/[M]{1,2}:[S]{1,2}(?![^"]*+")/i', $num_format)) { + return 'n_datetime'; + } + if (\preg_match('/[Y]{2,4}(?![^"]*+")/i', $num_format)) { + return 'n_date'; + } + if (\preg_match('/[D]{1,2}(?![^"]*+")/i', $num_format)) { + return 'n_date'; + } + if (\preg_match('/[M]{1,2}(?![^"]*+")/i', $num_format)) { + return 'n_date'; + } + if (\preg_match('/$(?![^"]*+")/', $num_format)) { + return 'n_numeric'; + } + if (\preg_match('/%(?![^"]*+")/', $num_format)) { + return 'n_numeric'; + } + if (\preg_match('/0(?![^"]*+")/', $num_format)) { + return 'n_numeric'; + } + return 'n_auto'; + } + //------------------------------------------------------------------ + private static function numberFormatStandardized($num_format) + { + if ($num_format == 'money') { + $num_format = 'dollar'; + } + if ($num_format == 'number') { + $num_format = 'integer'; + } + if ($num_format == 'string') { + $num_format = '@'; + } else { + if ($num_format == 'integer') { + $num_format = '0'; + } else { + if ($num_format == 'date') { + $num_format = 'YYYY-MM-DD'; + } else { + if ($num_format == 'datetime') { + $num_format = 'YYYY-MM-DD HH:MM:SS'; + } else { + if ($num_format == 'time') { + $num_format = 'HH:MM:SS'; + } else { + if ($num_format == 'price') { + $num_format = '#,##0.00'; + } else { + if ($num_format == 'dollar') { + $num_format = '[$$-1009]#,##0.00;[RED]-[$$-1009]#,##0.00'; + } else { + if ($num_format == 'euro') { + $num_format = '#,##0.00 [$€-407];[RED]-#,##0.00 [$€-407]'; + } + } + } + } + } + } + } + } + $ignore_until = ''; + $escaped = ''; + for ($i = 0, $ix = \strlen($num_format); $i < $ix; $i++) { + $c = $num_format[$i]; + if ($ignore_until == '' && $c == '[') { + $ignore_until = ']'; + } else { + if ($ignore_until == '' && $c == '"') { + $ignore_until = '"'; + } else { + if ($ignore_until == $c) { + $ignore_until = ''; + } + } + } + if ($ignore_until == '' && ($c == ' ' || $c == '-' || $c == '(' || $c == ')') && ($i == 0 || $num_format[$i - 1] != '_')) { + $escaped .= "\\" . $c; + } else { + $escaped .= $c; + } + } + return $escaped; + } + //------------------------------------------------------------------ + public static function add_to_list_get_index(&$haystack, $needle) + { + $existing_idx = \array_search($needle, $haystack, $strict = \true); + if ($existing_idx === \false) { + $existing_idx = \count($haystack); + $haystack[] = $needle; + } + return $existing_idx; + } + //------------------------------------------------------------------ + public static function convert_date_time($date_input) + { + $days = 0; + # Number of days since epoch + $seconds = 0; + # Time expressed as fraction of 24h hours in seconds + $year = $month = $day = 0; + $hour = $min = $sec = 0; + $date_time = $date_input; + if (\preg_match("/(\\d{4})\\-(\\d{2})\\-(\\d{2})/", $date_time, $matches)) { + list($junk, $year, $month, $day) = $matches; + } + if (\preg_match("/(\\d+):(\\d{2}):(\\d{2})/", $date_time, $matches)) { + list($junk, $hour, $min, $sec) = $matches; + $seconds = ($hour * 60 * 60 + $min * 60 + $sec) / (24 * 60 * 60); + } + //using 1900 as epoch, not 1904, ignoring 1904 special case + # Special cases for Excel. + if ("{$year}-{$month}-{$day}" == '1899-12-31') { + return $seconds; + } + # Excel 1900 epoch + if ("{$year}-{$month}-{$day}" == '1900-01-00') { + return $seconds; + } + # Excel 1900 epoch + if ("{$year}-{$month}-{$day}" == '1900-02-29') { + return 60 + $seconds; + } + # Excel false leapday + # We calculate the date by calculating the number of days since the epoch + # and adjust for the number of leap days. We calculate the number of leap + # days by normalising the year in relation to the epoch. Thus the year 2000 + # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays. + $epoch = 1900; + $offset = 0; + $norm = 300; + $range = $year - $epoch; + # Set month days and check for leap year. + $leap = $year % 400 == 0 || $year % 4 == 0 && $year % 100 ? 1 : 0; + $mdays = array(31, $leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + # Some boundary checks + if ($year != 0 || $month != 0 || $day != 0) { + if ($year < $epoch || $year > 9999) { + return 0; + } + if ($month < 1 || $month > 12) { + return 0; + } + if ($day < 1 || $day > $mdays[$month - 1]) { + return 0; + } + } + # Accumulate the number of days since the epoch. + $days = $day; + # Add days for current month + $days += \array_sum(\array_slice($mdays, 0, $month - 1)); + # Add days for past months + $days += $range * 365; + # Add days for past years + $days += \intval($range / 4); + # Add leapdays + $days -= \intval(($range + $offset) / 100); + # Subtract 100 year leapdays + $days += \intval(($range + $offset + $norm) / 400); + # Add 400 year leapdays + $days -= $leap; + # Already counted above + # Adjust for Excel erroneously treating 1900 as a leap year. + if ($days > 59) { + $days++; + } + return $days + $seconds; + } + //------------------------------------------------------------------ +} +class XLSXWriter_BuffererWriter +{ + protected $fd = null; + protected $buffer = ''; + protected $check_utf8 = \false; + public function __construct($filename, $fd_fopen_flags = 'w', $check_utf8 = \false) + { + $this->check_utf8 = $check_utf8; + $this->fd = \fopen($filename, $fd_fopen_flags); + if ($this->fd === \false) { + XLSXWriter::log("Unable to open {$filename} for writing."); + } + } + public function write($string) + { + $this->buffer .= $string; + if (isset($this->buffer[8191])) { + $this->purge(); + } + } + protected function purge() + { + if ($this->fd) { + if ($this->check_utf8 && !self::isValidUTF8($this->buffer)) { + XLSXWriter::log("Error, invalid UTF8 encoding detected."); + $this->check_utf8 = \false; + } + \fwrite($this->fd, $this->buffer); + $this->buffer = ''; + } + } + public function close() + { + $this->purge(); + if ($this->fd) { + \fclose($this->fd); + $this->fd = null; + } + } + public function __destruct() + { + $this->close(); + } + public function ftell() + { + if ($this->fd) { + $this->purge(); + return \ftell($this->fd); + } + return -1; + } + public function fseek($pos) + { + if ($this->fd) { + $this->purge(); + return \fseek($this->fd, $pos); + } + return -1; + } + protected static function isValidUTF8($string) + { + if (\function_exists('mb_check_encoding')) { + return \mb_check_encoding($string, 'UTF-8') ? \true : \false; + } + return \preg_match("//u", $string) ? \true : \false; + } +} +// vim: set filetype=php expandtab tabstop=4 shiftwidth=4 autoindent smartindent: diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/LICENSE new file mode 100644 index 00000000..5c970a42 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/AstRuntime.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/AstRuntime.php new file mode 100644 index 00000000..f30b3d84 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/AstRuntime.php @@ -0,0 +1,41 @@ +interpreter = new TreeInterpreter($fnDispatcher); + $this->parser = $parser ?: new Parser(); + } + /** + * Returns data from the provided input that matches a given JMESPath + * expression. + * + * @param string $expression JMESPath expression to evaluate + * @param mixed $data Data to search. This data should be data that + * is similar to data returned from json_decode + * using associative arrays rather than objects. + * + * @return mixed Returns the matching data or null + */ + public function __invoke($expression, $data) + { + if (!isset($this->cache[$expression])) { + // Clear the AST cache when it already holds 1024 entries. + if (\count($this->cache) >= 1024) { + $this->cache = []; + } + $this->cache[$expression] = $this->parser->parse($expression); + } + return $this->interpreter->visit($this->cache[$expression], $data); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/CompilerRuntime.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/CompilerRuntime.php new file mode 100644 index 00000000..fd6e3d2d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/CompilerRuntime.php @@ -0,0 +1,92 @@ +parser = $parser ?: new Parser(); + $this->compiler = new TreeCompiler(); + $dir = $dir ?: \sys_get_temp_dir(); + if (!\is_dir($dir) && !\mkdir($dir, 0755, \true)) { + throw new \RuntimeException("Unable to create cache directory: {$dir}"); + } + $this->cacheDir = \realpath($dir); + $this->interpreter = new TreeInterpreter(); + } + /** + * Returns data from the provided input that matches a given JMESPath + * expression. + * + * @param string $expression JMESPath expression to evaluate + * @param mixed $data Data to search. This data should be data that + * is similar to data returned from json_decode + * using associative arrays rather than objects. + * + * @return mixed Returns the matching data or null + * @throws \RuntimeException + */ + public function __invoke($expression, $data) + { + $functionName = self::functionName($expression); + if (!\function_exists($functionName)) { + $filename = "{$this->cacheDir}/{$functionName}.php"; + if (!\file_exists($filename)) { + $this->compile($filename, $expression, $functionName); + } + require $filename; + } + return $functionName($this->interpreter, $data); + } + /** + * @internal Shared with DebugRuntime so cache naming cannot drift. + */ + public static function functionName($expression) + { + return 'jmespath_' . \md5('jmespath:' . \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION . ':' . self::CACHE_VERSION . ':' . $expression); + } + /** @internal */ + public function getCacheDir() + { + return $this->cacheDir; + } + private function compile($filename, $expression, $functionName) + { + $code = $this->compiler->visit($this->parser->parse($expression), $functionName, $expression); + $tempFile = $filename . '.' . \bin2hex(\random_bytes(12)) . '.tmp'; + if (!\file_put_contents($tempFile, $code)) { + throw new \RuntimeException(\sprintf('Unable to write the compiled PHP code to: %s (%s)', $tempFile, \var_export(\error_get_last(), \true))); + } + if (!\rename($tempFile, $filename)) { + @\unlink($tempFile); + if (!\file_exists($filename)) { + throw new \RuntimeException("Unable to move the compiled PHP code to: {$filename}"); + } + // Another process won the race; its file is equivalent. + } elseif (\function_exists('opcache_invalidate')) { + \opcache_invalidate($filename, \true); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/DebugRuntime.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/DebugRuntime.php new file mode 100644 index 00000000..af2eea59 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/DebugRuntime.php @@ -0,0 +1,83 @@ +runtime = $runtime; + $this->out = $output ?: \STDOUT; + $this->lexer = new Lexer(); + $this->parser = new Parser($this->lexer); + } + public function __invoke($expression, $data) + { + if ($this->runtime instanceof CompilerRuntime) { + return $this->debugCompiled($expression, $data); + } + return $this->debugInterpreted($expression, $data); + } + private function debugInterpreted($expression, $data) + { + return $this->debugCallback(function () use($expression, $data) { + $runtime = $this->runtime; + return $runtime($expression, $data); + }, $expression, $data); + } + private function debugCompiled($expression, $data) + { + $result = $this->debugCallback(function () use($expression, $data) { + $runtime = $this->runtime; + return $runtime($expression, $data); + }, $expression, $data); + $this->dumpCompiledCode($expression); + return $result; + } + private function dumpTokens($expression) + { + $lexer = new Lexer(); + \fwrite($this->out, "Tokens\n======\n\n"); + $tokens = $lexer->tokenize($expression); + foreach ($tokens as $t) { + \fprintf($this->out, "%3d %-13s %s\n", $t['pos'], $t['type'], \json_encode($t['value'])); + } + \fwrite($this->out, "\n"); + } + private function dumpAst($expression) + { + $parser = new Parser(); + $ast = $parser->parse($expression); + \fwrite($this->out, "AST\n========\n\n"); + \fwrite($this->out, \json_encode($ast, \JSON_PRETTY_PRINT) . "\n"); + } + private function dumpCompiledCode($expression) + { + \fwrite($this->out, "Code\n========\n\n"); + $functionName = CompilerRuntime::functionName($expression); + $filename = $this->runtime->getCacheDir() . '/' . $functionName . '.php'; + \fwrite($this->out, "File: {$filename}\n\n"); + \fwrite($this->out, \is_file($filename) ? \file_get_contents($filename) : "(not present: {$functionName} was already loaded in this process, likely from another cache directory)\n"); + } + private function debugCallback(callable $debugFn, $expression, $data) + { + \fprintf($this->out, "Expression\n==========\n\n%s\n\n", $expression); + $this->dumpTokens($expression); + $this->dumpAst($expression); + \fprintf($this->out, "\nData\n====\n\n%s\n\n", \json_encode($data, \JSON_PRETTY_PRINT)); + $startTime = \microtime(\true); + $result = $debugFn(); + $total = \microtime(\true) - $startTime; + \fprintf($this->out, "\nResult\n======\n\n%s\n\n", \json_encode($result, \JSON_PRETTY_PRINT)); + \fwrite($this->out, "Time\n====\n\n"); + \fprintf($this->out, "Total time: %f ms\n\n", $total); + return $result; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Env.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Env.php new file mode 100644 index 00000000..3307c92d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Env.php @@ -0,0 +1,87 @@ +{'fn_' . $fn}($args); + } + private function fn_abs(array $args) + { + $this->validate('abs', $args, [['number']]); + return \abs($args[0]); + } + private function fn_avg(array $args) + { + $this->validate('avg', $args, [['array']]); + $sum = $this->reduce('avg:0', $args[0], ['number'], function ($a, $b) { + return Utils::add($a, $b); + }); + return $args[0] ? $sum / \count($args[0]) : null; + } + private function fn_ceil(array $args) + { + $this->validate('ceil', $args, [['number']]); + return \ceil($args[0]); + } + private function fn_contains(array $args) + { + $this->validate('contains', $args, [['string', 'array'], ['any']]); + if (\is_array($args[0])) { + foreach ($args[0] as $value) { + if (Utils::isEqual($value, $args[1])) { + return \true; + } + } + return \false; + } + return \is_string($args[1]) ? \mb_strpos($args[0], $args[1], 0, 'UTF-8') !== \false : \false; + } + private function fn_ends_with(array $args) + { + $this->validate('ends_with', $args, [['string'], ['string']]); + list($search, $suffix) = $args; + return $suffix === '' || \mb_substr($search, -\mb_strlen($suffix, 'UTF-8'), null, 'UTF-8') === $suffix; + } + private function fn_floor(array $args) + { + $this->validate('floor', $args, [['number']]); + return \floor($args[0]); + } + private function fn_not_null(array $args) + { + if (!$args) { + throw new \RuntimeException("not_null() expects 1 or more arguments, 0 were provided"); + } + return \array_reduce($args, function ($carry, $item) { + return $carry !== null ? $carry : $item; + }); + } + private function fn_join(array $args) + { + $this->validate('join', $args, [['string'], ['array']]); + $fn = function ($a, $b, $i) use($args) { + return $i ? $a . $args[0] . $b : $b; + }; + return $args[1] ? $this->reduce('join:0', $args[1], ['string'], $fn) : ''; + } + private function fn_keys(array $args) + { + $this->validate('keys', $args, [['object']]); + return \array_keys((array) $args[0]); + } + private function fn_length(array $args) + { + $this->validate('length', $args, [['string', 'array', 'object']]); + return \is_string($args[0]) ? \mb_strlen($args[0], 'UTF-8') : \count((array) $args[0]); + } + private function fn_max(array $args) + { + $this->validate('max', $args, [['array']]); + $fn = function ($a, $b, $i) { + return $i && self::compareValues($a, $b) >= 0 ? $a : $b; + }; + return $this->reduce('max:0', $args[0], ['number', 'string'], $fn); + } + private function fn_max_by(array $args) + { + $this->validate('max_by', $args, [['array'], ['expression']]); + $expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']); + $carryKey = null; + $fn = function ($carry, $item, $index) use($expr, &$carryKey) { + if (!$index) { + return $item; + } + if ($index === 1) { + $carryKey = $expr($carry); + } + $itemKey = $expr($item); + $this->validateSeq('max_by:0', ['number', 'string'], $carryKey, $itemKey); + if (self::compareValues($carryKey, $itemKey) >= 0) { + return $carry; + } + $carryKey = $itemKey; + return $item; + }; + return $this->reduce('max_by:1', $args[0], ['any'], $fn); + } + private function fn_min(array $args) + { + $this->validate('min', $args, [['array']]); + $fn = function ($a, $b, $i) { + return $i && self::compareValues($a, $b) <= 0 ? $a : $b; + }; + return $this->reduce('min:0', $args[0], ['number', 'string'], $fn); + } + private function fn_min_by(array $args) + { + $this->validate('min_by', $args, [['array'], ['expression']]); + $expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']); + $carryKey = null; + $fn = function ($carry, $item, $index) use($expr, &$carryKey) { + if (!$index) { + return $item; + } + if ($index === 1) { + $carryKey = $expr($carry); + } + $itemKey = $expr($item); + $this->validateSeq('min_by:0', ['number', 'string'], $carryKey, $itemKey); + if (self::compareValues($carryKey, $itemKey) <= 0) { + return $carry; + } + $carryKey = $itemKey; + return $item; + }; + return $this->reduce('min_by:1', $args[0], ['any'], $fn); + } + private function fn_reverse(array $args) + { + $this->validate('reverse', $args, [['array', 'string']]); + if (\is_array($args[0])) { + return \array_reverse($args[0]); + } elseif (\is_string($args[0])) { + return \implode('', \array_reverse(\mb_str_split($args[0], 1, 'UTF-8'))); + } else { + throw new \RuntimeException('Cannot reverse provided argument'); + } + } + private function fn_sum(array $args) + { + $this->validate('sum', $args, [['array']]); + $fn = function ($a, $b) { + return Utils::add($a, $b); + }; + return $args[0] ? $this->reduce('sum:0', $args[0], ['number'], $fn) : 0; + } + private function fn_sort(array $args) + { + $this->validate('sort', $args, [['array']]); + $valid = ['string', 'number']; + return Utils::stableSort($args[0], function ($a, $b) use($valid) { + $this->validateSeq('sort:0', $valid, $a, $b); + return self::compareValues($a, $b); + }); + } + private function fn_sort_by(array $args) + { + $this->validate('sort_by', $args, [['array'], ['expression']]); + $expr = $args[1]; + $valid = ['string', 'number']; + return Utils::stableSort($args[0], function ($a, $b) use($expr, $valid) { + $va = $expr($a); + $vb = $expr($b); + $this->validateSeq('sort_by:0', $valid, $va, $vb); + return self::compareValues($va, $vb); + }); + } + private function fn_starts_with(array $args) + { + $this->validate('starts_with', $args, [['string'], ['string']]); + list($search, $prefix) = $args; + return $prefix === '' || \mb_strpos($search, $prefix, 0, 'UTF-8') === 0; + } + private function fn_type(array $args) + { + $this->validateArity('type', \count($args), 1); + return Utils::type($args[0]); + } + private function fn_to_string(array $args) + { + $this->validateArity('to_string', \count($args), 1); + $v = $args[0]; + if (\is_string($v)) { + return $v; + } elseif (\is_object($v) && !$v instanceof \JsonSerializable && \method_exists($v, '__toString')) { + return (string) $v; + } + return \json_encode($v); + } + private function fn_to_number(array $args) + { + $this->validateArity('to_number', \count($args), 1); + $value = $args[0]; + if (Utils::type($value) == 'number') { + return $value; + } + if (!\is_string($value)) { + return null; + } + return $this->parseJsonNumber($value); + } + /** + * Parses a string conforming to the JSON number grammar (RFC 8259) into + * an int when exactly representable, otherwise a float. Returns null for + * non-conforming or non-finite input. + */ + private function parseJsonNumber($value) + { + if (!\preg_match('/^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/D', $value)) { + return null; + } + if (\preg_match('/^-?(?:0|[1-9][0-9]*)$/D', $value)) { + return $this->parseJsonInteger($value); + } + $number = (float) $value; + return \is_finite($number) ? $number : null; + } + private function parseJsonInteger($value) + { + $negative = $value[0] === '-'; + $digits = $negative ? \substr($value, 1) : $value; + $limit = $negative ? \substr((string) \PHP_INT_MIN, 1) : (string) \PHP_INT_MAX; + if (\strlen($digits) < \strlen($limit) || \strlen($digits) === \strlen($limit) && \strcmp($digits, $limit) <= 0) { + return (int) $value; + } + $number = (float) $value; + return \is_finite($number) ? $number : null; + } + private function fn_values(array $args) + { + $this->validate('values', $args, [['array', 'object']]); + return \array_values((array) $args[0]); + } + private function fn_merge(array $args) + { + if (!$args) { + throw new \RuntimeException("merge() expects 1 or more arguments, 0 were provided"); + } + return \call_user_func_array('array_replace', $args); + } + private function fn_to_array(array $args) + { + $this->validate('to_array', $args, [['any']]); + return Utils::isArray($args[0]) ? $args[0] : [$args[0]]; + } + private function fn_map(array $args) + { + $this->validate('map', $args, [['expression'], ['array']]); + $result = []; + foreach ($args[1] as $a) { + $result[] = $args[0]($a); + } + return $result; + } + private function typeError($from, $msg) + { + if (\mb_strpos($from, ':', 0, 'UTF-8')) { + list($fn, $pos) = \explode(':', $from); + throw new \RuntimeException(\sprintf('Argument %d of %s %s', $pos, $fn, $msg)); + } else { + throw new \RuntimeException(\sprintf('Type error: %s %s', $from, $msg)); + } + } + private function validateArity($from, $given, $expected) + { + if ($given != $expected) { + $err = "%s() expects {$expected} arguments, {$given} were provided"; + throw new \RuntimeException(\sprintf($err, $from)); + } + } + private function validate($from, $args, $types = []) + { + $this->validateArity($from, \count($args), \count($types)); + foreach ($args as $index => $value) { + if (!isset($types[$index]) || !$types[$index]) { + continue; + } + $this->validateType("{$from}:{$index}", $value, $types[$index]); + } + } + private function validateType($from, $value, array $types) + { + if ($types[0] == 'any' || \in_array(Utils::type($value), $types) || $value === [] && \in_array('object', $types)) { + return; + } + $msg = 'must be one of the following types: ' . \implode(', ', $types) . '. ' . Utils::type($value) . ' found'; + $this->typeError($from, $msg); + } + /** + * Validates value A and B, ensures they both are correctly typed, and of + * the same type. + * + * @param string $from String of function:argument_position + * @param array $types Array of valid value types. + * @param mixed $a Value A + * @param mixed $b Value B + */ + private function validateSeq($from, array $types, $a, $b) + { + $ta = Utils::type($a); + $tb = Utils::type($b); + if ($ta !== $tb) { + $msg = "encountered a type mismatch in sequence: {$ta}, {$tb}"; + $this->typeError($from, $msg); + } + $typeMatch = $types && $types[0] == 'any' || \in_array($ta, $types); + if (!$typeMatch) { + $msg = 'encountered a type error in sequence. The argument must be ' . 'an array of ' . \implode('|', $types) . ' types. ' . "Found {$ta}, {$tb}."; + $this->typeError($from, $msg); + } + } + /** + * Compares two values of the same JMESPath type. + * + * @param mixed $a Value A + * @param mixed $b Value B + * + * @return int Negative if $a < $b, zero if equal, positive if $a > $b. + */ + private static function compareValues($a, $b) + { + return Utils::type($a) === 'string' ? \strcmp((string) $a, (string) $b) : $a <=> $b; + } + /** + * Reduces and validates an array of values to a single value using a fn. + * + * @param string $from String of function:argument_position + * @param array $values Values to reduce. + * @param array $types Array of valid value types. + * @param callable $reduce Reduce function that accepts ($carry, $item). + * + * @return mixed + */ + private function reduce($from, array $values, array $types, callable $reduce) + { + $i = -1; + return \array_reduce($values, function ($carry, $item) use($from, $types, $reduce, &$i) { + if (++$i > 0) { + $this->validateSeq($from, $types, $carry, $item); + } + return $reduce($carry, $item, $i); + }); + } + /** + * Validates the return values of expressions as they are applied. + * + * @param string $from Function name : position + * @param callable $expr Expression function to validate. + * @param array $types Array of acceptable return type values. + * + * @return callable Returns a wrapped function + */ + private function wrapExpression($from, callable $expr, array $types) + { + list($fn, $pos) = \explode(':', $from); + $from = "The expression return value of argument {$pos} of {$fn}"; + return function ($value) use($from, $expr, $types) { + $value = $expr($value); + $this->validateType($from, $value, $types); + return $value; + }; + } + /** @internal Pass function name validation off to runtime */ + public function __call($name, $args) + { + $name = \str_replace('fn_', '', $name); + throw new \RuntimeException("Call to undefined function {$name}"); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/JmesPath.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/JmesPath.php new file mode 100644 index 00000000..ec73575c --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/JmesPath.php @@ -0,0 +1,18 @@ + self::STATE_LT, '>' => self::STATE_GT, '=' => self::STATE_EQ, '!' => self::STATE_NOT, '[' => self::STATE_LBRACKET, '|' => self::STATE_PIPE, '&' => self::STATE_AND, '`' => self::STATE_JSON_LITERAL, '"' => self::STATE_QUOTED_STRING, "'" => self::STATE_STRING_LITERAL, '-' => self::STATE_NUMBER, '0' => self::STATE_NUMBER, '1' => self::STATE_NUMBER, '2' => self::STATE_NUMBER, '3' => self::STATE_NUMBER, '4' => self::STATE_NUMBER, '5' => self::STATE_NUMBER, '6' => self::STATE_NUMBER, '7' => self::STATE_NUMBER, '8' => self::STATE_NUMBER, '9' => self::STATE_NUMBER, ' ' => self::STATE_WHITESPACE, "\t" => self::STATE_WHITESPACE, "\n" => self::STATE_WHITESPACE, "\r" => self::STATE_WHITESPACE, '.' => self::STATE_SINGLE_CHAR, '*' => self::STATE_SINGLE_CHAR, ']' => self::STATE_SINGLE_CHAR, ',' => self::STATE_SINGLE_CHAR, ':' => self::STATE_SINGLE_CHAR, '@' => self::STATE_SINGLE_CHAR, '(' => self::STATE_SINGLE_CHAR, ')' => self::STATE_SINGLE_CHAR, '{' => self::STATE_SINGLE_CHAR, '}' => self::STATE_SINGLE_CHAR, '_' => self::STATE_IDENTIFIER, 'A' => self::STATE_IDENTIFIER, 'B' => self::STATE_IDENTIFIER, 'C' => self::STATE_IDENTIFIER, 'D' => self::STATE_IDENTIFIER, 'E' => self::STATE_IDENTIFIER, 'F' => self::STATE_IDENTIFIER, 'G' => self::STATE_IDENTIFIER, 'H' => self::STATE_IDENTIFIER, 'I' => self::STATE_IDENTIFIER, 'J' => self::STATE_IDENTIFIER, 'K' => self::STATE_IDENTIFIER, 'L' => self::STATE_IDENTIFIER, 'M' => self::STATE_IDENTIFIER, 'N' => self::STATE_IDENTIFIER, 'O' => self::STATE_IDENTIFIER, 'P' => self::STATE_IDENTIFIER, 'Q' => self::STATE_IDENTIFIER, 'R' => self::STATE_IDENTIFIER, 'S' => self::STATE_IDENTIFIER, 'T' => self::STATE_IDENTIFIER, 'U' => self::STATE_IDENTIFIER, 'V' => self::STATE_IDENTIFIER, 'W' => self::STATE_IDENTIFIER, 'X' => self::STATE_IDENTIFIER, 'Y' => self::STATE_IDENTIFIER, 'Z' => self::STATE_IDENTIFIER, 'a' => self::STATE_IDENTIFIER, 'b' => self::STATE_IDENTIFIER, 'c' => self::STATE_IDENTIFIER, 'd' => self::STATE_IDENTIFIER, 'e' => self::STATE_IDENTIFIER, 'f' => self::STATE_IDENTIFIER, 'g' => self::STATE_IDENTIFIER, 'h' => self::STATE_IDENTIFIER, 'i' => self::STATE_IDENTIFIER, 'j' => self::STATE_IDENTIFIER, 'k' => self::STATE_IDENTIFIER, 'l' => self::STATE_IDENTIFIER, 'm' => self::STATE_IDENTIFIER, 'n' => self::STATE_IDENTIFIER, 'o' => self::STATE_IDENTIFIER, 'p' => self::STATE_IDENTIFIER, 'q' => self::STATE_IDENTIFIER, 'r' => self::STATE_IDENTIFIER, 's' => self::STATE_IDENTIFIER, 't' => self::STATE_IDENTIFIER, 'u' => self::STATE_IDENTIFIER, 'v' => self::STATE_IDENTIFIER, 'w' => self::STATE_IDENTIFIER, 'x' => self::STATE_IDENTIFIER, 'y' => self::STATE_IDENTIFIER, 'z' => self::STATE_IDENTIFIER]; + /** @var array Valid identifier characters after first character */ + private $validIdentifier = ['A' => \true, 'B' => \true, 'C' => \true, 'D' => \true, 'E' => \true, 'F' => \true, 'G' => \true, 'H' => \true, 'I' => \true, 'J' => \true, 'K' => \true, 'L' => \true, 'M' => \true, 'N' => \true, 'O' => \true, 'P' => \true, 'Q' => \true, 'R' => \true, 'S' => \true, 'T' => \true, 'U' => \true, 'V' => \true, 'W' => \true, 'X' => \true, 'Y' => \true, 'Z' => \true, 'a' => \true, 'b' => \true, 'c' => \true, 'd' => \true, 'e' => \true, 'f' => \true, 'g' => \true, 'h' => \true, 'i' => \true, 'j' => \true, 'k' => \true, 'l' => \true, 'm' => \true, 'n' => \true, 'o' => \true, 'p' => \true, 'q' => \true, 'r' => \true, 's' => \true, 't' => \true, 'u' => \true, 'v' => \true, 'w' => \true, 'x' => \true, 'y' => \true, 'z' => \true, '_' => \true, '0' => \true, '1' => \true, '2' => \true, '3' => \true, '4' => \true, '5' => \true, '6' => \true, '7' => \true, '8' => \true, '9' => \true]; + /** @var array Valid number characters after the first character */ + private $numbers = ['0' => \true, '1' => \true, '2' => \true, '3' => \true, '4' => \true, '5' => \true, '6' => \true, '7' => \true, '8' => \true, '9' => \true]; + /** @var array Map of simple single character tokens */ + private $simpleTokens = ['.' => self::T_DOT, '*' => self::T_STAR, ']' => self::T_RBRACKET, ',' => self::T_COMMA, ':' => self::T_COLON, '@' => self::T_CURRENT, '(' => self::T_LPAREN, ')' => self::T_RPAREN, '{' => self::T_LBRACE, '}' => self::T_RBRACE]; + /** + * Tokenize the JMESPath expression into an array of tokens hashes that + * contain a 'type', 'value', and 'key'. + * + * @param string $input JMESPath input + * + * @return array + * @throws SyntaxErrorException + */ + public function tokenize($input) + { + $tokens = []; + if ($input === '') { + goto eof; + } + $chars = \str_split($input); + while (\false !== ($current = \current($chars))) { + // Every character must be in the transition character table. + if (!isset(self::$transitionTable[$current])) { + $tokens[] = ['type' => self::T_UNKNOWN, 'pos' => \key($chars), 'value' => $current]; + \next($chars); + continue; + } + $state = self::$transitionTable[$current]; + if ($state === self::STATE_SINGLE_CHAR) { + // Consume simple tokens like ".", ",", "@", etc. + $tokens[] = ['type' => $this->simpleTokens[$current], 'pos' => \key($chars), 'value' => $current]; + \next($chars); + } elseif ($state === self::STATE_IDENTIFIER) { + // Consume identifiers + $start = \key($chars); + $buffer = ''; + do { + $buffer .= $current; + $current = \next($chars); + } while ($current !== \false && isset($this->validIdentifier[$current])); + $tokens[] = ['type' => self::T_IDENTIFIER, 'value' => $buffer, 'pos' => $start]; + } elseif ($state === self::STATE_WHITESPACE) { + // Skip whitespace + \next($chars); + } elseif ($state === self::STATE_LBRACKET) { + // Consume "[", "[?", and "[]" + $position = \key($chars); + $actual = \next($chars); + if ($actual === ']') { + \next($chars); + $tokens[] = ['type' => self::T_FLATTEN, 'pos' => $position, 'value' => '[]']; + } elseif ($actual === '?') { + \next($chars); + $tokens[] = ['type' => self::T_FILTER, 'pos' => $position, 'value' => '[?']; + } else { + $tokens[] = ['type' => self::T_LBRACKET, 'pos' => $position, 'value' => '[']; + } + } elseif ($state === self::STATE_STRING_LITERAL) { + // Consume raw string literals + $t = $this->inside($chars, "'", self::T_LITERAL); + $t['value'] = \str_replace("\\'", "'", $t['value']); + $tokens[] = $t; + } elseif ($state === self::STATE_PIPE) { + // Consume pipe and OR + $tokens[] = $this->matchOr($chars, '|', '|', self::T_OR, self::T_PIPE); + } elseif ($state == self::STATE_JSON_LITERAL) { + // Consume JSON literals + $token = $this->inside($chars, '`', self::T_LITERAL); + if ($token['type'] === self::T_LITERAL) { + $token['value'] = \str_replace('\\`', '`', $token['value']); + $token = $this->parseJson($token); + } + $tokens[] = $token; + } elseif ($state == self::STATE_NUMBER) { + // Consume numbers + $start = \key($chars); + $buffer = ''; + do { + $buffer .= $current; + $current = \next($chars); + } while ($current !== \false && isset($this->numbers[$current])); + $value = $this->parseIndexNumber($buffer); + $tokens[] = ['type' => $value === null ? self::T_UNKNOWN : self::T_NUMBER, 'value' => $value === null ? $buffer : $value, 'pos' => $start]; + } elseif ($state === self::STATE_QUOTED_STRING) { + // Consume quoted identifiers + $token = $this->inside($chars, '"', self::T_QUOTED_IDENTIFIER); + if ($token['type'] === self::T_QUOTED_IDENTIFIER) { + $token['value'] = '"' . $token['value'] . '"'; + $token = $this->parseJson($token); + } + $tokens[] = $token; + } elseif ($state === self::STATE_EQ) { + // Consume equals + $tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN); + } elseif ($state == self::STATE_AND) { + $tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF); + } elseif ($state === self::STATE_NOT) { + // Consume not equal + $tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT); + } else { + // either '<' or '>' + // Consume less than and greater than + $tokens[] = $this->matchOr($chars, $current, '=', self::T_COMPARATOR, self::T_COMPARATOR); + } + } + eof: + $tokens[] = ['type' => self::T_EOF, 'pos' => \mb_strlen($input, 'UTF-8'), 'value' => null]; + return $tokens; + } + /** + * Returns a token based on whether or not the next token matches the + * expected value. If it does, a token of "$type" is returned. Otherwise, + * a token of "$orElse" type is returned. + * + * @param array $chars Array of characters by reference. + * @param string $current The current character. + * @param string $expected Expected character. + * @param string $type Expected result type. + * @param string $orElse Otherwise return a token of this type. + * + * @return array Returns a conditional token. + */ + private function matchOr(array &$chars, $current, $expected, $type, $orElse) + { + if (\next($chars) === $expected) { + \next($chars); + return ['type' => $type, 'pos' => \key($chars) - 1, 'value' => $current . $expected]; + } + return ['type' => $orElse, 'pos' => \key($chars) - 1, 'value' => $current]; + } + /** + * Returns a token the is the result of consuming inside of delimiter + * characters. Escaped delimiters will be adjusted before returning a + * value. If the token is not closed, "unknown" is returned. + * + * @param array $chars Array of characters by reference. + * @param string $delim The delimiter character. + * @param string $type Token type. + * + * @return array Returns the consumed token. + */ + private function inside(array &$chars, $delim, $type) + { + $position = \key($chars); + $current = \next($chars); + $buffer = ''; + while ($current !== $delim) { + if ($current === '\\') { + $buffer .= '\\'; + $current = \next($chars); + } + if ($current === \false) { + // Unclosed delimiter + return ['type' => self::T_UNKNOWN, 'value' => $buffer, 'pos' => $position]; + } + $buffer .= $current; + $current = \next($chars); + } + \next($chars); + return ['type' => $type, 'value' => $buffer, 'pos' => $position]; + } + /** + * Parses a bare index/slice integer token ("-"? digit+). Returns null + * when the buffer is a lone "-" or the value cannot be represented as a + * PHP integer. + */ + private function parseIndexNumber($buffer) + { + if ($buffer === '-') { + return null; + } + $negative = $buffer[0] === '-'; + $digits = \ltrim($negative ? \substr($buffer, 1) : $buffer, '0') ?: '0'; + $limit = $negative ? \substr((string) \PHP_INT_MIN, 1) : (string) \PHP_INT_MAX; + if (\strlen($digits) > \strlen($limit) || \strlen($digits) === \strlen($limit) && \strcmp($digits, $limit) > 0) { + return null; + } + return (int) $buffer; + } + /** + * Parses a JSON token or sets the token type to "unknown" on error. + * + * @param array $token Token that needs parsing. + * + * @return array Returns a token with a parsed value. + */ + private function parseJson(array $token) + { + $value = \json_decode($token['value'], \true); + if ($error = \json_last_error()) { + // Legacy support for elided quotes. Try to parse again by adding + // quotes around the bad input value. + $value = \json_decode('"' . $token['value'] . '"', \true); + if ($error = \json_last_error()) { + $token['type'] = self::T_UNKNOWN; + return $token; + } + } + $token['value'] = $value; + return $token; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Parser.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Parser.php new file mode 100644 index 00000000..260f2546 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Parser.php @@ -0,0 +1,363 @@ + T::T_EOF]; + private static $currentNode = ['type' => T::T_CURRENT]; + private static $bp = [T::T_EOF => 0, T::T_QUOTED_IDENTIFIER => 0, T::T_IDENTIFIER => 0, T::T_UNKNOWN => 0, T::T_LITERAL => 0, T::T_RBRACKET => 0, T::T_RPAREN => 0, T::T_COMMA => 0, T::T_RBRACE => 0, T::T_NUMBER => 0, T::T_CURRENT => 0, T::T_EXPREF => 0, T::T_COLON => 0, T::T_PIPE => 1, T::T_OR => 2, T::T_AND => 3, T::T_COMPARATOR => 5, T::T_FLATTEN => 9, T::T_STAR => 20, T::T_FILTER => 21, T::T_DOT => 40, T::T_NOT => 45, T::T_LBRACE => 50, T::T_LBRACKET => 55, T::T_LPAREN => 60]; + /** @var array Acceptable tokens after a dot token */ + private static $afterDot = [ + T::T_IDENTIFIER => \true, + // foo.bar + T::T_QUOTED_IDENTIFIER => \true, + // foo."bar" + T::T_STAR => \true, + // foo.* + T::T_LBRACE => \true, + // foo[1] + T::T_LBRACKET => \true, + // foo{a: 0} + T::T_FILTER => \true, + ]; + /** + * @param Lexer|null $lexer Lexer used to tokenize expressions + */ + public function __construct(?Lexer $lexer = null) + { + $this->lexer = $lexer ?: new Lexer(); + } + /** + * Parses a JMESPath expression into an AST + * + * @param string $expression JMESPath expression to compile + * + * @return array Returns an array based AST + * @throws SyntaxErrorException + */ + public function parse($expression) + { + $this->expression = $expression; + $this->tokens = $this->lexer->tokenize($expression); + $this->tpos = -1; + $this->next(); + $result = $this->expr(); + if ($this->token['type'] === T::T_EOF) { + return $result; + } + throw $this->syntax('Did not reach the end of the token stream'); + } + /** + * Parses an expression while rbp < lbp. + * + * @param int $rbp Right bound precedence + * + * @return array + */ + private function expr($rbp = 0) + { + $left = $this->{"nud_{$this->token['type']}"}(); + while ($rbp < self::$bp[$this->token['type']]) { + $left = $this->{"led_{$this->token['type']}"}($left); + } + return $left; + } + private function nud_identifier() + { + $token = $this->token; + $this->next(); + return ['type' => 'field', 'value' => $token['value']]; + } + private function nud_quoted_identifier() + { + $token = $this->token; + $this->next(); + $this->assertNotToken(T::T_LPAREN); + return ['type' => 'field', 'value' => $token['value']]; + } + private function nud_current() + { + $this->next(); + return self::$currentNode; + } + private function nud_literal() + { + $token = $this->token; + $this->next(); + return ['type' => 'literal', 'value' => $token['value']]; + } + private function nud_expref() + { + $this->next(); + return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]]; + } + private function nud_not() + { + $this->next(); + return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]]; + } + private function nud_lparen() + { + $this->next(); + $result = $this->expr(0); + if ($this->token['type'] !== T::T_RPAREN) { + throw $this->syntax('Unclosed `(`'); + } + $this->next(); + return $result; + } + private function nud_lbrace() + { + static $validKeys = [T::T_QUOTED_IDENTIFIER => \true, T::T_IDENTIFIER => \true]; + $this->next($validKeys); + $pairs = []; + do { + $pairs[] = $this->parseKeyValuePair(); + if ($this->token['type'] == T::T_COMMA) { + $this->next($validKeys); + } + } while ($this->token['type'] !== T::T_RBRACE); + $this->next(); + return ['type' => 'multi_select_hash', 'children' => $pairs]; + } + private function nud_flatten() + { + return $this->led_flatten(self::$currentNode); + } + private function nud_filter() + { + return $this->led_filter(self::$currentNode); + } + private function nud_star() + { + return $this->parseWildcardObject(self::$currentNode); + } + private function nud_lbracket() + { + $this->next(); + $type = $this->token['type']; + if ($type == T::T_NUMBER || $type == T::T_COLON) { + return $this->parseArrayIndexExpression(); + } elseif ($type == T::T_STAR && $this->lookahead() == T::T_RBRACKET) { + return $this->parseWildcardArray(); + } else { + return $this->parseMultiSelectList(); + } + } + private function led_lbracket(array $left) + { + static $nextTypes = [T::T_NUMBER => \true, T::T_COLON => \true, T::T_STAR => \true]; + $this->next($nextTypes); + switch ($this->token['type']) { + case T::T_NUMBER: + case T::T_COLON: + return ['type' => 'subexpression', 'children' => [$left, $this->parseArrayIndexExpression()]]; + default: + return $this->parseWildcardArray($left); + } + } + private function led_flatten(array $left) + { + $this->next(); + return ['type' => 'projection', 'from' => 'array', 'children' => [['type' => T::T_FLATTEN, 'children' => [$left]], $this->parseProjection(self::$bp[T::T_FLATTEN])]]; + } + private function led_dot(array $left) + { + $this->next(self::$afterDot); + if ($this->token['type'] == T::T_STAR) { + return $this->parseWildcardObject($left); + } + return ['type' => 'subexpression', 'children' => [$left, $this->parseDot(self::$bp[T::T_DOT])]]; + } + private function led_or(array $left) + { + $this->next(); + return ['type' => T::T_OR, 'children' => [$left, $this->expr(self::$bp[T::T_OR])]]; + } + private function led_and(array $left) + { + $this->next(); + return ['type' => T::T_AND, 'children' => [$left, $this->expr(self::$bp[T::T_AND])]]; + } + private function led_pipe(array $left) + { + $this->next(); + return ['type' => T::T_PIPE, 'children' => [$left, $this->expr(self::$bp[T::T_PIPE])]]; + } + private function led_lparen(array $left) + { + if (!isset($left['type'], $left['value']) || $left['type'] !== 'field') { + throw $this->syntax('Invalid function name'); + } + $args = []; + $this->next(); + while ($this->token['type'] != T::T_RPAREN) { + $args[] = $this->expr(0); + if ($this->token['type'] == T::T_COMMA) { + $this->next(); + } + } + $this->next(); + return ['type' => 'function', 'value' => $left['value'], 'children' => $args]; + } + private function led_filter(array $left) + { + $this->next(); + $expression = $this->expr(); + if ($this->token['type'] != T::T_RBRACKET) { + throw $this->syntax('Expected a closing rbracket for the filter'); + } + $this->next(); + $rhs = $this->parseProjection(self::$bp[T::T_FILTER]); + return ['type' => 'projection', 'from' => 'array', 'children' => [$left ?: self::$currentNode, ['type' => 'condition', 'children' => [$expression, $rhs]]]]; + } + private function led_comparator(array $left) + { + $token = $this->token; + $this->next(); + return ['type' => T::T_COMPARATOR, 'value' => $token['value'], 'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])]]; + } + private function parseProjection($bp) + { + $type = $this->token['type']; + if (self::$bp[$type] < 10) { + return self::$currentNode; + } elseif ($type == T::T_DOT) { + $this->next(self::$afterDot); + return $this->parseDot($bp); + } elseif ($type == T::T_LBRACKET || $type == T::T_FILTER) { + return $this->expr($bp); + } + throw $this->syntax('Syntax error after projection'); + } + private function parseDot($bp) + { + if ($this->token['type'] == T::T_LBRACKET) { + $this->next(); + return $this->parseMultiSelectList(); + } elseif ($this->token['type'] == T::T_LBRACE) { + // Like the multi-select list above, a multi-select hash ends any + // projection: tokens that follow apply to the projected list. + return $this->nud_lbrace(); + } + return $this->expr($bp); + } + private function parseKeyValuePair() + { + static $validColon = [T::T_COLON => \true]; + $key = $this->token['value']; + $this->next($validColon); + $this->next(); + return ['type' => 'key_val_pair', 'value' => $key, 'children' => [$this->expr()]]; + } + private function parseWildcardObject(?array $left = null) + { + $this->next(); + return ['type' => 'projection', 'from' => 'object', 'children' => [$left ?: self::$currentNode, $this->parseProjection(self::$bp[T::T_STAR])]]; + } + private function parseWildcardArray(?array $left = null) + { + static $getRbracket = [T::T_RBRACKET => \true]; + $this->next($getRbracket); + $this->next(); + return ['type' => 'projection', 'from' => 'array', 'children' => [$left ?: self::$currentNode, $this->parseProjection(self::$bp[T::T_STAR])]]; + } + /** + * Parses an array index expression (e.g., [0], [1:2:3] + */ + private function parseArrayIndexExpression() + { + static $matchNext = [T::T_NUMBER => \true, T::T_COLON => \true, T::T_RBRACKET => \true]; + $pos = 0; + $parts = [null, null, null]; + $expected = $matchNext; + do { + if ($this->token['type'] == T::T_COLON) { + $pos++; + $expected = $matchNext; + } elseif ($this->token['type'] == T::T_NUMBER) { + $parts[$pos] = $this->token['value']; + $expected = [T::T_COLON => \true, T::T_RBRACKET => \true]; + } + $this->next($expected); + } while ($this->token['type'] != T::T_RBRACKET); + // Consume the closing bracket + $this->next(); + if ($pos === 0) { + // No colons were found so this is a simple index extraction + return ['type' => 'index', 'value' => $parts[0]]; + } + if ($pos > 2) { + throw $this->syntax('Invalid array slice syntax: too many colons'); + } + // Sliced array from start (e.g., [2:]) + return ['type' => 'projection', 'from' => 'array', 'children' => [['type' => 'slice', 'value' => $parts], $this->parseProjection(self::$bp[T::T_STAR])]]; + } + private function parseMultiSelectList() + { + $nodes = []; + do { + $nodes[] = $this->expr(); + if ($this->token['type'] == T::T_COMMA) { + $this->next(); + $this->assertNotToken(T::T_RBRACKET); + } + } while ($this->token['type'] !== T::T_RBRACKET); + $this->next(); + return ['type' => 'multi_select_list', 'children' => $nodes]; + } + private function syntax($msg) + { + return new SyntaxErrorException($msg, $this->token, $this->expression); + } + private function lookahead() + { + return !isset($this->tokens[$this->tpos + 1]) ? T::T_EOF : $this->tokens[$this->tpos + 1]['type']; + } + private function next(?array $match = null) + { + if (!isset($this->tokens[$this->tpos + 1])) { + $this->token = self::$nullToken; + } else { + $this->token = $this->tokens[++$this->tpos]; + } + if ($match && !isset($match[$this->token['type']])) { + throw $this->syntax($match); + } + } + private function assertNotToken($type) + { + if ($this->token['type'] == $type) { + throw $this->syntax("Token {$this->tpos} not allowed to be {$type}"); + } + } + /** + * @internal Handles undefined tokens without paying the cost of validation + */ + public function __call($method, $args) + { + $prefix = \substr($method, 0, 4); + if ($prefix == 'nud_' || $prefix == 'led_') { + $token = \substr($method, 4); + $message = "Unexpected \"{$token}\" token ({$method}). Expected one of" . " the following tokens: " . \implode(', ', \array_map(function ($i) { + return '"' . \substr($i, 4) . '"'; + }, \array_filter(\get_class_methods($this), function ($i) use($prefix) { + return \strpos($i, $prefix) === 0; + }))); + throw $this->syntax($message); + } + throw new \BadMethodCallException("Call to undefined method {$method}"); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/SyntaxErrorException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/SyntaxErrorException.php new file mode 100644 index 00000000..6b3a0d7f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/SyntaxErrorException.php @@ -0,0 +1,26 @@ + \mb_strlen($expression, 'UTF-8'), 'value' => null]; + $message = \sprintf("Syntax error at character %d\n", \max($token['pos'], 0)) . $expression . "\n" . \str_repeat(' ', \max($token['pos'], 0)) . "^\n"; + $message .= !\is_array($expectedTypesOrMessage) ? $expectedTypesOrMessage : $this->createTokenMessage($token, $expectedTypesOrMessage); + parent::__construct($message); + } + private function createTokenMessage(array $token, array $valid) + { + return \sprintf('Expected one of the following: %s; found %s "%s"', \implode(', ', \array_keys($valid)), $token['type'], $token['value']); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeCompiler.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeCompiler.php new file mode 100644 index 00000000..de75168e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeCompiler.php @@ -0,0 +1,225 @@ +vars = []; + $this->source = $this->indentation = ''; + $this->write("write('use JmesPath\\TreeInterpreter as Ti;')->write('use JmesPath\\FnDispatcher as Fd;')->write('use JmesPath\\Utils;')->write('')->write('function %s(Ti $interpreter, $value) {', $fnName)->indent()->dispatch($ast)->write('')->write('return $value;')->outdent()->write('}'); + return $this->source; + } + /** + * @param array $node + * @return mixed + */ + private function dispatch(array $node) + { + return $this->{"visit_{$node['type']}"}($node); + } + /** + * Creates a monotonically incrementing unique variable name by prefix. + * + * @param string $prefix Variable name prefix + * + * @return string + */ + private function makeVar($prefix) + { + if (!isset($this->vars[$prefix])) { + $this->vars[$prefix] = 0; + return '$' . $prefix; + } + return '$' . $prefix . ++$this->vars[$prefix]; + } + /** + * Writes the given line of source code. Pass positional arguments to write + * that match the format of sprintf. + * + * @param string $str String to write + * @return $this + */ + private function write($str) + { + $this->source .= $this->indentation; + if (\func_num_args() == 1) { + $this->source .= $str . "\n"; + return $this; + } + $this->source .= \vsprintf($str, \array_slice(\func_get_args(), 1)) . "\n"; + return $this; + } + /** + * Decreases the indentation level of code being written + * @return $this + */ + private function outdent() + { + $this->indentation = \substr($this->indentation, 0, -4); + return $this; + } + /** + * Increases the indentation level of code being written + * @return $this + */ + private function indent() + { + $this->indentation .= ' '; + return $this; + } + private function visit_or(array $node) + { + $a = $this->makeVar('beforeOr'); + return $this->write('%s = $value;', $a)->dispatch($node['children'][0])->write('if (!Utils::isTruthy($value)) {')->indent()->write('$value = %s;', $a)->dispatch($node['children'][1])->outdent()->write('}'); + } + private function visit_and(array $node) + { + $a = $this->makeVar('beforeAnd'); + return $this->write('%s = $value;', $a)->dispatch($node['children'][0])->write('if (Utils::isTruthy($value)) {')->indent()->write('$value = %s;', $a)->dispatch($node['children'][1])->outdent()->write('}'); + } + private function visit_not(array $node) + { + return $this->write('// Visiting not node')->dispatch($node['children'][0])->write('// Applying boolean not to result of not node')->write('$value = !Utils::isTruthy($value);'); + } + private function visit_subexpression(array $node) + { + return $this->dispatch($node['children'][0])->write('if ($value !== null) {')->indent()->dispatch($node['children'][1])->outdent()->write('}'); + } + private function visit_field(array $node) + { + $arr = '$value[' . \var_export($node['value'], \true) . ']'; + $obj = '$value->{' . \var_export($node['value'], \true) . '}'; + $this->write('if (is_array($value) || $value instanceof \\ArrayAccess) {')->indent()->write('$value = isset(%s) ? %s : null;', $arr, $arr)->outdent()->write('} elseif ($value instanceof \\stdClass) {')->indent()->write('$value = isset(%s) ? %s : null;', $obj, $obj)->outdent()->write("} else {")->indent()->write('$value = null;')->outdent()->write("}"); + return $this; + } + private function visit_index(array $node) + { + if ($node['value'] >= 0) { + $check = '$value[' . $node['value'] . ']'; + return $this->write('$value = (is_array($value) || $value instanceof \\ArrayAccess)' . ' && isset(%s) ? %s : null;', $check, $check); + } + $a = $this->makeVar('count'); + return $this->write('if (is_array($value) || ($value instanceof \\ArrayAccess && $value instanceof \\Countable)) {')->indent()->write('%s = count($value) + %s;', $a, $node['value'])->write('$value = isset($value[%s]) ? $value[%s] : null;', $a, $a)->outdent()->write('} else {')->indent()->write('$value = null;')->outdent()->write('}'); + } + private function visit_literal(array $node) + { + return $this->write('$value = %s;', \var_export($node['value'], \true)); + } + private function visit_pipe(array $node) + { + return $this->dispatch($node['children'][0])->dispatch($node['children'][1]); + } + private function visit_multi_select_list(array $node) + { + return $this->visit_multi_select_hash($node); + } + private function visit_multi_select_hash(array $node) + { + $listVal = $this->makeVar('list'); + $value = $this->makeVar('prev'); + $this->write('if ($value !== null) {')->indent()->write('%s = [];', $listVal)->write('%s = $value;', $value); + $first = \true; + foreach ($node['children'] as $child) { + if (!$first) { + $this->write('$value = %s;', $value); + } + $first = \false; + if ($node['type'] == 'multi_select_hash') { + $this->dispatch($child['children'][0]); + $key = \var_export($child['value'], \true); + $this->write('%s[%s] = $value;', $listVal, $key); + } else { + $this->dispatch($child); + $this->write('%s[] = $value;', $listVal); + } + } + return $this->write('$value = %s;', $listVal)->outdent()->write('}'); + } + private function visit_function(array $node) + { + $value = $this->makeVar('val'); + $args = $this->makeVar('args'); + $this->write('%s = $value;', $value)->write('%s = [];', $args); + foreach ($node['children'] as $arg) { + $this->dispatch($arg); + $this->write('%s[] = $value;', $args)->write('$value = %s;', $value); + } + return $this->write('$value = Fd::getInstance()->__invoke(%s, %s);', \var_export($node['value'], \true), $args); + } + private function visit_slice(array $node) + { + return $this->write('$value = !is_string($value) && !Utils::isArray($value)')->write(' ? null : Utils::slice($value, %s, %s, %s);', \var_export($node['value'][0], \true), \var_export($node['value'][1], \true), \var_export($node['value'][2], \true)); + } + private function visit_current(array $node) + { + return $this->write('// Visiting current node (no-op)'); + } + private function visit_expref(array $node) + { + $child = \var_export($node['children'][0], \true); + return $this->write('$value = function ($value) use ($interpreter) {')->indent()->write('return $interpreter->visit(%s, $value);', $child)->outdent()->write('};'); + } + private function visit_flatten(array $node) + { + $this->dispatch($node['children'][0]); + $merged = $this->makeVar('merged'); + $val = $this->makeVar('val'); + $this->write('// Visiting merge node')->write('if (!Utils::isArray($value)) {')->indent()->write('$value = null;')->outdent()->write('} else {')->indent()->write('%s = [];', $merged)->write('foreach ($value as %s) {', $val)->indent()->write('if (is_array(%s) && array_key_exists(0, %s)) {', $val, $val)->indent()->write('%s = array_merge(%s, %s);', $merged, $merged, $val)->outdent()->write('} elseif (%s !== []) {', $val)->indent()->write('%s[] = %s;', $merged, $val)->outdent()->write('}')->outdent()->write('}')->write('$value = %s;', $merged)->outdent()->write('}'); + return $this; + } + private function visit_projection(array $node) + { + $val = $this->makeVar('val'); + $collected = $this->makeVar('collected'); + $this->write('// Visiting projection node')->dispatch($node['children'][0])->write(''); + if (!isset($node['from'])) { + $this->write('if (!is_array($value) && !($value instanceof \\stdClass)) { $value = null; }'); + } elseif ($node['from'] == 'object') { + $this->write('if (!Utils::isObject($value)) { $value = null; }'); + } elseif ($node['from'] == 'array') { + $this->write('if (!Utils::isArray($value)) { $value = null; }'); + } + $this->write('if ($value !== null) {')->indent()->write('%s = [];', $collected)->write('foreach ((array) $value as %s) {', $val)->indent()->write('$value = %s;', $val)->dispatch($node['children'][1])->write('if ($value !== null) {')->indent()->write('%s[] = $value;', $collected)->outdent()->write('}')->outdent()->write('}')->write('$value = %s;', $collected)->outdent()->write('}'); + return $this; + } + private function visit_condition(array $node) + { + $value = $this->makeVar('beforeCondition'); + return $this->write('%s = $value;', $value)->write('// Visiting condition node')->dispatch($node['children'][0])->write('// Checking result of condition node')->write('if (Utils::isTruthy($value)) {')->indent()->write('$value = %s;', $value)->dispatch($node['children'][1])->outdent()->write('} else {')->indent()->write('$value = null;')->outdent()->write('}'); + } + private function visit_comparator(array $node) + { + $value = $this->makeVar('val'); + $a = $this->makeVar('left'); + $b = $this->makeVar('right'); + $this->write('// Visiting comparator node')->write('%s = $value;', $value)->dispatch($node['children'][0])->write('%s = $value;', $a)->write('$value = %s;', $value)->dispatch($node['children'][1])->write('%s = $value;', $b); + if ($node['value'] == '==') { + $this->write('$value = Utils::isEqual(%s, %s);', $a, $b); + } elseif ($node['value'] == '!=') { + $this->write('$value = !Utils::isEqual(%s, %s);', $a, $b); + } else { + $this->write('$value = (is_int(%s) || is_float(%s)) && (is_int(%s) || is_float(%s)) && %s %s %s;', $a, $a, $b, $b, $a, $node['value'], $b); + } + return $this; + } + /** @internal */ + public function __call($method, $args) + { + throw new \RuntimeException(\sprintf('Invalid node encountered: %s', \json_encode($args[0]))); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeInterpreter.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeInterpreter.php new file mode 100644 index 00000000..ead7f825 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/TreeInterpreter.php @@ -0,0 +1,183 @@ +fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance(); + } + /** + * Visits each node in a JMESPath AST and returns the evaluated result. + * + * @param array $node JMESPath AST node + * @param mixed $data Data to evaluate + * + * @return mixed + */ + public function visit(array $node, $data) + { + return $this->dispatch($node, $data); + } + /** + * Recursively traverses an AST using depth-first, pre-order traversal. + * The evaluation logic for each node type is embedded into a large switch + * statement to avoid the cost of "double dispatch". + * @return mixed + */ + private function dispatch(array $node, $value) + { + $dispatcher = $this->fnDispatcher; + switch ($node['type']) { + case 'field': + if (\is_array($value) || $value instanceof \ArrayAccess) { + return isset($value[$node['value']]) ? $value[$node['value']] : null; + } elseif ($value instanceof \stdClass) { + return isset($value->{$node['value']}) ? $value->{$node['value']} : null; + } + return null; + case 'subexpression': + return $this->dispatch($node['children'][1], $this->dispatch($node['children'][0], $value)); + case 'index': + if (!Utils::isArray($value)) { + return null; + } + $idx = $node['value'] >= 0 ? $node['value'] : $node['value'] + \count($value); + return isset($value[$idx]) ? $value[$idx] : null; + case 'projection': + $left = $this->dispatch($node['children'][0], $value); + $from = isset($node['from']) ? $node['from'] : null; + switch ($from) { + case 'object': + if (!Utils::isObject($left)) { + return null; + } + break; + case 'array': + if (!Utils::isArray($left)) { + return null; + } + break; + default: + if (!\is_array($left) && !$left instanceof \stdClass) { + return null; + } + } + $collected = []; + foreach ((array) $left as $val) { + $result = $this->dispatch($node['children'][1], $val); + if ($result !== null) { + $collected[] = $result; + } + } + return $collected; + case 'flatten': + static $skipElement = []; + $value = $this->dispatch($node['children'][0], $value); + if (!Utils::isArray($value)) { + return null; + } + $merged = []; + foreach ($value as $values) { + // Only merge up arrays lists and not hashes + if (\is_array($values) && \array_key_exists(0, $values)) { + $merged = \array_merge($merged, $values); + } elseif ($values !== $skipElement) { + $merged[] = $values; + } + } + return $merged; + case 'literal': + return $node['value']; + case 'current': + return $value; + case 'or': + $result = $this->dispatch($node['children'][0], $value); + return Utils::isTruthy($result) ? $result : $this->dispatch($node['children'][1], $value); + case 'and': + $result = $this->dispatch($node['children'][0], $value); + return Utils::isTruthy($result) ? $this->dispatch($node['children'][1], $value) : $result; + case 'not': + return !Utils::isTruthy($this->dispatch($node['children'][0], $value)); + case 'pipe': + return $this->dispatch($node['children'][1], $this->dispatch($node['children'][0], $value)); + case 'multi_select_list': + if ($value === null) { + return null; + } + $collected = []; + foreach ($node['children'] as $node) { + $collected[] = $this->dispatch($node, $value); + } + return $collected; + case 'multi_select_hash': + if ($value === null) { + return null; + } + $collected = []; + foreach ($node['children'] as $node) { + $collected[$node['value']] = $this->dispatch($node['children'][0], $value); + } + return $collected; + case 'comparator': + $left = $this->dispatch($node['children'][0], $value); + $right = $this->dispatch($node['children'][1], $value); + if ($node['value'] == '==') { + return Utils::isEqual($left, $right); + } elseif ($node['value'] == '!=') { + return !Utils::isEqual($left, $right); + } else { + return self::relativeCmp($left, $right, $node['value']); + } + case 'condition': + return Utils::isTruthy($this->dispatch($node['children'][0], $value)) ? $this->dispatch($node['children'][1], $value) : null; + case 'function': + $args = []; + foreach ($node['children'] as $arg) { + $args[] = $this->dispatch($arg, $value); + } + return $dispatcher($node['value'], $args); + case 'slice': + return \is_string($value) || Utils::isArray($value) ? Utils::slice($value, $node['value'][0], $node['value'][1], $node['value'][2]) : null; + case 'expref': + $apply = $node['children'][0]; + return function ($value) use($apply) { + return $this->visit($apply, $value); + }; + default: + throw new \RuntimeException("Unknown node type: {$node['type']}"); + } + } + /** + * @return bool + */ + private static function relativeCmp($left, $right, $cmp) + { + if (!(\is_int($left) || \is_float($left)) || !(\is_int($right) || \is_float($right))) { + return \false; + } + switch ($cmp) { + case '>': + return $left > $right; + case '>=': + return $left >= $right; + case '<': + return $left < $right; + case '<=': + return $left <= $right; + default: + throw new \RuntimeException("Invalid comparison: {$cmp}"); + } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Utils.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Utils.php new file mode 100644 index 00000000..6287113f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/mtdowling/jmespath.php/src/Utils.php @@ -0,0 +1,242 @@ + 'boolean', 'string' => 'string', 'NULL' => 'null', 'double' => 'number', 'float' => 'number', 'integer' => 'number']; + /** + * Returns true if the value is truthy + * + * @param mixed $value Value to check + * + * @return bool + */ + public static function isTruthy($value) + { + if (!$value) { + return $value === 0 || $value === 0.0 || $value === '0'; + } elseif ($value instanceof \stdClass) { + return (bool) \get_object_vars($value); + } else { + return \true; + } + } + /** + * Gets the JMESPath type equivalent of a PHP variable. + * + * @param mixed $arg PHP variable + * @return string Returns the JSON data type + * @throws \InvalidArgumentException when an unknown type is given. + */ + public static function type($arg) + { + $type = \gettype($arg); + if (isset(self::$typeMap[$type])) { + return self::$typeMap[$type]; + } elseif ($type === 'array') { + if (empty($arg)) { + return 'array'; + } + \reset($arg); + return \key($arg) === 0 ? 'array' : 'object'; + } elseif ($arg instanceof \stdClass) { + return 'object'; + } elseif ($arg instanceof \Closure) { + return 'expression'; + } elseif ($arg instanceof \ArrayAccess && $arg instanceof \Countable) { + return \count($arg) == 0 || $arg->offsetExists(0) ? 'array' : 'object'; + } elseif (\is_object($arg)) { + if (\method_exists($arg, '__toString')) { + return 'string'; + } + throw new \InvalidArgumentException('Unable to determine JMESPath type from ' . \get_class($arg)); + } + throw new \InvalidArgumentException('Unable to determine JMESPath type from ' . \gettype($arg)); + } + /** + * Determine if the provided value is a JMESPath compatible object. + * + * @param mixed $value + * + * @return bool + */ + public static function isObject($value) + { + if (\is_array($value)) { + return !$value || \array_keys($value)[0] !== 0; + } + // Handle array-like values. Must be empty or offset 0 does not exist + return $value instanceof \Countable && $value instanceof \ArrayAccess ? \count($value) == 0 || !$value->offsetExists(0) : $value instanceof \stdClass; + } + /** + * Determine if the provided value is a JMESPath compatible array. + * + * @param mixed $value + * + * @return bool + */ + public static function isArray($value) + { + if (\is_array($value)) { + return !$value || \array_keys($value)[0] === 0; + } + // Handle array-like values. Must be empty or offset 0 exists. + return $value instanceof \Countable && $value instanceof \ArrayAccess ? \count($value) == 0 || $value->offsetExists(0) : \false; + } + /** + * JSON-semantic equality: one number type, structural comparison for arrays + * and objects, and no object key-order sensitivity. + * Empty arrays and empty objects compare equal because PHP cannot represent + * that distinction after associative JSON decoding. + * + * @param mixed $a First value to compare + * @param mixed $b Second value to compare + * + * @return bool + */ + public static function isEqual($a, $b) + { + $typeA = self::type($a); + $typeB = self::type($b); + if ($typeA !== $typeB) { + return ($typeA === 'array' || $typeA === 'object') && ($typeB === 'array' || $typeB === 'object') && (array) $a === [] && (array) $b === []; + } + if ($typeA === 'number') { + return $a == $b; + } + if ($typeA === 'array' || $typeA === 'object') { + $a = (array) $a; + $b = (array) $b; + if (\count($a) !== \count($b)) { + return \false; + } + foreach ($a as $key => $value) { + if (!\array_key_exists($key, $b) || !self::isEqual($value, $b[$key])) { + return \false; + } + } + return \true; + } + return $a === $b; + } + /** + * Safely add together two values. + * + * @param mixed $a First value to add + * @param mixed $b Second value to add + * + * @return int|float + */ + public static function add($a, $b) + { + if (\is_numeric($a)) { + if (\is_numeric($b)) { + return $a + $b; + } else { + return $a; + } + } else { + if (\is_numeric($b)) { + return $b; + } else { + return 0; + } + } + } + /** + * JMESPath requires a stable sorting algorithm, so here we'll implement + * a simple Schwartzian transform that uses array index positions as tie + * breakers. + * + * @param array $data List or map of data to sort + * @param callable $sortFn Callable used to sort values + * + * @return array Returns the sorted array + * @link https://en.wikipedia.org/wiki/Schwartzian_transform + */ + public static function stableSort(array $data, callable $sortFn) + { + // Decorate each item by creating an array of [value, index] + \array_walk($data, function (&$v, $k) { + $v = [$v, $k]; + }); + // Sort by the sort function and use the index as a tie-breaker + \uasort($data, function ($a, $b) use($sortFn) { + return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1); + }); + // Undecorate each item and return the resulting sorted array + return \array_map(function ($v) { + return $v[0]; + }, \array_values($data)); + } + /** + * Creates a Python-style slice of a string or array. + * + * @param array|string $value Value to slice + * @param int|null $start Starting position + * @param int|null $stop Stop position + * @param int $step Step (1, 2, -1, -2, etc.) + * + * @return array|string + * @throws \InvalidArgumentException + */ + public static function slice($value, $start = null, $stop = null, $step = 1) + { + if (!\is_string($value) && !self::isArray($value)) { + throw new \InvalidArgumentException('Expects string or array'); + } + return self::sliceIndices($value, $start, $stop, $step); + } + private static function adjustEndpoint($length, $endpoint, $step) + { + if ($endpoint < 0) { + $endpoint += $length; + if ($endpoint < 0) { + $endpoint = $step < 0 ? -1 : 0; + } + } elseif ($endpoint >= $length) { + $endpoint = $step < 0 ? $length - 1 : $length; + } + return $endpoint; + } + private static function adjustSlice($length, $start, $stop, $step) + { + if ($step === null) { + $step = 1; + } elseif ($step === 0) { + throw new \RuntimeException('step cannot be 0'); + } + if ($start === null) { + $start = $step < 0 ? $length - 1 : 0; + } else { + $start = self::adjustEndpoint($length, $start, $step); + } + if ($stop === null) { + $stop = $step < 0 ? -1 : $length; + } else { + $stop = self::adjustEndpoint($length, $stop, $step); + } + return [$start, $stop, $step]; + } + private static function sliceIndices($subject, $start, $stop, $step) + { + $type = \gettype($subject); + if ($type == 'string') { + $subject = \mb_str_split($subject, 1, 'UTF-8'); + } + $len = \count($subject); + list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step); + $result = []; + if ($step > 0) { + for ($i = $start; $i < $stop; $i += $step) { + $result[] = $subject[$i]; + } + } else { + for ($i = $start; $i > $stop; $i += $step) { + $result[] = $subject[$i]; + } + } + return $type == 'string' ? \implode('', $result) : $result; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/LICENSE.txt b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/LICENSE.txt new file mode 100644 index 00000000..91acaca6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/LICENSE.txt @@ -0,0 +1,48 @@ +The MIT License (MIT) + +Copyright (c) 2016 - 2022 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ +This library was based on the work of Steve "Sc00bz" Thomas. +------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2014 Steve Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32.php new file mode 100644 index 00000000..1a0f580f --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32.php @@ -0,0 +1,440 @@ + 96 && $src < 123) $ret += $src - 97 + 1; // -64 + $ret += (0x60 - $src & $src - 0x7b) >> 8 & $src - 96; + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (0x31 - $src & $src - 0x38) >> 8 & $src - 23; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 5-bit integers + * into 8-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return int + */ + protected static function decode5BitsUpper(int $src) : int + { + $ret = -1; + // if ($src > 64 && $src < 91) $ret += $src - 65 + 1; // -64 + $ret += (0x40 - $src & $src - 0x5b) >> 8 & $src - 64; + // if ($src > 0x31 && $src < 0x38) $ret += $src - 24 + 1; // -23 + $ret += (0x31 - $src & $src - 0x38) >> 8 & $src - 23; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode5Bits(int $src) : string + { + $diff = 0x61; + // if ($src > 25) $ret -= 72; + $diff -= 25 - $src >> 8 & 73; + return pack('C', $src + $diff); + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return string + */ + protected static function encode5BitsUpper(int $src) : string + { + $diff = 0x41; + // if ($src > 25) $ret -= 40; + $diff -= 25 - $src >> 8 & 41; + return pack('C', $src + $diff); + } + /** + * @param string $encodedString + * @param bool $upper + * @return string + */ + public static function decodeNoPadding( + #[SensitiveParameter] + string $encodedString, + bool $upper = \false + ) : string + { + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 7) === 0) { + for ($j = 0; $j < 7 && $j < $srcLen; ++$j) { + if ($encodedString[$srcLen - $j - 1] === '=') { + throw new InvalidArgumentException("decodeNoPadding() doesn't tolerate padding"); + } + } + } + return static::doDecode($encodedString, $upper, \true); + } + /** + * Base32 decoding + * + * @param string $src + * @param bool $upper + * @param bool $strictPadding + * @return string + * + * @throws TypeError + */ + protected static function doDecode( + #[SensitiveParameter] + string $src, + bool $upper = \false, + bool $strictPadding = \false + ) : string + { + // We do this to reduce code duplication: + $method = $upper ? 'decode5BitsUpper' : 'decode5Bits'; + // Remove padding + $srcLen = Binary::safeStrlen($src); + if ($srcLen === 0) { + return ''; + } + if ($strictPadding) { + if (($srcLen & 7) === 0) { + for ($j = 0; $j < 7; ++$j) { + if ($src[$srcLen - 1] === '=') { + $srcLen--; + } else { + break; + } + } + } + if (($srcLen & 7) === 1) { + throw new RangeException('Incorrect padding'); + } + } else { + $src = \rtrim($src, '='); + $srcLen = Binary::safeStrlen($src); + } + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 8 <= $srcLen; $i += 8) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($src, $i, 8)); + /** @var int $c0 */ + $c0 = static::$method($chunk[1]); + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + /** @var int $c6 */ + $c6 = static::$method($chunk[7]); + /** @var int $c7 */ + $c7 = static::$method($chunk[8]); + $dest .= pack('CCCCC', ($c0 << 3 | $c1 >> 2) & 0xff, ($c1 << 6 | $c2 << 1 | $c3 >> 4) & 0xff, ($c3 << 4 | $c4 >> 1) & 0xff, ($c4 << 7 | $c5 << 2 | $c6 >> 3) & 0xff, ($c6 << 5 | $c7) & 0xff); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6 | $c7) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + /** @var int $c0 */ + $c0 = static::$method($chunk[1]); + if ($i + 6 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + /** @var int $c6 */ + $c6 = static::$method($chunk[7]); + $dest .= pack('CCCC', ($c0 << 3 | $c1 >> 2) & 0xff, ($c1 << 6 | $c2 << 1 | $c3 >> 4) & 0xff, ($c3 << 4 | $c4 >> 1) & 0xff, ($c4 << 7 | $c5 << 2 | $c6 >> 3) & 0xff); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5 | $c6) >> 8; + if ($strictPadding) { + $err |= $c6 << 5 & 0xff; + } + } elseif ($i + 5 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + /** @var int $c5 */ + $c5 = static::$method($chunk[6]); + $dest .= pack('CCCC', ($c0 << 3 | $c1 >> 2) & 0xff, ($c1 << 6 | $c2 << 1 | $c3 >> 4) & 0xff, ($c3 << 4 | $c4 >> 1) & 0xff, ($c4 << 7 | $c5 << 2) & 0xff); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4 | $c5) >> 8; + } elseif ($i + 4 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + /** @var int $c4 */ + $c4 = static::$method($chunk[5]); + $dest .= pack('CCC', ($c0 << 3 | $c1 >> 2) & 0xff, ($c1 << 6 | $c2 << 1 | $c3 >> 4) & 0xff, ($c3 << 4 | $c4 >> 1) & 0xff); + $err |= ($c0 | $c1 | $c2 | $c3 | $c4) >> 8; + if ($strictPadding) { + $err |= $c4 << 7 & 0xff; + } + } elseif ($i + 3 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + /** @var int $c3 */ + $c3 = static::$method($chunk[4]); + $dest .= pack('CC', ($c0 << 3 | $c1 >> 2) & 0xff, ($c1 << 6 | $c2 << 1 | $c3 >> 4) & 0xff); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + if ($strictPadding) { + $err |= $c3 << 4 & 0xff; + } + } elseif ($i + 2 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + /** @var int $c2 */ + $c2 = static::$method($chunk[3]); + $dest .= pack('CC', ($c0 << 3 | $c1 >> 2) & 0xff, ($c1 << 6 | $c2 << 1) & 0xff); + $err |= ($c0 | $c1 | $c2) >> 8; + if ($strictPadding) { + $err |= $c2 << 6 & 0xff; + } + } elseif ($i + 1 < $srcLen) { + /** @var int $c1 */ + $c1 = static::$method($chunk[2]); + $dest .= pack('C', ($c0 << 3 | $c1 >> 2) & 0xff); + $err |= ($c0 | $c1) >> 8; + if ($strictPadding) { + $err |= $c1 << 6 & 0xff; + } + } else { + $dest .= pack('C', $c0 << 3 & 0xff); + $err |= $c0 >> 8; + } + } + $check = $err === 0; + if (!$check) { + throw new RangeException('Base32::doDecode() only expects characters in the correct base32 alphabet'); + } + return $dest; + } + /** + * Base32 Encoding + * + * @param string $src + * @param bool $upper + * @param bool $pad + * @return string + * @throws TypeError + */ + protected static function doEncode( + #[SensitiveParameter] + string $src, + bool $upper = \false, + $pad = \true + ) : string + { + // We do this to reduce code duplication: + $method = $upper ? 'encode5BitsUpper' : 'encode5Bits'; + $dest = ''; + $srcLen = Binary::safeStrlen($src); + // Main loop (no padding): + for ($i = 0; $i + 5 <= $srcLen; $i += 5) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($src, $i, 5)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $b3 = $chunk[4]; + $b4 = $chunk[5]; + $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method(($b1 << 4 | $b2 >> 4) & 31) . static::$method(($b2 << 1 | $b3 >> 7) & 31) . static::$method($b3 >> 2 & 31) . static::$method(($b3 << 3 | $b4 >> 5) & 31) . static::$method($b4 & 31); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 3 < $srcLen) { + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $b3 = $chunk[4]; + $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method(($b1 << 4 | $b2 >> 4) & 31) . static::$method(($b2 << 1 | $b3 >> 7) & 31) . static::$method($b3 >> 2 & 31) . static::$method($b3 << 3 & 31); + if ($pad) { + $dest .= '='; + } + } elseif ($i + 2 < $srcLen) { + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method(($b1 << 4 | $b2 >> 4) & 31) . static::$method($b2 << 1 & 31); + if ($pad) { + $dest .= '==='; + } + } elseif ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= static::$method($b0 >> 3 & 31) . static::$method(($b0 << 2 | $b1 >> 6) & 31) . static::$method($b1 >> 1 & 31) . static::$method($b1 << 4 & 31); + if ($pad) { + $dest .= '===='; + } + } else { + $dest .= static::$method($b0 >> 3 & 31) . static::$method($b0 << 2 & 31); + if ($pad) { + $dest .= '======'; + } + } + } + return $dest; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32Hex.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32Hex.php new file mode 100644 index 00000000..b1b26472 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base32Hex.php @@ -0,0 +1,99 @@ + 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47 + $ret += (0x2f - $src & $src - 0x3a) >> 8 & $src - 47; + // if ($src > 0x60 && $src < 0x77) ret += $src - 0x61 + 10 + 1; // -86 + $ret += (0x60 - $src & $src - 0x77) >> 8 & $src - 86; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 5-bit integers + * into 8-bit integers. + * + * @param int $src + * @return int + */ + protected static function decode5BitsUpper(int $src) : int + { + $ret = -1; + // if ($src > 0x30 && $src < 0x3a) ret += $src - 0x2e + 1; // -47 + $ret += (0x2f - $src & $src - 0x3a) >> 8 & $src - 47; + // if ($src > 0x40 && $src < 0x57) ret += $src - 0x41 + 10 + 1; // -54 + $ret += (0x40 - $src & $src - 0x57) >> 8 & $src - 54; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode5Bits(int $src) : string + { + $src += 0x30; + // if ($src > 0x39) $src += 0x61 - 0x3a; // 39 + $src += 0x39 - $src >> 8 & 39; + return pack('C', $src); + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 5-bit integers. + * + * Uppercase variant. + * + * @param int $src + * @return string + */ + protected static function encode5BitsUpper(int $src) : string + { + $src += 0x30; + // if ($src > 0x39) $src += 0x41 - 0x3a; // 7 + $src += 0x39 - $src >> 8 & 7; + return pack('C', $src); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64.php new file mode 100644 index 00000000..ddcc0c34 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64.php @@ -0,0 +1,338 @@ + 0) { + try { + return sodium_bin2base64($binString, $variant); + } catch (SodiumException $ex) { + throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); + } + } + } + return static::doEncode($binString, \true); + } + /** + * Encode into Base64, no = padding + * + * Base64 character set "[A-Z][a-z][0-9]+/" + * + * @param string $src + * @return string + * + * @throws TypeError + */ + public static function encodeUnpadded( + #[SensitiveParameter] + string $src + ) : string + { + if (extension_loaded('sodium')) { + switch (static::class) { + case Base64::class: + $variant = SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING; + break; + case Base64UrlSafe::class: + $variant = SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; + break; + default: + $variant = 0; + } + if ($variant > 0) { + try { + return sodium_bin2base64($src, $variant); + } catch (SodiumException $ex) { + throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); + } + } + } + return static::doEncode($src, \false); + } + /** + * @param string $src + * @param bool $pad Include = padding? + * @return string + * + * @throws TypeError + */ + protected static function doEncode( + #[SensitiveParameter] + string $src, + bool $pad = \true + ) : string + { + $dest = ''; + $srcLen = Binary::safeStrlen($src); + // Main loop (no padding): + for ($i = 0; $i + 3 <= $srcLen; $i += 3) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($src, $i, 3)); + $b0 = $chunk[1]; + $b1 = $chunk[2]; + $b2 = $chunk[3]; + $dest .= static::encode6Bits($b0 >> 2) . static::encode6Bits(($b0 << 4 | $b1 >> 4) & 63) . static::encode6Bits(($b1 << 2 | $b2 >> 6) & 63) . static::encode6Bits($b2 & 63); + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($src, $i, $srcLen - $i)); + $b0 = $chunk[1]; + if ($i + 1 < $srcLen) { + $b1 = $chunk[2]; + $dest .= static::encode6Bits($b0 >> 2) . static::encode6Bits(($b0 << 4 | $b1 >> 4) & 63) . static::encode6Bits($b1 << 2 & 63); + if ($pad) { + $dest .= '='; + } + } else { + $dest .= static::encode6Bits($b0 >> 2) . static::encode6Bits($b0 << 4 & 63); + if ($pad) { + $dest .= '=='; + } + } + } + return $dest; + } + /** + * decode from base64 into binary + * + * Base64 character set "./[A-Z][a-z][0-9]" + * + * @param string $encodedString + * @param bool $strictPadding + * @return string + * + * @throws RangeException + * @throws TypeError + */ + public static function decode( + #[SensitiveParameter] + string $encodedString, + bool $strictPadding = \false + ) : string + { + // Remove padding + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if ($strictPadding) { + if (($srcLen & 3) === 0) { + if ($encodedString[$srcLen - 1] === '=') { + $srcLen--; + if ($encodedString[$srcLen - 1] === '=') { + $srcLen--; + } + } + } + if (($srcLen & 3) === 1) { + throw new RangeException('Incorrect padding'); + } + if ($encodedString[$srcLen - 1] === '=') { + throw new RangeException('Incorrect padding'); + } + if (extension_loaded('sodium')) { + switch (static::class) { + case Base64::class: + $variant = SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING; + break; + case Base64UrlSafe::class: + $variant = SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; + break; + default: + $variant = 0; + } + if ($variant > 0) { + try { + return sodium_base642bin(Binary::safeSubstr($encodedString, 0, $srcLen), $variant); + } catch (SodiumException $ex) { + throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); + } + } + } + } else { + $encodedString = rtrim($encodedString, '='); + $srcLen = Binary::safeStrlen($encodedString); + } + $err = 0; + $dest = ''; + // Main loop (no padding): + for ($i = 0; $i + 4 <= $srcLen; $i += 4) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($encodedString, $i, 4)); + $c0 = static::decode6Bits($chunk[1]); + $c1 = static::decode6Bits($chunk[2]); + $c2 = static::decode6Bits($chunk[3]); + $c3 = static::decode6Bits($chunk[4]); + $dest .= pack('CCC', ($c0 << 2 | $c1 >> 4) & 0xff, ($c1 << 4 | $c2 >> 2) & 0xff, ($c2 << 6 | $c3) & 0xff); + $err |= ($c0 | $c1 | $c2 | $c3) >> 8; + } + // The last chunk, which may have padding: + if ($i < $srcLen) { + /** @var array $chunk */ + $chunk = unpack('C*', Binary::safeSubstr($encodedString, $i, $srcLen - $i)); + $c0 = static::decode6Bits($chunk[1]); + if ($i + 2 < $srcLen) { + $c1 = static::decode6Bits($chunk[2]); + $c2 = static::decode6Bits($chunk[3]); + $dest .= pack('CC', ($c0 << 2 | $c1 >> 4) & 0xff, ($c1 << 4 | $c2 >> 2) & 0xff); + $err |= ($c0 | $c1 | $c2) >> 8; + if ($strictPadding) { + $err |= $c2 << 6 & 0xff; + } + } elseif ($i + 1 < $srcLen) { + $c1 = static::decode6Bits($chunk[2]); + $dest .= pack('C', ($c0 << 2 | $c1 >> 4) & 0xff); + $err |= ($c0 | $c1) >> 8; + if ($strictPadding) { + $err |= $c1 << 4 & 0xff; + } + } elseif ($strictPadding) { + $err |= 1; + } + } + $check = $err === 0; + if (!$check) { + throw new RangeException('Base64::decode() only expects characters in the correct base64 alphabet'); + } + return $dest; + } + /** + * @param string $encodedString + * @return string + */ + public static function decodeNoPadding( + #[SensitiveParameter] + string $encodedString + ) : string + { + $srcLen = Binary::safeStrlen($encodedString); + if ($srcLen === 0) { + return ''; + } + if (($srcLen & 3) === 0) { + // If $strLen is not zero, and it is divisible by 4, then it's at least 4. + if ($encodedString[$srcLen - 1] === '=' || $encodedString[$srcLen - 2] === '=') { + throw new InvalidArgumentException("decodeNoPadding() doesn't tolerate padding"); + } + } + return static::decode($encodedString, \true); + } + /** + * Uses bitwise operators instead of table-lookups to turn 6-bit integers + * into 8-bit integers. + * + * Base64 character set: + * [A-Z] [a-z] [0-9] + / + * 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f + * + * @param int $src + * @return int + */ + protected static function decode6Bits(int $src) : int + { + $ret = -1; + // if ($src > 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (0x40 - $src & $src - 0x5b) >> 8 & $src - 64; + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (0x60 - $src & $src - 0x7b) >> 8 & $src - 70; + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (0x2f - $src & $src - 0x3a) >> 8 & $src + 5; + // if ($src == 0x2b) $ret += 62 + 1; + $ret += (0x2a - $src & $src - 0x2c) >> 8 & 63; + // if ($src == 0x2f) ret += 63 + 1; + $ret += (0x2e - $src & $src - 0x30) >> 8 & 64; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src) : string + { + $diff = 0x41; + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += 25 - $src >> 8 & 6; + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= 51 - $src >> 8 & 75; + // if ($src > 61) $diff += 0x2b - 0x30 - 10; // -15 + $diff -= 61 - $src >> 8 & 15; + // if ($src > 62) $diff += 0x2f - 0x2b - 1; // 3 + $diff += 62 - $src >> 8 & 3; + return pack('C', $src + $diff); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlash.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlash.php new file mode 100644 index 00000000..7154b2b0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlash.php @@ -0,0 +1,79 @@ + 0x2d && $src < 0x30) ret += $src - 0x2e + 1; // -45 + $ret += (0x2d - $src & $src - 0x30) >> 8 & $src - 45; + // if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 2 + 1; // -62 + $ret += (0x40 - $src & $src - 0x5b) >> 8 & $src - 62; + // if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 28 + 1; // -68 + $ret += (0x60 - $src & $src - 0x7b) >> 8 & $src - 68; + // if ($src > 0x2f && $src < 0x3a) ret += $src - 0x30 + 54 + 1; // 7 + $ret += (0x2f - $src & $src - 0x3a) >> 8 & $src + 7; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src) : string + { + $src += 0x2e; + // if ($src > 0x2f) $src += 0x41 - 0x30; // 17 + $src += 0x2f - $src >> 8 & 17; + // if ($src > 0x5a) $src += 0x61 - 0x5b; // 6 + $src += 0x5a - $src >> 8 & 6; + // if ($src > 0x7a) $src += 0x30 - 0x7b; // -75 + $src -= 0x7a - $src >> 8 & 75; + return pack('C', $src); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php new file mode 100644 index 00000000..a5963475 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64DotSlashOrdered.php @@ -0,0 +1,75 @@ + 0x2d && $src < 0x3a) ret += $src - 0x2e + 1; // -45 + $ret += (0x2d - $src & $src - 0x3a) >> 8 & $src - 45; + // if ($src > 0x40 && $src < 0x5b) ret += $src - 0x41 + 12 + 1; // -52 + $ret += (0x40 - $src & $src - 0x5b) >> 8 & $src - 52; + // if ($src > 0x60 && $src < 0x7b) ret += $src - 0x61 + 38 + 1; // -58 + $ret += (0x60 - $src & $src - 0x7b) >> 8 & $src - 58; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src) : string + { + $src += 0x2e; + // if ($src > 0x39) $src += 0x41 - 0x3a; // 7 + $src += 0x39 - $src >> 8 & 7; + // if ($src > 0x5a) $src += 0x61 - 0x5b; // 6 + $src += 0x5a - $src >> 8 & 6; + return pack('C', $src); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64UrlSafe.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64UrlSafe.php new file mode 100644 index 00000000..5ba9ab8b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Base64UrlSafe.php @@ -0,0 +1,83 @@ + 0x40 && $src < 0x5b) $ret += $src - 0x41 + 1; // -64 + $ret += (0x40 - $src & $src - 0x5b) >> 8 & $src - 64; + // if ($src > 0x60 && $src < 0x7b) $ret += $src - 0x61 + 26 + 1; // -70 + $ret += (0x60 - $src & $src - 0x7b) >> 8 & $src - 70; + // if ($src > 0x2f && $src < 0x3a) $ret += $src - 0x30 + 52 + 1; // 5 + $ret += (0x2f - $src & $src - 0x3a) >> 8 & $src + 5; + // if ($src == 0x2c) $ret += 62 + 1; + $ret += (0x2c - $src & $src - 0x2e) >> 8 & 63; + // if ($src == 0x5f) ret += 63 + 1; + $ret += (0x5e - $src & $src - 0x60) >> 8 & 64; + return $ret; + } + /** + * Uses bitwise operators instead of table-lookups to turn 8-bit integers + * into 6-bit integers. + * + * @param int $src + * @return string + */ + protected static function encode6Bits(int $src) : string + { + $diff = 0x41; + // if ($src > 25) $diff += 0x61 - 0x41 - 26; // 6 + $diff += 25 - $src >> 8 & 6; + // if ($src > 51) $diff += 0x30 - 0x61 - 26; // -75 + $diff -= 51 - $src >> 8 & 75; + // if ($src > 61) $diff += 0x2d - 0x30 - 10; // -13 + $diff -= 61 - $src >> 8 & 13; + // if ($src > 62) $diff += 0x5f - 0x2b - 1; // 3 + $diff += 62 - $src >> 8 & 49; + return pack('C', $src + $diff); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Binary.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Binary.php new file mode 100644 index 00000000..40af6766 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/Binary.php @@ -0,0 +1,98 @@ +getMessage(), $ex->getCode(), $ex); + } + } + $hex = ''; + $len = Binary::safeStrlen($binString); + for ($i = 0; $i < $len; ++$i) { + /** @var array $chunk */ + $chunk = unpack('C', $binString[$i]); + $c = $chunk[1] & 0xf; + $b = $chunk[1] >> 4; + $hex .= pack('CC', 87 + $b + ($b - 10 >> 8 & ~38), 87 + $c + ($c - 10 >> 8 & ~38)); + } + return $hex; + } + /** + * Convert a binary string into a hexadecimal string without cache-timing + * leaks, returning uppercase letters (as per RFC 4648) + * + * @param string $binString (raw binary) + * @return string + * @throws TypeError + */ + public static function encodeUpper( + #[SensitiveParameter] + string $binString + ) : string + { + $hex = ''; + $len = Binary::safeStrlen($binString); + for ($i = 0; $i < $len; ++$i) { + /** @var array $chunk */ + $chunk = unpack('C', $binString[$i]); + $c = $chunk[1] & 0xf; + $b = $chunk[1] >> 4; + $hex .= pack('CC', 55 + $b + ($b - 10 >> 8 & ~6), 55 + $c + ($c - 10 >> 8 & ~6)); + } + return $hex; + } + /** + * Convert a hexadecimal string into a binary string without cache-timing + * leaks + * + * @param string $encodedString + * @param bool $strictPadding + * @return string (raw binary) + * @throws RangeException + */ + public static function decode( + #[SensitiveParameter] + string $encodedString, + bool $strictPadding = \false + ) : string + { + if (extension_loaded('sodium') && $strictPadding) { + try { + return sodium_hex2bin($encodedString); + } catch (SodiumException $ex) { + throw new RangeException($ex->getMessage(), $ex->getCode(), $ex); + } + } + $hex_pos = 0; + $bin = ''; + $c_acc = 0; + $hex_len = Binary::safeStrlen($encodedString); + $state = 0; + if (($hex_len & 1) !== 0) { + if ($strictPadding) { + throw new RangeException('Expected an even number of hexadecimal characters'); + } else { + $encodedString = '0' . $encodedString; + ++$hex_len; + } + } + /** @var array $chunk */ + $chunk = unpack('C*', $encodedString); + while ($hex_pos < $hex_len) { + ++$hex_pos; + $c = $chunk[$hex_pos]; + $c_num = $c ^ 48; + $c_num0 = $c_num - 10 >> 8; + $c_alpha = ($c & ~32) - 55; + $c_alpha0 = ($c_alpha - 10 ^ $c_alpha - 16) >> 8; + if (($c_num0 | $c_alpha0) === 0) { + throw new RangeException('Expected hexadecimal character'); + } + $c_val = $c_num0 & $c_num | $c_alpha & $c_alpha0; + if ($state === 0) { + $c_acc = $c_val * 16; + } else { + $bin .= pack('C', $c_acc | $c_val); + } + $state ^= 1; + } + return $bin; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/RFC4648.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/RFC4648.php new file mode 100644 index 00000000..63594502 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/paragonie/constant_time_encoding/src/RFC4648.php @@ -0,0 +1,206 @@ + "Zm9v" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64Encode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base64::encode($str); + } + /** + * RFC 4648 Base64 decoding + * + * "Zm9v" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64Decode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base64::decode($str, \true); + } + /** + * RFC 4648 Base64 (URL Safe) encoding + * + * "foo" -> "Zm9v" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64UrlSafeEncode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base64UrlSafe::encode($str); + } + /** + * RFC 4648 Base64 (URL Safe) decoding + * + * "Zm9v" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base64UrlSafeDecode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base64UrlSafe::decode($str, \true); + } + /** + * RFC 4648 Base32 encoding + * + * "foo" -> "MZXW6===" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32Encode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base32::encodeUpper($str); + } + /** + * RFC 4648 Base32 encoding + * + * "MZXW6===" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32Decode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base32::decodeUpper($str, \true); + } + /** + * RFC 4648 Base32-Hex encoding + * + * "foo" -> "CPNMU===" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32HexEncode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base32::encodeUpper($str); + } + /** + * RFC 4648 Base32-Hex decoding + * + * "CPNMU===" -> "foo" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base32HexDecode( + #[\SensitiveParameter] + string $str + ) : string + { + return Base32::decodeUpper($str, \true); + } + /** + * RFC 4648 Base16 decoding + * + * "foo" -> "666F6F" + * + * @param string $str + * @return string + * + * @throws TypeError + */ + public static function base16Encode( + #[\SensitiveParameter] + string $str + ) : string + { + return Hex::encodeUpper($str); + } + /** + * RFC 4648 Base16 decoding + * + * "666F6F" -> "foo" + * + * @param string $str + * @return string + */ + public static function base16Decode( + #[\SensitiveParameter] + string $str + ) : string + { + return Hex::decode($str, \true); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/CacheException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/CacheException.php new file mode 100644 index 00000000..c239c155 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * // Emit headers iteratively: + * foreach ($message->getHeaders() as $name => $values) { + * foreach ($values as $value) { + * header(sprintf('%s: %s', $name, $value), false); + * } + * } + * + * While header names are not case-sensitive, getHeaders() will preserve the + * exact case in which headers were originally specified. + * + * @return string[][] Returns an associative array of the message's headers. Each + * key MUST be a header name, and each value MUST be an array of strings + * for that header. + */ + public function getHeaders() : array; + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader(string $name) : bool; + /** + * Retrieves a message header value by the given case-insensitive name. + * + * This method returns an array of all the header values of the given + * case-insensitive header name. + * + * If the header does not appear in the message, this method MUST return an + * empty array. + * + * @param string $name Case-insensitive header field name. + * @return string[] An array of string values as provided for the given + * header. If the header does not appear in the message, this method MUST + * return an empty array. + */ + public function getHeader(string $name) : array; + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + * + * If the header does not appear in the message, this method MUST return + * an empty string. + * + * @param string $name Case-insensitive header field name. + * @return string A string of values as provided for the given header + * concatenated together using a comma. If the header does not appear in + * the message, this method MUST return an empty string. + */ + public function getHeaderLine(string $name) : string; + /** + * Return an instance with the provided value replacing the specified header. + * + * While header names are case-insensitive, the casing of the header will + * be preserved by this function, and returned from getHeaders(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new and/or updated header and value. + * + * @param string $name Case-insensitive header field name. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withHeader(string $name, $value) : MessageInterface; + /** + * Return an instance with the specified header appended with the given value. + * + * Existing values for the specified header will be maintained. The new + * value(s) will be appended to the existing list. If the header did not + * exist previously, it will be added. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * new header and/or value. + * + * @param string $name Case-insensitive header field name to add. + * @param string|string[] $value Header value(s). + * @return static + * @throws \InvalidArgumentException for invalid header names or values. + */ + public function withAddedHeader(string $name, $value) : MessageInterface; + /** + * Return an instance without the specified header. + * + * Header resolution MUST be done without case-sensitivity. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the named header. + * + * @param string $name Case-insensitive header field name to remove. + * @return static + */ + public function withoutHeader(string $name) : MessageInterface; + /** + * Gets the body of the message. + * + * @return StreamInterface Returns the body as a stream. + */ + public function getBody() : StreamInterface; + /** + * Return an instance with the specified message body. + * + * The body MUST be a StreamInterface object. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return a new instance that has the + * new body stream. + * + * @param StreamInterface $body Body. + * @return static + * @throws \InvalidArgumentException When the body is not valid. + */ + public function withBody(StreamInterface $body) : MessageInterface; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/RequestInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/RequestInterface.php new file mode 100644 index 00000000..becc9d32 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/RequestInterface.php @@ -0,0 +1,124 @@ +getQuery()` + * or from the `QUERY_STRING` server param. + * + * @return array + */ + public function getQueryParams() : array; + /** + * Return an instance with the specified query string arguments. + * + * These values SHOULD remain immutable over the course of the incoming + * request. They MAY be injected during instantiation, such as from PHP's + * $_GET superglobal, or MAY be derived from some other value such as the + * URI. In cases where the arguments are parsed from the URI, the data + * MUST be compatible with what PHP's parse_str() would return for + * purposes of how duplicate query parameters are handled, and how nested + * sets are handled. + * + * Setting query string arguments MUST NOT change the URI stored by the + * request, nor the values in the server params. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated query string arguments. + * + * @param array $query Array of query string arguments, typically from + * $_GET. + * @return static + */ + public function withQueryParams(array $query) : ServerRequestInterface; + /** + * Retrieve normalized file upload data. + * + * This method returns upload metadata in a normalized tree, with each leaf + * an instance of Psr\Http\Message\UploadedFileInterface. + * + * These values MAY be prepared from $_FILES or the message body during + * instantiation, or MAY be injected via withUploadedFiles(). + * + * @return array An array tree of UploadedFileInterface instances; an empty + * array MUST be returned if no data is present. + */ + public function getUploadedFiles() : array; + /** + * Create a new instance with the specified uploaded files. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param array $uploadedFiles An array tree of UploadedFileInterface instances. + * @return static + * @throws \InvalidArgumentException if an invalid structure is provided. + */ + public function withUploadedFiles(array $uploadedFiles) : ServerRequestInterface; + /** + * Retrieve any parameters provided in the request body. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, this method MUST + * return the contents of $_POST. + * + * Otherwise, this method may return any results of deserializing + * the request body content; as parsing returns structured content, the + * potential types MUST be arrays or objects only. A null value indicates + * the absence of body content. + * + * @return null|array|object The deserialized body parameters, if any. + * These will typically be an array or object. + */ + public function getParsedBody(); + /** + * Return an instance with the specified body parameters. + * + * These MAY be injected during instantiation. + * + * If the request Content-Type is either application/x-www-form-urlencoded + * or multipart/form-data, and the request method is POST, use this method + * ONLY to inject the contents of $_POST. + * + * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of + * deserializing the request body content. Deserialization/parsing returns + * structured data, and, as such, this method ONLY accepts arrays or objects, + * or a null value if nothing was available to parse. + * + * As an example, if content negotiation determines that the request data + * is a JSON payload, this method could be used to create a request + * instance with the deserialized parameters. + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated body parameters. + * + * @param null|array|object $data The deserialized body data. This will + * typically be in an array or object. + * @return static + * @throws \InvalidArgumentException if an unsupported argument type is + * provided. + */ + public function withParsedBody($data) : ServerRequestInterface; + /** + * Retrieve attributes derived from the request. + * + * The request "attributes" may be used to allow injection of any + * parameters derived from the request: e.g., the results of path + * match operations; the results of decrypting cookies; the results of + * deserializing non-form-encoded message bodies; etc. Attributes + * will be application and request specific, and CAN be mutable. + * + * @return array Attributes derived from the request. + */ + public function getAttributes() : array; + /** + * Retrieve a single derived request attribute. + * + * Retrieves a single derived request attribute as described in + * getAttributes(). If the attribute has not been previously set, returns + * the default value as provided. + * + * This method obviates the need for a hasAttribute() method, as it allows + * specifying a default value to return if the attribute is not found. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $default Default value to return if the attribute does not exist. + * @return mixed + */ + public function getAttribute(string $name, $default = null); + /** + * Return an instance with the specified derived request attribute. + * + * This method allows setting a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that has the + * updated attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @param mixed $value The value of the attribute. + * @return static + */ + public function withAttribute(string $name, $value) : ServerRequestInterface; + /** + * Return an instance that removes the specified derived request attribute. + * + * This method allows removing a single derived request attribute as + * described in getAttributes(). + * + * This method MUST be implemented in such a way as to retain the + * immutability of the message, and MUST return an instance that removes + * the attribute. + * + * @see getAttributes() + * @param string $name The attribute name. + * @return static + */ + public function withoutAttribute(string $name) : ServerRequestInterface; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/StreamInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/StreamInterface.php new file mode 100644 index 00000000..13c0b1ab --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/http-message/src/StreamInterface.php @@ -0,0 +1,144 @@ + + * [user-info@]host[:port] + *
    + * + * If the port component is not set or is the standard port for the current + * scheme, it SHOULD NOT be included. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2 + * @return string The URI authority, in "[user-info@]host[:port]" format. + */ + public function getAuthority() : string; + /** + * Retrieve the user information component of the URI. + * + * If no user information is present, this method MUST return an empty + * string. + * + * If a user is present in the URI, this will return that value; + * additionally, if the password is also present, it will be appended to the + * user value, with a colon (":") separating the values. + * + * The trailing "@" character is not part of the user information and MUST + * NOT be added. + * + * @return string The URI user information, in "username[:password]" format. + */ + public function getUserInfo() : string; + /** + * Retrieve the host component of the URI. + * + * If no host is present, this method MUST return an empty string. + * + * The value returned MUST be normalized to lowercase, per RFC 3986 + * Section 3.2.2. + * + * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 + * @return string The URI host. + */ + public function getHost() : string; + /** + * Retrieve the port component of the URI. + * + * If a port is present, and it is non-standard for the current scheme, + * this method MUST return it as an integer. If the port is the standard port + * used with the current scheme, this method SHOULD return null. + * + * If no port is present, and no scheme is present, this method MUST return + * a null value. + * + * If no port is present, but a scheme is present, this method MAY return + * the standard port for that scheme, but SHOULD return null. + * + * @return null|int The URI port. + */ + public function getPort() : ?int; + /** + * Retrieve the path component of the URI. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * Normally, the empty path "" and absolute path "/" are considered equal as + * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically + * do this normalization because in contexts with a trimmed base path, e.g. + * the front controller, this difference becomes significant. It's the task + * of the user to handle both "" and "/". + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.3. + * + * As an example, if the value should include a slash ("/") not intended as + * delimiter between path segments, that value MUST be passed in encoded + * form (e.g., "%2F") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.3 + * @return string The URI path. + */ + public function getPath() : string; + /** + * Retrieve the query string of the URI. + * + * If no query string is present, this method MUST return an empty string. + * + * The leading "?" character is not part of the query and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.4. + * + * As an example, if a value in a key/value pair of the query string should + * include an ampersand ("&") not intended as a delimiter between values, + * that value MUST be passed in encoded form (e.g., "%26") to the instance. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.4 + * @return string The URI query string. + */ + public function getQuery() : string; + /** + * Retrieve the fragment component of the URI. + * + * If no fragment is present, this method MUST return an empty string. + * + * The leading "#" character is not part of the fragment and MUST NOT be + * added. + * + * The value returned MUST be percent-encoded, but MUST NOT double-encode + * any characters. To determine what characters to encode, please refer to + * RFC 3986, Sections 2 and 3.5. + * + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see https://tools.ietf.org/html/rfc3986#section-3.5 + * @return string The URI fragment. + */ + public function getFragment() : string; + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * @return static A new instance with the specified scheme. + * @throws \InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme(string $scheme) : UriInterface; + /** + * Return an instance with the specified user information. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified user information. + * + * Password is optional, but the user information MUST include the + * user; an empty string for the user is equivalent to removing user + * information. + * + * @param string $user The user name to use for authority. + * @param null|string $password The password associated with $user. + * @return static A new instance with the specified user information. + */ + public function withUserInfo(string $user, ?string $password = null) : UriInterface; + /** + * Return an instance with the specified host. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified host. + * + * An empty host value is equivalent to removing the host. + * + * @param string $host The hostname to use with the new instance. + * @return static A new instance with the specified host. + * @throws \InvalidArgumentException for invalid hostnames. + */ + public function withHost(string $host) : UriInterface; + /** + * Return an instance with the specified port. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified port. + * + * Implementations MUST raise an exception for ports outside the + * established TCP and UDP port ranges. + * + * A null value provided for the port is equivalent to removing the port + * information. + * + * @param null|int $port The port to use with the new instance; a null value + * removes the port information. + * @return static A new instance with the specified port. + * @throws \InvalidArgumentException for invalid ports. + */ + public function withPort(?int $port) : UriInterface; + /** + * Return an instance with the specified path. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified path. + * + * The path can either be empty or absolute (starting with a slash) or + * rootless (not starting with a slash). Implementations MUST support all + * three syntaxes. + * + * If the path is intended to be domain-relative rather than path relative then + * it must begin with a slash ("/"). Paths not starting with a slash ("/") + * are assumed to be relative to some base path known to the application or + * consumer. + * + * Users can provide both encoded and decoded path characters. + * Implementations ensure the correct encoding as outlined in getPath(). + * + * @param string $path The path to use with the new instance. + * @return static A new instance with the specified path. + * @throws \InvalidArgumentException for invalid paths. + */ + public function withPath(string $path) : UriInterface; + /** + * Return an instance with the specified query string. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified query string. + * + * Users can provide both encoded and decoded query characters. + * Implementations ensure the correct encoding as outlined in getQuery(). + * + * An empty query string value is equivalent to removing the query string. + * + * @param string $query The query string to use with the new instance. + * @return static A new instance with the specified query string. + * @throws \InvalidArgumentException for invalid query strings. + */ + public function withQuery(string $query) : UriInterface; + /** + * Return an instance with the specified URI fragment. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified URI fragment. + * + * Users can provide both encoded and decoded fragment characters. + * Implementations ensure the correct encoding as outlined in getFragment(). + * + * An empty fragment value is equivalent to removing the fragment. + * + * @param string $fragment The fragment to use with the new instance. + * @return static A new instance with the specified fragment. + */ + public function withFragment(string $fragment) : UriInterface; + /** + * Return the string representation as a URI reference. + * + * Depending on which components of the URI are present, the resulting + * string is either a full URI or relative reference according to RFC 3986, + * Section 4.1. The method concatenates the various components of the URI, + * using the appropriate delimiters: + * + * - If a scheme is present, it MUST be suffixed by ":". + * - If an authority is present, it MUST be prefixed by "//". + * - The path can be concatenated without delimiters. But there are two + * cases where the path has to be adjusted to make the URI reference + * valid as PHP does not allow to throw an exception in __toString(): + * - If the path is rootless and an authority is present, the path MUST + * be prefixed by "/". + * - If the path is starting with more than one "/" and no authority is + * present, the starting slashes MUST be reduced to one. + * - If a query is present, it MUST be prefixed by "?". + * - If a fragment is present, it MUST be prefixed by "#". + * + * @see http://tools.ietf.org/html/rfc3986#section-4.1 + * @return string + */ + public function __toString() : string; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/LICENSE new file mode 100644 index 00000000..474c952b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/AbstractLogger.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 00000000..5646536e --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,121 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + /** + * Normal but significant events. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + /** + * Detailed debug information. + * + * @param string $message + * @param mixed[] $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/InvalidArgumentException.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 00000000..0ccd7a5a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerInterface.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 00000000..0baeaa71 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,117 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public abstract function log($level, $message, array $context = array()); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/NullLogger.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 00000000..f5416cc3 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/LICENSE new file mode 100644 index 00000000..0ed3a246 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/function.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/function.php new file mode 100644 index 00000000..d4371504 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas + */ + function trigger_deprecation(string $package, string $version, string $message, ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Idn.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Idn.php new file mode 100644 index 00000000..f28814cd --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Idn.php @@ -0,0 +1,736 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn; + +use WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn\Resources\unidata\DisallowedRanges; +use WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn\Resources\unidata\Regex; +/** + * @see https://www.unicode.org/reports/tr46/ + * + * @internal + */ +final class Idn +{ + public const ERROR_EMPTY_LABEL = 1; + public const ERROR_LABEL_TOO_LONG = 2; + public const ERROR_DOMAIN_NAME_TOO_LONG = 4; + public const ERROR_LEADING_HYPHEN = 8; + public const ERROR_TRAILING_HYPHEN = 0x10; + public const ERROR_HYPHEN_3_4 = 0x20; + public const ERROR_LEADING_COMBINING_MARK = 0x40; + public const ERROR_DISALLOWED = 0x80; + public const ERROR_PUNYCODE = 0x100; + public const ERROR_LABEL_HAS_DOT = 0x200; + public const ERROR_INVALID_ACE_LABEL = 0x400; + public const ERROR_BIDI = 0x800; + public const ERROR_CONTEXTJ = 0x1000; + public const ERROR_CONTEXTO_PUNCTUATION = 0x2000; + public const ERROR_CONTEXTO_DIGITS = 0x4000; + public const INTL_IDNA_VARIANT_2003 = 0; + public const INTL_IDNA_VARIANT_UTS46 = 1; + public const IDNA_DEFAULT = 0; + public const IDNA_ALLOW_UNASSIGNED = 1; + public const IDNA_USE_STD3_RULES = 2; + public const IDNA_CHECK_BIDI = 4; + public const IDNA_CHECK_CONTEXTJ = 8; + public const IDNA_NONTRANSITIONAL_TO_ASCII = 16; + public const IDNA_NONTRANSITIONAL_TO_UNICODE = 32; + public const MAX_DOMAIN_SIZE = 253; + public const MAX_LABEL_SIZE = 63; + public const BASE = 36; + public const TMIN = 1; + public const TMAX = 26; + public const SKEW = 38; + public const DAMP = 700; + public const INITIAL_BIAS = 72; + public const INITIAL_N = 128; + public const DELIMITER = '-'; + public const MAX_INT = 2147483647; + /** + * Contains the numeric value of a basic code point (for use in representing integers) in the + * range 0 to BASE-1, or -1 if b is does not represent a value. + * + * @var array + */ + private static $basicToDigit = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]; + /** + * @var array + */ + private static $virama; + /** + * @var array + */ + private static $mapped; + /** + * @var array + */ + private static $ignored; + /** + * @var array + */ + private static $deviation; + /** + * @var array + */ + private static $disallowed; + /** + * @var array + */ + private static $disallowed_STD3_mapped; + /** + * @var array + */ + private static $disallowed_STD3_valid; + /** + * @var bool + */ + private static $mappingTableLoaded = \false; + /** + * @see https://www.unicode.org/reports/tr46/#ToASCII + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_ascii($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (\PHP_VERSION_ID > 80400 && '' === $domainName) { + throw new \ValueError('idn_to_ascii(): Argument #1 ($domain) cannot be empty'); + } + if (self::INTL_IDNA_VARIANT_2003 === $variant) { + @\trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + $options = ['CheckHyphens' => \true, 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_ASCII), 'VerifyDnsLength' => \true]; + $info = new Info(); + $labels = self::process((string) $domainName, $options, $info); + foreach ($labels as $i => $label) { + // Only convert labels to punycode that contain non-ASCII code points + if (1 === \preg_match('/[^\\x00-\\x7F]/', $label)) { + try { + $label = 'xn--' . self::punycodeEncode($label); + } catch (\Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + } + $labels[$i] = $label; + } + } + if ($options['VerifyDnsLength']) { + self::validateDomainAndLabelLength($labels, $info); + } + $idna_info = ['result' => \implode('.', $labels), 'isTransitionalDifferent' => $info->transitionalDifferent, 'errors' => $info->errors]; + return 0 === $info->errors ? $idna_info['result'] : \false; + } + /** + * @see https://www.unicode.org/reports/tr46/#ToUnicode + * + * @param string $domainName + * @param int $options + * @param int $variant + * @param array $idna_info + * + * @return string|false + */ + public static function idn_to_utf8($domainName, $options = self::IDNA_DEFAULT, $variant = self::INTL_IDNA_VARIANT_UTS46, &$idna_info = []) + { + if (\PHP_VERSION_ID > 80400 && '' === $domainName) { + throw new \ValueError('idn_to_utf8(): Argument #1 ($domain) cannot be empty'); + } + if (self::INTL_IDNA_VARIANT_2003 === $variant) { + @\trigger_error('idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED); + } + $info = new Info(); + $labels = self::process((string) $domainName, ['CheckHyphens' => \true, 'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI), 'CheckJoiners' => self::INTL_IDNA_VARIANT_UTS46 === $variant && 0 !== ($options & self::IDNA_CHECK_CONTEXTJ), 'UseSTD3ASCIIRules' => 0 !== ($options & self::IDNA_USE_STD3_RULES), 'Transitional_Processing' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 === ($options & self::IDNA_NONTRANSITIONAL_TO_UNICODE)], $info); + $idna_info = ['result' => \implode('.', $labels), 'isTransitionalDifferent' => $info->transitionalDifferent, 'errors' => $info->errors]; + return 0 === $info->errors ? $idna_info['result'] : \false; + } + /** + * @param string $label + * + * @return bool + */ + private static function isValidContextJ(array $codePoints, $label) + { + if (!isset(self::$virama)) { + self::$virama = (require __DIR__ . \DIRECTORY_SEPARATOR . 'Resources' . \DIRECTORY_SEPARATOR . 'unidata' . \DIRECTORY_SEPARATOR . 'virama.php'); + } + $offset = 0; + foreach ($codePoints as $i => $codePoint) { + if (0x200c !== $codePoint && 0x200d !== $codePoint) { + continue; + } + if (!isset($codePoints[$i - 1])) { + return \false; + } + // If Canonical_Combining_Class(Before(cp)) .eq. Virama Then True; + if (isset(self::$virama[$codePoints[$i - 1]])) { + continue; + } + // If RegExpMatch((Joining_Type:{L,D})(Joining_Type:T)*\u200C(Joining_Type:T)*(Joining_Type:{R,D})) Then + // True; + // Generated RegExp = ([Joining_Type:{L,D}][Joining_Type:T]*\u200C[Joining_Type:T]*)[Joining_Type:{R,D}] + if (0x200c === $codePoint && 1 === \preg_match(Regex::ZWNJ, $label, $matches, \PREG_OFFSET_CAPTURE, $offset)) { + $offset += \strlen($matches[1][0]); + continue; + } + return \false; + } + return \true; + } + /** + * @see https://www.unicode.org/reports/tr46/#ProcessingStepMap + * + * @param string $input + * @param array $options + * + * @return string + */ + private static function mapCodePoints($input, array $options, Info $info) + { + $str = ''; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + $transitional = $options['Transitional_Processing']; + foreach (self::utf8Decode($input) as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + switch ($data['status']) { + case 'disallowed': + case 'valid': + $str .= \mb_chr($codePoint, 'utf-8'); + break; + case 'ignored': + // Do nothing. + break; + case 'mapped': + $str .= $transitional && 0x1e9e === $codePoint ? 'ss' : $data['mapping']; + break; + case 'deviation': + $info->transitionalDifferent = \true; + $str .= $transitional ? $data['mapping'] : \mb_chr($codePoint, 'utf-8'); + break; + } + } + return $str; + } + /** + * @see https://www.unicode.org/reports/tr46/#Processing + * + * @param string $domain + * @param array $options + * + * @return array + */ + private static function process($domain, array $options, Info $info) + { + // If VerifyDnsLength is not set, we are doing ToUnicode otherwise we are doing ToASCII and + // we need to respect the VerifyDnsLength option. + $checkForEmptyLabels = !isset($options['VerifyDnsLength']) || $options['VerifyDnsLength']; + if ($checkForEmptyLabels && '' === $domain) { + $info->errors |= self::ERROR_EMPTY_LABEL; + return [$domain]; + } + // Step 1. Map each code point in the domain name string + $domain = self::mapCodePoints($domain, $options, $info); + // Step 2. Normalize the domain name string to Unicode Normalization Form C. + if (!\Normalizer::isNormalized($domain, \Normalizer::FORM_C)) { + $domain = \Normalizer::normalize($domain, \Normalizer::FORM_C); + } + // Step 3. Break the string into labels at U+002E (.) FULL STOP. + $labels = \explode('.', $domain); + $lastLabelIndex = \count($labels) - 1; + // Step 4. Convert and validate each label in the domain name string. + foreach ($labels as $i => $label) { + $validationOptions = $options; + if ('xn--' === \substr($label, 0, 4)) { + // Step 4.1. If the label contains any non-ASCII code point (i.e., a code point greater than U+007F), + // record that there was an error, and continue with the next label. + if (\preg_match('/[^\\x00-\\x7F]/', $label)) { + $info->errors |= self::ERROR_PUNYCODE; + continue; + } + // Step 4.2. Attempt to convert the rest of the label to Unicode according to Punycode [RFC3492]. If + // that conversion fails, record that there was an error, and continue + // with the next label. Otherwise replace the original label in the string by the results of the + // conversion. Per UTS #46 revision 33, if the conversion succeeds but the result is empty or + // contains only ASCII code points, record that there was an error and continue with the next label. + try { + $label = self::punycodeDecode(\substr($label, 4)); + } catch (\Exception $e) { + $info->errors |= self::ERROR_PUNYCODE; + continue; + } + if ('' === $label || 1 !== \preg_match('/[^\\x00-\\x7F]/', $label)) { + $info->errors |= self::ERROR_INVALID_ACE_LABEL; + continue; + } + $validationOptions['Transitional_Processing'] = \false; + $labels[$i] = $label; + } + self::validateLabel($label, $info, $validationOptions, $i > 0 && $i === $lastLabelIndex); + } + if ($info->bidiDomain && !$info->validBidiDomain) { + $info->errors |= self::ERROR_BIDI; + } + // Any input domain name string that does not record an error has been successfully + // processed according to this specification. Conversely, if an input domain_name string + // causes an error, then the processing of the input domain_name string fails. Determining + // what to do with error input is up to the caller, and not in the scope of this document. + return $labels; + } + /** + * @see https://tools.ietf.org/html/rfc5893#section-2 + * + * @param string $label + */ + private static function validateBidiLabel($label, Info $info) + { + if (1 === \preg_match(Regex::RTL_LABEL, $label)) { + $info->bidiDomain = \true; + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the R or AL property, it is an RTL label + if (1 !== \preg_match(Regex::BIDI_STEP_1_RTL, $label)) { + $info->validBidiDomain = \false; + return; + } + // Step 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, + // CS, ET, ON, BN, or NSM are allowed. + if (1 === \preg_match(Regex::BIDI_STEP_2, $label)) { + $info->validBidiDomain = \false; + return; + } + // Step 3. In an RTL label, the end of the label must be a character with Bidi property + // R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM. + if (1 !== \preg_match(Regex::BIDI_STEP_3, $label)) { + $info->validBidiDomain = \false; + return; + } + // Step 4. In an RTL label, if an EN is present, no AN may be present, and vice versa. + if (1 === \preg_match(Regex::BIDI_STEP_4_AN, $label) && 1 === \preg_match(Regex::BIDI_STEP_4_EN, $label)) { + $info->validBidiDomain = \false; + return; + } + return; + } + // We are a LTR label + // Step 1. The first character must be a character with Bidi property L, R, or AL. + // If it has the L property, it is an LTR label. + if (1 !== \preg_match(Regex::BIDI_STEP_1_LTR, $label)) { + $info->validBidiDomain = \false; + return; + } + // Step 5. In an LTR label, only characters with the Bidi properties L, EN, + // ES, CS, ET, ON, BN, or NSM are allowed. + if (1 === \preg_match(Regex::BIDI_STEP_5, $label)) { + $info->validBidiDomain = \false; + return; + } + // Step 6.In an LTR label, the end of the label must be a character with Bidi property L or + // EN, followed by zero or more characters with Bidi property NSM. + if (1 !== \preg_match(Regex::BIDI_STEP_6, $label)) { + $info->validBidiDomain = \false; + return; + } + } + /** + * @param array $labels + */ + private static function validateDomainAndLabelLength(array $labels, Info $info) + { + $maxDomainSize = self::MAX_DOMAIN_SIZE; + $length = \count($labels); + // Number of "." delimiters. + $domainLength = $length - 1; + // If the last label is empty and it is not the first label, then it is the root label. + // Increase the max size by 1, making it 254, to account for the root label's "." + // delimiter. This also means we don't need to check the last label's length for being too + // long. + if ($length > 1 && '' === $labels[$length - 1]) { + ++$maxDomainSize; + --$length; + } + for ($i = 0; $i < $length; ++$i) { + $bytes = \strlen($labels[$i]); + $domainLength += $bytes; + if ($bytes > self::MAX_LABEL_SIZE) { + $info->errors |= self::ERROR_LABEL_TOO_LONG; + } + } + if ($domainLength > $maxDomainSize) { + $info->errors |= self::ERROR_DOMAIN_NAME_TOO_LONG; + } + } + /** + * @see https://www.unicode.org/reports/tr46/#Validity_Criteria + * + * @param string $label + * @param array $options + * @param bool $canBeEmpty + */ + private static function validateLabel($label, Info $info, array $options, $canBeEmpty) + { + if ('' === $label) { + if (!$canBeEmpty && (!isset($options['VerifyDnsLength']) || $options['VerifyDnsLength'])) { + $info->errors |= self::ERROR_EMPTY_LABEL; + } + return; + } + // Step 1. The label must be in Unicode Normalization Form C. + if (!\Normalizer::isNormalized($label, \Normalizer::FORM_C)) { + $info->errors |= self::ERROR_INVALID_ACE_LABEL; + } + $codePoints = self::utf8Decode($label); + if ($options['CheckHyphens']) { + // Step 2. If CheckHyphens, the label must not contain a U+002D HYPHEN-MINUS character + // in both the thrid and fourth positions. + if (isset($codePoints[2], $codePoints[3]) && 0x2d === $codePoints[2] && 0x2d === $codePoints[3]) { + $info->errors |= self::ERROR_HYPHEN_3_4; + } + // Step 3. If CheckHyphens, the label must neither begin nor end with a U+002D + // HYPHEN-MINUS character. + if ('-' === \substr($label, 0, 1)) { + $info->errors |= self::ERROR_LEADING_HYPHEN; + } + if ('-' === \substr($label, -1, 1)) { + $info->errors |= self::ERROR_TRAILING_HYPHEN; + } + } elseif ('xn--' === \substr($label, 0, 4)) { + $info->errors |= self::ERROR_PUNYCODE; + } + // Step 4. The label must not contain a U+002E (.) FULL STOP. + if (\false !== \strpos($label, '.')) { + $info->errors |= self::ERROR_LABEL_HAS_DOT; + } + // Step 5. The label must not begin with a combining mark, that is: General_Category=Mark. + if (1 === \preg_match(Regex::COMBINING_MARK, $label)) { + $info->errors |= self::ERROR_LEADING_COMBINING_MARK; + } + // Step 6. Each code point in the label must only have certain status values according to + // Section 5, IDNA Mapping Table: + $transitional = $options['Transitional_Processing']; + $useSTD3ASCIIRules = $options['UseSTD3ASCIIRules']; + foreach ($codePoints as $codePoint) { + $data = self::lookupCodePointStatus($codePoint, $useSTD3ASCIIRules); + $status = $data['status']; + if ('valid' === $status || !$transitional && 'deviation' === $status) { + continue; + } + $info->errors |= self::ERROR_DISALLOWED; + break; + } + // Step 7. If CheckJoiners, the label must satisify the ContextJ rules from Appendix A, in + // The Unicode Code Points and Internationalized Domain Names for Applications (IDNA) + // [IDNA2008]. + if ($options['CheckJoiners'] && !self::isValidContextJ($codePoints, $label)) { + $info->errors |= self::ERROR_CONTEXTJ; + } + // Step 8. If CheckBidi, and if the domain name is a Bidi domain name, then the label must + // satisfy all six of the numbered conditions in [IDNA2008] RFC 5893, Section 2. + if ($options['CheckBidi'] && (!$info->bidiDomain || $info->validBidiDomain)) { + self::validateBidiLabel($label, $info); + } + } + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.2 + * + * @param string $input + * + * @return string + */ + private static function punycodeDecode($input) + { + $n = self::INITIAL_N; + $out = 0; + $i = 0; + $bias = self::INITIAL_BIAS; + $lastDelimIndex = \strrpos($input, self::DELIMITER); + $b = \false === $lastDelimIndex ? 0 : $lastDelimIndex; + $inputLength = \strlen($input); + $output = []; + $bytes = \array_map('ord', \str_split($input)); + for ($j = 0; $j < $b; ++$j) { + if ($bytes[$j] > 0x7f) { + throw new \Exception('Invalid input'); + } + $output[$out++] = $input[$j]; + } + if ($b > 0) { + ++$b; + } + for ($in = $b; $in < $inputLength; ++$out) { + $oldi = $i; + $w = 1; + for ($k = self::BASE;; $k += self::BASE) { + if ($in >= $inputLength) { + throw new \Exception('Invalid input'); + } + $digit = self::$basicToDigit[$bytes[$in++] & 0xff]; + if ($digit < 0) { + throw new \Exception('Invalid input'); + } + if ($digit > \intdiv(self::MAX_INT - $i, $w)) { + throw new \Exception('Integer overflow'); + } + $i += $digit * $w; + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + if ($digit < $t) { + break; + } + $baseMinusT = self::BASE - $t; + if ($w > \intdiv(self::MAX_INT, $baseMinusT)) { + throw new \Exception('Integer overflow'); + } + $w *= $baseMinusT; + } + $outPlusOne = $out + 1; + $bias = self::adaptBias($i - $oldi, $outPlusOne, 0 === $oldi); + if (\intdiv($i, $outPlusOne) > self::MAX_INT - $n) { + throw new \Exception('Integer overflow'); + } + $n += \intdiv($i, $outPlusOne); + $i %= $outPlusOne; + \array_splice($output, $i++, 0, [\mb_chr($n, 'utf-8')]); + } + return \implode('', $output); + } + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.3 + * + * @param string $input + * + * @return string + */ + private static function punycodeEncode($input) + { + $n = self::INITIAL_N; + $delta = 0; + $out = 0; + $bias = self::INITIAL_BIAS; + $inputLength = 0; + $output = ''; + $iter = self::utf8Decode($input); + foreach ($iter as $codePoint) { + ++$inputLength; + if ($codePoint < 0x80) { + $output .= \chr($codePoint); + ++$out; + } + } + $h = $out; + $b = $out; + if ($b > 0) { + $output .= self::DELIMITER; + ++$out; + } + while ($h < $inputLength) { + $m = self::MAX_INT; + foreach ($iter as $codePoint) { + if ($codePoint >= $n && $codePoint < $m) { + $m = $codePoint; + } + } + if ($m - $n > \intdiv(self::MAX_INT - $delta, $h + 1)) { + throw new \Exception('Integer overflow'); + } + $delta += ($m - $n) * ($h + 1); + $n = $m; + foreach ($iter as $codePoint) { + if ($codePoint < $n && 0 === ++$delta) { + throw new \Exception('Integer overflow'); + } + if ($codePoint === $n) { + $q = $delta; + for ($k = self::BASE;; $k += self::BASE) { + if ($k <= $bias) { + $t = self::TMIN; + } elseif ($k >= $bias + self::TMAX) { + $t = self::TMAX; + } else { + $t = $k - $bias; + } + if ($q < $t) { + break; + } + $qMinusT = $q - $t; + $baseMinusT = self::BASE - $t; + $output .= self::encodeDigit($t + $qMinusT % $baseMinusT, \false); + ++$out; + $q = \intdiv($qMinusT, $baseMinusT); + } + $output .= self::encodeDigit($q, \false); + ++$out; + $bias = self::adaptBias($delta, $h + 1, $h === $b); + $delta = 0; + ++$h; + } + } + ++$delta; + ++$n; + } + return $output; + } + /** + * @see https://tools.ietf.org/html/rfc3492#section-6.1 + * + * @param int $delta + * @param int $numPoints + * @param bool $firstTime + * + * @return int + */ + private static function adaptBias($delta, $numPoints, $firstTime) + { + // xxx >> 1 is a faster way of doing intdiv(xxx, 2) + $delta = $firstTime ? \intdiv($delta, self::DAMP) : $delta >> 1; + $delta += \intdiv($delta, $numPoints); + $k = 0; + while ($delta > (self::BASE - self::TMIN) * self::TMAX >> 1) { + $delta = \intdiv($delta, self::BASE - self::TMIN); + $k += self::BASE; + } + return $k + \intdiv((self::BASE - self::TMIN + 1) * $delta, $delta + self::SKEW); + } + /** + * @param int $d + * @param bool $flag + * + * @return string + */ + private static function encodeDigit($d, $flag) + { + return \chr($d + 22 + 75 * ($d < 26 ? 1 : 0) - (($flag ? 1 : 0) << 5)); + } + /** + * Takes a UTF-8 encoded string and converts it into a series of integer code points. Any + * invalid byte sequences will be replaced by a U+FFFD replacement code point. + * + * @see https://encoding.spec.whatwg.org/#utf-8-decoder + * + * @param string $input + * + * @return array + */ + private static function utf8Decode($input) + { + $bytesSeen = 0; + $bytesNeeded = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xbf; + $codePoint = 0; + $codePoints = []; + $length = \strlen($input); + for ($i = 0; $i < $length; ++$i) { + $byte = \ord($input[$i]); + if (0 === $bytesNeeded) { + if ($byte >= 0x0 && $byte <= 0x7f) { + $codePoints[] = $byte; + continue; + } + if ($byte >= 0xc2 && $byte <= 0xdf) { + $bytesNeeded = 1; + $codePoint = $byte & 0x1f; + } elseif ($byte >= 0xe0 && $byte <= 0xef) { + if (0xe0 === $byte) { + $lowerBoundary = 0xa0; + } elseif (0xed === $byte) { + $upperBoundary = 0x9f; + } + $bytesNeeded = 2; + $codePoint = $byte & 0xf; + } elseif ($byte >= 0xf0 && $byte <= 0xf4) { + if (0xf0 === $byte) { + $lowerBoundary = 0x90; + } elseif (0xf4 === $byte) { + $upperBoundary = 0x8f; + } + $bytesNeeded = 3; + $codePoint = $byte & 0x7; + } else { + $codePoints[] = 0xfffd; + } + continue; + } + if ($byte < $lowerBoundary || $byte > $upperBoundary) { + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + $lowerBoundary = 0x80; + $upperBoundary = 0xbf; + --$i; + $codePoints[] = 0xfffd; + continue; + } + $lowerBoundary = 0x80; + $upperBoundary = 0xbf; + $codePoint = $codePoint << 6 | $byte & 0x3f; + if (++$bytesSeen !== $bytesNeeded) { + continue; + } + $codePoints[] = $codePoint; + $codePoint = 0; + $bytesNeeded = 0; + $bytesSeen = 0; + } + // String unexpectedly ended, so append a U+FFFD code point. + if (0 !== $bytesNeeded) { + $codePoints[] = 0xfffd; + } + return $codePoints; + } + /** + * @param int $codePoint + * @param bool $useSTD3ASCIIRules + * + * @return array{status: string, mapping?: string} + */ + private static function lookupCodePointStatus($codePoint, $useSTD3ASCIIRules) + { + if (!self::$mappingTableLoaded) { + self::$mappingTableLoaded = \true; + self::$mapped = (require __DIR__ . '/Resources/unidata/mapped.php'); + self::$ignored = (require __DIR__ . '/Resources/unidata/ignored.php'); + self::$deviation = (require __DIR__ . '/Resources/unidata/deviation.php'); + self::$disallowed = (require __DIR__ . '/Resources/unidata/disallowed.php'); + self::$disallowed_STD3_mapped = (require __DIR__ . '/Resources/unidata/disallowed_STD3_mapped.php'); + self::$disallowed_STD3_valid = (require __DIR__ . '/Resources/unidata/disallowed_STD3_valid.php'); + } + if (isset(self::$mapped[$codePoint])) { + return ['status' => 'mapped', 'mapping' => self::$mapped[$codePoint]]; + } + if (isset(self::$ignored[$codePoint])) { + return ['status' => 'ignored']; + } + if (isset(self::$deviation[$codePoint])) { + return ['status' => 'deviation', 'mapping' => self::$deviation[$codePoint]]; + } + if (isset(self::$disallowed[$codePoint]) || DisallowedRanges::inRange($codePoint)) { + return ['status' => 'disallowed']; + } + $isDisallowedMapped = isset(self::$disallowed_STD3_mapped[$codePoint]); + if ($isDisallowedMapped || isset(self::$disallowed_STD3_valid[$codePoint])) { + $status = 'disallowed'; + if (!$useSTD3ASCIIRules) { + $status = $isDisallowedMapped ? 'mapped' : 'valid'; + } + if ($isDisallowedMapped) { + return ['status' => $status, 'mapping' => self::$disallowed_STD3_mapped[$codePoint]]; + } + return ['status' => $status]; + } + return ['status' => 'valid']; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Info.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Info.php new file mode 100644 index 00000000..6a026206 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Info.php @@ -0,0 +1,22 @@ + and Trevor Rowbotham + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn; + +/** + * @internal + */ +class Info +{ + public $bidiDomain = \false; + public $errors = 0; + public $validBidiDomain = \true; + public $transitionalDifferent = \false; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/LICENSE new file mode 100644 index 00000000..fd0a0626 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-present Fabien Potencier and Trevor Rowbotham + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php new file mode 100644 index 00000000..4becde22 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/DisallowedRanges.php @@ -0,0 +1,294 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn\Resources\unidata; + +/** + * @internal + */ +final class DisallowedRanges +{ + /** + * @param int $codePoint + * + * @return bool + */ + public static function inRange($codePoint) + { + if ($codePoint >= 128 && $codePoint <= 159) { + return \true; + } + if ($codePoint >= 2155 && $codePoint <= 2207) { + return \true; + } + if ($codePoint >= 3676 && $codePoint <= 3712) { + return \true; + } + if ($codePoint >= 3808 && $codePoint <= 3839) { + return \true; + } + if ($codePoint >= 4059 && $codePoint <= 4095) { + return \true; + } + if ($codePoint >= 4256 && $codePoint <= 4293) { + return \true; + } + if ($codePoint >= 6849 && $codePoint <= 6911) { + return \true; + } + if ($codePoint >= 11859 && $codePoint <= 11903) { + return \true; + } + if ($codePoint >= 42955 && $codePoint <= 42996) { + return \true; + } + if ($codePoint >= 55296 && $codePoint <= 57343) { + return \true; + } + if ($codePoint >= 57344 && $codePoint <= 63743) { + return \true; + } + if ($codePoint >= 64218 && $codePoint <= 64255) { + return \true; + } + if ($codePoint >= 64976 && $codePoint <= 65007) { + return \true; + } + if ($codePoint >= 65630 && $codePoint <= 65663) { + return \true; + } + if ($codePoint >= 65953 && $codePoint <= 65999) { + return \true; + } + if ($codePoint >= 66046 && $codePoint <= 66175) { + return \true; + } + if ($codePoint >= 66518 && $codePoint <= 66559) { + return \true; + } + if ($codePoint >= 66928 && $codePoint <= 67071) { + return \true; + } + if ($codePoint >= 67432 && $codePoint <= 67583) { + return \true; + } + if ($codePoint >= 67760 && $codePoint <= 67807) { + return \true; + } + if ($codePoint >= 67904 && $codePoint <= 67967) { + return \true; + } + if ($codePoint >= 68256 && $codePoint <= 68287) { + return \true; + } + if ($codePoint >= 68528 && $codePoint <= 68607) { + return \true; + } + if ($codePoint >= 68681 && $codePoint <= 68735) { + return \true; + } + if ($codePoint >= 68922 && $codePoint <= 69215) { + return \true; + } + if ($codePoint >= 69298 && $codePoint <= 69375) { + return \true; + } + if ($codePoint >= 69466 && $codePoint <= 69551) { + return \true; + } + if ($codePoint >= 70207 && $codePoint <= 70271) { + return \true; + } + if ($codePoint >= 70517 && $codePoint <= 70655) { + return \true; + } + if ($codePoint >= 70874 && $codePoint <= 71039) { + return \true; + } + if ($codePoint >= 71134 && $codePoint <= 71167) { + return \true; + } + if ($codePoint >= 71370 && $codePoint <= 71423) { + return \true; + } + if ($codePoint >= 71488 && $codePoint <= 71679) { + return \true; + } + if ($codePoint >= 71740 && $codePoint <= 71839) { + return \true; + } + if ($codePoint >= 72026 && $codePoint <= 72095) { + return \true; + } + if ($codePoint >= 72441 && $codePoint <= 72703) { + return \true; + } + if ($codePoint >= 72887 && $codePoint <= 72959) { + return \true; + } + if ($codePoint >= 73130 && $codePoint <= 73439) { + return \true; + } + if ($codePoint >= 73465 && $codePoint <= 73647) { + return \true; + } + if ($codePoint >= 74650 && $codePoint <= 74751) { + return \true; + } + if ($codePoint >= 75076 && $codePoint <= 77823) { + return \true; + } + if ($codePoint >= 78905 && $codePoint <= 82943) { + return \true; + } + if ($codePoint >= 83527 && $codePoint <= 92159) { + return \true; + } + if ($codePoint >= 92784 && $codePoint <= 92879) { + return \true; + } + if ($codePoint >= 93072 && $codePoint <= 93759) { + return \true; + } + if ($codePoint >= 93851 && $codePoint <= 93951) { + return \true; + } + if ($codePoint >= 94112 && $codePoint <= 94175) { + return \true; + } + if ($codePoint >= 101590 && $codePoint <= 101631) { + return \true; + } + if ($codePoint >= 101641 && $codePoint <= 110591) { + return \true; + } + if ($codePoint >= 110879 && $codePoint <= 110927) { + return \true; + } + if ($codePoint >= 111356 && $codePoint <= 113663) { + return \true; + } + if ($codePoint >= 113828 && $codePoint <= 118783) { + return \true; + } + if ($codePoint >= 119366 && $codePoint <= 119519) { + return \true; + } + if ($codePoint >= 119673 && $codePoint <= 119807) { + return \true; + } + if ($codePoint >= 121520 && $codePoint <= 122879) { + return \true; + } + if ($codePoint >= 122923 && $codePoint <= 123135) { + return \true; + } + if ($codePoint >= 123216 && $codePoint <= 123583) { + return \true; + } + if ($codePoint >= 123648 && $codePoint <= 124927) { + return \true; + } + if ($codePoint >= 125143 && $codePoint <= 125183) { + return \true; + } + if ($codePoint >= 125280 && $codePoint <= 126064) { + return \true; + } + if ($codePoint >= 126133 && $codePoint <= 126208) { + return \true; + } + if ($codePoint >= 126270 && $codePoint <= 126463) { + return \true; + } + if ($codePoint >= 126652 && $codePoint <= 126703) { + return \true; + } + if ($codePoint >= 126706 && $codePoint <= 126975) { + return \true; + } + if ($codePoint >= 127406 && $codePoint <= 127461) { + return \true; + } + if ($codePoint >= 127590 && $codePoint <= 127743) { + return \true; + } + if ($codePoint >= 129202 && $codePoint <= 129279) { + return \true; + } + if ($codePoint >= 129751 && $codePoint <= 129791) { + return \true; + } + if ($codePoint >= 129995 && $codePoint <= 130031) { + return \true; + } + if ($codePoint >= 130042 && $codePoint <= 131069) { + return \true; + } + if ($codePoint >= 173790 && $codePoint <= 173823) { + return \true; + } + if ($codePoint >= 191457 && $codePoint <= 194559) { + return \true; + } + if ($codePoint >= 195102 && $codePoint <= 196605) { + return \true; + } + if ($codePoint >= 201547 && $codePoint <= 262141) { + return \true; + } + if ($codePoint >= 262144 && $codePoint <= 327677) { + return \true; + } + if ($codePoint >= 327680 && $codePoint <= 393213) { + return \true; + } + if ($codePoint >= 393216 && $codePoint <= 458749) { + return \true; + } + if ($codePoint >= 458752 && $codePoint <= 524285) { + return \true; + } + if ($codePoint >= 524288 && $codePoint <= 589821) { + return \true; + } + if ($codePoint >= 589824 && $codePoint <= 655357) { + return \true; + } + if ($codePoint >= 655360 && $codePoint <= 720893) { + return \true; + } + if ($codePoint >= 720896 && $codePoint <= 786429) { + return \true; + } + if ($codePoint >= 786432 && $codePoint <= 851965) { + return \true; + } + if ($codePoint >= 851968 && $codePoint <= 917501) { + return \true; + } + if ($codePoint >= 917536 && $codePoint <= 917631) { + return \true; + } + if ($codePoint >= 917632 && $codePoint <= 917759) { + return \true; + } + if ($codePoint >= 918000 && $codePoint <= 983037) { + return \true; + } + if ($codePoint >= 983040 && $codePoint <= 1048573) { + return \true; + } + if ($codePoint >= 1048576 && $codePoint <= 1114109) { + return \true; + } + return \false; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/Regex.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/Regex.php new file mode 100644 index 00000000..2c6ecb7a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/Regex.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn\Resources\unidata; + +/** + * @internal + */ +final class Regex +{ + const COMBINING_MARK = '/^[\\x{0300}-\\x{036F}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{0591}-\\x{05BD}\\x{05BF}\\x{05C1}-\\x{05C2}\\x{05C4}-\\x{05C5}\\x{05C7}\\x{0610}-\\x{061A}\\x{064B}-\\x{065F}\\x{0670}\\x{06D6}-\\x{06DC}\\x{06DF}-\\x{06E4}\\x{06E7}-\\x{06E8}\\x{06EA}-\\x{06ED}\\x{0711}\\x{0730}-\\x{074A}\\x{07A6}-\\x{07B0}\\x{07EB}-\\x{07F3}\\x{07FD}\\x{0816}-\\x{0819}\\x{081B}-\\x{0823}\\x{0825}-\\x{0827}\\x{0829}-\\x{082D}\\x{0859}-\\x{085B}\\x{08D3}-\\x{08E1}\\x{08E3}-\\x{0902}\\x{0903}\\x{093A}\\x{093B}\\x{093C}\\x{093E}-\\x{0940}\\x{0941}-\\x{0948}\\x{0949}-\\x{094C}\\x{094D}\\x{094E}-\\x{094F}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{0982}-\\x{0983}\\x{09BC}\\x{09BE}-\\x{09C0}\\x{09C1}-\\x{09C4}\\x{09C7}-\\x{09C8}\\x{09CB}-\\x{09CC}\\x{09CD}\\x{09D7}\\x{09E2}-\\x{09E3}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A03}\\x{0A3C}\\x{0A3E}-\\x{0A40}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0A83}\\x{0ABC}\\x{0ABE}-\\x{0AC0}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0AC9}\\x{0ACB}-\\x{0ACC}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B02}-\\x{0B03}\\x{0B3C}\\x{0B3E}\\x{0B3F}\\x{0B40}\\x{0B41}-\\x{0B44}\\x{0B47}-\\x{0B48}\\x{0B4B}-\\x{0B4C}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B57}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BBE}-\\x{0BBF}\\x{0BC0}\\x{0BC1}-\\x{0BC2}\\x{0BC6}-\\x{0BC8}\\x{0BCA}-\\x{0BCC}\\x{0BCD}\\x{0BD7}\\x{0C00}\\x{0C01}-\\x{0C03}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C41}-\\x{0C44}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C81}\\x{0C82}-\\x{0C83}\\x{0CBC}\\x{0CBE}\\x{0CBF}\\x{0CC0}-\\x{0CC4}\\x{0CC6}\\x{0CC7}-\\x{0CC8}\\x{0CCA}-\\x{0CCB}\\x{0CCC}-\\x{0CCD}\\x{0CD5}-\\x{0CD6}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D02}-\\x{0D03}\\x{0D3B}-\\x{0D3C}\\x{0D3E}-\\x{0D40}\\x{0D41}-\\x{0D44}\\x{0D46}-\\x{0D48}\\x{0D4A}-\\x{0D4C}\\x{0D4D}\\x{0D57}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0D82}-\\x{0D83}\\x{0DCA}\\x{0DCF}-\\x{0DD1}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0DD8}-\\x{0DDF}\\x{0DF2}-\\x{0DF3}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F3E}-\\x{0F3F}\\x{0F71}-\\x{0F7E}\\x{0F7F}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102B}-\\x{102C}\\x{102D}-\\x{1030}\\x{1031}\\x{1032}-\\x{1037}\\x{1038}\\x{1039}-\\x{103A}\\x{103B}-\\x{103C}\\x{103D}-\\x{103E}\\x{1056}-\\x{1057}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1062}-\\x{1064}\\x{1067}-\\x{106D}\\x{1071}-\\x{1074}\\x{1082}\\x{1083}-\\x{1084}\\x{1085}-\\x{1086}\\x{1087}-\\x{108C}\\x{108D}\\x{108F}\\x{109A}-\\x{109C}\\x{109D}\\x{135D}-\\x{135F}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B6}\\x{17B7}-\\x{17BD}\\x{17BE}-\\x{17C5}\\x{17C6}\\x{17C7}-\\x{17C8}\\x{17C9}-\\x{17D3}\\x{17DD}\\x{180B}-\\x{180D}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1923}-\\x{1926}\\x{1927}-\\x{1928}\\x{1929}-\\x{192B}\\x{1930}-\\x{1931}\\x{1932}\\x{1933}-\\x{1938}\\x{1939}-\\x{193B}\\x{1A17}-\\x{1A18}\\x{1A19}-\\x{1A1A}\\x{1A1B}\\x{1A55}\\x{1A56}\\x{1A57}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A61}\\x{1A62}\\x{1A63}-\\x{1A64}\\x{1A65}-\\x{1A6C}\\x{1A6D}-\\x{1A72}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B04}\\x{1B34}\\x{1B35}\\x{1B36}-\\x{1B3A}\\x{1B3B}\\x{1B3C}\\x{1B3D}-\\x{1B41}\\x{1B42}\\x{1B43}-\\x{1B44}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1B82}\\x{1BA1}\\x{1BA2}-\\x{1BA5}\\x{1BA6}-\\x{1BA7}\\x{1BA8}-\\x{1BA9}\\x{1BAA}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE7}\\x{1BE8}-\\x{1BE9}\\x{1BEA}-\\x{1BEC}\\x{1BED}\\x{1BEE}\\x{1BEF}-\\x{1BF1}\\x{1BF2}-\\x{1BF3}\\x{1C24}-\\x{1C2B}\\x{1C2C}-\\x{1C33}\\x{1C34}-\\x{1C35}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE1}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF7}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2CEF}-\\x{2CF1}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{302A}-\\x{302D}\\x{302E}-\\x{302F}\\x{3099}-\\x{309A}\\x{A66F}\\x{A670}-\\x{A672}\\x{A674}-\\x{A67D}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A802}\\x{A806}\\x{A80B}\\x{A823}-\\x{A824}\\x{A825}-\\x{A826}\\x{A827}\\x{A82C}\\x{A880}-\\x{A881}\\x{A8B4}-\\x{A8C3}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A952}-\\x{A953}\\x{A980}-\\x{A982}\\x{A983}\\x{A9B3}\\x{A9B4}-\\x{A9B5}\\x{A9B6}-\\x{A9B9}\\x{A9BA}-\\x{A9BB}\\x{A9BC}-\\x{A9BD}\\x{A9BE}-\\x{A9C0}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA2F}-\\x{AA30}\\x{AA31}-\\x{AA32}\\x{AA33}-\\x{AA34}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA4D}\\x{AA7B}\\x{AA7C}\\x{AA7D}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEB}\\x{AAEC}-\\x{AAED}\\x{AAEE}-\\x{AAEF}\\x{AAF5}\\x{AAF6}\\x{ABE3}-\\x{ABE4}\\x{ABE5}\\x{ABE6}-\\x{ABE7}\\x{ABE8}\\x{ABE9}-\\x{ABEA}\\x{ABEC}\\x{ABED}\\x{FB1E}\\x{FE00}-\\x{FE0F}\\x{FE20}-\\x{FE2F}\\x{101FD}\\x{102E0}\\x{10376}-\\x{1037A}\\x{10A01}-\\x{10A03}\\x{10A05}-\\x{10A06}\\x{10A0C}-\\x{10A0F}\\x{10A38}-\\x{10A3A}\\x{10A3F}\\x{10AE5}-\\x{10AE6}\\x{10D24}-\\x{10D27}\\x{10EAB}-\\x{10EAC}\\x{10F46}-\\x{10F50}\\x{11000}\\x{11001}\\x{11002}\\x{11038}-\\x{11046}\\x{1107F}-\\x{11081}\\x{11082}\\x{110B0}-\\x{110B2}\\x{110B3}-\\x{110B6}\\x{110B7}-\\x{110B8}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112C}\\x{1112D}-\\x{11134}\\x{11145}-\\x{11146}\\x{11173}\\x{11180}-\\x{11181}\\x{11182}\\x{111B3}-\\x{111B5}\\x{111B6}-\\x{111BE}\\x{111BF}-\\x{111C0}\\x{111C9}-\\x{111CC}\\x{111CE}\\x{111CF}\\x{1122C}-\\x{1122E}\\x{1122F}-\\x{11231}\\x{11232}-\\x{11233}\\x{11234}\\x{11235}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E0}-\\x{112E2}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{11302}-\\x{11303}\\x{1133B}-\\x{1133C}\\x{1133E}-\\x{1133F}\\x{11340}\\x{11341}-\\x{11344}\\x{11347}-\\x{11348}\\x{1134B}-\\x{1134D}\\x{11357}\\x{11362}-\\x{11363}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11435}-\\x{11437}\\x{11438}-\\x{1143F}\\x{11440}-\\x{11441}\\x{11442}-\\x{11444}\\x{11445}\\x{11446}\\x{1145E}\\x{114B0}-\\x{114B2}\\x{114B3}-\\x{114B8}\\x{114B9}\\x{114BA}\\x{114BB}-\\x{114BE}\\x{114BF}-\\x{114C0}\\x{114C1}\\x{114C2}-\\x{114C3}\\x{115AF}-\\x{115B1}\\x{115B2}-\\x{115B5}\\x{115B8}-\\x{115BB}\\x{115BC}-\\x{115BD}\\x{115BE}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11630}-\\x{11632}\\x{11633}-\\x{1163A}\\x{1163B}-\\x{1163C}\\x{1163D}\\x{1163E}\\x{1163F}-\\x{11640}\\x{116AB}\\x{116AC}\\x{116AD}\\x{116AE}-\\x{116AF}\\x{116B0}-\\x{116B5}\\x{116B6}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11720}-\\x{11721}\\x{11722}-\\x{11725}\\x{11726}\\x{11727}-\\x{1172B}\\x{1182C}-\\x{1182E}\\x{1182F}-\\x{11837}\\x{11838}\\x{11839}-\\x{1183A}\\x{11930}-\\x{11935}\\x{11937}-\\x{11938}\\x{1193B}-\\x{1193C}\\x{1193D}\\x{1193E}\\x{11940}\\x{11942}\\x{11943}\\x{119D1}-\\x{119D3}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119DC}-\\x{119DF}\\x{119E0}\\x{119E4}\\x{11A01}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A39}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A57}-\\x{11A58}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A97}\\x{11A98}-\\x{11A99}\\x{11C2F}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C3E}\\x{11C3F}\\x{11C92}-\\x{11CA7}\\x{11CA9}\\x{11CAA}-\\x{11CB0}\\x{11CB1}\\x{11CB2}-\\x{11CB3}\\x{11CB4}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D8A}-\\x{11D8E}\\x{11D90}-\\x{11D91}\\x{11D93}-\\x{11D94}\\x{11D95}\\x{11D96}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{11EF5}-\\x{11EF6}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F51}-\\x{16F87}\\x{16F8F}-\\x{16F92}\\x{16FE4}\\x{16FF0}-\\x{16FF1}\\x{1BC9D}-\\x{1BC9E}\\x{1D165}-\\x{1D166}\\x{1D167}-\\x{1D169}\\x{1D16D}-\\x{1D172}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D242}-\\x{1D244}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E8D0}-\\x{1E8D6}\\x{1E944}-\\x{1E94A}\\x{E0100}-\\x{E01EF}]/u'; + const RTL_LABEL = '/[\\x{0590}\\x{05BE}\\x{05C0}\\x{05C3}\\x{05C6}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0600}-\\x{0605}\\x{0608}\\x{060B}\\x{060D}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{0660}-\\x{0669}\\x{066B}-\\x{066C}\\x{066D}\\x{066E}-\\x{066F}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06DD}\\x{06E5}-\\x{06E6}\\x{06EE}-\\x{06EF}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0712}-\\x{072F}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07F4}-\\x{07F5}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{081A}\\x{0824}\\x{0828}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{08E2}\\x{200F}\\x{FB1D}\\x{FB1F}-\\x{FB28}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFE}-\\x{FDFF}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A04}\\x{10A07}-\\x{10A0B}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A3B}-\\x{10A3E}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D28}-\\x{10D2F}\\x{10D30}-\\x{10D39}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E60}-\\x{10E7E}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}]/u'; + const BIDI_STEP_1_LTR = '/^[^\\x{0000}-\\x{0008}\\x{0009}\\x{000A}\\x{000B}\\x{000C}\\x{000D}\\x{000E}-\\x{001B}\\x{001C}-\\x{001E}\\x{001F}\\x{0020}\\x{0021}-\\x{0022}\\x{0023}\\x{0024}\\x{0025}\\x{0026}-\\x{0027}\\x{0028}\\x{0029}\\x{002A}\\x{002B}\\x{002C}\\x{002D}\\x{002E}-\\x{002F}\\x{0030}-\\x{0039}\\x{003A}\\x{003B}\\x{003C}-\\x{003E}\\x{003F}-\\x{0040}\\x{005B}\\x{005C}\\x{005D}\\x{005E}\\x{005F}\\x{0060}\\x{007B}\\x{007C}\\x{007D}\\x{007E}\\x{007F}-\\x{0084}\\x{0085}\\x{0086}-\\x{009F}\\x{00A0}\\x{00A1}\\x{00A2}-\\x{00A5}\\x{00A6}\\x{00A7}\\x{00A8}\\x{00A9}\\x{00AB}\\x{00AC}\\x{00AD}\\x{00AE}\\x{00AF}\\x{00B0}\\x{00B1}\\x{00B2}-\\x{00B3}\\x{00B4}\\x{00B6}-\\x{00B7}\\x{00B8}\\x{00B9}\\x{00BB}\\x{00BC}-\\x{00BE}\\x{00BF}\\x{00D7}\\x{00F7}\\x{02B9}-\\x{02BA}\\x{02C2}-\\x{02C5}\\x{02C6}-\\x{02CF}\\x{02D2}-\\x{02DF}\\x{02E5}-\\x{02EB}\\x{02EC}\\x{02ED}\\x{02EF}-\\x{02FF}\\x{0300}-\\x{036F}\\x{0374}\\x{0375}\\x{037E}\\x{0384}-\\x{0385}\\x{0387}\\x{03F6}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{058A}\\x{058D}-\\x{058E}\\x{058F}\\x{0590}\\x{0591}-\\x{05BD}\\x{05BE}\\x{05BF}\\x{05C0}\\x{05C1}-\\x{05C2}\\x{05C3}\\x{05C4}-\\x{05C5}\\x{05C6}\\x{05C7}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0600}-\\x{0605}\\x{0606}-\\x{0607}\\x{0608}\\x{0609}-\\x{060A}\\x{060B}\\x{060C}\\x{060D}\\x{060E}-\\x{060F}\\x{0610}-\\x{061A}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{064B}-\\x{065F}\\x{0660}-\\x{0669}\\x{066A}\\x{066B}-\\x{066C}\\x{066D}\\x{066E}-\\x{066F}\\x{0670}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06D6}-\\x{06DC}\\x{06DD}\\x{06DE}\\x{06DF}-\\x{06E4}\\x{06E5}-\\x{06E6}\\x{06E7}-\\x{06E8}\\x{06E9}\\x{06EA}-\\x{06ED}\\x{06EE}-\\x{06EF}\\x{06F0}-\\x{06F9}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0711}\\x{0712}-\\x{072F}\\x{0730}-\\x{074A}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07A6}-\\x{07B0}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07EB}-\\x{07F3}\\x{07F4}-\\x{07F5}\\x{07F6}\\x{07F7}-\\x{07F9}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FD}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{0816}-\\x{0819}\\x{081A}\\x{081B}-\\x{0823}\\x{0824}\\x{0825}-\\x{0827}\\x{0828}\\x{0829}-\\x{082D}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{0859}-\\x{085B}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{08D3}-\\x{08E1}\\x{08E2}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09F2}-\\x{09F3}\\x{09FB}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AF1}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0BF3}-\\x{0BF8}\\x{0BF9}\\x{0BFA}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C78}-\\x{0C7E}\\x{0C81}\\x{0CBC}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E3F}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F3A}\\x{0F3B}\\x{0F3C}\\x{0F3D}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1390}-\\x{1399}\\x{1400}\\x{1680}\\x{169B}\\x{169C}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DB}\\x{17DD}\\x{17F0}-\\x{17F9}\\x{1800}-\\x{1805}\\x{1806}\\x{1807}-\\x{180A}\\x{180B}-\\x{180D}\\x{180E}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1940}\\x{1944}-\\x{1945}\\x{19DE}-\\x{19FF}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{1FBD}\\x{1FBF}-\\x{1FC1}\\x{1FCD}-\\x{1FCF}\\x{1FDD}-\\x{1FDF}\\x{1FED}-\\x{1FEF}\\x{1FFD}-\\x{1FFE}\\x{2000}-\\x{200A}\\x{200B}-\\x{200D}\\x{200F}\\x{2010}-\\x{2015}\\x{2016}-\\x{2017}\\x{2018}\\x{2019}\\x{201A}\\x{201B}-\\x{201C}\\x{201D}\\x{201E}\\x{201F}\\x{2020}-\\x{2027}\\x{2028}\\x{2029}\\x{202A}\\x{202B}\\x{202C}\\x{202D}\\x{202E}\\x{202F}\\x{2030}-\\x{2034}\\x{2035}-\\x{2038}\\x{2039}\\x{203A}\\x{203B}-\\x{203E}\\x{203F}-\\x{2040}\\x{2041}-\\x{2043}\\x{2044}\\x{2045}\\x{2046}\\x{2047}-\\x{2051}\\x{2052}\\x{2053}\\x{2054}\\x{2055}-\\x{205E}\\x{205F}\\x{2060}-\\x{2064}\\x{2065}\\x{2066}\\x{2067}\\x{2068}\\x{2069}\\x{206A}-\\x{206F}\\x{2070}\\x{2074}-\\x{2079}\\x{207A}-\\x{207B}\\x{207C}\\x{207D}\\x{207E}\\x{2080}-\\x{2089}\\x{208A}-\\x{208B}\\x{208C}\\x{208D}\\x{208E}\\x{20A0}-\\x{20BF}\\x{20C0}-\\x{20CF}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2100}-\\x{2101}\\x{2103}-\\x{2106}\\x{2108}-\\x{2109}\\x{2114}\\x{2116}-\\x{2117}\\x{2118}\\x{211E}-\\x{2123}\\x{2125}\\x{2127}\\x{2129}\\x{212E}\\x{213A}-\\x{213B}\\x{2140}-\\x{2144}\\x{214A}\\x{214B}\\x{214C}-\\x{214D}\\x{2150}-\\x{215F}\\x{2189}\\x{218A}-\\x{218B}\\x{2190}-\\x{2194}\\x{2195}-\\x{2199}\\x{219A}-\\x{219B}\\x{219C}-\\x{219F}\\x{21A0}\\x{21A1}-\\x{21A2}\\x{21A3}\\x{21A4}-\\x{21A5}\\x{21A6}\\x{21A7}-\\x{21AD}\\x{21AE}\\x{21AF}-\\x{21CD}\\x{21CE}-\\x{21CF}\\x{21D0}-\\x{21D1}\\x{21D2}\\x{21D3}\\x{21D4}\\x{21D5}-\\x{21F3}\\x{21F4}-\\x{2211}\\x{2212}\\x{2213}\\x{2214}-\\x{22FF}\\x{2300}-\\x{2307}\\x{2308}\\x{2309}\\x{230A}\\x{230B}\\x{230C}-\\x{231F}\\x{2320}-\\x{2321}\\x{2322}-\\x{2328}\\x{2329}\\x{232A}\\x{232B}-\\x{2335}\\x{237B}\\x{237C}\\x{237D}-\\x{2394}\\x{2396}-\\x{239A}\\x{239B}-\\x{23B3}\\x{23B4}-\\x{23DB}\\x{23DC}-\\x{23E1}\\x{23E2}-\\x{2426}\\x{2440}-\\x{244A}\\x{2460}-\\x{2487}\\x{2488}-\\x{249B}\\x{24EA}-\\x{24FF}\\x{2500}-\\x{25B6}\\x{25B7}\\x{25B8}-\\x{25C0}\\x{25C1}\\x{25C2}-\\x{25F7}\\x{25F8}-\\x{25FF}\\x{2600}-\\x{266E}\\x{266F}\\x{2670}-\\x{26AB}\\x{26AD}-\\x{2767}\\x{2768}\\x{2769}\\x{276A}\\x{276B}\\x{276C}\\x{276D}\\x{276E}\\x{276F}\\x{2770}\\x{2771}\\x{2772}\\x{2773}\\x{2774}\\x{2775}\\x{2776}-\\x{2793}\\x{2794}-\\x{27BF}\\x{27C0}-\\x{27C4}\\x{27C5}\\x{27C6}\\x{27C7}-\\x{27E5}\\x{27E6}\\x{27E7}\\x{27E8}\\x{27E9}\\x{27EA}\\x{27EB}\\x{27EC}\\x{27ED}\\x{27EE}\\x{27EF}\\x{27F0}-\\x{27FF}\\x{2900}-\\x{2982}\\x{2983}\\x{2984}\\x{2985}\\x{2986}\\x{2987}\\x{2988}\\x{2989}\\x{298A}\\x{298B}\\x{298C}\\x{298D}\\x{298E}\\x{298F}\\x{2990}\\x{2991}\\x{2992}\\x{2993}\\x{2994}\\x{2995}\\x{2996}\\x{2997}\\x{2998}\\x{2999}-\\x{29D7}\\x{29D8}\\x{29D9}\\x{29DA}\\x{29DB}\\x{29DC}-\\x{29FB}\\x{29FC}\\x{29FD}\\x{29FE}-\\x{2AFF}\\x{2B00}-\\x{2B2F}\\x{2B30}-\\x{2B44}\\x{2B45}-\\x{2B46}\\x{2B47}-\\x{2B4C}\\x{2B4D}-\\x{2B73}\\x{2B76}-\\x{2B95}\\x{2B97}-\\x{2BFF}\\x{2CE5}-\\x{2CEA}\\x{2CEF}-\\x{2CF1}\\x{2CF9}-\\x{2CFC}\\x{2CFD}\\x{2CFE}-\\x{2CFF}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{2E00}-\\x{2E01}\\x{2E02}\\x{2E03}\\x{2E04}\\x{2E05}\\x{2E06}-\\x{2E08}\\x{2E09}\\x{2E0A}\\x{2E0B}\\x{2E0C}\\x{2E0D}\\x{2E0E}-\\x{2E16}\\x{2E17}\\x{2E18}-\\x{2E19}\\x{2E1A}\\x{2E1B}\\x{2E1C}\\x{2E1D}\\x{2E1E}-\\x{2E1F}\\x{2E20}\\x{2E21}\\x{2E22}\\x{2E23}\\x{2E24}\\x{2E25}\\x{2E26}\\x{2E27}\\x{2E28}\\x{2E29}\\x{2E2A}-\\x{2E2E}\\x{2E2F}\\x{2E30}-\\x{2E39}\\x{2E3A}-\\x{2E3B}\\x{2E3C}-\\x{2E3F}\\x{2E40}\\x{2E41}\\x{2E42}\\x{2E43}-\\x{2E4F}\\x{2E50}-\\x{2E51}\\x{2E52}\\x{2E80}-\\x{2E99}\\x{2E9B}-\\x{2EF3}\\x{2F00}-\\x{2FD5}\\x{2FF0}-\\x{2FFB}\\x{3000}\\x{3001}-\\x{3003}\\x{3004}\\x{3008}\\x{3009}\\x{300A}\\x{300B}\\x{300C}\\x{300D}\\x{300E}\\x{300F}\\x{3010}\\x{3011}\\x{3012}-\\x{3013}\\x{3014}\\x{3015}\\x{3016}\\x{3017}\\x{3018}\\x{3019}\\x{301A}\\x{301B}\\x{301C}\\x{301D}\\x{301E}-\\x{301F}\\x{3020}\\x{302A}-\\x{302D}\\x{3030}\\x{3036}-\\x{3037}\\x{303D}\\x{303E}-\\x{303F}\\x{3099}-\\x{309A}\\x{309B}-\\x{309C}\\x{30A0}\\x{30FB}\\x{31C0}-\\x{31E3}\\x{321D}-\\x{321E}\\x{3250}\\x{3251}-\\x{325F}\\x{327C}-\\x{327E}\\x{32B1}-\\x{32BF}\\x{32CC}-\\x{32CF}\\x{3377}-\\x{337A}\\x{33DE}-\\x{33DF}\\x{33FF}\\x{4DC0}-\\x{4DFF}\\x{A490}-\\x{A4C6}\\x{A60D}-\\x{A60F}\\x{A66F}\\x{A670}-\\x{A672}\\x{A673}\\x{A674}-\\x{A67D}\\x{A67E}\\x{A67F}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A700}-\\x{A716}\\x{A717}-\\x{A71F}\\x{A720}-\\x{A721}\\x{A788}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A828}-\\x{A82B}\\x{A82C}\\x{A838}\\x{A839}\\x{A874}-\\x{A877}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{AB6A}-\\x{AB6B}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1D}\\x{FB1E}\\x{FB1F}-\\x{FB28}\\x{FB29}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD3E}\\x{FD3F}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDD0}-\\x{FDEF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFD}\\x{FDFE}-\\x{FDFF}\\x{FE00}-\\x{FE0F}\\x{FE10}-\\x{FE16}\\x{FE17}\\x{FE18}\\x{FE19}\\x{FE20}-\\x{FE2F}\\x{FE30}\\x{FE31}-\\x{FE32}\\x{FE33}-\\x{FE34}\\x{FE35}\\x{FE36}\\x{FE37}\\x{FE38}\\x{FE39}\\x{FE3A}\\x{FE3B}\\x{FE3C}\\x{FE3D}\\x{FE3E}\\x{FE3F}\\x{FE40}\\x{FE41}\\x{FE42}\\x{FE43}\\x{FE44}\\x{FE45}-\\x{FE46}\\x{FE47}\\x{FE48}\\x{FE49}-\\x{FE4C}\\x{FE4D}-\\x{FE4F}\\x{FE50}\\x{FE51}\\x{FE52}\\x{FE54}\\x{FE55}\\x{FE56}-\\x{FE57}\\x{FE58}\\x{FE59}\\x{FE5A}\\x{FE5B}\\x{FE5C}\\x{FE5D}\\x{FE5E}\\x{FE5F}\\x{FE60}-\\x{FE61}\\x{FE62}\\x{FE63}\\x{FE64}-\\x{FE66}\\x{FE68}\\x{FE69}\\x{FE6A}\\x{FE6B}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{FEFF}\\x{FF01}-\\x{FF02}\\x{FF03}\\x{FF04}\\x{FF05}\\x{FF06}-\\x{FF07}\\x{FF08}\\x{FF09}\\x{FF0A}\\x{FF0B}\\x{FF0C}\\x{FF0D}\\x{FF0E}-\\x{FF0F}\\x{FF10}-\\x{FF19}\\x{FF1A}\\x{FF1B}\\x{FF1C}-\\x{FF1E}\\x{FF1F}-\\x{FF20}\\x{FF3B}\\x{FF3C}\\x{FF3D}\\x{FF3E}\\x{FF3F}\\x{FF40}\\x{FF5B}\\x{FF5C}\\x{FF5D}\\x{FF5E}\\x{FF5F}\\x{FF60}\\x{FF61}\\x{FF62}\\x{FF63}\\x{FF64}-\\x{FF65}\\x{FFE0}-\\x{FFE1}\\x{FFE2}\\x{FFE3}\\x{FFE4}\\x{FFE5}-\\x{FFE6}\\x{FFE8}\\x{FFE9}-\\x{FFEC}\\x{FFED}-\\x{FFEE}\\x{FFF0}-\\x{FFF8}\\x{FFF9}-\\x{FFFB}\\x{FFFC}-\\x{FFFD}\\x{FFFE}-\\x{FFFF}\\x{10101}\\x{10140}-\\x{10174}\\x{10175}-\\x{10178}\\x{10179}-\\x{10189}\\x{1018A}-\\x{1018B}\\x{1018C}\\x{10190}-\\x{1019C}\\x{101A0}\\x{101FD}\\x{102E0}\\x{102E1}-\\x{102FB}\\x{10376}-\\x{1037A}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{1091F}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A01}-\\x{10A03}\\x{10A04}\\x{10A05}-\\x{10A06}\\x{10A07}-\\x{10A0B}\\x{10A0C}-\\x{10A0F}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A38}-\\x{10A3A}\\x{10A3B}-\\x{10A3E}\\x{10A3F}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE5}-\\x{10AE6}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B39}-\\x{10B3F}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D24}-\\x{10D27}\\x{10D28}-\\x{10D2F}\\x{10D30}-\\x{10D39}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E60}-\\x{10E7E}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAB}-\\x{10EAC}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F46}-\\x{10F50}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{11001}\\x{11038}-\\x{11046}\\x{11052}-\\x{11065}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{11660}-\\x{1166C}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A06}\\x{11A09}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{11FD5}-\\x{11FDC}\\x{11FDD}-\\x{11FE0}\\x{11FE1}-\\x{11FF1}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE2}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1BCA0}-\\x{1BCA3}\\x{1D167}-\\x{1D169}\\x{1D173}-\\x{1D17A}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D200}-\\x{1D241}\\x{1D242}-\\x{1D244}\\x{1D245}\\x{1D300}-\\x{1D356}\\x{1D6DB}\\x{1D715}\\x{1D74F}\\x{1D789}\\x{1D7C3}\\x{1D7CE}-\\x{1D7FF}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E2FF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D0}-\\x{1E8D6}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E944}-\\x{1E94A}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF0}-\\x{1EEF1}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}\\x{1F000}-\\x{1F02B}\\x{1F030}-\\x{1F093}\\x{1F0A0}-\\x{1F0AE}\\x{1F0B1}-\\x{1F0BF}\\x{1F0C1}-\\x{1F0CF}\\x{1F0D1}-\\x{1F0F5}\\x{1F100}-\\x{1F10A}\\x{1F10B}-\\x{1F10C}\\x{1F10D}-\\x{1F10F}\\x{1F12F}\\x{1F16A}-\\x{1F16F}\\x{1F1AD}\\x{1F260}-\\x{1F265}\\x{1F300}-\\x{1F3FA}\\x{1F3FB}-\\x{1F3FF}\\x{1F400}-\\x{1F6D7}\\x{1F6E0}-\\x{1F6EC}\\x{1F6F0}-\\x{1F6FC}\\x{1F700}-\\x{1F773}\\x{1F780}-\\x{1F7D8}\\x{1F7E0}-\\x{1F7EB}\\x{1F800}-\\x{1F80B}\\x{1F810}-\\x{1F847}\\x{1F850}-\\x{1F859}\\x{1F860}-\\x{1F887}\\x{1F890}-\\x{1F8AD}\\x{1F8B0}-\\x{1F8B1}\\x{1F900}-\\x{1F978}\\x{1F97A}-\\x{1F9CB}\\x{1F9CD}-\\x{1FA53}\\x{1FA60}-\\x{1FA6D}\\x{1FA70}-\\x{1FA74}\\x{1FA78}-\\x{1FA7A}\\x{1FA80}-\\x{1FA86}\\x{1FA90}-\\x{1FAA8}\\x{1FAB0}-\\x{1FAB6}\\x{1FAC0}-\\x{1FAC2}\\x{1FAD0}-\\x{1FAD6}\\x{1FB00}-\\x{1FB92}\\x{1FB94}-\\x{1FBCA}\\x{1FBF0}-\\x{1FBF9}\\x{1FFFE}-\\x{1FFFF}\\x{2FFFE}-\\x{2FFFF}\\x{3FFFE}-\\x{3FFFF}\\x{4FFFE}-\\x{4FFFF}\\x{5FFFE}-\\x{5FFFF}\\x{6FFFE}-\\x{6FFFF}\\x{7FFFE}-\\x{7FFFF}\\x{8FFFE}-\\x{8FFFF}\\x{9FFFE}-\\x{9FFFF}\\x{AFFFE}-\\x{AFFFF}\\x{BFFFE}-\\x{BFFFF}\\x{CFFFE}-\\x{CFFFF}\\x{DFFFE}-\\x{E0000}\\x{E0001}\\x{E0002}-\\x{E001F}\\x{E0020}-\\x{E007F}\\x{E0080}-\\x{E00FF}\\x{E0100}-\\x{E01EF}\\x{E01F0}-\\x{E0FFF}\\x{EFFFE}-\\x{EFFFF}\\x{FFFFE}-\\x{FFFFF}\\x{10FFFE}-\\x{10FFFF}]/u'; + const BIDI_STEP_1_RTL = '/^[\\x{0590}\\x{05BE}\\x{05C0}\\x{05C3}\\x{05C6}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0608}\\x{060B}\\x{060D}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{066D}\\x{066E}-\\x{066F}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06E5}-\\x{06E6}\\x{06EE}-\\x{06EF}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0712}-\\x{072F}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07F4}-\\x{07F5}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{081A}\\x{0824}\\x{0828}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{200F}\\x{FB1D}\\x{FB1F}-\\x{FB28}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFE}-\\x{FDFF}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A04}\\x{10A07}-\\x{10A0B}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A3B}-\\x{10A3E}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D28}-\\x{10D2F}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}]/u'; + const BIDI_STEP_2 = '/[^\\x{0000}-\\x{0008}\\x{000E}-\\x{001B}\\x{0021}-\\x{0022}\\x{0023}\\x{0024}\\x{0025}\\x{0026}-\\x{0027}\\x{0028}\\x{0029}\\x{002A}\\x{002B}\\x{002C}\\x{002D}\\x{002E}-\\x{002F}\\x{0030}-\\x{0039}\\x{003A}\\x{003B}\\x{003C}-\\x{003E}\\x{003F}-\\x{0040}\\x{005B}\\x{005C}\\x{005D}\\x{005E}\\x{005F}\\x{0060}\\x{007B}\\x{007C}\\x{007D}\\x{007E}\\x{007F}-\\x{0084}\\x{0086}-\\x{009F}\\x{00A0}\\x{00A1}\\x{00A2}-\\x{00A5}\\x{00A6}\\x{00A7}\\x{00A8}\\x{00A9}\\x{00AB}\\x{00AC}\\x{00AD}\\x{00AE}\\x{00AF}\\x{00B0}\\x{00B1}\\x{00B2}-\\x{00B3}\\x{00B4}\\x{00B6}-\\x{00B7}\\x{00B8}\\x{00B9}\\x{00BB}\\x{00BC}-\\x{00BE}\\x{00BF}\\x{00D7}\\x{00F7}\\x{02B9}-\\x{02BA}\\x{02C2}-\\x{02C5}\\x{02C6}-\\x{02CF}\\x{02D2}-\\x{02DF}\\x{02E5}-\\x{02EB}\\x{02EC}\\x{02ED}\\x{02EF}-\\x{02FF}\\x{0300}-\\x{036F}\\x{0374}\\x{0375}\\x{037E}\\x{0384}-\\x{0385}\\x{0387}\\x{03F6}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{058A}\\x{058D}-\\x{058E}\\x{058F}\\x{0590}\\x{0591}-\\x{05BD}\\x{05BE}\\x{05BF}\\x{05C0}\\x{05C1}-\\x{05C2}\\x{05C3}\\x{05C4}-\\x{05C5}\\x{05C6}\\x{05C7}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0600}-\\x{0605}\\x{0606}-\\x{0607}\\x{0608}\\x{0609}-\\x{060A}\\x{060B}\\x{060C}\\x{060D}\\x{060E}-\\x{060F}\\x{0610}-\\x{061A}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{064B}-\\x{065F}\\x{0660}-\\x{0669}\\x{066A}\\x{066B}-\\x{066C}\\x{066D}\\x{066E}-\\x{066F}\\x{0670}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06D6}-\\x{06DC}\\x{06DD}\\x{06DE}\\x{06DF}-\\x{06E4}\\x{06E5}-\\x{06E6}\\x{06E7}-\\x{06E8}\\x{06E9}\\x{06EA}-\\x{06ED}\\x{06EE}-\\x{06EF}\\x{06F0}-\\x{06F9}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0711}\\x{0712}-\\x{072F}\\x{0730}-\\x{074A}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07A6}-\\x{07B0}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07EB}-\\x{07F3}\\x{07F4}-\\x{07F5}\\x{07F6}\\x{07F7}-\\x{07F9}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FD}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{0816}-\\x{0819}\\x{081A}\\x{081B}-\\x{0823}\\x{0824}\\x{0825}-\\x{0827}\\x{0828}\\x{0829}-\\x{082D}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{0859}-\\x{085B}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{08D3}-\\x{08E1}\\x{08E2}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09F2}-\\x{09F3}\\x{09FB}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AF1}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0BF3}-\\x{0BF8}\\x{0BF9}\\x{0BFA}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C78}-\\x{0C7E}\\x{0C81}\\x{0CBC}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E3F}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F3A}\\x{0F3B}\\x{0F3C}\\x{0F3D}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1390}-\\x{1399}\\x{1400}\\x{169B}\\x{169C}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DB}\\x{17DD}\\x{17F0}-\\x{17F9}\\x{1800}-\\x{1805}\\x{1806}\\x{1807}-\\x{180A}\\x{180B}-\\x{180D}\\x{180E}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1940}\\x{1944}-\\x{1945}\\x{19DE}-\\x{19FF}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{1FBD}\\x{1FBF}-\\x{1FC1}\\x{1FCD}-\\x{1FCF}\\x{1FDD}-\\x{1FDF}\\x{1FED}-\\x{1FEF}\\x{1FFD}-\\x{1FFE}\\x{200B}-\\x{200D}\\x{200F}\\x{2010}-\\x{2015}\\x{2016}-\\x{2017}\\x{2018}\\x{2019}\\x{201A}\\x{201B}-\\x{201C}\\x{201D}\\x{201E}\\x{201F}\\x{2020}-\\x{2027}\\x{202F}\\x{2030}-\\x{2034}\\x{2035}-\\x{2038}\\x{2039}\\x{203A}\\x{203B}-\\x{203E}\\x{203F}-\\x{2040}\\x{2041}-\\x{2043}\\x{2044}\\x{2045}\\x{2046}\\x{2047}-\\x{2051}\\x{2052}\\x{2053}\\x{2054}\\x{2055}-\\x{205E}\\x{2060}-\\x{2064}\\x{2065}\\x{206A}-\\x{206F}\\x{2070}\\x{2074}-\\x{2079}\\x{207A}-\\x{207B}\\x{207C}\\x{207D}\\x{207E}\\x{2080}-\\x{2089}\\x{208A}-\\x{208B}\\x{208C}\\x{208D}\\x{208E}\\x{20A0}-\\x{20BF}\\x{20C0}-\\x{20CF}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2100}-\\x{2101}\\x{2103}-\\x{2106}\\x{2108}-\\x{2109}\\x{2114}\\x{2116}-\\x{2117}\\x{2118}\\x{211E}-\\x{2123}\\x{2125}\\x{2127}\\x{2129}\\x{212E}\\x{213A}-\\x{213B}\\x{2140}-\\x{2144}\\x{214A}\\x{214B}\\x{214C}-\\x{214D}\\x{2150}-\\x{215F}\\x{2189}\\x{218A}-\\x{218B}\\x{2190}-\\x{2194}\\x{2195}-\\x{2199}\\x{219A}-\\x{219B}\\x{219C}-\\x{219F}\\x{21A0}\\x{21A1}-\\x{21A2}\\x{21A3}\\x{21A4}-\\x{21A5}\\x{21A6}\\x{21A7}-\\x{21AD}\\x{21AE}\\x{21AF}-\\x{21CD}\\x{21CE}-\\x{21CF}\\x{21D0}-\\x{21D1}\\x{21D2}\\x{21D3}\\x{21D4}\\x{21D5}-\\x{21F3}\\x{21F4}-\\x{2211}\\x{2212}\\x{2213}\\x{2214}-\\x{22FF}\\x{2300}-\\x{2307}\\x{2308}\\x{2309}\\x{230A}\\x{230B}\\x{230C}-\\x{231F}\\x{2320}-\\x{2321}\\x{2322}-\\x{2328}\\x{2329}\\x{232A}\\x{232B}-\\x{2335}\\x{237B}\\x{237C}\\x{237D}-\\x{2394}\\x{2396}-\\x{239A}\\x{239B}-\\x{23B3}\\x{23B4}-\\x{23DB}\\x{23DC}-\\x{23E1}\\x{23E2}-\\x{2426}\\x{2440}-\\x{244A}\\x{2460}-\\x{2487}\\x{2488}-\\x{249B}\\x{24EA}-\\x{24FF}\\x{2500}-\\x{25B6}\\x{25B7}\\x{25B8}-\\x{25C0}\\x{25C1}\\x{25C2}-\\x{25F7}\\x{25F8}-\\x{25FF}\\x{2600}-\\x{266E}\\x{266F}\\x{2670}-\\x{26AB}\\x{26AD}-\\x{2767}\\x{2768}\\x{2769}\\x{276A}\\x{276B}\\x{276C}\\x{276D}\\x{276E}\\x{276F}\\x{2770}\\x{2771}\\x{2772}\\x{2773}\\x{2774}\\x{2775}\\x{2776}-\\x{2793}\\x{2794}-\\x{27BF}\\x{27C0}-\\x{27C4}\\x{27C5}\\x{27C6}\\x{27C7}-\\x{27E5}\\x{27E6}\\x{27E7}\\x{27E8}\\x{27E9}\\x{27EA}\\x{27EB}\\x{27EC}\\x{27ED}\\x{27EE}\\x{27EF}\\x{27F0}-\\x{27FF}\\x{2900}-\\x{2982}\\x{2983}\\x{2984}\\x{2985}\\x{2986}\\x{2987}\\x{2988}\\x{2989}\\x{298A}\\x{298B}\\x{298C}\\x{298D}\\x{298E}\\x{298F}\\x{2990}\\x{2991}\\x{2992}\\x{2993}\\x{2994}\\x{2995}\\x{2996}\\x{2997}\\x{2998}\\x{2999}-\\x{29D7}\\x{29D8}\\x{29D9}\\x{29DA}\\x{29DB}\\x{29DC}-\\x{29FB}\\x{29FC}\\x{29FD}\\x{29FE}-\\x{2AFF}\\x{2B00}-\\x{2B2F}\\x{2B30}-\\x{2B44}\\x{2B45}-\\x{2B46}\\x{2B47}-\\x{2B4C}\\x{2B4D}-\\x{2B73}\\x{2B76}-\\x{2B95}\\x{2B97}-\\x{2BFF}\\x{2CE5}-\\x{2CEA}\\x{2CEF}-\\x{2CF1}\\x{2CF9}-\\x{2CFC}\\x{2CFD}\\x{2CFE}-\\x{2CFF}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{2E00}-\\x{2E01}\\x{2E02}\\x{2E03}\\x{2E04}\\x{2E05}\\x{2E06}-\\x{2E08}\\x{2E09}\\x{2E0A}\\x{2E0B}\\x{2E0C}\\x{2E0D}\\x{2E0E}-\\x{2E16}\\x{2E17}\\x{2E18}-\\x{2E19}\\x{2E1A}\\x{2E1B}\\x{2E1C}\\x{2E1D}\\x{2E1E}-\\x{2E1F}\\x{2E20}\\x{2E21}\\x{2E22}\\x{2E23}\\x{2E24}\\x{2E25}\\x{2E26}\\x{2E27}\\x{2E28}\\x{2E29}\\x{2E2A}-\\x{2E2E}\\x{2E2F}\\x{2E30}-\\x{2E39}\\x{2E3A}-\\x{2E3B}\\x{2E3C}-\\x{2E3F}\\x{2E40}\\x{2E41}\\x{2E42}\\x{2E43}-\\x{2E4F}\\x{2E50}-\\x{2E51}\\x{2E52}\\x{2E80}-\\x{2E99}\\x{2E9B}-\\x{2EF3}\\x{2F00}-\\x{2FD5}\\x{2FF0}-\\x{2FFB}\\x{3001}-\\x{3003}\\x{3004}\\x{3008}\\x{3009}\\x{300A}\\x{300B}\\x{300C}\\x{300D}\\x{300E}\\x{300F}\\x{3010}\\x{3011}\\x{3012}-\\x{3013}\\x{3014}\\x{3015}\\x{3016}\\x{3017}\\x{3018}\\x{3019}\\x{301A}\\x{301B}\\x{301C}\\x{301D}\\x{301E}-\\x{301F}\\x{3020}\\x{302A}-\\x{302D}\\x{3030}\\x{3036}-\\x{3037}\\x{303D}\\x{303E}-\\x{303F}\\x{3099}-\\x{309A}\\x{309B}-\\x{309C}\\x{30A0}\\x{30FB}\\x{31C0}-\\x{31E3}\\x{321D}-\\x{321E}\\x{3250}\\x{3251}-\\x{325F}\\x{327C}-\\x{327E}\\x{32B1}-\\x{32BF}\\x{32CC}-\\x{32CF}\\x{3377}-\\x{337A}\\x{33DE}-\\x{33DF}\\x{33FF}\\x{4DC0}-\\x{4DFF}\\x{A490}-\\x{A4C6}\\x{A60D}-\\x{A60F}\\x{A66F}\\x{A670}-\\x{A672}\\x{A673}\\x{A674}-\\x{A67D}\\x{A67E}\\x{A67F}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A700}-\\x{A716}\\x{A717}-\\x{A71F}\\x{A720}-\\x{A721}\\x{A788}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A828}-\\x{A82B}\\x{A82C}\\x{A838}\\x{A839}\\x{A874}-\\x{A877}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{AB6A}-\\x{AB6B}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1D}\\x{FB1E}\\x{FB1F}-\\x{FB28}\\x{FB29}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD3E}\\x{FD3F}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDD0}-\\x{FDEF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFD}\\x{FDFE}-\\x{FDFF}\\x{FE00}-\\x{FE0F}\\x{FE10}-\\x{FE16}\\x{FE17}\\x{FE18}\\x{FE19}\\x{FE20}-\\x{FE2F}\\x{FE30}\\x{FE31}-\\x{FE32}\\x{FE33}-\\x{FE34}\\x{FE35}\\x{FE36}\\x{FE37}\\x{FE38}\\x{FE39}\\x{FE3A}\\x{FE3B}\\x{FE3C}\\x{FE3D}\\x{FE3E}\\x{FE3F}\\x{FE40}\\x{FE41}\\x{FE42}\\x{FE43}\\x{FE44}\\x{FE45}-\\x{FE46}\\x{FE47}\\x{FE48}\\x{FE49}-\\x{FE4C}\\x{FE4D}-\\x{FE4F}\\x{FE50}\\x{FE51}\\x{FE52}\\x{FE54}\\x{FE55}\\x{FE56}-\\x{FE57}\\x{FE58}\\x{FE59}\\x{FE5A}\\x{FE5B}\\x{FE5C}\\x{FE5D}\\x{FE5E}\\x{FE5F}\\x{FE60}-\\x{FE61}\\x{FE62}\\x{FE63}\\x{FE64}-\\x{FE66}\\x{FE68}\\x{FE69}\\x{FE6A}\\x{FE6B}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{FEFF}\\x{FF01}-\\x{FF02}\\x{FF03}\\x{FF04}\\x{FF05}\\x{FF06}-\\x{FF07}\\x{FF08}\\x{FF09}\\x{FF0A}\\x{FF0B}\\x{FF0C}\\x{FF0D}\\x{FF0E}-\\x{FF0F}\\x{FF10}-\\x{FF19}\\x{FF1A}\\x{FF1B}\\x{FF1C}-\\x{FF1E}\\x{FF1F}-\\x{FF20}\\x{FF3B}\\x{FF3C}\\x{FF3D}\\x{FF3E}\\x{FF3F}\\x{FF40}\\x{FF5B}\\x{FF5C}\\x{FF5D}\\x{FF5E}\\x{FF5F}\\x{FF60}\\x{FF61}\\x{FF62}\\x{FF63}\\x{FF64}-\\x{FF65}\\x{FFE0}-\\x{FFE1}\\x{FFE2}\\x{FFE3}\\x{FFE4}\\x{FFE5}-\\x{FFE6}\\x{FFE8}\\x{FFE9}-\\x{FFEC}\\x{FFED}-\\x{FFEE}\\x{FFF0}-\\x{FFF8}\\x{FFF9}-\\x{FFFB}\\x{FFFC}-\\x{FFFD}\\x{FFFE}-\\x{FFFF}\\x{10101}\\x{10140}-\\x{10174}\\x{10175}-\\x{10178}\\x{10179}-\\x{10189}\\x{1018A}-\\x{1018B}\\x{1018C}\\x{10190}-\\x{1019C}\\x{101A0}\\x{101FD}\\x{102E0}\\x{102E1}-\\x{102FB}\\x{10376}-\\x{1037A}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{1091F}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A01}-\\x{10A03}\\x{10A04}\\x{10A05}-\\x{10A06}\\x{10A07}-\\x{10A0B}\\x{10A0C}-\\x{10A0F}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A38}-\\x{10A3A}\\x{10A3B}-\\x{10A3E}\\x{10A3F}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE5}-\\x{10AE6}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B39}-\\x{10B3F}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D24}-\\x{10D27}\\x{10D28}-\\x{10D2F}\\x{10D30}-\\x{10D39}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E60}-\\x{10E7E}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAB}-\\x{10EAC}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F46}-\\x{10F50}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{11001}\\x{11038}-\\x{11046}\\x{11052}-\\x{11065}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{11660}-\\x{1166C}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A06}\\x{11A09}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{11FD5}-\\x{11FDC}\\x{11FDD}-\\x{11FE0}\\x{11FE1}-\\x{11FF1}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE2}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1BCA0}-\\x{1BCA3}\\x{1D167}-\\x{1D169}\\x{1D173}-\\x{1D17A}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D200}-\\x{1D241}\\x{1D242}-\\x{1D244}\\x{1D245}\\x{1D300}-\\x{1D356}\\x{1D6DB}\\x{1D715}\\x{1D74F}\\x{1D789}\\x{1D7C3}\\x{1D7CE}-\\x{1D7FF}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E2FF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D0}-\\x{1E8D6}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E944}-\\x{1E94A}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF0}-\\x{1EEF1}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}\\x{1F000}-\\x{1F02B}\\x{1F030}-\\x{1F093}\\x{1F0A0}-\\x{1F0AE}\\x{1F0B1}-\\x{1F0BF}\\x{1F0C1}-\\x{1F0CF}\\x{1F0D1}-\\x{1F0F5}\\x{1F100}-\\x{1F10A}\\x{1F10B}-\\x{1F10C}\\x{1F10D}-\\x{1F10F}\\x{1F12F}\\x{1F16A}-\\x{1F16F}\\x{1F1AD}\\x{1F260}-\\x{1F265}\\x{1F300}-\\x{1F3FA}\\x{1F3FB}-\\x{1F3FF}\\x{1F400}-\\x{1F6D7}\\x{1F6E0}-\\x{1F6EC}\\x{1F6F0}-\\x{1F6FC}\\x{1F700}-\\x{1F773}\\x{1F780}-\\x{1F7D8}\\x{1F7E0}-\\x{1F7EB}\\x{1F800}-\\x{1F80B}\\x{1F810}-\\x{1F847}\\x{1F850}-\\x{1F859}\\x{1F860}-\\x{1F887}\\x{1F890}-\\x{1F8AD}\\x{1F8B0}-\\x{1F8B1}\\x{1F900}-\\x{1F978}\\x{1F97A}-\\x{1F9CB}\\x{1F9CD}-\\x{1FA53}\\x{1FA60}-\\x{1FA6D}\\x{1FA70}-\\x{1FA74}\\x{1FA78}-\\x{1FA7A}\\x{1FA80}-\\x{1FA86}\\x{1FA90}-\\x{1FAA8}\\x{1FAB0}-\\x{1FAB6}\\x{1FAC0}-\\x{1FAC2}\\x{1FAD0}-\\x{1FAD6}\\x{1FB00}-\\x{1FB92}\\x{1FB94}-\\x{1FBCA}\\x{1FBF0}-\\x{1FBF9}\\x{1FFFE}-\\x{1FFFF}\\x{2FFFE}-\\x{2FFFF}\\x{3FFFE}-\\x{3FFFF}\\x{4FFFE}-\\x{4FFFF}\\x{5FFFE}-\\x{5FFFF}\\x{6FFFE}-\\x{6FFFF}\\x{7FFFE}-\\x{7FFFF}\\x{8FFFE}-\\x{8FFFF}\\x{9FFFE}-\\x{9FFFF}\\x{AFFFE}-\\x{AFFFF}\\x{BFFFE}-\\x{BFFFF}\\x{CFFFE}-\\x{CFFFF}\\x{DFFFE}-\\x{E0000}\\x{E0001}\\x{E0002}-\\x{E001F}\\x{E0020}-\\x{E007F}\\x{E0080}-\\x{E00FF}\\x{E0100}-\\x{E01EF}\\x{E01F0}-\\x{E0FFF}\\x{EFFFE}-\\x{EFFFF}\\x{FFFFE}-\\x{FFFFF}\\x{10FFFE}-\\x{10FFFF}]/u'; + const BIDI_STEP_3 = '/[\\x{0030}-\\x{0039}\\x{00B2}-\\x{00B3}\\x{00B9}\\x{0590}\\x{05BE}\\x{05C0}\\x{05C3}\\x{05C6}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0600}-\\x{0605}\\x{0608}\\x{060B}\\x{060D}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{0660}-\\x{0669}\\x{066B}-\\x{066C}\\x{066D}\\x{066E}-\\x{066F}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06DD}\\x{06E5}-\\x{06E6}\\x{06EE}-\\x{06EF}\\x{06F0}-\\x{06F9}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0712}-\\x{072F}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07F4}-\\x{07F5}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{081A}\\x{0824}\\x{0828}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{08E2}\\x{200F}\\x{2070}\\x{2074}-\\x{2079}\\x{2080}-\\x{2089}\\x{2488}-\\x{249B}\\x{FB1D}\\x{FB1F}-\\x{FB28}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFE}-\\x{FDFF}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{FF10}-\\x{FF19}\\x{102E1}-\\x{102FB}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A04}\\x{10A07}-\\x{10A0B}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A3B}-\\x{10A3E}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D28}-\\x{10D2F}\\x{10D30}-\\x{10D39}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E60}-\\x{10E7E}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{1D7CE}-\\x{1D7FF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}\\x{1F100}-\\x{1F10A}\\x{1FBF0}-\\x{1FBF9}][\\x{0300}-\\x{036F}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{0591}-\\x{05BD}\\x{05BF}\\x{05C1}-\\x{05C2}\\x{05C4}-\\x{05C5}\\x{05C7}\\x{0610}-\\x{061A}\\x{064B}-\\x{065F}\\x{0670}\\x{06D6}-\\x{06DC}\\x{06DF}-\\x{06E4}\\x{06E7}-\\x{06E8}\\x{06EA}-\\x{06ED}\\x{0711}\\x{0730}-\\x{074A}\\x{07A6}-\\x{07B0}\\x{07EB}-\\x{07F3}\\x{07FD}\\x{0816}-\\x{0819}\\x{081B}-\\x{0823}\\x{0825}-\\x{0827}\\x{0829}-\\x{082D}\\x{0859}-\\x{085B}\\x{08D3}-\\x{08E1}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C81}\\x{0CBC}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DD}\\x{180B}-\\x{180D}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2CEF}-\\x{2CF1}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{302A}-\\x{302D}\\x{3099}-\\x{309A}\\x{A66F}\\x{A670}-\\x{A672}\\x{A674}-\\x{A67D}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A82C}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1E}\\x{FE00}-\\x{FE0F}\\x{FE20}-\\x{FE2F}\\x{101FD}\\x{102E0}\\x{10376}-\\x{1037A}\\x{10A01}-\\x{10A03}\\x{10A05}-\\x{10A06}\\x{10A0C}-\\x{10A0F}\\x{10A38}-\\x{10A3A}\\x{10A3F}\\x{10AE5}-\\x{10AE6}\\x{10D24}-\\x{10D27}\\x{10EAB}-\\x{10EAC}\\x{10F46}-\\x{10F50}\\x{11001}\\x{11038}-\\x{11046}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A06}\\x{11A09}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1D167}-\\x{1D169}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D242}-\\x{1D244}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E8D0}-\\x{1E8D6}\\x{1E944}-\\x{1E94A}\\x{E0100}-\\x{E01EF}]*$/u'; + const BIDI_STEP_4_AN = '/[\\x{0600}-\\x{0605}\\x{0660}-\\x{0669}\\x{066B}-\\x{066C}\\x{06DD}\\x{08E2}\\x{10D30}-\\x{10D39}\\x{10E60}-\\x{10E7E}]/u'; + const BIDI_STEP_4_EN = '/[\\x{0030}-\\x{0039}\\x{00B2}-\\x{00B3}\\x{00B9}\\x{06F0}-\\x{06F9}\\x{2070}\\x{2074}-\\x{2079}\\x{2080}-\\x{2089}\\x{2488}-\\x{249B}\\x{FF10}-\\x{FF19}\\x{102E1}-\\x{102FB}\\x{1D7CE}-\\x{1D7FF}\\x{1F100}-\\x{1F10A}\\x{1FBF0}-\\x{1FBF9}]/u'; + const BIDI_STEP_5 = '/[\\x{0009}\\x{000A}\\x{000B}\\x{000C}\\x{000D}\\x{001C}-\\x{001E}\\x{001F}\\x{0020}\\x{0085}\\x{0590}\\x{05BE}\\x{05C0}\\x{05C3}\\x{05C6}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0600}-\\x{0605}\\x{0608}\\x{060B}\\x{060D}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{0660}-\\x{0669}\\x{066B}-\\x{066C}\\x{066D}\\x{066E}-\\x{066F}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06DD}\\x{06E5}-\\x{06E6}\\x{06EE}-\\x{06EF}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0712}-\\x{072F}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07F4}-\\x{07F5}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{081A}\\x{0824}\\x{0828}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{08E2}\\x{1680}\\x{2000}-\\x{200A}\\x{200F}\\x{2028}\\x{2029}\\x{202A}\\x{202B}\\x{202C}\\x{202D}\\x{202E}\\x{205F}\\x{2066}\\x{2067}\\x{2068}\\x{2069}\\x{3000}\\x{FB1D}\\x{FB1F}-\\x{FB28}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFE}-\\x{FDFF}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A04}\\x{10A07}-\\x{10A0B}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A3B}-\\x{10A3E}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D28}-\\x{10D2F}\\x{10D30}-\\x{10D39}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E60}-\\x{10E7E}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}]/u'; + const BIDI_STEP_6 = '/[^\\x{0000}-\\x{0008}\\x{0009}\\x{000A}\\x{000B}\\x{000C}\\x{000D}\\x{000E}-\\x{001B}\\x{001C}-\\x{001E}\\x{001F}\\x{0020}\\x{0021}-\\x{0022}\\x{0023}\\x{0024}\\x{0025}\\x{0026}-\\x{0027}\\x{0028}\\x{0029}\\x{002A}\\x{002B}\\x{002C}\\x{002D}\\x{002E}-\\x{002F}\\x{003A}\\x{003B}\\x{003C}-\\x{003E}\\x{003F}-\\x{0040}\\x{005B}\\x{005C}\\x{005D}\\x{005E}\\x{005F}\\x{0060}\\x{007B}\\x{007C}\\x{007D}\\x{007E}\\x{007F}-\\x{0084}\\x{0085}\\x{0086}-\\x{009F}\\x{00A0}\\x{00A1}\\x{00A2}-\\x{00A5}\\x{00A6}\\x{00A7}\\x{00A8}\\x{00A9}\\x{00AB}\\x{00AC}\\x{00AD}\\x{00AE}\\x{00AF}\\x{00B0}\\x{00B1}\\x{00B4}\\x{00B6}-\\x{00B7}\\x{00B8}\\x{00BB}\\x{00BC}-\\x{00BE}\\x{00BF}\\x{00D7}\\x{00F7}\\x{02B9}-\\x{02BA}\\x{02C2}-\\x{02C5}\\x{02C6}-\\x{02CF}\\x{02D2}-\\x{02DF}\\x{02E5}-\\x{02EB}\\x{02EC}\\x{02ED}\\x{02EF}-\\x{02FF}\\x{0300}-\\x{036F}\\x{0374}\\x{0375}\\x{037E}\\x{0384}-\\x{0385}\\x{0387}\\x{03F6}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{058A}\\x{058D}-\\x{058E}\\x{058F}\\x{0590}\\x{0591}-\\x{05BD}\\x{05BE}\\x{05BF}\\x{05C0}\\x{05C1}-\\x{05C2}\\x{05C3}\\x{05C4}-\\x{05C5}\\x{05C6}\\x{05C7}\\x{05C8}-\\x{05CF}\\x{05D0}-\\x{05EA}\\x{05EB}-\\x{05EE}\\x{05EF}-\\x{05F2}\\x{05F3}-\\x{05F4}\\x{05F5}-\\x{05FF}\\x{0600}-\\x{0605}\\x{0606}-\\x{0607}\\x{0608}\\x{0609}-\\x{060A}\\x{060B}\\x{060C}\\x{060D}\\x{060E}-\\x{060F}\\x{0610}-\\x{061A}\\x{061B}\\x{061C}\\x{061D}\\x{061E}-\\x{061F}\\x{0620}-\\x{063F}\\x{0640}\\x{0641}-\\x{064A}\\x{064B}-\\x{065F}\\x{0660}-\\x{0669}\\x{066A}\\x{066B}-\\x{066C}\\x{066D}\\x{066E}-\\x{066F}\\x{0670}\\x{0671}-\\x{06D3}\\x{06D4}\\x{06D5}\\x{06D6}-\\x{06DC}\\x{06DD}\\x{06DE}\\x{06DF}-\\x{06E4}\\x{06E5}-\\x{06E6}\\x{06E7}-\\x{06E8}\\x{06E9}\\x{06EA}-\\x{06ED}\\x{06EE}-\\x{06EF}\\x{06FA}-\\x{06FC}\\x{06FD}-\\x{06FE}\\x{06FF}\\x{0700}-\\x{070D}\\x{070E}\\x{070F}\\x{0710}\\x{0711}\\x{0712}-\\x{072F}\\x{0730}-\\x{074A}\\x{074B}-\\x{074C}\\x{074D}-\\x{07A5}\\x{07A6}-\\x{07B0}\\x{07B1}\\x{07B2}-\\x{07BF}\\x{07C0}-\\x{07C9}\\x{07CA}-\\x{07EA}\\x{07EB}-\\x{07F3}\\x{07F4}-\\x{07F5}\\x{07F6}\\x{07F7}-\\x{07F9}\\x{07FA}\\x{07FB}-\\x{07FC}\\x{07FD}\\x{07FE}-\\x{07FF}\\x{0800}-\\x{0815}\\x{0816}-\\x{0819}\\x{081A}\\x{081B}-\\x{0823}\\x{0824}\\x{0825}-\\x{0827}\\x{0828}\\x{0829}-\\x{082D}\\x{082E}-\\x{082F}\\x{0830}-\\x{083E}\\x{083F}\\x{0840}-\\x{0858}\\x{0859}-\\x{085B}\\x{085C}-\\x{085D}\\x{085E}\\x{085F}\\x{0860}-\\x{086A}\\x{086B}-\\x{086F}\\x{0870}-\\x{089F}\\x{08A0}-\\x{08B4}\\x{08B5}\\x{08B6}-\\x{08C7}\\x{08C8}-\\x{08D2}\\x{08D3}-\\x{08E1}\\x{08E2}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09F2}-\\x{09F3}\\x{09FB}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AF1}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0BF3}-\\x{0BF8}\\x{0BF9}\\x{0BFA}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C78}-\\x{0C7E}\\x{0C81}\\x{0CBC}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E3F}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F3A}\\x{0F3B}\\x{0F3C}\\x{0F3D}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1390}-\\x{1399}\\x{1400}\\x{1680}\\x{169B}\\x{169C}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DB}\\x{17DD}\\x{17F0}-\\x{17F9}\\x{1800}-\\x{1805}\\x{1806}\\x{1807}-\\x{180A}\\x{180B}-\\x{180D}\\x{180E}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1940}\\x{1944}-\\x{1945}\\x{19DE}-\\x{19FF}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{1FBD}\\x{1FBF}-\\x{1FC1}\\x{1FCD}-\\x{1FCF}\\x{1FDD}-\\x{1FDF}\\x{1FED}-\\x{1FEF}\\x{1FFD}-\\x{1FFE}\\x{2000}-\\x{200A}\\x{200B}-\\x{200D}\\x{200F}\\x{2010}-\\x{2015}\\x{2016}-\\x{2017}\\x{2018}\\x{2019}\\x{201A}\\x{201B}-\\x{201C}\\x{201D}\\x{201E}\\x{201F}\\x{2020}-\\x{2027}\\x{2028}\\x{2029}\\x{202A}\\x{202B}\\x{202C}\\x{202D}\\x{202E}\\x{202F}\\x{2030}-\\x{2034}\\x{2035}-\\x{2038}\\x{2039}\\x{203A}\\x{203B}-\\x{203E}\\x{203F}-\\x{2040}\\x{2041}-\\x{2043}\\x{2044}\\x{2045}\\x{2046}\\x{2047}-\\x{2051}\\x{2052}\\x{2053}\\x{2054}\\x{2055}-\\x{205E}\\x{205F}\\x{2060}-\\x{2064}\\x{2065}\\x{2066}\\x{2067}\\x{2068}\\x{2069}\\x{206A}-\\x{206F}\\x{207A}-\\x{207B}\\x{207C}\\x{207D}\\x{207E}\\x{208A}-\\x{208B}\\x{208C}\\x{208D}\\x{208E}\\x{20A0}-\\x{20BF}\\x{20C0}-\\x{20CF}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2100}-\\x{2101}\\x{2103}-\\x{2106}\\x{2108}-\\x{2109}\\x{2114}\\x{2116}-\\x{2117}\\x{2118}\\x{211E}-\\x{2123}\\x{2125}\\x{2127}\\x{2129}\\x{212E}\\x{213A}-\\x{213B}\\x{2140}-\\x{2144}\\x{214A}\\x{214B}\\x{214C}-\\x{214D}\\x{2150}-\\x{215F}\\x{2189}\\x{218A}-\\x{218B}\\x{2190}-\\x{2194}\\x{2195}-\\x{2199}\\x{219A}-\\x{219B}\\x{219C}-\\x{219F}\\x{21A0}\\x{21A1}-\\x{21A2}\\x{21A3}\\x{21A4}-\\x{21A5}\\x{21A6}\\x{21A7}-\\x{21AD}\\x{21AE}\\x{21AF}-\\x{21CD}\\x{21CE}-\\x{21CF}\\x{21D0}-\\x{21D1}\\x{21D2}\\x{21D3}\\x{21D4}\\x{21D5}-\\x{21F3}\\x{21F4}-\\x{2211}\\x{2212}\\x{2213}\\x{2214}-\\x{22FF}\\x{2300}-\\x{2307}\\x{2308}\\x{2309}\\x{230A}\\x{230B}\\x{230C}-\\x{231F}\\x{2320}-\\x{2321}\\x{2322}-\\x{2328}\\x{2329}\\x{232A}\\x{232B}-\\x{2335}\\x{237B}\\x{237C}\\x{237D}-\\x{2394}\\x{2396}-\\x{239A}\\x{239B}-\\x{23B3}\\x{23B4}-\\x{23DB}\\x{23DC}-\\x{23E1}\\x{23E2}-\\x{2426}\\x{2440}-\\x{244A}\\x{2460}-\\x{2487}\\x{24EA}-\\x{24FF}\\x{2500}-\\x{25B6}\\x{25B7}\\x{25B8}-\\x{25C0}\\x{25C1}\\x{25C2}-\\x{25F7}\\x{25F8}-\\x{25FF}\\x{2600}-\\x{266E}\\x{266F}\\x{2670}-\\x{26AB}\\x{26AD}-\\x{2767}\\x{2768}\\x{2769}\\x{276A}\\x{276B}\\x{276C}\\x{276D}\\x{276E}\\x{276F}\\x{2770}\\x{2771}\\x{2772}\\x{2773}\\x{2774}\\x{2775}\\x{2776}-\\x{2793}\\x{2794}-\\x{27BF}\\x{27C0}-\\x{27C4}\\x{27C5}\\x{27C6}\\x{27C7}-\\x{27E5}\\x{27E6}\\x{27E7}\\x{27E8}\\x{27E9}\\x{27EA}\\x{27EB}\\x{27EC}\\x{27ED}\\x{27EE}\\x{27EF}\\x{27F0}-\\x{27FF}\\x{2900}-\\x{2982}\\x{2983}\\x{2984}\\x{2985}\\x{2986}\\x{2987}\\x{2988}\\x{2989}\\x{298A}\\x{298B}\\x{298C}\\x{298D}\\x{298E}\\x{298F}\\x{2990}\\x{2991}\\x{2992}\\x{2993}\\x{2994}\\x{2995}\\x{2996}\\x{2997}\\x{2998}\\x{2999}-\\x{29D7}\\x{29D8}\\x{29D9}\\x{29DA}\\x{29DB}\\x{29DC}-\\x{29FB}\\x{29FC}\\x{29FD}\\x{29FE}-\\x{2AFF}\\x{2B00}-\\x{2B2F}\\x{2B30}-\\x{2B44}\\x{2B45}-\\x{2B46}\\x{2B47}-\\x{2B4C}\\x{2B4D}-\\x{2B73}\\x{2B76}-\\x{2B95}\\x{2B97}-\\x{2BFF}\\x{2CE5}-\\x{2CEA}\\x{2CEF}-\\x{2CF1}\\x{2CF9}-\\x{2CFC}\\x{2CFD}\\x{2CFE}-\\x{2CFF}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{2E00}-\\x{2E01}\\x{2E02}\\x{2E03}\\x{2E04}\\x{2E05}\\x{2E06}-\\x{2E08}\\x{2E09}\\x{2E0A}\\x{2E0B}\\x{2E0C}\\x{2E0D}\\x{2E0E}-\\x{2E16}\\x{2E17}\\x{2E18}-\\x{2E19}\\x{2E1A}\\x{2E1B}\\x{2E1C}\\x{2E1D}\\x{2E1E}-\\x{2E1F}\\x{2E20}\\x{2E21}\\x{2E22}\\x{2E23}\\x{2E24}\\x{2E25}\\x{2E26}\\x{2E27}\\x{2E28}\\x{2E29}\\x{2E2A}-\\x{2E2E}\\x{2E2F}\\x{2E30}-\\x{2E39}\\x{2E3A}-\\x{2E3B}\\x{2E3C}-\\x{2E3F}\\x{2E40}\\x{2E41}\\x{2E42}\\x{2E43}-\\x{2E4F}\\x{2E50}-\\x{2E51}\\x{2E52}\\x{2E80}-\\x{2E99}\\x{2E9B}-\\x{2EF3}\\x{2F00}-\\x{2FD5}\\x{2FF0}-\\x{2FFB}\\x{3000}\\x{3001}-\\x{3003}\\x{3004}\\x{3008}\\x{3009}\\x{300A}\\x{300B}\\x{300C}\\x{300D}\\x{300E}\\x{300F}\\x{3010}\\x{3011}\\x{3012}-\\x{3013}\\x{3014}\\x{3015}\\x{3016}\\x{3017}\\x{3018}\\x{3019}\\x{301A}\\x{301B}\\x{301C}\\x{301D}\\x{301E}-\\x{301F}\\x{3020}\\x{302A}-\\x{302D}\\x{3030}\\x{3036}-\\x{3037}\\x{303D}\\x{303E}-\\x{303F}\\x{3099}-\\x{309A}\\x{309B}-\\x{309C}\\x{30A0}\\x{30FB}\\x{31C0}-\\x{31E3}\\x{321D}-\\x{321E}\\x{3250}\\x{3251}-\\x{325F}\\x{327C}-\\x{327E}\\x{32B1}-\\x{32BF}\\x{32CC}-\\x{32CF}\\x{3377}-\\x{337A}\\x{33DE}-\\x{33DF}\\x{33FF}\\x{4DC0}-\\x{4DFF}\\x{A490}-\\x{A4C6}\\x{A60D}-\\x{A60F}\\x{A66F}\\x{A670}-\\x{A672}\\x{A673}\\x{A674}-\\x{A67D}\\x{A67E}\\x{A67F}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A700}-\\x{A716}\\x{A717}-\\x{A71F}\\x{A720}-\\x{A721}\\x{A788}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A828}-\\x{A82B}\\x{A82C}\\x{A838}\\x{A839}\\x{A874}-\\x{A877}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{AB6A}-\\x{AB6B}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1D}\\x{FB1E}\\x{FB1F}-\\x{FB28}\\x{FB29}\\x{FB2A}-\\x{FB36}\\x{FB37}\\x{FB38}-\\x{FB3C}\\x{FB3D}\\x{FB3E}\\x{FB3F}\\x{FB40}-\\x{FB41}\\x{FB42}\\x{FB43}-\\x{FB44}\\x{FB45}\\x{FB46}-\\x{FB4F}\\x{FB50}-\\x{FBB1}\\x{FBB2}-\\x{FBC1}\\x{FBC2}-\\x{FBD2}\\x{FBD3}-\\x{FD3D}\\x{FD3E}\\x{FD3F}\\x{FD40}-\\x{FD4F}\\x{FD50}-\\x{FD8F}\\x{FD90}-\\x{FD91}\\x{FD92}-\\x{FDC7}\\x{FDC8}-\\x{FDCF}\\x{FDD0}-\\x{FDEF}\\x{FDF0}-\\x{FDFB}\\x{FDFC}\\x{FDFD}\\x{FDFE}-\\x{FDFF}\\x{FE00}-\\x{FE0F}\\x{FE10}-\\x{FE16}\\x{FE17}\\x{FE18}\\x{FE19}\\x{FE20}-\\x{FE2F}\\x{FE30}\\x{FE31}-\\x{FE32}\\x{FE33}-\\x{FE34}\\x{FE35}\\x{FE36}\\x{FE37}\\x{FE38}\\x{FE39}\\x{FE3A}\\x{FE3B}\\x{FE3C}\\x{FE3D}\\x{FE3E}\\x{FE3F}\\x{FE40}\\x{FE41}\\x{FE42}\\x{FE43}\\x{FE44}\\x{FE45}-\\x{FE46}\\x{FE47}\\x{FE48}\\x{FE49}-\\x{FE4C}\\x{FE4D}-\\x{FE4F}\\x{FE50}\\x{FE51}\\x{FE52}\\x{FE54}\\x{FE55}\\x{FE56}-\\x{FE57}\\x{FE58}\\x{FE59}\\x{FE5A}\\x{FE5B}\\x{FE5C}\\x{FE5D}\\x{FE5E}\\x{FE5F}\\x{FE60}-\\x{FE61}\\x{FE62}\\x{FE63}\\x{FE64}-\\x{FE66}\\x{FE68}\\x{FE69}\\x{FE6A}\\x{FE6B}\\x{FE70}-\\x{FE74}\\x{FE75}\\x{FE76}-\\x{FEFC}\\x{FEFD}-\\x{FEFE}\\x{FEFF}\\x{FF01}-\\x{FF02}\\x{FF03}\\x{FF04}\\x{FF05}\\x{FF06}-\\x{FF07}\\x{FF08}\\x{FF09}\\x{FF0A}\\x{FF0B}\\x{FF0C}\\x{FF0D}\\x{FF0E}-\\x{FF0F}\\x{FF1A}\\x{FF1B}\\x{FF1C}-\\x{FF1E}\\x{FF1F}-\\x{FF20}\\x{FF3B}\\x{FF3C}\\x{FF3D}\\x{FF3E}\\x{FF3F}\\x{FF40}\\x{FF5B}\\x{FF5C}\\x{FF5D}\\x{FF5E}\\x{FF5F}\\x{FF60}\\x{FF61}\\x{FF62}\\x{FF63}\\x{FF64}-\\x{FF65}\\x{FFE0}-\\x{FFE1}\\x{FFE2}\\x{FFE3}\\x{FFE4}\\x{FFE5}-\\x{FFE6}\\x{FFE8}\\x{FFE9}-\\x{FFEC}\\x{FFED}-\\x{FFEE}\\x{FFF0}-\\x{FFF8}\\x{FFF9}-\\x{FFFB}\\x{FFFC}-\\x{FFFD}\\x{FFFE}-\\x{FFFF}\\x{10101}\\x{10140}-\\x{10174}\\x{10175}-\\x{10178}\\x{10179}-\\x{10189}\\x{1018A}-\\x{1018B}\\x{1018C}\\x{10190}-\\x{1019C}\\x{101A0}\\x{101FD}\\x{102E0}\\x{10376}-\\x{1037A}\\x{10800}-\\x{10805}\\x{10806}-\\x{10807}\\x{10808}\\x{10809}\\x{1080A}-\\x{10835}\\x{10836}\\x{10837}-\\x{10838}\\x{10839}-\\x{1083B}\\x{1083C}\\x{1083D}-\\x{1083E}\\x{1083F}-\\x{10855}\\x{10856}\\x{10857}\\x{10858}-\\x{1085F}\\x{10860}-\\x{10876}\\x{10877}-\\x{10878}\\x{10879}-\\x{1087F}\\x{10880}-\\x{1089E}\\x{1089F}-\\x{108A6}\\x{108A7}-\\x{108AF}\\x{108B0}-\\x{108DF}\\x{108E0}-\\x{108F2}\\x{108F3}\\x{108F4}-\\x{108F5}\\x{108F6}-\\x{108FA}\\x{108FB}-\\x{108FF}\\x{10900}-\\x{10915}\\x{10916}-\\x{1091B}\\x{1091C}-\\x{1091E}\\x{1091F}\\x{10920}-\\x{10939}\\x{1093A}-\\x{1093E}\\x{1093F}\\x{10940}-\\x{1097F}\\x{10980}-\\x{109B7}\\x{109B8}-\\x{109BB}\\x{109BC}-\\x{109BD}\\x{109BE}-\\x{109BF}\\x{109C0}-\\x{109CF}\\x{109D0}-\\x{109D1}\\x{109D2}-\\x{109FF}\\x{10A00}\\x{10A01}-\\x{10A03}\\x{10A04}\\x{10A05}-\\x{10A06}\\x{10A07}-\\x{10A0B}\\x{10A0C}-\\x{10A0F}\\x{10A10}-\\x{10A13}\\x{10A14}\\x{10A15}-\\x{10A17}\\x{10A18}\\x{10A19}-\\x{10A35}\\x{10A36}-\\x{10A37}\\x{10A38}-\\x{10A3A}\\x{10A3B}-\\x{10A3E}\\x{10A3F}\\x{10A40}-\\x{10A48}\\x{10A49}-\\x{10A4F}\\x{10A50}-\\x{10A58}\\x{10A59}-\\x{10A5F}\\x{10A60}-\\x{10A7C}\\x{10A7D}-\\x{10A7E}\\x{10A7F}\\x{10A80}-\\x{10A9C}\\x{10A9D}-\\x{10A9F}\\x{10AA0}-\\x{10ABF}\\x{10AC0}-\\x{10AC7}\\x{10AC8}\\x{10AC9}-\\x{10AE4}\\x{10AE5}-\\x{10AE6}\\x{10AE7}-\\x{10AEA}\\x{10AEB}-\\x{10AEF}\\x{10AF0}-\\x{10AF6}\\x{10AF7}-\\x{10AFF}\\x{10B00}-\\x{10B35}\\x{10B36}-\\x{10B38}\\x{10B39}-\\x{10B3F}\\x{10B40}-\\x{10B55}\\x{10B56}-\\x{10B57}\\x{10B58}-\\x{10B5F}\\x{10B60}-\\x{10B72}\\x{10B73}-\\x{10B77}\\x{10B78}-\\x{10B7F}\\x{10B80}-\\x{10B91}\\x{10B92}-\\x{10B98}\\x{10B99}-\\x{10B9C}\\x{10B9D}-\\x{10BA8}\\x{10BA9}-\\x{10BAF}\\x{10BB0}-\\x{10BFF}\\x{10C00}-\\x{10C48}\\x{10C49}-\\x{10C7F}\\x{10C80}-\\x{10CB2}\\x{10CB3}-\\x{10CBF}\\x{10CC0}-\\x{10CF2}\\x{10CF3}-\\x{10CF9}\\x{10CFA}-\\x{10CFF}\\x{10D00}-\\x{10D23}\\x{10D24}-\\x{10D27}\\x{10D28}-\\x{10D2F}\\x{10D30}-\\x{10D39}\\x{10D3A}-\\x{10D3F}\\x{10D40}-\\x{10E5F}\\x{10E60}-\\x{10E7E}\\x{10E7F}\\x{10E80}-\\x{10EA9}\\x{10EAA}\\x{10EAB}-\\x{10EAC}\\x{10EAD}\\x{10EAE}-\\x{10EAF}\\x{10EB0}-\\x{10EB1}\\x{10EB2}-\\x{10EFF}\\x{10F00}-\\x{10F1C}\\x{10F1D}-\\x{10F26}\\x{10F27}\\x{10F28}-\\x{10F2F}\\x{10F30}-\\x{10F45}\\x{10F46}-\\x{10F50}\\x{10F51}-\\x{10F54}\\x{10F55}-\\x{10F59}\\x{10F5A}-\\x{10F6F}\\x{10F70}-\\x{10FAF}\\x{10FB0}-\\x{10FC4}\\x{10FC5}-\\x{10FCB}\\x{10FCC}-\\x{10FDF}\\x{10FE0}-\\x{10FF6}\\x{10FF7}-\\x{10FFF}\\x{11001}\\x{11038}-\\x{11046}\\x{11052}-\\x{11065}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{11660}-\\x{1166C}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A06}\\x{11A09}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{11FD5}-\\x{11FDC}\\x{11FDD}-\\x{11FE0}\\x{11FE1}-\\x{11FF1}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE2}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1BCA0}-\\x{1BCA3}\\x{1D167}-\\x{1D169}\\x{1D173}-\\x{1D17A}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D200}-\\x{1D241}\\x{1D242}-\\x{1D244}\\x{1D245}\\x{1D300}-\\x{1D356}\\x{1D6DB}\\x{1D715}\\x{1D74F}\\x{1D789}\\x{1D7C3}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E2FF}\\x{1E800}-\\x{1E8C4}\\x{1E8C5}-\\x{1E8C6}\\x{1E8C7}-\\x{1E8CF}\\x{1E8D0}-\\x{1E8D6}\\x{1E8D7}-\\x{1E8FF}\\x{1E900}-\\x{1E943}\\x{1E944}-\\x{1E94A}\\x{1E94B}\\x{1E94C}-\\x{1E94F}\\x{1E950}-\\x{1E959}\\x{1E95A}-\\x{1E95D}\\x{1E95E}-\\x{1E95F}\\x{1E960}-\\x{1EC6F}\\x{1EC70}\\x{1EC71}-\\x{1ECAB}\\x{1ECAC}\\x{1ECAD}-\\x{1ECAF}\\x{1ECB0}\\x{1ECB1}-\\x{1ECB4}\\x{1ECB5}-\\x{1ECBF}\\x{1ECC0}-\\x{1ECFF}\\x{1ED00}\\x{1ED01}-\\x{1ED2D}\\x{1ED2E}\\x{1ED2F}-\\x{1ED3D}\\x{1ED3E}-\\x{1ED4F}\\x{1ED50}-\\x{1EDFF}\\x{1EE00}-\\x{1EE03}\\x{1EE04}\\x{1EE05}-\\x{1EE1F}\\x{1EE20}\\x{1EE21}-\\x{1EE22}\\x{1EE23}\\x{1EE24}\\x{1EE25}-\\x{1EE26}\\x{1EE27}\\x{1EE28}\\x{1EE29}-\\x{1EE32}\\x{1EE33}\\x{1EE34}-\\x{1EE37}\\x{1EE38}\\x{1EE39}\\x{1EE3A}\\x{1EE3B}\\x{1EE3C}-\\x{1EE41}\\x{1EE42}\\x{1EE43}-\\x{1EE46}\\x{1EE47}\\x{1EE48}\\x{1EE49}\\x{1EE4A}\\x{1EE4B}\\x{1EE4C}\\x{1EE4D}-\\x{1EE4F}\\x{1EE50}\\x{1EE51}-\\x{1EE52}\\x{1EE53}\\x{1EE54}\\x{1EE55}-\\x{1EE56}\\x{1EE57}\\x{1EE58}\\x{1EE59}\\x{1EE5A}\\x{1EE5B}\\x{1EE5C}\\x{1EE5D}\\x{1EE5E}\\x{1EE5F}\\x{1EE60}\\x{1EE61}-\\x{1EE62}\\x{1EE63}\\x{1EE64}\\x{1EE65}-\\x{1EE66}\\x{1EE67}-\\x{1EE6A}\\x{1EE6B}\\x{1EE6C}-\\x{1EE72}\\x{1EE73}\\x{1EE74}-\\x{1EE77}\\x{1EE78}\\x{1EE79}-\\x{1EE7C}\\x{1EE7D}\\x{1EE7E}\\x{1EE7F}\\x{1EE80}-\\x{1EE89}\\x{1EE8A}\\x{1EE8B}-\\x{1EE9B}\\x{1EE9C}-\\x{1EEA0}\\x{1EEA1}-\\x{1EEA3}\\x{1EEA4}\\x{1EEA5}-\\x{1EEA9}\\x{1EEAA}\\x{1EEAB}-\\x{1EEBB}\\x{1EEBC}-\\x{1EEEF}\\x{1EEF0}-\\x{1EEF1}\\x{1EEF2}-\\x{1EEFF}\\x{1EF00}-\\x{1EFFF}\\x{1F000}-\\x{1F02B}\\x{1F030}-\\x{1F093}\\x{1F0A0}-\\x{1F0AE}\\x{1F0B1}-\\x{1F0BF}\\x{1F0C1}-\\x{1F0CF}\\x{1F0D1}-\\x{1F0F5}\\x{1F10B}-\\x{1F10C}\\x{1F10D}-\\x{1F10F}\\x{1F12F}\\x{1F16A}-\\x{1F16F}\\x{1F1AD}\\x{1F260}-\\x{1F265}\\x{1F300}-\\x{1F3FA}\\x{1F3FB}-\\x{1F3FF}\\x{1F400}-\\x{1F6D7}\\x{1F6E0}-\\x{1F6EC}\\x{1F6F0}-\\x{1F6FC}\\x{1F700}-\\x{1F773}\\x{1F780}-\\x{1F7D8}\\x{1F7E0}-\\x{1F7EB}\\x{1F800}-\\x{1F80B}\\x{1F810}-\\x{1F847}\\x{1F850}-\\x{1F859}\\x{1F860}-\\x{1F887}\\x{1F890}-\\x{1F8AD}\\x{1F8B0}-\\x{1F8B1}\\x{1F900}-\\x{1F978}\\x{1F97A}-\\x{1F9CB}\\x{1F9CD}-\\x{1FA53}\\x{1FA60}-\\x{1FA6D}\\x{1FA70}-\\x{1FA74}\\x{1FA78}-\\x{1FA7A}\\x{1FA80}-\\x{1FA86}\\x{1FA90}-\\x{1FAA8}\\x{1FAB0}-\\x{1FAB6}\\x{1FAC0}-\\x{1FAC2}\\x{1FAD0}-\\x{1FAD6}\\x{1FB00}-\\x{1FB92}\\x{1FB94}-\\x{1FBCA}\\x{1FFFE}-\\x{1FFFF}\\x{2FFFE}-\\x{2FFFF}\\x{3FFFE}-\\x{3FFFF}\\x{4FFFE}-\\x{4FFFF}\\x{5FFFE}-\\x{5FFFF}\\x{6FFFE}-\\x{6FFFF}\\x{7FFFE}-\\x{7FFFF}\\x{8FFFE}-\\x{8FFFF}\\x{9FFFE}-\\x{9FFFF}\\x{AFFFE}-\\x{AFFFF}\\x{BFFFE}-\\x{BFFFF}\\x{CFFFE}-\\x{CFFFF}\\x{DFFFE}-\\x{E0000}\\x{E0001}\\x{E0002}-\\x{E001F}\\x{E0020}-\\x{E007F}\\x{E0080}-\\x{E00FF}\\x{E0100}-\\x{E01EF}\\x{E01F0}-\\x{E0FFF}\\x{EFFFE}-\\x{EFFFF}\\x{FFFFE}-\\x{FFFFF}\\x{10FFFE}-\\x{10FFFF}][\\x{0300}-\\x{036F}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{0591}-\\x{05BD}\\x{05BF}\\x{05C1}-\\x{05C2}\\x{05C4}-\\x{05C5}\\x{05C7}\\x{0610}-\\x{061A}\\x{064B}-\\x{065F}\\x{0670}\\x{06D6}-\\x{06DC}\\x{06DF}-\\x{06E4}\\x{06E7}-\\x{06E8}\\x{06EA}-\\x{06ED}\\x{0711}\\x{0730}-\\x{074A}\\x{07A6}-\\x{07B0}\\x{07EB}-\\x{07F3}\\x{07FD}\\x{0816}-\\x{0819}\\x{081B}-\\x{0823}\\x{0825}-\\x{0827}\\x{0829}-\\x{082D}\\x{0859}-\\x{085B}\\x{08D3}-\\x{08E1}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C81}\\x{0CBC}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DD}\\x{180B}-\\x{180D}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2CEF}-\\x{2CF1}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{302A}-\\x{302D}\\x{3099}-\\x{309A}\\x{A66F}\\x{A670}-\\x{A672}\\x{A674}-\\x{A67D}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A82C}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1E}\\x{FE00}-\\x{FE0F}\\x{FE20}-\\x{FE2F}\\x{101FD}\\x{102E0}\\x{10376}-\\x{1037A}\\x{10A01}-\\x{10A03}\\x{10A05}-\\x{10A06}\\x{10A0C}-\\x{10A0F}\\x{10A38}-\\x{10A3A}\\x{10A3F}\\x{10AE5}-\\x{10AE6}\\x{10D24}-\\x{10D27}\\x{10EAB}-\\x{10EAC}\\x{10F46}-\\x{10F50}\\x{11001}\\x{11038}-\\x{11046}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A06}\\x{11A09}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1D167}-\\x{1D169}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D242}-\\x{1D244}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E8D0}-\\x{1E8D6}\\x{1E944}-\\x{1E94A}\\x{E0100}-\\x{E01EF}]*$/u'; + const ZWNJ = '/([\\x{A872}\\x{10ACD}\\x{10AD7}\\x{10D00}\\x{10FCB}\\x{0620}\\x{0626}\\x{0628}\\x{062A}-\\x{062E}\\x{0633}-\\x{063F}\\x{0641}-\\x{0647}\\x{0649}-\\x{064A}\\x{066E}-\\x{066F}\\x{0678}-\\x{0687}\\x{069A}-\\x{06BF}\\x{06C1}-\\x{06C2}\\x{06CC}\\x{06CE}\\x{06D0}-\\x{06D1}\\x{06FA}-\\x{06FC}\\x{06FF}\\x{0712}-\\x{0714}\\x{071A}-\\x{071D}\\x{071F}-\\x{0727}\\x{0729}\\x{072B}\\x{072D}-\\x{072E}\\x{074E}-\\x{0758}\\x{075C}-\\x{076A}\\x{076D}-\\x{0770}\\x{0772}\\x{0775}-\\x{0777}\\x{077A}-\\x{077F}\\x{07CA}-\\x{07EA}\\x{0841}-\\x{0845}\\x{0848}\\x{084A}-\\x{0853}\\x{0855}\\x{0860}\\x{0862}-\\x{0865}\\x{0868}\\x{08A0}-\\x{08A9}\\x{08AF}-\\x{08B0}\\x{08B3}-\\x{08B4}\\x{08B6}-\\x{08B8}\\x{08BA}-\\x{08C7}\\x{1807}\\x{1820}-\\x{1842}\\x{1843}\\x{1844}-\\x{1878}\\x{1887}-\\x{18A8}\\x{18AA}\\x{A840}-\\x{A871}\\x{10AC0}-\\x{10AC4}\\x{10AD3}-\\x{10AD6}\\x{10AD8}-\\x{10ADC}\\x{10ADE}-\\x{10AE0}\\x{10AEB}-\\x{10AEE}\\x{10B80}\\x{10B82}\\x{10B86}-\\x{10B88}\\x{10B8A}-\\x{10B8B}\\x{10B8D}\\x{10B90}\\x{10BAD}-\\x{10BAE}\\x{10D01}-\\x{10D21}\\x{10D23}\\x{10F30}-\\x{10F32}\\x{10F34}-\\x{10F44}\\x{10F51}-\\x{10F53}\\x{10FB0}\\x{10FB2}-\\x{10FB3}\\x{10FB8}\\x{10FBB}-\\x{10FBC}\\x{10FBE}-\\x{10FBF}\\x{10FC1}\\x{10FC4}\\x{10FCA}\\x{1E900}-\\x{1E943}][\\x{00AD}\\x{0300}-\\x{036F}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{0591}-\\x{05BD}\\x{05BF}\\x{05C1}-\\x{05C2}\\x{05C4}-\\x{05C5}\\x{05C7}\\x{0610}-\\x{061A}\\x{061C}\\x{064B}-\\x{065F}\\x{0670}\\x{06D6}-\\x{06DC}\\x{06DF}-\\x{06E4}\\x{06E7}-\\x{06E8}\\x{06EA}-\\x{06ED}\\x{070F}\\x{0711}\\x{0730}-\\x{074A}\\x{07A6}-\\x{07B0}\\x{07EB}-\\x{07F3}\\x{07FD}\\x{0816}-\\x{0819}\\x{081B}-\\x{0823}\\x{0825}-\\x{0827}\\x{0829}-\\x{082D}\\x{0859}-\\x{085B}\\x{08D3}-\\x{08E1}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C81}\\x{0CBC}\\x{0CBF}\\x{0CC6}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DD}\\x{180B}-\\x{180D}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{200B}\\x{200E}-\\x{200F}\\x{202A}-\\x{202E}\\x{2060}-\\x{2064}\\x{206A}-\\x{206F}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2CEF}-\\x{2CF1}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{302A}-\\x{302D}\\x{3099}-\\x{309A}\\x{A66F}\\x{A670}-\\x{A672}\\x{A674}-\\x{A67D}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A82C}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1E}\\x{FE00}-\\x{FE0F}\\x{FE20}-\\x{FE2F}\\x{FEFF}\\x{FFF9}-\\x{FFFB}\\x{101FD}\\x{102E0}\\x{10376}-\\x{1037A}\\x{10A01}-\\x{10A03}\\x{10A05}-\\x{10A06}\\x{10A0C}-\\x{10A0F}\\x{10A38}-\\x{10A3A}\\x{10A3F}\\x{10AE5}-\\x{10AE6}\\x{10D24}-\\x{10D27}\\x{10EAB}-\\x{10EAC}\\x{10F46}-\\x{10F50}\\x{11001}\\x{11038}-\\x{11046}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C3F}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{13430}-\\x{13438}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1BCA0}-\\x{1BCA3}\\x{1D167}-\\x{1D169}\\x{1D173}-\\x{1D17A}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D242}-\\x{1D244}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E8D0}-\\x{1E8D6}\\x{1E944}-\\x{1E94A}\\x{1E94B}\\x{E0001}\\x{E0020}-\\x{E007F}\\x{E0100}-\\x{E01EF}]*\\x{200C}[\\x{00AD}\\x{0300}-\\x{036F}\\x{0483}-\\x{0487}\\x{0488}-\\x{0489}\\x{0591}-\\x{05BD}\\x{05BF}\\x{05C1}-\\x{05C2}\\x{05C4}-\\x{05C5}\\x{05C7}\\x{0610}-\\x{061A}\\x{061C}\\x{064B}-\\x{065F}\\x{0670}\\x{06D6}-\\x{06DC}\\x{06DF}-\\x{06E4}\\x{06E7}-\\x{06E8}\\x{06EA}-\\x{06ED}\\x{070F}\\x{0711}\\x{0730}-\\x{074A}\\x{07A6}-\\x{07B0}\\x{07EB}-\\x{07F3}\\x{07FD}\\x{0816}-\\x{0819}\\x{081B}-\\x{0823}\\x{0825}-\\x{0827}\\x{0829}-\\x{082D}\\x{0859}-\\x{085B}\\x{08D3}-\\x{08E1}\\x{08E3}-\\x{0902}\\x{093A}\\x{093C}\\x{0941}-\\x{0948}\\x{094D}\\x{0951}-\\x{0957}\\x{0962}-\\x{0963}\\x{0981}\\x{09BC}\\x{09C1}-\\x{09C4}\\x{09CD}\\x{09E2}-\\x{09E3}\\x{09FE}\\x{0A01}-\\x{0A02}\\x{0A3C}\\x{0A41}-\\x{0A42}\\x{0A47}-\\x{0A48}\\x{0A4B}-\\x{0A4D}\\x{0A51}\\x{0A70}-\\x{0A71}\\x{0A75}\\x{0A81}-\\x{0A82}\\x{0ABC}\\x{0AC1}-\\x{0AC5}\\x{0AC7}-\\x{0AC8}\\x{0ACD}\\x{0AE2}-\\x{0AE3}\\x{0AFA}-\\x{0AFF}\\x{0B01}\\x{0B3C}\\x{0B3F}\\x{0B41}-\\x{0B44}\\x{0B4D}\\x{0B55}-\\x{0B56}\\x{0B62}-\\x{0B63}\\x{0B82}\\x{0BC0}\\x{0BCD}\\x{0C00}\\x{0C04}\\x{0C3E}-\\x{0C40}\\x{0C46}-\\x{0C48}\\x{0C4A}-\\x{0C4D}\\x{0C55}-\\x{0C56}\\x{0C62}-\\x{0C63}\\x{0C81}\\x{0CBC}\\x{0CBF}\\x{0CC6}\\x{0CCC}-\\x{0CCD}\\x{0CE2}-\\x{0CE3}\\x{0D00}-\\x{0D01}\\x{0D3B}-\\x{0D3C}\\x{0D41}-\\x{0D44}\\x{0D4D}\\x{0D62}-\\x{0D63}\\x{0D81}\\x{0DCA}\\x{0DD2}-\\x{0DD4}\\x{0DD6}\\x{0E31}\\x{0E34}-\\x{0E3A}\\x{0E47}-\\x{0E4E}\\x{0EB1}\\x{0EB4}-\\x{0EBC}\\x{0EC8}-\\x{0ECD}\\x{0F18}-\\x{0F19}\\x{0F35}\\x{0F37}\\x{0F39}\\x{0F71}-\\x{0F7E}\\x{0F80}-\\x{0F84}\\x{0F86}-\\x{0F87}\\x{0F8D}-\\x{0F97}\\x{0F99}-\\x{0FBC}\\x{0FC6}\\x{102D}-\\x{1030}\\x{1032}-\\x{1037}\\x{1039}-\\x{103A}\\x{103D}-\\x{103E}\\x{1058}-\\x{1059}\\x{105E}-\\x{1060}\\x{1071}-\\x{1074}\\x{1082}\\x{1085}-\\x{1086}\\x{108D}\\x{109D}\\x{135D}-\\x{135F}\\x{1712}-\\x{1714}\\x{1732}-\\x{1734}\\x{1752}-\\x{1753}\\x{1772}-\\x{1773}\\x{17B4}-\\x{17B5}\\x{17B7}-\\x{17BD}\\x{17C6}\\x{17C9}-\\x{17D3}\\x{17DD}\\x{180B}-\\x{180D}\\x{1885}-\\x{1886}\\x{18A9}\\x{1920}-\\x{1922}\\x{1927}-\\x{1928}\\x{1932}\\x{1939}-\\x{193B}\\x{1A17}-\\x{1A18}\\x{1A1B}\\x{1A56}\\x{1A58}-\\x{1A5E}\\x{1A60}\\x{1A62}\\x{1A65}-\\x{1A6C}\\x{1A73}-\\x{1A7C}\\x{1A7F}\\x{1AB0}-\\x{1ABD}\\x{1ABE}\\x{1ABF}-\\x{1AC0}\\x{1B00}-\\x{1B03}\\x{1B34}\\x{1B36}-\\x{1B3A}\\x{1B3C}\\x{1B42}\\x{1B6B}-\\x{1B73}\\x{1B80}-\\x{1B81}\\x{1BA2}-\\x{1BA5}\\x{1BA8}-\\x{1BA9}\\x{1BAB}-\\x{1BAD}\\x{1BE6}\\x{1BE8}-\\x{1BE9}\\x{1BED}\\x{1BEF}-\\x{1BF1}\\x{1C2C}-\\x{1C33}\\x{1C36}-\\x{1C37}\\x{1CD0}-\\x{1CD2}\\x{1CD4}-\\x{1CE0}\\x{1CE2}-\\x{1CE8}\\x{1CED}\\x{1CF4}\\x{1CF8}-\\x{1CF9}\\x{1DC0}-\\x{1DF9}\\x{1DFB}-\\x{1DFF}\\x{200B}\\x{200E}-\\x{200F}\\x{202A}-\\x{202E}\\x{2060}-\\x{2064}\\x{206A}-\\x{206F}\\x{20D0}-\\x{20DC}\\x{20DD}-\\x{20E0}\\x{20E1}\\x{20E2}-\\x{20E4}\\x{20E5}-\\x{20F0}\\x{2CEF}-\\x{2CF1}\\x{2D7F}\\x{2DE0}-\\x{2DFF}\\x{302A}-\\x{302D}\\x{3099}-\\x{309A}\\x{A66F}\\x{A670}-\\x{A672}\\x{A674}-\\x{A67D}\\x{A69E}-\\x{A69F}\\x{A6F0}-\\x{A6F1}\\x{A802}\\x{A806}\\x{A80B}\\x{A825}-\\x{A826}\\x{A82C}\\x{A8C4}-\\x{A8C5}\\x{A8E0}-\\x{A8F1}\\x{A8FF}\\x{A926}-\\x{A92D}\\x{A947}-\\x{A951}\\x{A980}-\\x{A982}\\x{A9B3}\\x{A9B6}-\\x{A9B9}\\x{A9BC}-\\x{A9BD}\\x{A9E5}\\x{AA29}-\\x{AA2E}\\x{AA31}-\\x{AA32}\\x{AA35}-\\x{AA36}\\x{AA43}\\x{AA4C}\\x{AA7C}\\x{AAB0}\\x{AAB2}-\\x{AAB4}\\x{AAB7}-\\x{AAB8}\\x{AABE}-\\x{AABF}\\x{AAC1}\\x{AAEC}-\\x{AAED}\\x{AAF6}\\x{ABE5}\\x{ABE8}\\x{ABED}\\x{FB1E}\\x{FE00}-\\x{FE0F}\\x{FE20}-\\x{FE2F}\\x{FEFF}\\x{FFF9}-\\x{FFFB}\\x{101FD}\\x{102E0}\\x{10376}-\\x{1037A}\\x{10A01}-\\x{10A03}\\x{10A05}-\\x{10A06}\\x{10A0C}-\\x{10A0F}\\x{10A38}-\\x{10A3A}\\x{10A3F}\\x{10AE5}-\\x{10AE6}\\x{10D24}-\\x{10D27}\\x{10EAB}-\\x{10EAC}\\x{10F46}-\\x{10F50}\\x{11001}\\x{11038}-\\x{11046}\\x{1107F}-\\x{11081}\\x{110B3}-\\x{110B6}\\x{110B9}-\\x{110BA}\\x{11100}-\\x{11102}\\x{11127}-\\x{1112B}\\x{1112D}-\\x{11134}\\x{11173}\\x{11180}-\\x{11181}\\x{111B6}-\\x{111BE}\\x{111C9}-\\x{111CC}\\x{111CF}\\x{1122F}-\\x{11231}\\x{11234}\\x{11236}-\\x{11237}\\x{1123E}\\x{112DF}\\x{112E3}-\\x{112EA}\\x{11300}-\\x{11301}\\x{1133B}-\\x{1133C}\\x{11340}\\x{11366}-\\x{1136C}\\x{11370}-\\x{11374}\\x{11438}-\\x{1143F}\\x{11442}-\\x{11444}\\x{11446}\\x{1145E}\\x{114B3}-\\x{114B8}\\x{114BA}\\x{114BF}-\\x{114C0}\\x{114C2}-\\x{114C3}\\x{115B2}-\\x{115B5}\\x{115BC}-\\x{115BD}\\x{115BF}-\\x{115C0}\\x{115DC}-\\x{115DD}\\x{11633}-\\x{1163A}\\x{1163D}\\x{1163F}-\\x{11640}\\x{116AB}\\x{116AD}\\x{116B0}-\\x{116B5}\\x{116B7}\\x{1171D}-\\x{1171F}\\x{11722}-\\x{11725}\\x{11727}-\\x{1172B}\\x{1182F}-\\x{11837}\\x{11839}-\\x{1183A}\\x{1193B}-\\x{1193C}\\x{1193E}\\x{11943}\\x{119D4}-\\x{119D7}\\x{119DA}-\\x{119DB}\\x{119E0}\\x{11A01}-\\x{11A0A}\\x{11A33}-\\x{11A38}\\x{11A3B}-\\x{11A3E}\\x{11A47}\\x{11A51}-\\x{11A56}\\x{11A59}-\\x{11A5B}\\x{11A8A}-\\x{11A96}\\x{11A98}-\\x{11A99}\\x{11C30}-\\x{11C36}\\x{11C38}-\\x{11C3D}\\x{11C3F}\\x{11C92}-\\x{11CA7}\\x{11CAA}-\\x{11CB0}\\x{11CB2}-\\x{11CB3}\\x{11CB5}-\\x{11CB6}\\x{11D31}-\\x{11D36}\\x{11D3A}\\x{11D3C}-\\x{11D3D}\\x{11D3F}-\\x{11D45}\\x{11D47}\\x{11D90}-\\x{11D91}\\x{11D95}\\x{11D97}\\x{11EF3}-\\x{11EF4}\\x{13430}-\\x{13438}\\x{16AF0}-\\x{16AF4}\\x{16B30}-\\x{16B36}\\x{16F4F}\\x{16F8F}-\\x{16F92}\\x{16FE4}\\x{1BC9D}-\\x{1BC9E}\\x{1BCA0}-\\x{1BCA3}\\x{1D167}-\\x{1D169}\\x{1D173}-\\x{1D17A}\\x{1D17B}-\\x{1D182}\\x{1D185}-\\x{1D18B}\\x{1D1AA}-\\x{1D1AD}\\x{1D242}-\\x{1D244}\\x{1DA00}-\\x{1DA36}\\x{1DA3B}-\\x{1DA6C}\\x{1DA75}\\x{1DA84}\\x{1DA9B}-\\x{1DA9F}\\x{1DAA1}-\\x{1DAAF}\\x{1E000}-\\x{1E006}\\x{1E008}-\\x{1E018}\\x{1E01B}-\\x{1E021}\\x{1E023}-\\x{1E024}\\x{1E026}-\\x{1E02A}\\x{1E130}-\\x{1E136}\\x{1E2EC}-\\x{1E2EF}\\x{1E8D0}-\\x{1E8D6}\\x{1E944}-\\x{1E94A}\\x{1E94B}\\x{E0001}\\x{E0020}-\\x{E007F}\\x{E0100}-\\x{E01EF}]*)[\\x{0622}-\\x{0625}\\x{0627}\\x{0629}\\x{062F}-\\x{0632}\\x{0648}\\x{0671}-\\x{0673}\\x{0675}-\\x{0677}\\x{0688}-\\x{0699}\\x{06C0}\\x{06C3}-\\x{06CB}\\x{06CD}\\x{06CF}\\x{06D2}-\\x{06D3}\\x{06D5}\\x{06EE}-\\x{06EF}\\x{0710}\\x{0715}-\\x{0719}\\x{071E}\\x{0728}\\x{072A}\\x{072C}\\x{072F}\\x{074D}\\x{0759}-\\x{075B}\\x{076B}-\\x{076C}\\x{0771}\\x{0773}-\\x{0774}\\x{0778}-\\x{0779}\\x{0840}\\x{0846}-\\x{0847}\\x{0849}\\x{0854}\\x{0856}-\\x{0858}\\x{0867}\\x{0869}-\\x{086A}\\x{08AA}-\\x{08AC}\\x{08AE}\\x{08B1}-\\x{08B2}\\x{08B9}\\x{10AC5}\\x{10AC7}\\x{10AC9}-\\x{10ACA}\\x{10ACE}-\\x{10AD2}\\x{10ADD}\\x{10AE1}\\x{10AE4}\\x{10AEF}\\x{10B81}\\x{10B83}-\\x{10B85}\\x{10B89}\\x{10B8C}\\x{10B8E}-\\x{10B8F}\\x{10B91}\\x{10BA9}-\\x{10BAC}\\x{10D22}\\x{10F33}\\x{10F54}\\x{10FB4}-\\x{10FB6}\\x{10FB9}-\\x{10FBA}\\x{10FBD}\\x{10FC2}-\\x{10FC3}\\x{10FC9}\\x{0620}\\x{0626}\\x{0628}\\x{062A}-\\x{062E}\\x{0633}-\\x{063F}\\x{0641}-\\x{0647}\\x{0649}-\\x{064A}\\x{066E}-\\x{066F}\\x{0678}-\\x{0687}\\x{069A}-\\x{06BF}\\x{06C1}-\\x{06C2}\\x{06CC}\\x{06CE}\\x{06D0}-\\x{06D1}\\x{06FA}-\\x{06FC}\\x{06FF}\\x{0712}-\\x{0714}\\x{071A}-\\x{071D}\\x{071F}-\\x{0727}\\x{0729}\\x{072B}\\x{072D}-\\x{072E}\\x{074E}-\\x{0758}\\x{075C}-\\x{076A}\\x{076D}-\\x{0770}\\x{0772}\\x{0775}-\\x{0777}\\x{077A}-\\x{077F}\\x{07CA}-\\x{07EA}\\x{0841}-\\x{0845}\\x{0848}\\x{084A}-\\x{0853}\\x{0855}\\x{0860}\\x{0862}-\\x{0865}\\x{0868}\\x{08A0}-\\x{08A9}\\x{08AF}-\\x{08B0}\\x{08B3}-\\x{08B4}\\x{08B6}-\\x{08B8}\\x{08BA}-\\x{08C7}\\x{1807}\\x{1820}-\\x{1842}\\x{1843}\\x{1844}-\\x{1878}\\x{1887}-\\x{18A8}\\x{18AA}\\x{A840}-\\x{A871}\\x{10AC0}-\\x{10AC4}\\x{10AD3}-\\x{10AD6}\\x{10AD8}-\\x{10ADC}\\x{10ADE}-\\x{10AE0}\\x{10AEB}-\\x{10AEE}\\x{10B80}\\x{10B82}\\x{10B86}-\\x{10B88}\\x{10B8A}-\\x{10B8B}\\x{10B8D}\\x{10B90}\\x{10BAD}-\\x{10BAE}\\x{10D01}-\\x{10D21}\\x{10D23}\\x{10F30}-\\x{10F32}\\x{10F34}-\\x{10F44}\\x{10F51}-\\x{10F53}\\x{10FB0}\\x{10FB2}-\\x{10FB3}\\x{10FB8}\\x{10FBB}-\\x{10FBC}\\x{10FBE}-\\x{10FBF}\\x{10FC1}\\x{10FC4}\\x{10FCA}\\x{1E900}-\\x{1E943}]/u'; +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/deviation.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/deviation.php new file mode 100644 index 00000000..b36b08a0 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/deviation.php @@ -0,0 +1,5 @@ + 'ss', 962 => 'σ', 8204 => '', 8205 => ''); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php new file mode 100644 index 00000000..b09a649b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed.php @@ -0,0 +1,5 @@ + \true, 889 => \true, 896 => \true, 897 => \true, 898 => \true, 899 => \true, 907 => \true, 909 => \true, 930 => \true, 1216 => \true, 1328 => \true, 1367 => \true, 1368 => \true, 1419 => \true, 1420 => \true, 1424 => \true, 1480 => \true, 1481 => \true, 1482 => \true, 1483 => \true, 1484 => \true, 1485 => \true, 1486 => \true, 1487 => \true, 1515 => \true, 1516 => \true, 1517 => \true, 1518 => \true, 1525 => \true, 1526 => \true, 1527 => \true, 1528 => \true, 1529 => \true, 1530 => \true, 1531 => \true, 1532 => \true, 1533 => \true, 1534 => \true, 1535 => \true, 1536 => \true, 1537 => \true, 1538 => \true, 1539 => \true, 1540 => \true, 1541 => \true, 1564 => \true, 1565 => \true, 1757 => \true, 1806 => \true, 1807 => \true, 1867 => \true, 1868 => \true, 1970 => \true, 1971 => \true, 1972 => \true, 1973 => \true, 1974 => \true, 1975 => \true, 1976 => \true, 1977 => \true, 1978 => \true, 1979 => \true, 1980 => \true, 1981 => \true, 1982 => \true, 1983 => \true, 2043 => \true, 2044 => \true, 2094 => \true, 2095 => \true, 2111 => \true, 2140 => \true, 2141 => \true, 2143 => \true, 2229 => \true, 2248 => \true, 2249 => \true, 2250 => \true, 2251 => \true, 2252 => \true, 2253 => \true, 2254 => \true, 2255 => \true, 2256 => \true, 2257 => \true, 2258 => \true, 2274 => \true, 2436 => \true, 2445 => \true, 2446 => \true, 2449 => \true, 2450 => \true, 2473 => \true, 2481 => \true, 2483 => \true, 2484 => \true, 2485 => \true, 2490 => \true, 2491 => \true, 2501 => \true, 2502 => \true, 2505 => \true, 2506 => \true, 2511 => \true, 2512 => \true, 2513 => \true, 2514 => \true, 2515 => \true, 2516 => \true, 2517 => \true, 2518 => \true, 2520 => \true, 2521 => \true, 2522 => \true, 2523 => \true, 2526 => \true, 2532 => \true, 2533 => \true, 2559 => \true, 2560 => \true, 2564 => \true, 2571 => \true, 2572 => \true, 2573 => \true, 2574 => \true, 2577 => \true, 2578 => \true, 2601 => \true, 2609 => \true, 2612 => \true, 2615 => \true, 2618 => \true, 2619 => \true, 2621 => \true, 2627 => \true, 2628 => \true, 2629 => \true, 2630 => \true, 2633 => \true, 2634 => \true, 2638 => \true, 2639 => \true, 2640 => \true, 2642 => \true, 2643 => \true, 2644 => \true, 2645 => \true, 2646 => \true, 2647 => \true, 2648 => \true, 2653 => \true, 2655 => \true, 2656 => \true, 2657 => \true, 2658 => \true, 2659 => \true, 2660 => \true, 2661 => \true, 2679 => \true, 2680 => \true, 2681 => \true, 2682 => \true, 2683 => \true, 2684 => \true, 2685 => \true, 2686 => \true, 2687 => \true, 2688 => \true, 2692 => \true, 2702 => \true, 2706 => \true, 2729 => \true, 2737 => \true, 2740 => \true, 2746 => \true, 2747 => \true, 2758 => \true, 2762 => \true, 2766 => \true, 2767 => \true, 2769 => \true, 2770 => \true, 2771 => \true, 2772 => \true, 2773 => \true, 2774 => \true, 2775 => \true, 2776 => \true, 2777 => \true, 2778 => \true, 2779 => \true, 2780 => \true, 2781 => \true, 2782 => \true, 2783 => \true, 2788 => \true, 2789 => \true, 2802 => \true, 2803 => \true, 2804 => \true, 2805 => \true, 2806 => \true, 2807 => \true, 2808 => \true, 2816 => \true, 2820 => \true, 2829 => \true, 2830 => \true, 2833 => \true, 2834 => \true, 2857 => \true, 2865 => \true, 2868 => \true, 2874 => \true, 2875 => \true, 2885 => \true, 2886 => \true, 2889 => \true, 2890 => \true, 2894 => \true, 2895 => \true, 2896 => \true, 2897 => \true, 2898 => \true, 2899 => \true, 2900 => \true, 2904 => \true, 2905 => \true, 2906 => \true, 2907 => \true, 2910 => \true, 2916 => \true, 2917 => \true, 2936 => \true, 2937 => \true, 2938 => \true, 2939 => \true, 2940 => \true, 2941 => \true, 2942 => \true, 2943 => \true, 2944 => \true, 2945 => \true, 2948 => \true, 2955 => \true, 2956 => \true, 2957 => \true, 2961 => \true, 2966 => \true, 2967 => \true, 2968 => \true, 2971 => \true, 2973 => \true, 2976 => \true, 2977 => \true, 2978 => \true, 2981 => \true, 2982 => \true, 2983 => \true, 2987 => \true, 2988 => \true, 2989 => \true, 3002 => \true, 3003 => \true, 3004 => \true, 3005 => \true, 3011 => \true, 3012 => \true, 3013 => \true, 3017 => \true, 3022 => \true, 3023 => \true, 3025 => \true, 3026 => \true, 3027 => \true, 3028 => \true, 3029 => \true, 3030 => \true, 3032 => \true, 3033 => \true, 3034 => \true, 3035 => \true, 3036 => \true, 3037 => \true, 3038 => \true, 3039 => \true, 3040 => \true, 3041 => \true, 3042 => \true, 3043 => \true, 3044 => \true, 3045 => \true, 3067 => \true, 3068 => \true, 3069 => \true, 3070 => \true, 3071 => \true, 3085 => \true, 3089 => \true, 3113 => \true, 3130 => \true, 3131 => \true, 3132 => \true, 3141 => \true, 3145 => \true, 3150 => \true, 3151 => \true, 3152 => \true, 3153 => \true, 3154 => \true, 3155 => \true, 3156 => \true, 3159 => \true, 3163 => \true, 3164 => \true, 3165 => \true, 3166 => \true, 3167 => \true, 3172 => \true, 3173 => \true, 3184 => \true, 3185 => \true, 3186 => \true, 3187 => \true, 3188 => \true, 3189 => \true, 3190 => \true, 3213 => \true, 3217 => \true, 3241 => \true, 3252 => \true, 3258 => \true, 3259 => \true, 3269 => \true, 3273 => \true, 3278 => \true, 3279 => \true, 3280 => \true, 3281 => \true, 3282 => \true, 3283 => \true, 3284 => \true, 3287 => \true, 3288 => \true, 3289 => \true, 3290 => \true, 3291 => \true, 3292 => \true, 3293 => \true, 3295 => \true, 3300 => \true, 3301 => \true, 3312 => \true, 3315 => \true, 3316 => \true, 3317 => \true, 3318 => \true, 3319 => \true, 3320 => \true, 3321 => \true, 3322 => \true, 3323 => \true, 3324 => \true, 3325 => \true, 3326 => \true, 3327 => \true, 3341 => \true, 3345 => \true, 3397 => \true, 3401 => \true, 3408 => \true, 3409 => \true, 3410 => \true, 3411 => \true, 3428 => \true, 3429 => \true, 3456 => \true, 3460 => \true, 3479 => \true, 3480 => \true, 3481 => \true, 3506 => \true, 3516 => \true, 3518 => \true, 3519 => \true, 3527 => \true, 3528 => \true, 3529 => \true, 3531 => \true, 3532 => \true, 3533 => \true, 3534 => \true, 3541 => \true, 3543 => \true, 3552 => \true, 3553 => \true, 3554 => \true, 3555 => \true, 3556 => \true, 3557 => \true, 3568 => \true, 3569 => \true, 3573 => \true, 3574 => \true, 3575 => \true, 3576 => \true, 3577 => \true, 3578 => \true, 3579 => \true, 3580 => \true, 3581 => \true, 3582 => \true, 3583 => \true, 3584 => \true, 3643 => \true, 3644 => \true, 3645 => \true, 3646 => \true, 3715 => \true, 3717 => \true, 3723 => \true, 3748 => \true, 3750 => \true, 3774 => \true, 3775 => \true, 3781 => \true, 3783 => \true, 3790 => \true, 3791 => \true, 3802 => \true, 3803 => \true, 3912 => \true, 3949 => \true, 3950 => \true, 3951 => \true, 3952 => \true, 3992 => \true, 4029 => \true, 4045 => \true, 4294 => \true, 4296 => \true, 4297 => \true, 4298 => \true, 4299 => \true, 4300 => \true, 4302 => \true, 4303 => \true, 4447 => \true, 4448 => \true, 4681 => \true, 4686 => \true, 4687 => \true, 4695 => \true, 4697 => \true, 4702 => \true, 4703 => \true, 4745 => \true, 4750 => \true, 4751 => \true, 4785 => \true, 4790 => \true, 4791 => \true, 4799 => \true, 4801 => \true, 4806 => \true, 4807 => \true, 4823 => \true, 4881 => \true, 4886 => \true, 4887 => \true, 4955 => \true, 4956 => \true, 4989 => \true, 4990 => \true, 4991 => \true, 5018 => \true, 5019 => \true, 5020 => \true, 5021 => \true, 5022 => \true, 5023 => \true, 5110 => \true, 5111 => \true, 5118 => \true, 5119 => \true, 5760 => \true, 5789 => \true, 5790 => \true, 5791 => \true, 5881 => \true, 5882 => \true, 5883 => \true, 5884 => \true, 5885 => \true, 5886 => \true, 5887 => \true, 5901 => \true, 5909 => \true, 5910 => \true, 5911 => \true, 5912 => \true, 5913 => \true, 5914 => \true, 5915 => \true, 5916 => \true, 5917 => \true, 5918 => \true, 5919 => \true, 5943 => \true, 5944 => \true, 5945 => \true, 5946 => \true, 5947 => \true, 5948 => \true, 5949 => \true, 5950 => \true, 5951 => \true, 5972 => \true, 5973 => \true, 5974 => \true, 5975 => \true, 5976 => \true, 5977 => \true, 5978 => \true, 5979 => \true, 5980 => \true, 5981 => \true, 5982 => \true, 5983 => \true, 5997 => \true, 6001 => \true, 6004 => \true, 6005 => \true, 6006 => \true, 6007 => \true, 6008 => \true, 6009 => \true, 6010 => \true, 6011 => \true, 6012 => \true, 6013 => \true, 6014 => \true, 6015 => \true, 6068 => \true, 6069 => \true, 6110 => \true, 6111 => \true, 6122 => \true, 6123 => \true, 6124 => \true, 6125 => \true, 6126 => \true, 6127 => \true, 6138 => \true, 6139 => \true, 6140 => \true, 6141 => \true, 6142 => \true, 6143 => \true, 6150 => \true, 6158 => \true, 6159 => \true, 6170 => \true, 6171 => \true, 6172 => \true, 6173 => \true, 6174 => \true, 6175 => \true, 6265 => \true, 6266 => \true, 6267 => \true, 6268 => \true, 6269 => \true, 6270 => \true, 6271 => \true, 6315 => \true, 6316 => \true, 6317 => \true, 6318 => \true, 6319 => \true, 6390 => \true, 6391 => \true, 6392 => \true, 6393 => \true, 6394 => \true, 6395 => \true, 6396 => \true, 6397 => \true, 6398 => \true, 6399 => \true, 6431 => \true, 6444 => \true, 6445 => \true, 6446 => \true, 6447 => \true, 6460 => \true, 6461 => \true, 6462 => \true, 6463 => \true, 6465 => \true, 6466 => \true, 6467 => \true, 6510 => \true, 6511 => \true, 6517 => \true, 6518 => \true, 6519 => \true, 6520 => \true, 6521 => \true, 6522 => \true, 6523 => \true, 6524 => \true, 6525 => \true, 6526 => \true, 6527 => \true, 6572 => \true, 6573 => \true, 6574 => \true, 6575 => \true, 6602 => \true, 6603 => \true, 6604 => \true, 6605 => \true, 6606 => \true, 6607 => \true, 6619 => \true, 6620 => \true, 6621 => \true, 6684 => \true, 6685 => \true, 6751 => \true, 6781 => \true, 6782 => \true, 6794 => \true, 6795 => \true, 6796 => \true, 6797 => \true, 6798 => \true, 6799 => \true, 6810 => \true, 6811 => \true, 6812 => \true, 6813 => \true, 6814 => \true, 6815 => \true, 6830 => \true, 6831 => \true, 6988 => \true, 6989 => \true, 6990 => \true, 6991 => \true, 7037 => \true, 7038 => \true, 7039 => \true, 7156 => \true, 7157 => \true, 7158 => \true, 7159 => \true, 7160 => \true, 7161 => \true, 7162 => \true, 7163 => \true, 7224 => \true, 7225 => \true, 7226 => \true, 7242 => \true, 7243 => \true, 7244 => \true, 7305 => \true, 7306 => \true, 7307 => \true, 7308 => \true, 7309 => \true, 7310 => \true, 7311 => \true, 7355 => \true, 7356 => \true, 7368 => \true, 7369 => \true, 7370 => \true, 7371 => \true, 7372 => \true, 7373 => \true, 7374 => \true, 7375 => \true, 7419 => \true, 7420 => \true, 7421 => \true, 7422 => \true, 7423 => \true, 7674 => \true, 7958 => \true, 7959 => \true, 7966 => \true, 7967 => \true, 8006 => \true, 8007 => \true, 8014 => \true, 8015 => \true, 8024 => \true, 8026 => \true, 8028 => \true, 8030 => \true, 8062 => \true, 8063 => \true, 8117 => \true, 8133 => \true, 8148 => \true, 8149 => \true, 8156 => \true, 8176 => \true, 8177 => \true, 8181 => \true, 8191 => \true, 8206 => \true, 8207 => \true, 8228 => \true, 8229 => \true, 8230 => \true, 8232 => \true, 8233 => \true, 8234 => \true, 8235 => \true, 8236 => \true, 8237 => \true, 8238 => \true, 8289 => \true, 8290 => \true, 8291 => \true, 8293 => \true, 8294 => \true, 8295 => \true, 8296 => \true, 8297 => \true, 8298 => \true, 8299 => \true, 8300 => \true, 8301 => \true, 8302 => \true, 8303 => \true, 8306 => \true, 8307 => \true, 8335 => \true, 8349 => \true, 8350 => \true, 8351 => \true, 8384 => \true, 8385 => \true, 8386 => \true, 8387 => \true, 8388 => \true, 8389 => \true, 8390 => \true, 8391 => \true, 8392 => \true, 8393 => \true, 8394 => \true, 8395 => \true, 8396 => \true, 8397 => \true, 8398 => \true, 8399 => \true, 8433 => \true, 8434 => \true, 8435 => \true, 8436 => \true, 8437 => \true, 8438 => \true, 8439 => \true, 8440 => \true, 8441 => \true, 8442 => \true, 8443 => \true, 8444 => \true, 8445 => \true, 8446 => \true, 8447 => \true, 8498 => \true, 8579 => \true, 8588 => \true, 8589 => \true, 8590 => \true, 8591 => \true, 9255 => \true, 9256 => \true, 9257 => \true, 9258 => \true, 9259 => \true, 9260 => \true, 9261 => \true, 9262 => \true, 9263 => \true, 9264 => \true, 9265 => \true, 9266 => \true, 9267 => \true, 9268 => \true, 9269 => \true, 9270 => \true, 9271 => \true, 9272 => \true, 9273 => \true, 9274 => \true, 9275 => \true, 9276 => \true, 9277 => \true, 9278 => \true, 9279 => \true, 9291 => \true, 9292 => \true, 9293 => \true, 9294 => \true, 9295 => \true, 9296 => \true, 9297 => \true, 9298 => \true, 9299 => \true, 9300 => \true, 9301 => \true, 9302 => \true, 9303 => \true, 9304 => \true, 9305 => \true, 9306 => \true, 9307 => \true, 9308 => \true, 9309 => \true, 9310 => \true, 9311 => \true, 9352 => \true, 9353 => \true, 9354 => \true, 9355 => \true, 9356 => \true, 9357 => \true, 9358 => \true, 9359 => \true, 9360 => \true, 9361 => \true, 9362 => \true, 9363 => \true, 9364 => \true, 9365 => \true, 9366 => \true, 9367 => \true, 9368 => \true, 9369 => \true, 9370 => \true, 9371 => \true, 11124 => \true, 11125 => \true, 11158 => \true, 11311 => \true, 11359 => \true, 11508 => \true, 11509 => \true, 11510 => \true, 11511 => \true, 11512 => \true, 11558 => \true, 11560 => \true, 11561 => \true, 11562 => \true, 11563 => \true, 11564 => \true, 11566 => \true, 11567 => \true, 11624 => \true, 11625 => \true, 11626 => \true, 11627 => \true, 11628 => \true, 11629 => \true, 11630 => \true, 11633 => \true, 11634 => \true, 11635 => \true, 11636 => \true, 11637 => \true, 11638 => \true, 11639 => \true, 11640 => \true, 11641 => \true, 11642 => \true, 11643 => \true, 11644 => \true, 11645 => \true, 11646 => \true, 11671 => \true, 11672 => \true, 11673 => \true, 11674 => \true, 11675 => \true, 11676 => \true, 11677 => \true, 11678 => \true, 11679 => \true, 11687 => \true, 11695 => \true, 11703 => \true, 11711 => \true, 11719 => \true, 11727 => \true, 11735 => \true, 11743 => \true, 11930 => \true, 12020 => \true, 12021 => \true, 12022 => \true, 12023 => \true, 12024 => \true, 12025 => \true, 12026 => \true, 12027 => \true, 12028 => \true, 12029 => \true, 12030 => \true, 12031 => \true, 12246 => \true, 12247 => \true, 12248 => \true, 12249 => \true, 12250 => \true, 12251 => \true, 12252 => \true, 12253 => \true, 12254 => \true, 12255 => \true, 12256 => \true, 12257 => \true, 12258 => \true, 12259 => \true, 12260 => \true, 12261 => \true, 12262 => \true, 12263 => \true, 12264 => \true, 12265 => \true, 12266 => \true, 12267 => \true, 12268 => \true, 12269 => \true, 12270 => \true, 12271 => \true, 12272 => \true, 12273 => \true, 12274 => \true, 12275 => \true, 12276 => \true, 12277 => \true, 12278 => \true, 12279 => \true, 12280 => \true, 12281 => \true, 12282 => \true, 12283 => \true, 12284 => \true, 12285 => \true, 12286 => \true, 12287 => \true, 12352 => \true, 12439 => \true, 12440 => \true, 12544 => \true, 12545 => \true, 12546 => \true, 12547 => \true, 12548 => \true, 12592 => \true, 12644 => \true, 12687 => \true, 12772 => \true, 12773 => \true, 12774 => \true, 12775 => \true, 12776 => \true, 12777 => \true, 12778 => \true, 12779 => \true, 12780 => \true, 12781 => \true, 12782 => \true, 12783 => \true, 12831 => \true, 13250 => \true, 13255 => \true, 13272 => \true, 40957 => \true, 40958 => \true, 40959 => \true, 42125 => \true, 42126 => \true, 42127 => \true, 42183 => \true, 42184 => \true, 42185 => \true, 42186 => \true, 42187 => \true, 42188 => \true, 42189 => \true, 42190 => \true, 42191 => \true, 42540 => \true, 42541 => \true, 42542 => \true, 42543 => \true, 42544 => \true, 42545 => \true, 42546 => \true, 42547 => \true, 42548 => \true, 42549 => \true, 42550 => \true, 42551 => \true, 42552 => \true, 42553 => \true, 42554 => \true, 42555 => \true, 42556 => \true, 42557 => \true, 42558 => \true, 42559 => \true, 42744 => \true, 42745 => \true, 42746 => \true, 42747 => \true, 42748 => \true, 42749 => \true, 42750 => \true, 42751 => \true, 42944 => \true, 42945 => \true, 43053 => \true, 43054 => \true, 43055 => \true, 43066 => \true, 43067 => \true, 43068 => \true, 43069 => \true, 43070 => \true, 43071 => \true, 43128 => \true, 43129 => \true, 43130 => \true, 43131 => \true, 43132 => \true, 43133 => \true, 43134 => \true, 43135 => \true, 43206 => \true, 43207 => \true, 43208 => \true, 43209 => \true, 43210 => \true, 43211 => \true, 43212 => \true, 43213 => \true, 43226 => \true, 43227 => \true, 43228 => \true, 43229 => \true, 43230 => \true, 43231 => \true, 43348 => \true, 43349 => \true, 43350 => \true, 43351 => \true, 43352 => \true, 43353 => \true, 43354 => \true, 43355 => \true, 43356 => \true, 43357 => \true, 43358 => \true, 43389 => \true, 43390 => \true, 43391 => \true, 43470 => \true, 43482 => \true, 43483 => \true, 43484 => \true, 43485 => \true, 43519 => \true, 43575 => \true, 43576 => \true, 43577 => \true, 43578 => \true, 43579 => \true, 43580 => \true, 43581 => \true, 43582 => \true, 43583 => \true, 43598 => \true, 43599 => \true, 43610 => \true, 43611 => \true, 43715 => \true, 43716 => \true, 43717 => \true, 43718 => \true, 43719 => \true, 43720 => \true, 43721 => \true, 43722 => \true, 43723 => \true, 43724 => \true, 43725 => \true, 43726 => \true, 43727 => \true, 43728 => \true, 43729 => \true, 43730 => \true, 43731 => \true, 43732 => \true, 43733 => \true, 43734 => \true, 43735 => \true, 43736 => \true, 43737 => \true, 43738 => \true, 43767 => \true, 43768 => \true, 43769 => \true, 43770 => \true, 43771 => \true, 43772 => \true, 43773 => \true, 43774 => \true, 43775 => \true, 43776 => \true, 43783 => \true, 43784 => \true, 43791 => \true, 43792 => \true, 43799 => \true, 43800 => \true, 43801 => \true, 43802 => \true, 43803 => \true, 43804 => \true, 43805 => \true, 43806 => \true, 43807 => \true, 43815 => \true, 43823 => \true, 43884 => \true, 43885 => \true, 43886 => \true, 43887 => \true, 44014 => \true, 44015 => \true, 44026 => \true, 44027 => \true, 44028 => \true, 44029 => \true, 44030 => \true, 44031 => \true, 55204 => \true, 55205 => \true, 55206 => \true, 55207 => \true, 55208 => \true, 55209 => \true, 55210 => \true, 55211 => \true, 55212 => \true, 55213 => \true, 55214 => \true, 55215 => \true, 55239 => \true, 55240 => \true, 55241 => \true, 55242 => \true, 55292 => \true, 55293 => \true, 55294 => \true, 55295 => \true, 64110 => \true, 64111 => \true, 64263 => \true, 64264 => \true, 64265 => \true, 64266 => \true, 64267 => \true, 64268 => \true, 64269 => \true, 64270 => \true, 64271 => \true, 64272 => \true, 64273 => \true, 64274 => \true, 64280 => \true, 64281 => \true, 64282 => \true, 64283 => \true, 64284 => \true, 64311 => \true, 64317 => \true, 64319 => \true, 64322 => \true, 64325 => \true, 64450 => \true, 64451 => \true, 64452 => \true, 64453 => \true, 64454 => \true, 64455 => \true, 64456 => \true, 64457 => \true, 64458 => \true, 64459 => \true, 64460 => \true, 64461 => \true, 64462 => \true, 64463 => \true, 64464 => \true, 64465 => \true, 64466 => \true, 64832 => \true, 64833 => \true, 64834 => \true, 64835 => \true, 64836 => \true, 64837 => \true, 64838 => \true, 64839 => \true, 64840 => \true, 64841 => \true, 64842 => \true, 64843 => \true, 64844 => \true, 64845 => \true, 64846 => \true, 64847 => \true, 64912 => \true, 64913 => \true, 64968 => \true, 64969 => \true, 64970 => \true, 64971 => \true, 64972 => \true, 64973 => \true, 64974 => \true, 64975 => \true, 65022 => \true, 65023 => \true, 65042 => \true, 65049 => \true, 65050 => \true, 65051 => \true, 65052 => \true, 65053 => \true, 65054 => \true, 65055 => \true, 65072 => \true, 65106 => \true, 65107 => \true, 65127 => \true, 65132 => \true, 65133 => \true, 65134 => \true, 65135 => \true, 65141 => \true, 65277 => \true, 65278 => \true, 65280 => \true, 65440 => \true, 65471 => \true, 65472 => \true, 65473 => \true, 65480 => \true, 65481 => \true, 65488 => \true, 65489 => \true, 65496 => \true, 65497 => \true, 65501 => \true, 65502 => \true, 65503 => \true, 65511 => \true, 65519 => \true, 65520 => \true, 65521 => \true, 65522 => \true, 65523 => \true, 65524 => \true, 65525 => \true, 65526 => \true, 65527 => \true, 65528 => \true, 65529 => \true, 65530 => \true, 65531 => \true, 65532 => \true, 65533 => \true, 65534 => \true, 65535 => \true, 65548 => \true, 65575 => \true, 65595 => \true, 65598 => \true, 65614 => \true, 65615 => \true, 65787 => \true, 65788 => \true, 65789 => \true, 65790 => \true, 65791 => \true, 65795 => \true, 65796 => \true, 65797 => \true, 65798 => \true, 65844 => \true, 65845 => \true, 65846 => \true, 65935 => \true, 65949 => \true, 65950 => \true, 65951 => \true, 66205 => \true, 66206 => \true, 66207 => \true, 66257 => \true, 66258 => \true, 66259 => \true, 66260 => \true, 66261 => \true, 66262 => \true, 66263 => \true, 66264 => \true, 66265 => \true, 66266 => \true, 66267 => \true, 66268 => \true, 66269 => \true, 66270 => \true, 66271 => \true, 66300 => \true, 66301 => \true, 66302 => \true, 66303 => \true, 66340 => \true, 66341 => \true, 66342 => \true, 66343 => \true, 66344 => \true, 66345 => \true, 66346 => \true, 66347 => \true, 66348 => \true, 66379 => \true, 66380 => \true, 66381 => \true, 66382 => \true, 66383 => \true, 66427 => \true, 66428 => \true, 66429 => \true, 66430 => \true, 66431 => \true, 66462 => \true, 66500 => \true, 66501 => \true, 66502 => \true, 66503 => \true, 66718 => \true, 66719 => \true, 66730 => \true, 66731 => \true, 66732 => \true, 66733 => \true, 66734 => \true, 66735 => \true, 66772 => \true, 66773 => \true, 66774 => \true, 66775 => \true, 66812 => \true, 66813 => \true, 66814 => \true, 66815 => \true, 66856 => \true, 66857 => \true, 66858 => \true, 66859 => \true, 66860 => \true, 66861 => \true, 66862 => \true, 66863 => \true, 66916 => \true, 66917 => \true, 66918 => \true, 66919 => \true, 66920 => \true, 66921 => \true, 66922 => \true, 66923 => \true, 66924 => \true, 66925 => \true, 66926 => \true, 67383 => \true, 67384 => \true, 67385 => \true, 67386 => \true, 67387 => \true, 67388 => \true, 67389 => \true, 67390 => \true, 67391 => \true, 67414 => \true, 67415 => \true, 67416 => \true, 67417 => \true, 67418 => \true, 67419 => \true, 67420 => \true, 67421 => \true, 67422 => \true, 67423 => \true, 67590 => \true, 67591 => \true, 67593 => \true, 67638 => \true, 67641 => \true, 67642 => \true, 67643 => \true, 67645 => \true, 67646 => \true, 67670 => \true, 67743 => \true, 67744 => \true, 67745 => \true, 67746 => \true, 67747 => \true, 67748 => \true, 67749 => \true, 67750 => \true, 67827 => \true, 67830 => \true, 67831 => \true, 67832 => \true, 67833 => \true, 67834 => \true, 67868 => \true, 67869 => \true, 67870 => \true, 67898 => \true, 67899 => \true, 67900 => \true, 67901 => \true, 67902 => \true, 68024 => \true, 68025 => \true, 68026 => \true, 68027 => \true, 68048 => \true, 68049 => \true, 68100 => \true, 68103 => \true, 68104 => \true, 68105 => \true, 68106 => \true, 68107 => \true, 68116 => \true, 68120 => \true, 68150 => \true, 68151 => \true, 68155 => \true, 68156 => \true, 68157 => \true, 68158 => \true, 68169 => \true, 68170 => \true, 68171 => \true, 68172 => \true, 68173 => \true, 68174 => \true, 68175 => \true, 68185 => \true, 68186 => \true, 68187 => \true, 68188 => \true, 68189 => \true, 68190 => \true, 68191 => \true, 68327 => \true, 68328 => \true, 68329 => \true, 68330 => \true, 68343 => \true, 68344 => \true, 68345 => \true, 68346 => \true, 68347 => \true, 68348 => \true, 68349 => \true, 68350 => \true, 68351 => \true, 68406 => \true, 68407 => \true, 68408 => \true, 68438 => \true, 68439 => \true, 68467 => \true, 68468 => \true, 68469 => \true, 68470 => \true, 68471 => \true, 68498 => \true, 68499 => \true, 68500 => \true, 68501 => \true, 68502 => \true, 68503 => \true, 68504 => \true, 68509 => \true, 68510 => \true, 68511 => \true, 68512 => \true, 68513 => \true, 68514 => \true, 68515 => \true, 68516 => \true, 68517 => \true, 68518 => \true, 68519 => \true, 68520 => \true, 68787 => \true, 68788 => \true, 68789 => \true, 68790 => \true, 68791 => \true, 68792 => \true, 68793 => \true, 68794 => \true, 68795 => \true, 68796 => \true, 68797 => \true, 68798 => \true, 68799 => \true, 68851 => \true, 68852 => \true, 68853 => \true, 68854 => \true, 68855 => \true, 68856 => \true, 68857 => \true, 68904 => \true, 68905 => \true, 68906 => \true, 68907 => \true, 68908 => \true, 68909 => \true, 68910 => \true, 68911 => \true, 69247 => \true, 69290 => \true, 69294 => \true, 69295 => \true, 69416 => \true, 69417 => \true, 69418 => \true, 69419 => \true, 69420 => \true, 69421 => \true, 69422 => \true, 69423 => \true, 69580 => \true, 69581 => \true, 69582 => \true, 69583 => \true, 69584 => \true, 69585 => \true, 69586 => \true, 69587 => \true, 69588 => \true, 69589 => \true, 69590 => \true, 69591 => \true, 69592 => \true, 69593 => \true, 69594 => \true, 69595 => \true, 69596 => \true, 69597 => \true, 69598 => \true, 69599 => \true, 69623 => \true, 69624 => \true, 69625 => \true, 69626 => \true, 69627 => \true, 69628 => \true, 69629 => \true, 69630 => \true, 69631 => \true, 69710 => \true, 69711 => \true, 69712 => \true, 69713 => \true, 69744 => \true, 69745 => \true, 69746 => \true, 69747 => \true, 69748 => \true, 69749 => \true, 69750 => \true, 69751 => \true, 69752 => \true, 69753 => \true, 69754 => \true, 69755 => \true, 69756 => \true, 69757 => \true, 69758 => \true, 69821 => \true, 69826 => \true, 69827 => \true, 69828 => \true, 69829 => \true, 69830 => \true, 69831 => \true, 69832 => \true, 69833 => \true, 69834 => \true, 69835 => \true, 69836 => \true, 69837 => \true, 69838 => \true, 69839 => \true, 69865 => \true, 69866 => \true, 69867 => \true, 69868 => \true, 69869 => \true, 69870 => \true, 69871 => \true, 69882 => \true, 69883 => \true, 69884 => \true, 69885 => \true, 69886 => \true, 69887 => \true, 69941 => \true, 69960 => \true, 69961 => \true, 69962 => \true, 69963 => \true, 69964 => \true, 69965 => \true, 69966 => \true, 69967 => \true, 70007 => \true, 70008 => \true, 70009 => \true, 70010 => \true, 70011 => \true, 70012 => \true, 70013 => \true, 70014 => \true, 70015 => \true, 70112 => \true, 70133 => \true, 70134 => \true, 70135 => \true, 70136 => \true, 70137 => \true, 70138 => \true, 70139 => \true, 70140 => \true, 70141 => \true, 70142 => \true, 70143 => \true, 70162 => \true, 70279 => \true, 70281 => \true, 70286 => \true, 70302 => \true, 70314 => \true, 70315 => \true, 70316 => \true, 70317 => \true, 70318 => \true, 70319 => \true, 70379 => \true, 70380 => \true, 70381 => \true, 70382 => \true, 70383 => \true, 70394 => \true, 70395 => \true, 70396 => \true, 70397 => \true, 70398 => \true, 70399 => \true, 70404 => \true, 70413 => \true, 70414 => \true, 70417 => \true, 70418 => \true, 70441 => \true, 70449 => \true, 70452 => \true, 70458 => \true, 70469 => \true, 70470 => \true, 70473 => \true, 70474 => \true, 70478 => \true, 70479 => \true, 70481 => \true, 70482 => \true, 70483 => \true, 70484 => \true, 70485 => \true, 70486 => \true, 70488 => \true, 70489 => \true, 70490 => \true, 70491 => \true, 70492 => \true, 70500 => \true, 70501 => \true, 70509 => \true, 70510 => \true, 70511 => \true, 70748 => \true, 70754 => \true, 70755 => \true, 70756 => \true, 70757 => \true, 70758 => \true, 70759 => \true, 70760 => \true, 70761 => \true, 70762 => \true, 70763 => \true, 70764 => \true, 70765 => \true, 70766 => \true, 70767 => \true, 70768 => \true, 70769 => \true, 70770 => \true, 70771 => \true, 70772 => \true, 70773 => \true, 70774 => \true, 70775 => \true, 70776 => \true, 70777 => \true, 70778 => \true, 70779 => \true, 70780 => \true, 70781 => \true, 70782 => \true, 70783 => \true, 70856 => \true, 70857 => \true, 70858 => \true, 70859 => \true, 70860 => \true, 70861 => \true, 70862 => \true, 70863 => \true, 71094 => \true, 71095 => \true, 71237 => \true, 71238 => \true, 71239 => \true, 71240 => \true, 71241 => \true, 71242 => \true, 71243 => \true, 71244 => \true, 71245 => \true, 71246 => \true, 71247 => \true, 71258 => \true, 71259 => \true, 71260 => \true, 71261 => \true, 71262 => \true, 71263 => \true, 71277 => \true, 71278 => \true, 71279 => \true, 71280 => \true, 71281 => \true, 71282 => \true, 71283 => \true, 71284 => \true, 71285 => \true, 71286 => \true, 71287 => \true, 71288 => \true, 71289 => \true, 71290 => \true, 71291 => \true, 71292 => \true, 71293 => \true, 71294 => \true, 71295 => \true, 71353 => \true, 71354 => \true, 71355 => \true, 71356 => \true, 71357 => \true, 71358 => \true, 71359 => \true, 71451 => \true, 71452 => \true, 71468 => \true, 71469 => \true, 71470 => \true, 71471 => \true, 71923 => \true, 71924 => \true, 71925 => \true, 71926 => \true, 71927 => \true, 71928 => \true, 71929 => \true, 71930 => \true, 71931 => \true, 71932 => \true, 71933 => \true, 71934 => \true, 71943 => \true, 71944 => \true, 71946 => \true, 71947 => \true, 71956 => \true, 71959 => \true, 71990 => \true, 71993 => \true, 71994 => \true, 72007 => \true, 72008 => \true, 72009 => \true, 72010 => \true, 72011 => \true, 72012 => \true, 72013 => \true, 72014 => \true, 72015 => \true, 72104 => \true, 72105 => \true, 72152 => \true, 72153 => \true, 72165 => \true, 72166 => \true, 72167 => \true, 72168 => \true, 72169 => \true, 72170 => \true, 72171 => \true, 72172 => \true, 72173 => \true, 72174 => \true, 72175 => \true, 72176 => \true, 72177 => \true, 72178 => \true, 72179 => \true, 72180 => \true, 72181 => \true, 72182 => \true, 72183 => \true, 72184 => \true, 72185 => \true, 72186 => \true, 72187 => \true, 72188 => \true, 72189 => \true, 72190 => \true, 72191 => \true, 72264 => \true, 72265 => \true, 72266 => \true, 72267 => \true, 72268 => \true, 72269 => \true, 72270 => \true, 72271 => \true, 72355 => \true, 72356 => \true, 72357 => \true, 72358 => \true, 72359 => \true, 72360 => \true, 72361 => \true, 72362 => \true, 72363 => \true, 72364 => \true, 72365 => \true, 72366 => \true, 72367 => \true, 72368 => \true, 72369 => \true, 72370 => \true, 72371 => \true, 72372 => \true, 72373 => \true, 72374 => \true, 72375 => \true, 72376 => \true, 72377 => \true, 72378 => \true, 72379 => \true, 72380 => \true, 72381 => \true, 72382 => \true, 72383 => \true, 72713 => \true, 72759 => \true, 72774 => \true, 72775 => \true, 72776 => \true, 72777 => \true, 72778 => \true, 72779 => \true, 72780 => \true, 72781 => \true, 72782 => \true, 72783 => \true, 72813 => \true, 72814 => \true, 72815 => \true, 72848 => \true, 72849 => \true, 72872 => \true, 72967 => \true, 72970 => \true, 73015 => \true, 73016 => \true, 73017 => \true, 73019 => \true, 73022 => \true, 73032 => \true, 73033 => \true, 73034 => \true, 73035 => \true, 73036 => \true, 73037 => \true, 73038 => \true, 73039 => \true, 73050 => \true, 73051 => \true, 73052 => \true, 73053 => \true, 73054 => \true, 73055 => \true, 73062 => \true, 73065 => \true, 73103 => \true, 73106 => \true, 73113 => \true, 73114 => \true, 73115 => \true, 73116 => \true, 73117 => \true, 73118 => \true, 73119 => \true, 73649 => \true, 73650 => \true, 73651 => \true, 73652 => \true, 73653 => \true, 73654 => \true, 73655 => \true, 73656 => \true, 73657 => \true, 73658 => \true, 73659 => \true, 73660 => \true, 73661 => \true, 73662 => \true, 73663 => \true, 73714 => \true, 73715 => \true, 73716 => \true, 73717 => \true, 73718 => \true, 73719 => \true, 73720 => \true, 73721 => \true, 73722 => \true, 73723 => \true, 73724 => \true, 73725 => \true, 73726 => \true, 74863 => \true, 74869 => \true, 74870 => \true, 74871 => \true, 74872 => \true, 74873 => \true, 74874 => \true, 74875 => \true, 74876 => \true, 74877 => \true, 74878 => \true, 74879 => \true, 78895 => \true, 78896 => \true, 78897 => \true, 78898 => \true, 78899 => \true, 78900 => \true, 78901 => \true, 78902 => \true, 78903 => \true, 78904 => \true, 92729 => \true, 92730 => \true, 92731 => \true, 92732 => \true, 92733 => \true, 92734 => \true, 92735 => \true, 92767 => \true, 92778 => \true, 92779 => \true, 92780 => \true, 92781 => \true, 92910 => \true, 92911 => \true, 92918 => \true, 92919 => \true, 92920 => \true, 92921 => \true, 92922 => \true, 92923 => \true, 92924 => \true, 92925 => \true, 92926 => \true, 92927 => \true, 92998 => \true, 92999 => \true, 93000 => \true, 93001 => \true, 93002 => \true, 93003 => \true, 93004 => \true, 93005 => \true, 93006 => \true, 93007 => \true, 93018 => \true, 93026 => \true, 93048 => \true, 93049 => \true, 93050 => \true, 93051 => \true, 93052 => \true, 94027 => \true, 94028 => \true, 94029 => \true, 94030 => \true, 94088 => \true, 94089 => \true, 94090 => \true, 94091 => \true, 94092 => \true, 94093 => \true, 94094 => \true, 94181 => \true, 94182 => \true, 94183 => \true, 94184 => \true, 94185 => \true, 94186 => \true, 94187 => \true, 94188 => \true, 94189 => \true, 94190 => \true, 94191 => \true, 94194 => \true, 94195 => \true, 94196 => \true, 94197 => \true, 94198 => \true, 94199 => \true, 94200 => \true, 94201 => \true, 94202 => \true, 94203 => \true, 94204 => \true, 94205 => \true, 94206 => \true, 94207 => \true, 100344 => \true, 100345 => \true, 100346 => \true, 100347 => \true, 100348 => \true, 100349 => \true, 100350 => \true, 100351 => \true, 110931 => \true, 110932 => \true, 110933 => \true, 110934 => \true, 110935 => \true, 110936 => \true, 110937 => \true, 110938 => \true, 110939 => \true, 110940 => \true, 110941 => \true, 110942 => \true, 110943 => \true, 110944 => \true, 110945 => \true, 110946 => \true, 110947 => \true, 110952 => \true, 110953 => \true, 110954 => \true, 110955 => \true, 110956 => \true, 110957 => \true, 110958 => \true, 110959 => \true, 113771 => \true, 113772 => \true, 113773 => \true, 113774 => \true, 113775 => \true, 113789 => \true, 113790 => \true, 113791 => \true, 113801 => \true, 113802 => \true, 113803 => \true, 113804 => \true, 113805 => \true, 113806 => \true, 113807 => \true, 113818 => \true, 113819 => \true, 119030 => \true, 119031 => \true, 119032 => \true, 119033 => \true, 119034 => \true, 119035 => \true, 119036 => \true, 119037 => \true, 119038 => \true, 119039 => \true, 119079 => \true, 119080 => \true, 119155 => \true, 119156 => \true, 119157 => \true, 119158 => \true, 119159 => \true, 119160 => \true, 119161 => \true, 119162 => \true, 119273 => \true, 119274 => \true, 119275 => \true, 119276 => \true, 119277 => \true, 119278 => \true, 119279 => \true, 119280 => \true, 119281 => \true, 119282 => \true, 119283 => \true, 119284 => \true, 119285 => \true, 119286 => \true, 119287 => \true, 119288 => \true, 119289 => \true, 119290 => \true, 119291 => \true, 119292 => \true, 119293 => \true, 119294 => \true, 119295 => \true, 119540 => \true, 119541 => \true, 119542 => \true, 119543 => \true, 119544 => \true, 119545 => \true, 119546 => \true, 119547 => \true, 119548 => \true, 119549 => \true, 119550 => \true, 119551 => \true, 119639 => \true, 119640 => \true, 119641 => \true, 119642 => \true, 119643 => \true, 119644 => \true, 119645 => \true, 119646 => \true, 119647 => \true, 119893 => \true, 119965 => \true, 119968 => \true, 119969 => \true, 119971 => \true, 119972 => \true, 119975 => \true, 119976 => \true, 119981 => \true, 119994 => \true, 119996 => \true, 120004 => \true, 120070 => \true, 120075 => \true, 120076 => \true, 120085 => \true, 120093 => \true, 120122 => \true, 120127 => \true, 120133 => \true, 120135 => \true, 120136 => \true, 120137 => \true, 120145 => \true, 120486 => \true, 120487 => \true, 120780 => \true, 120781 => \true, 121484 => \true, 121485 => \true, 121486 => \true, 121487 => \true, 121488 => \true, 121489 => \true, 121490 => \true, 121491 => \true, 121492 => \true, 121493 => \true, 121494 => \true, 121495 => \true, 121496 => \true, 121497 => \true, 121498 => \true, 121504 => \true, 122887 => \true, 122905 => \true, 122906 => \true, 122914 => \true, 122917 => \true, 123181 => \true, 123182 => \true, 123183 => \true, 123198 => \true, 123199 => \true, 123210 => \true, 123211 => \true, 123212 => \true, 123213 => \true, 123642 => \true, 123643 => \true, 123644 => \true, 123645 => \true, 123646 => \true, 125125 => \true, 125126 => \true, 125260 => \true, 125261 => \true, 125262 => \true, 125263 => \true, 125274 => \true, 125275 => \true, 125276 => \true, 125277 => \true, 126468 => \true, 126496 => \true, 126499 => \true, 126501 => \true, 126502 => \true, 126504 => \true, 126515 => \true, 126520 => \true, 126522 => \true, 126524 => \true, 126525 => \true, 126526 => \true, 126527 => \true, 126528 => \true, 126529 => \true, 126531 => \true, 126532 => \true, 126533 => \true, 126534 => \true, 126536 => \true, 126538 => \true, 126540 => \true, 126544 => \true, 126547 => \true, 126549 => \true, 126550 => \true, 126552 => \true, 126554 => \true, 126556 => \true, 126558 => \true, 126560 => \true, 126563 => \true, 126565 => \true, 126566 => \true, 126571 => \true, 126579 => \true, 126584 => \true, 126589 => \true, 126591 => \true, 126602 => \true, 126620 => \true, 126621 => \true, 126622 => \true, 126623 => \true, 126624 => \true, 126628 => \true, 126634 => \true, 127020 => \true, 127021 => \true, 127022 => \true, 127023 => \true, 127124 => \true, 127125 => \true, 127126 => \true, 127127 => \true, 127128 => \true, 127129 => \true, 127130 => \true, 127131 => \true, 127132 => \true, 127133 => \true, 127134 => \true, 127135 => \true, 127151 => \true, 127152 => \true, 127168 => \true, 127184 => \true, 127222 => \true, 127223 => \true, 127224 => \true, 127225 => \true, 127226 => \true, 127227 => \true, 127228 => \true, 127229 => \true, 127230 => \true, 127231 => \true, 127232 => \true, 127491 => \true, 127492 => \true, 127493 => \true, 127494 => \true, 127495 => \true, 127496 => \true, 127497 => \true, 127498 => \true, 127499 => \true, 127500 => \true, 127501 => \true, 127502 => \true, 127503 => \true, 127548 => \true, 127549 => \true, 127550 => \true, 127551 => \true, 127561 => \true, 127562 => \true, 127563 => \true, 127564 => \true, 127565 => \true, 127566 => \true, 127567 => \true, 127570 => \true, 127571 => \true, 127572 => \true, 127573 => \true, 127574 => \true, 127575 => \true, 127576 => \true, 127577 => \true, 127578 => \true, 127579 => \true, 127580 => \true, 127581 => \true, 127582 => \true, 127583 => \true, 128728 => \true, 128729 => \true, 128730 => \true, 128731 => \true, 128732 => \true, 128733 => \true, 128734 => \true, 128735 => \true, 128749 => \true, 128750 => \true, 128751 => \true, 128765 => \true, 128766 => \true, 128767 => \true, 128884 => \true, 128885 => \true, 128886 => \true, 128887 => \true, 128888 => \true, 128889 => \true, 128890 => \true, 128891 => \true, 128892 => \true, 128893 => \true, 128894 => \true, 128895 => \true, 128985 => \true, 128986 => \true, 128987 => \true, 128988 => \true, 128989 => \true, 128990 => \true, 128991 => \true, 129004 => \true, 129005 => \true, 129006 => \true, 129007 => \true, 129008 => \true, 129009 => \true, 129010 => \true, 129011 => \true, 129012 => \true, 129013 => \true, 129014 => \true, 129015 => \true, 129016 => \true, 129017 => \true, 129018 => \true, 129019 => \true, 129020 => \true, 129021 => \true, 129022 => \true, 129023 => \true, 129036 => \true, 129037 => \true, 129038 => \true, 129039 => \true, 129096 => \true, 129097 => \true, 129098 => \true, 129099 => \true, 129100 => \true, 129101 => \true, 129102 => \true, 129103 => \true, 129114 => \true, 129115 => \true, 129116 => \true, 129117 => \true, 129118 => \true, 129119 => \true, 129160 => \true, 129161 => \true, 129162 => \true, 129163 => \true, 129164 => \true, 129165 => \true, 129166 => \true, 129167 => \true, 129198 => \true, 129199 => \true, 129401 => \true, 129484 => \true, 129620 => \true, 129621 => \true, 129622 => \true, 129623 => \true, 129624 => \true, 129625 => \true, 129626 => \true, 129627 => \true, 129628 => \true, 129629 => \true, 129630 => \true, 129631 => \true, 129646 => \true, 129647 => \true, 129653 => \true, 129654 => \true, 129655 => \true, 129659 => \true, 129660 => \true, 129661 => \true, 129662 => \true, 129663 => \true, 129671 => \true, 129672 => \true, 129673 => \true, 129674 => \true, 129675 => \true, 129676 => \true, 129677 => \true, 129678 => \true, 129679 => \true, 129705 => \true, 129706 => \true, 129707 => \true, 129708 => \true, 129709 => \true, 129710 => \true, 129711 => \true, 129719 => \true, 129720 => \true, 129721 => \true, 129722 => \true, 129723 => \true, 129724 => \true, 129725 => \true, 129726 => \true, 129727 => \true, 129731 => \true, 129732 => \true, 129733 => \true, 129734 => \true, 129735 => \true, 129736 => \true, 129737 => \true, 129738 => \true, 129739 => \true, 129740 => \true, 129741 => \true, 129742 => \true, 129743 => \true, 129939 => \true, 131070 => \true, 131071 => \true, 177973 => \true, 177974 => \true, 177975 => \true, 177976 => \true, 177977 => \true, 177978 => \true, 177979 => \true, 177980 => \true, 177981 => \true, 177982 => \true, 177983 => \true, 178206 => \true, 178207 => \true, 183970 => \true, 183971 => \true, 183972 => \true, 183973 => \true, 183974 => \true, 183975 => \true, 183976 => \true, 183977 => \true, 183978 => \true, 183979 => \true, 183980 => \true, 183981 => \true, 183982 => \true, 183983 => \true, 194664 => \true, 194676 => \true, 194847 => \true, 194911 => \true, 195007 => \true, 196606 => \true, 196607 => \true, 262142 => \true, 262143 => \true, 327678 => \true, 327679 => \true, 393214 => \true, 393215 => \true, 458750 => \true, 458751 => \true, 524286 => \true, 524287 => \true, 589822 => \true, 589823 => \true, 655358 => \true, 655359 => \true, 720894 => \true, 720895 => \true, 786430 => \true, 786431 => \true, 851966 => \true, 851967 => \true, 917502 => \true, 917503 => \true, 917504 => \true, 917505 => \true, 917506 => \true, 917507 => \true, 917508 => \true, 917509 => \true, 917510 => \true, 917511 => \true, 917512 => \true, 917513 => \true, 917514 => \true, 917515 => \true, 917516 => \true, 917517 => \true, 917518 => \true, 917519 => \true, 917520 => \true, 917521 => \true, 917522 => \true, 917523 => \true, 917524 => \true, 917525 => \true, 917526 => \true, 917527 => \true, 917528 => \true, 917529 => \true, 917530 => \true, 917531 => \true, 917532 => \true, 917533 => \true, 917534 => \true, 917535 => \true, 983038 => \true, 983039 => \true, 1048574 => \true, 1048575 => \true, 1114110 => \true, 1114111 => \true); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php new file mode 100644 index 00000000..ac638a98 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_mapped.php @@ -0,0 +1,5 @@ + ' ', 168 => ' ̈', 175 => ' ̄', 180 => ' ́', 184 => ' ̧', 728 => ' ̆', 729 => ' ̇', 730 => ' ̊', 731 => ' ̨', 732 => ' ̃', 733 => ' ̋', 890 => ' ι', 894 => ';', 900 => ' ́', 901 => ' ̈́', 8125 => ' ̓', 8127 => ' ̓', 8128 => ' ͂', 8129 => ' ̈͂', 8141 => ' ̓̀', 8142 => ' ̓́', 8143 => ' ̓͂', 8157 => ' ̔̀', 8158 => ' ̔́', 8159 => ' ̔͂', 8173 => ' ̈̀', 8174 => ' ̈́', 8175 => '`', 8189 => ' ́', 8190 => ' ̔', 8192 => ' ', 8193 => ' ', 8194 => ' ', 8195 => ' ', 8196 => ' ', 8197 => ' ', 8198 => ' ', 8199 => ' ', 8200 => ' ', 8201 => ' ', 8202 => ' ', 8215 => ' ̳', 8239 => ' ', 8252 => '!!', 8254 => ' ̅', 8263 => '??', 8264 => '?!', 8265 => '!?', 8287 => ' ', 8314 => '+', 8316 => '=', 8317 => '(', 8318 => ')', 8330 => '+', 8332 => '=', 8333 => '(', 8334 => ')', 8448 => 'a/c', 8449 => 'a/s', 8453 => 'c/o', 8454 => 'c/u', 9332 => '(1)', 9333 => '(2)', 9334 => '(3)', 9335 => '(4)', 9336 => '(5)', 9337 => '(6)', 9338 => '(7)', 9339 => '(8)', 9340 => '(9)', 9341 => '(10)', 9342 => '(11)', 9343 => '(12)', 9344 => '(13)', 9345 => '(14)', 9346 => '(15)', 9347 => '(16)', 9348 => '(17)', 9349 => '(18)', 9350 => '(19)', 9351 => '(20)', 9372 => '(a)', 9373 => '(b)', 9374 => '(c)', 9375 => '(d)', 9376 => '(e)', 9377 => '(f)', 9378 => '(g)', 9379 => '(h)', 9380 => '(i)', 9381 => '(j)', 9382 => '(k)', 9383 => '(l)', 9384 => '(m)', 9385 => '(n)', 9386 => '(o)', 9387 => '(p)', 9388 => '(q)', 9389 => '(r)', 9390 => '(s)', 9391 => '(t)', 9392 => '(u)', 9393 => '(v)', 9394 => '(w)', 9395 => '(x)', 9396 => '(y)', 9397 => '(z)', 10868 => '::=', 10869 => '==', 10870 => '===', 12288 => ' ', 12443 => ' ゙', 12444 => ' ゚', 12800 => '(ᄀ)', 12801 => '(ᄂ)', 12802 => '(ᄃ)', 12803 => '(ᄅ)', 12804 => '(ᄆ)', 12805 => '(ᄇ)', 12806 => '(ᄉ)', 12807 => '(ᄋ)', 12808 => '(ᄌ)', 12809 => '(ᄎ)', 12810 => '(ᄏ)', 12811 => '(ᄐ)', 12812 => '(ᄑ)', 12813 => '(ᄒ)', 12814 => '(가)', 12815 => '(나)', 12816 => '(다)', 12817 => '(라)', 12818 => '(마)', 12819 => '(바)', 12820 => '(사)', 12821 => '(아)', 12822 => '(자)', 12823 => '(차)', 12824 => '(카)', 12825 => '(타)', 12826 => '(파)', 12827 => '(하)', 12828 => '(주)', 12829 => '(오전)', 12830 => '(오후)', 12832 => '(一)', 12833 => '(二)', 12834 => '(三)', 12835 => '(四)', 12836 => '(五)', 12837 => '(六)', 12838 => '(七)', 12839 => '(八)', 12840 => '(九)', 12841 => '(十)', 12842 => '(月)', 12843 => '(火)', 12844 => '(水)', 12845 => '(木)', 12846 => '(金)', 12847 => '(土)', 12848 => '(日)', 12849 => '(株)', 12850 => '(有)', 12851 => '(社)', 12852 => '(名)', 12853 => '(特)', 12854 => '(財)', 12855 => '(祝)', 12856 => '(労)', 12857 => '(代)', 12858 => '(呼)', 12859 => '(学)', 12860 => '(監)', 12861 => '(企)', 12862 => '(資)', 12863 => '(協)', 12864 => '(祭)', 12865 => '(休)', 12866 => '(自)', 12867 => '(至)', 64297 => '+', 64606 => ' ٌّ', 64607 => ' ٍّ', 64608 => ' َّ', 64609 => ' ُّ', 64610 => ' ِّ', 64611 => ' ّٰ', 65018 => 'صلى الله عليه وسلم', 65019 => 'جل جلاله', 65040 => ',', 65043 => ':', 65044 => ';', 65045 => '!', 65046 => '?', 65075 => '_', 65076 => '_', 65077 => '(', 65078 => ')', 65079 => '{', 65080 => '}', 65095 => '[', 65096 => ']', 65097 => ' ̅', 65098 => ' ̅', 65099 => ' ̅', 65100 => ' ̅', 65101 => '_', 65102 => '_', 65103 => '_', 65104 => ',', 65108 => ';', 65109 => ':', 65110 => '?', 65111 => '!', 65113 => '(', 65114 => ')', 65115 => '{', 65116 => '}', 65119 => '#', 65120 => '&', 65121 => '*', 65122 => '+', 65124 => '<', 65125 => '>', 65126 => '=', 65128 => '\\', 65129 => '$', 65130 => '%', 65131 => '@', 65136 => ' ً', 65138 => ' ٌ', 65140 => ' ٍ', 65142 => ' َ', 65144 => ' ُ', 65146 => ' ِ', 65148 => ' ّ', 65150 => ' ْ', 65281 => '!', 65282 => '"', 65283 => '#', 65284 => '$', 65285 => '%', 65286 => '&', 65287 => '\'', 65288 => '(', 65289 => ')', 65290 => '*', 65291 => '+', 65292 => ',', 65295 => '/', 65306 => ':', 65307 => ';', 65308 => '<', 65309 => '=', 65310 => '>', 65311 => '?', 65312 => '@', 65339 => '[', 65340 => '\\', 65341 => ']', 65342 => '^', 65343 => '_', 65344 => '`', 65371 => '{', 65372 => '|', 65373 => '}', 65374 => '~', 65507 => ' ̄', 127233 => '0,', 127234 => '1,', 127235 => '2,', 127236 => '3,', 127237 => '4,', 127238 => '5,', 127239 => '6,', 127240 => '7,', 127241 => '8,', 127242 => '9,', 127248 => '(a)', 127249 => '(b)', 127250 => '(c)', 127251 => '(d)', 127252 => '(e)', 127253 => '(f)', 127254 => '(g)', 127255 => '(h)', 127256 => '(i)', 127257 => '(j)', 127258 => '(k)', 127259 => '(l)', 127260 => '(m)', 127261 => '(n)', 127262 => '(o)', 127263 => '(p)', 127264 => '(q)', 127265 => '(r)', 127266 => '(s)', 127267 => '(t)', 127268 => '(u)', 127269 => '(v)', 127270 => '(w)', 127271 => '(x)', 127272 => '(y)', 127273 => '(z)'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php new file mode 100644 index 00000000..99196bed --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/disallowed_STD3_valid.php @@ -0,0 +1,5 @@ + \true, 1 => \true, 2 => \true, 3 => \true, 4 => \true, 5 => \true, 6 => \true, 7 => \true, 8 => \true, 9 => \true, 10 => \true, 11 => \true, 12 => \true, 13 => \true, 14 => \true, 15 => \true, 16 => \true, 17 => \true, 18 => \true, 19 => \true, 20 => \true, 21 => \true, 22 => \true, 23 => \true, 24 => \true, 25 => \true, 26 => \true, 27 => \true, 28 => \true, 29 => \true, 30 => \true, 31 => \true, 32 => \true, 33 => \true, 34 => \true, 35 => \true, 36 => \true, 37 => \true, 38 => \true, 39 => \true, 40 => \true, 41 => \true, 42 => \true, 43 => \true, 44 => \true, 47 => \true, 58 => \true, 59 => \true, 60 => \true, 61 => \true, 62 => \true, 63 => \true, 64 => \true, 91 => \true, 92 => \true, 93 => \true, 94 => \true, 95 => \true, 96 => \true, 123 => \true, 124 => \true, 125 => \true, 126 => \true, 127 => \true, 8800 => \true, 8814 => \true, 8815 => \true); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/ignored.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/ignored.php new file mode 100644 index 00000000..7c441c1d --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/ignored.php @@ -0,0 +1,5 @@ + \true, 847 => \true, 6155 => \true, 6156 => \true, 6157 => \true, 8203 => \true, 8288 => \true, 8292 => \true, 65024 => \true, 65025 => \true, 65026 => \true, 65027 => \true, 65028 => \true, 65029 => \true, 65030 => \true, 65031 => \true, 65032 => \true, 65033 => \true, 65034 => \true, 65035 => \true, 65036 => \true, 65037 => \true, 65038 => \true, 65039 => \true, 65279 => \true, 113824 => \true, 113825 => \true, 113826 => \true, 113827 => \true, 917760 => \true, 917761 => \true, 917762 => \true, 917763 => \true, 917764 => \true, 917765 => \true, 917766 => \true, 917767 => \true, 917768 => \true, 917769 => \true, 917770 => \true, 917771 => \true, 917772 => \true, 917773 => \true, 917774 => \true, 917775 => \true, 917776 => \true, 917777 => \true, 917778 => \true, 917779 => \true, 917780 => \true, 917781 => \true, 917782 => \true, 917783 => \true, 917784 => \true, 917785 => \true, 917786 => \true, 917787 => \true, 917788 => \true, 917789 => \true, 917790 => \true, 917791 => \true, 917792 => \true, 917793 => \true, 917794 => \true, 917795 => \true, 917796 => \true, 917797 => \true, 917798 => \true, 917799 => \true, 917800 => \true, 917801 => \true, 917802 => \true, 917803 => \true, 917804 => \true, 917805 => \true, 917806 => \true, 917807 => \true, 917808 => \true, 917809 => \true, 917810 => \true, 917811 => \true, 917812 => \true, 917813 => \true, 917814 => \true, 917815 => \true, 917816 => \true, 917817 => \true, 917818 => \true, 917819 => \true, 917820 => \true, 917821 => \true, 917822 => \true, 917823 => \true, 917824 => \true, 917825 => \true, 917826 => \true, 917827 => \true, 917828 => \true, 917829 => \true, 917830 => \true, 917831 => \true, 917832 => \true, 917833 => \true, 917834 => \true, 917835 => \true, 917836 => \true, 917837 => \true, 917838 => \true, 917839 => \true, 917840 => \true, 917841 => \true, 917842 => \true, 917843 => \true, 917844 => \true, 917845 => \true, 917846 => \true, 917847 => \true, 917848 => \true, 917849 => \true, 917850 => \true, 917851 => \true, 917852 => \true, 917853 => \true, 917854 => \true, 917855 => \true, 917856 => \true, 917857 => \true, 917858 => \true, 917859 => \true, 917860 => \true, 917861 => \true, 917862 => \true, 917863 => \true, 917864 => \true, 917865 => \true, 917866 => \true, 917867 => \true, 917868 => \true, 917869 => \true, 917870 => \true, 917871 => \true, 917872 => \true, 917873 => \true, 917874 => \true, 917875 => \true, 917876 => \true, 917877 => \true, 917878 => \true, 917879 => \true, 917880 => \true, 917881 => \true, 917882 => \true, 917883 => \true, 917884 => \true, 917885 => \true, 917886 => \true, 917887 => \true, 917888 => \true, 917889 => \true, 917890 => \true, 917891 => \true, 917892 => \true, 917893 => \true, 917894 => \true, 917895 => \true, 917896 => \true, 917897 => \true, 917898 => \true, 917899 => \true, 917900 => \true, 917901 => \true, 917902 => \true, 917903 => \true, 917904 => \true, 917905 => \true, 917906 => \true, 917907 => \true, 917908 => \true, 917909 => \true, 917910 => \true, 917911 => \true, 917912 => \true, 917913 => \true, 917914 => \true, 917915 => \true, 917916 => \true, 917917 => \true, 917918 => \true, 917919 => \true, 917920 => \true, 917921 => \true, 917922 => \true, 917923 => \true, 917924 => \true, 917925 => \true, 917926 => \true, 917927 => \true, 917928 => \true, 917929 => \true, 917930 => \true, 917931 => \true, 917932 => \true, 917933 => \true, 917934 => \true, 917935 => \true, 917936 => \true, 917937 => \true, 917938 => \true, 917939 => \true, 917940 => \true, 917941 => \true, 917942 => \true, 917943 => \true, 917944 => \true, 917945 => \true, 917946 => \true, 917947 => \true, 917948 => \true, 917949 => \true, 917950 => \true, 917951 => \true, 917952 => \true, 917953 => \true, 917954 => \true, 917955 => \true, 917956 => \true, 917957 => \true, 917958 => \true, 917959 => \true, 917960 => \true, 917961 => \true, 917962 => \true, 917963 => \true, 917964 => \true, 917965 => \true, 917966 => \true, 917967 => \true, 917968 => \true, 917969 => \true, 917970 => \true, 917971 => \true, 917972 => \true, 917973 => \true, 917974 => \true, 917975 => \true, 917976 => \true, 917977 => \true, 917978 => \true, 917979 => \true, 917980 => \true, 917981 => \true, 917982 => \true, 917983 => \true, 917984 => \true, 917985 => \true, 917986 => \true, 917987 => \true, 917988 => \true, 917989 => \true, 917990 => \true, 917991 => \true, 917992 => \true, 917993 => \true, 917994 => \true, 917995 => \true, 917996 => \true, 917997 => \true, 917998 => \true, 917999 => \true); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/mapped.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/mapped.php new file mode 100644 index 00000000..d06ffa38 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/mapped.php @@ -0,0 +1,5 @@ + 'a', 66 => 'b', 67 => 'c', 68 => 'd', 69 => 'e', 70 => 'f', 71 => 'g', 72 => 'h', 73 => 'i', 74 => 'j', 75 => 'k', 76 => 'l', 77 => 'm', 78 => 'n', 79 => 'o', 80 => 'p', 81 => 'q', 82 => 'r', 83 => 's', 84 => 't', 85 => 'u', 86 => 'v', 87 => 'w', 88 => 'x', 89 => 'y', 90 => 'z', 170 => 'a', 178 => '2', 179 => '3', 181 => 'μ', 185 => '1', 186 => 'o', 188 => '1⁄4', 189 => '1⁄2', 190 => '3⁄4', 192 => 'à', 193 => 'á', 194 => 'â', 195 => 'ã', 196 => 'ä', 197 => 'å', 198 => 'æ', 199 => 'ç', 200 => 'è', 201 => 'é', 202 => 'ê', 203 => 'ë', 204 => 'ì', 205 => 'í', 206 => 'î', 207 => 'ï', 208 => 'ð', 209 => 'ñ', 210 => 'ò', 211 => 'ó', 212 => 'ô', 213 => 'õ', 214 => 'ö', 216 => 'ø', 217 => 'ù', 218 => 'ú', 219 => 'û', 220 => 'ü', 221 => 'ý', 222 => 'þ', 256 => 'ā', 258 => 'ă', 260 => 'ą', 262 => 'ć', 264 => 'ĉ', 266 => 'ċ', 268 => 'č', 270 => 'ď', 272 => 'đ', 274 => 'ē', 276 => 'ĕ', 278 => 'ė', 280 => 'ę', 282 => 'ě', 284 => 'ĝ', 286 => 'ğ', 288 => 'ġ', 290 => 'ģ', 292 => 'ĥ', 294 => 'ħ', 296 => 'ĩ', 298 => 'ī', 300 => 'ĭ', 302 => 'į', 304 => 'i̇', 306 => 'ij', 307 => 'ij', 308 => 'ĵ', 310 => 'ķ', 313 => 'ĺ', 315 => 'ļ', 317 => 'ľ', 319 => 'l·', 320 => 'l·', 321 => 'ł', 323 => 'ń', 325 => 'ņ', 327 => 'ň', 329 => 'ʼn', 330 => 'ŋ', 332 => 'ō', 334 => 'ŏ', 336 => 'ő', 338 => 'œ', 340 => 'ŕ', 342 => 'ŗ', 344 => 'ř', 346 => 'ś', 348 => 'ŝ', 350 => 'ş', 352 => 'š', 354 => 'ţ', 356 => 'ť', 358 => 'ŧ', 360 => 'ũ', 362 => 'ū', 364 => 'ŭ', 366 => 'ů', 368 => 'ű', 370 => 'ų', 372 => 'ŵ', 374 => 'ŷ', 376 => 'ÿ', 377 => 'ź', 379 => 'ż', 381 => 'ž', 383 => 's', 385 => 'ɓ', 386 => 'ƃ', 388 => 'ƅ', 390 => 'ɔ', 391 => 'ƈ', 393 => 'ɖ', 394 => 'ɗ', 395 => 'ƌ', 398 => 'ǝ', 399 => 'ə', 400 => 'ɛ', 401 => 'ƒ', 403 => 'ɠ', 404 => 'ɣ', 406 => 'ɩ', 407 => 'ɨ', 408 => 'ƙ', 412 => 'ɯ', 413 => 'ɲ', 415 => 'ɵ', 416 => 'ơ', 418 => 'ƣ', 420 => 'ƥ', 422 => 'ʀ', 423 => 'ƨ', 425 => 'ʃ', 428 => 'ƭ', 430 => 'ʈ', 431 => 'ư', 433 => 'ʊ', 434 => 'ʋ', 435 => 'ƴ', 437 => 'ƶ', 439 => 'ʒ', 440 => 'ƹ', 444 => 'ƽ', 452 => 'dž', 453 => 'dž', 454 => 'dž', 455 => 'lj', 456 => 'lj', 457 => 'lj', 458 => 'nj', 459 => 'nj', 460 => 'nj', 461 => 'ǎ', 463 => 'ǐ', 465 => 'ǒ', 467 => 'ǔ', 469 => 'ǖ', 471 => 'ǘ', 473 => 'ǚ', 475 => 'ǜ', 478 => 'ǟ', 480 => 'ǡ', 482 => 'ǣ', 484 => 'ǥ', 486 => 'ǧ', 488 => 'ǩ', 490 => 'ǫ', 492 => 'ǭ', 494 => 'ǯ', 497 => 'dz', 498 => 'dz', 499 => 'dz', 500 => 'ǵ', 502 => 'ƕ', 503 => 'ƿ', 504 => 'ǹ', 506 => 'ǻ', 508 => 'ǽ', 510 => 'ǿ', 512 => 'ȁ', 514 => 'ȃ', 516 => 'ȅ', 518 => 'ȇ', 520 => 'ȉ', 522 => 'ȋ', 524 => 'ȍ', 526 => 'ȏ', 528 => 'ȑ', 530 => 'ȓ', 532 => 'ȕ', 534 => 'ȗ', 536 => 'ș', 538 => 'ț', 540 => 'ȝ', 542 => 'ȟ', 544 => 'ƞ', 546 => 'ȣ', 548 => 'ȥ', 550 => 'ȧ', 552 => 'ȩ', 554 => 'ȫ', 556 => 'ȭ', 558 => 'ȯ', 560 => 'ȱ', 562 => 'ȳ', 570 => 'ⱥ', 571 => 'ȼ', 573 => 'ƚ', 574 => 'ⱦ', 577 => 'ɂ', 579 => 'ƀ', 580 => 'ʉ', 581 => 'ʌ', 582 => 'ɇ', 584 => 'ɉ', 586 => 'ɋ', 588 => 'ɍ', 590 => 'ɏ', 688 => 'h', 689 => 'ɦ', 690 => 'j', 691 => 'r', 692 => 'ɹ', 693 => 'ɻ', 694 => 'ʁ', 695 => 'w', 696 => 'y', 736 => 'ɣ', 737 => 'l', 738 => 's', 739 => 'x', 740 => 'ʕ', 832 => '̀', 833 => '́', 835 => '̓', 836 => '̈́', 837 => 'ι', 880 => 'ͱ', 882 => 'ͳ', 884 => 'ʹ', 886 => 'ͷ', 895 => 'ϳ', 902 => 'ά', 903 => '·', 904 => 'έ', 905 => 'ή', 906 => 'ί', 908 => 'ό', 910 => 'ύ', 911 => 'ώ', 913 => 'α', 914 => 'β', 915 => 'γ', 916 => 'δ', 917 => 'ε', 918 => 'ζ', 919 => 'η', 920 => 'θ', 921 => 'ι', 922 => 'κ', 923 => 'λ', 924 => 'μ', 925 => 'ν', 926 => 'ξ', 927 => 'ο', 928 => 'π', 929 => 'ρ', 931 => 'σ', 932 => 'τ', 933 => 'υ', 934 => 'φ', 935 => 'χ', 936 => 'ψ', 937 => 'ω', 938 => 'ϊ', 939 => 'ϋ', 975 => 'ϗ', 976 => 'β', 977 => 'θ', 978 => 'υ', 979 => 'ύ', 980 => 'ϋ', 981 => 'φ', 982 => 'π', 984 => 'ϙ', 986 => 'ϛ', 988 => 'ϝ', 990 => 'ϟ', 992 => 'ϡ', 994 => 'ϣ', 996 => 'ϥ', 998 => 'ϧ', 1000 => 'ϩ', 1002 => 'ϫ', 1004 => 'ϭ', 1006 => 'ϯ', 1008 => 'κ', 1009 => 'ρ', 1010 => 'σ', 1012 => 'θ', 1013 => 'ε', 1015 => 'ϸ', 1017 => 'σ', 1018 => 'ϻ', 1021 => 'ͻ', 1022 => 'ͼ', 1023 => 'ͽ', 1024 => 'ѐ', 1025 => 'ё', 1026 => 'ђ', 1027 => 'ѓ', 1028 => 'є', 1029 => 'ѕ', 1030 => 'і', 1031 => 'ї', 1032 => 'ј', 1033 => 'љ', 1034 => 'њ', 1035 => 'ћ', 1036 => 'ќ', 1037 => 'ѝ', 1038 => 'ў', 1039 => 'џ', 1040 => 'а', 1041 => 'б', 1042 => 'в', 1043 => 'г', 1044 => 'д', 1045 => 'е', 1046 => 'ж', 1047 => 'з', 1048 => 'и', 1049 => 'й', 1050 => 'к', 1051 => 'л', 1052 => 'м', 1053 => 'н', 1054 => 'о', 1055 => 'п', 1056 => 'р', 1057 => 'с', 1058 => 'т', 1059 => 'у', 1060 => 'ф', 1061 => 'х', 1062 => 'ц', 1063 => 'ч', 1064 => 'ш', 1065 => 'щ', 1066 => 'ъ', 1067 => 'ы', 1068 => 'ь', 1069 => 'э', 1070 => 'ю', 1071 => 'я', 1120 => 'ѡ', 1122 => 'ѣ', 1124 => 'ѥ', 1126 => 'ѧ', 1128 => 'ѩ', 1130 => 'ѫ', 1132 => 'ѭ', 1134 => 'ѯ', 1136 => 'ѱ', 1138 => 'ѳ', 1140 => 'ѵ', 1142 => 'ѷ', 1144 => 'ѹ', 1146 => 'ѻ', 1148 => 'ѽ', 1150 => 'ѿ', 1152 => 'ҁ', 1162 => 'ҋ', 1164 => 'ҍ', 1166 => 'ҏ', 1168 => 'ґ', 1170 => 'ғ', 1172 => 'ҕ', 1174 => 'җ', 1176 => 'ҙ', 1178 => 'қ', 1180 => 'ҝ', 1182 => 'ҟ', 1184 => 'ҡ', 1186 => 'ң', 1188 => 'ҥ', 1190 => 'ҧ', 1192 => 'ҩ', 1194 => 'ҫ', 1196 => 'ҭ', 1198 => 'ү', 1200 => 'ұ', 1202 => 'ҳ', 1204 => 'ҵ', 1206 => 'ҷ', 1208 => 'ҹ', 1210 => 'һ', 1212 => 'ҽ', 1214 => 'ҿ', 1217 => 'ӂ', 1219 => 'ӄ', 1221 => 'ӆ', 1223 => 'ӈ', 1225 => 'ӊ', 1227 => 'ӌ', 1229 => 'ӎ', 1232 => 'ӑ', 1234 => 'ӓ', 1236 => 'ӕ', 1238 => 'ӗ', 1240 => 'ә', 1242 => 'ӛ', 1244 => 'ӝ', 1246 => 'ӟ', 1248 => 'ӡ', 1250 => 'ӣ', 1252 => 'ӥ', 1254 => 'ӧ', 1256 => 'ө', 1258 => 'ӫ', 1260 => 'ӭ', 1262 => 'ӯ', 1264 => 'ӱ', 1266 => 'ӳ', 1268 => 'ӵ', 1270 => 'ӷ', 1272 => 'ӹ', 1274 => 'ӻ', 1276 => 'ӽ', 1278 => 'ӿ', 1280 => 'ԁ', 1282 => 'ԃ', 1284 => 'ԅ', 1286 => 'ԇ', 1288 => 'ԉ', 1290 => 'ԋ', 1292 => 'ԍ', 1294 => 'ԏ', 1296 => 'ԑ', 1298 => 'ԓ', 1300 => 'ԕ', 1302 => 'ԗ', 1304 => 'ԙ', 1306 => 'ԛ', 1308 => 'ԝ', 1310 => 'ԟ', 1312 => 'ԡ', 1314 => 'ԣ', 1316 => 'ԥ', 1318 => 'ԧ', 1320 => 'ԩ', 1322 => 'ԫ', 1324 => 'ԭ', 1326 => 'ԯ', 1329 => 'ա', 1330 => 'բ', 1331 => 'գ', 1332 => 'դ', 1333 => 'ե', 1334 => 'զ', 1335 => 'է', 1336 => 'ը', 1337 => 'թ', 1338 => 'ժ', 1339 => 'ի', 1340 => 'լ', 1341 => 'խ', 1342 => 'ծ', 1343 => 'կ', 1344 => 'հ', 1345 => 'ձ', 1346 => 'ղ', 1347 => 'ճ', 1348 => 'մ', 1349 => 'յ', 1350 => 'ն', 1351 => 'շ', 1352 => 'ո', 1353 => 'չ', 1354 => 'պ', 1355 => 'ջ', 1356 => 'ռ', 1357 => 'ս', 1358 => 'վ', 1359 => 'տ', 1360 => 'ր', 1361 => 'ց', 1362 => 'ւ', 1363 => 'փ', 1364 => 'ք', 1365 => 'օ', 1366 => 'ֆ', 1415 => 'եւ', 1653 => 'اٴ', 1654 => 'وٴ', 1655 => 'ۇٴ', 1656 => 'يٴ', 2392 => 'क़', 2393 => 'ख़', 2394 => 'ग़', 2395 => 'ज़', 2396 => 'ड़', 2397 => 'ढ़', 2398 => 'फ़', 2399 => 'य़', 2524 => 'ড়', 2525 => 'ঢ়', 2527 => 'য়', 2611 => 'ਲ਼', 2614 => 'ਸ਼', 2649 => 'ਖ਼', 2650 => 'ਗ਼', 2651 => 'ਜ਼', 2654 => 'ਫ਼', 2908 => 'ଡ଼', 2909 => 'ଢ଼', 3635 => 'ํา', 3763 => 'ໍາ', 3804 => 'ຫນ', 3805 => 'ຫມ', 3852 => '་', 3907 => 'གྷ', 3917 => 'ཌྷ', 3922 => 'དྷ', 3927 => 'བྷ', 3932 => 'ཛྷ', 3945 => 'ཀྵ', 3955 => 'ཱི', 3957 => 'ཱུ', 3958 => 'ྲྀ', 3959 => 'ྲཱྀ', 3960 => 'ླྀ', 3961 => 'ླཱྀ', 3969 => 'ཱྀ', 3987 => 'ྒྷ', 3997 => 'ྜྷ', 4002 => 'ྡྷ', 4007 => 'ྦྷ', 4012 => 'ྫྷ', 4025 => 'ྐྵ', 4295 => 'ⴧ', 4301 => 'ⴭ', 4348 => 'ნ', 5112 => 'Ᏸ', 5113 => 'Ᏹ', 5114 => 'Ᏺ', 5115 => 'Ᏻ', 5116 => 'Ᏼ', 5117 => 'Ᏽ', 7296 => 'в', 7297 => 'д', 7298 => 'о', 7299 => 'с', 7300 => 'т', 7301 => 'т', 7302 => 'ъ', 7303 => 'ѣ', 7304 => 'ꙋ', 7312 => 'ა', 7313 => 'ბ', 7314 => 'გ', 7315 => 'დ', 7316 => 'ე', 7317 => 'ვ', 7318 => 'ზ', 7319 => 'თ', 7320 => 'ი', 7321 => 'კ', 7322 => 'ლ', 7323 => 'მ', 7324 => 'ნ', 7325 => 'ო', 7326 => 'პ', 7327 => 'ჟ', 7328 => 'რ', 7329 => 'ს', 7330 => 'ტ', 7331 => 'უ', 7332 => 'ფ', 7333 => 'ქ', 7334 => 'ღ', 7335 => 'ყ', 7336 => 'შ', 7337 => 'ჩ', 7338 => 'ც', 7339 => 'ძ', 7340 => 'წ', 7341 => 'ჭ', 7342 => 'ხ', 7343 => 'ჯ', 7344 => 'ჰ', 7345 => 'ჱ', 7346 => 'ჲ', 7347 => 'ჳ', 7348 => 'ჴ', 7349 => 'ჵ', 7350 => 'ჶ', 7351 => 'ჷ', 7352 => 'ჸ', 7353 => 'ჹ', 7354 => 'ჺ', 7357 => 'ჽ', 7358 => 'ჾ', 7359 => 'ჿ', 7468 => 'a', 7469 => 'æ', 7470 => 'b', 7472 => 'd', 7473 => 'e', 7474 => 'ǝ', 7475 => 'g', 7476 => 'h', 7477 => 'i', 7478 => 'j', 7479 => 'k', 7480 => 'l', 7481 => 'm', 7482 => 'n', 7484 => 'o', 7485 => 'ȣ', 7486 => 'p', 7487 => 'r', 7488 => 't', 7489 => 'u', 7490 => 'w', 7491 => 'a', 7492 => 'ɐ', 7493 => 'ɑ', 7494 => 'ᴂ', 7495 => 'b', 7496 => 'd', 7497 => 'e', 7498 => 'ə', 7499 => 'ɛ', 7500 => 'ɜ', 7501 => 'g', 7503 => 'k', 7504 => 'm', 7505 => 'ŋ', 7506 => 'o', 7507 => 'ɔ', 7508 => 'ᴖ', 7509 => 'ᴗ', 7510 => 'p', 7511 => 't', 7512 => 'u', 7513 => 'ᴝ', 7514 => 'ɯ', 7515 => 'v', 7516 => 'ᴥ', 7517 => 'β', 7518 => 'γ', 7519 => 'δ', 7520 => 'φ', 7521 => 'χ', 7522 => 'i', 7523 => 'r', 7524 => 'u', 7525 => 'v', 7526 => 'β', 7527 => 'γ', 7528 => 'ρ', 7529 => 'φ', 7530 => 'χ', 7544 => 'н', 7579 => 'ɒ', 7580 => 'c', 7581 => 'ɕ', 7582 => 'ð', 7583 => 'ɜ', 7584 => 'f', 7585 => 'ɟ', 7586 => 'ɡ', 7587 => 'ɥ', 7588 => 'ɨ', 7589 => 'ɩ', 7590 => 'ɪ', 7591 => 'ᵻ', 7592 => 'ʝ', 7593 => 'ɭ', 7594 => 'ᶅ', 7595 => 'ʟ', 7596 => 'ɱ', 7597 => 'ɰ', 7598 => 'ɲ', 7599 => 'ɳ', 7600 => 'ɴ', 7601 => 'ɵ', 7602 => 'ɸ', 7603 => 'ʂ', 7604 => 'ʃ', 7605 => 'ƫ', 7606 => 'ʉ', 7607 => 'ʊ', 7608 => 'ᴜ', 7609 => 'ʋ', 7610 => 'ʌ', 7611 => 'z', 7612 => 'ʐ', 7613 => 'ʑ', 7614 => 'ʒ', 7615 => 'θ', 7680 => 'ḁ', 7682 => 'ḃ', 7684 => 'ḅ', 7686 => 'ḇ', 7688 => 'ḉ', 7690 => 'ḋ', 7692 => 'ḍ', 7694 => 'ḏ', 7696 => 'ḑ', 7698 => 'ḓ', 7700 => 'ḕ', 7702 => 'ḗ', 7704 => 'ḙ', 7706 => 'ḛ', 7708 => 'ḝ', 7710 => 'ḟ', 7712 => 'ḡ', 7714 => 'ḣ', 7716 => 'ḥ', 7718 => 'ḧ', 7720 => 'ḩ', 7722 => 'ḫ', 7724 => 'ḭ', 7726 => 'ḯ', 7728 => 'ḱ', 7730 => 'ḳ', 7732 => 'ḵ', 7734 => 'ḷ', 7736 => 'ḹ', 7738 => 'ḻ', 7740 => 'ḽ', 7742 => 'ḿ', 7744 => 'ṁ', 7746 => 'ṃ', 7748 => 'ṅ', 7750 => 'ṇ', 7752 => 'ṉ', 7754 => 'ṋ', 7756 => 'ṍ', 7758 => 'ṏ', 7760 => 'ṑ', 7762 => 'ṓ', 7764 => 'ṕ', 7766 => 'ṗ', 7768 => 'ṙ', 7770 => 'ṛ', 7772 => 'ṝ', 7774 => 'ṟ', 7776 => 'ṡ', 7778 => 'ṣ', 7780 => 'ṥ', 7782 => 'ṧ', 7784 => 'ṩ', 7786 => 'ṫ', 7788 => 'ṭ', 7790 => 'ṯ', 7792 => 'ṱ', 7794 => 'ṳ', 7796 => 'ṵ', 7798 => 'ṷ', 7800 => 'ṹ', 7802 => 'ṻ', 7804 => 'ṽ', 7806 => 'ṿ', 7808 => 'ẁ', 7810 => 'ẃ', 7812 => 'ẅ', 7814 => 'ẇ', 7816 => 'ẉ', 7818 => 'ẋ', 7820 => 'ẍ', 7822 => 'ẏ', 7824 => 'ẑ', 7826 => 'ẓ', 7828 => 'ẕ', 7834 => 'aʾ', 7835 => 'ṡ', 7838 => 'ss', 7840 => 'ạ', 7842 => 'ả', 7844 => 'ấ', 7846 => 'ầ', 7848 => 'ẩ', 7850 => 'ẫ', 7852 => 'ậ', 7854 => 'ắ', 7856 => 'ằ', 7858 => 'ẳ', 7860 => 'ẵ', 7862 => 'ặ', 7864 => 'ẹ', 7866 => 'ẻ', 7868 => 'ẽ', 7870 => 'ế', 7872 => 'ề', 7874 => 'ể', 7876 => 'ễ', 7878 => 'ệ', 7880 => 'ỉ', 7882 => 'ị', 7884 => 'ọ', 7886 => 'ỏ', 7888 => 'ố', 7890 => 'ồ', 7892 => 'ổ', 7894 => 'ỗ', 7896 => 'ộ', 7898 => 'ớ', 7900 => 'ờ', 7902 => 'ở', 7904 => 'ỡ', 7906 => 'ợ', 7908 => 'ụ', 7910 => 'ủ', 7912 => 'ứ', 7914 => 'ừ', 7916 => 'ử', 7918 => 'ữ', 7920 => 'ự', 7922 => 'ỳ', 7924 => 'ỵ', 7926 => 'ỷ', 7928 => 'ỹ', 7930 => 'ỻ', 7932 => 'ỽ', 7934 => 'ỿ', 7944 => 'ἀ', 7945 => 'ἁ', 7946 => 'ἂ', 7947 => 'ἃ', 7948 => 'ἄ', 7949 => 'ἅ', 7950 => 'ἆ', 7951 => 'ἇ', 7960 => 'ἐ', 7961 => 'ἑ', 7962 => 'ἒ', 7963 => 'ἓ', 7964 => 'ἔ', 7965 => 'ἕ', 7976 => 'ἠ', 7977 => 'ἡ', 7978 => 'ἢ', 7979 => 'ἣ', 7980 => 'ἤ', 7981 => 'ἥ', 7982 => 'ἦ', 7983 => 'ἧ', 7992 => 'ἰ', 7993 => 'ἱ', 7994 => 'ἲ', 7995 => 'ἳ', 7996 => 'ἴ', 7997 => 'ἵ', 7998 => 'ἶ', 7999 => 'ἷ', 8008 => 'ὀ', 8009 => 'ὁ', 8010 => 'ὂ', 8011 => 'ὃ', 8012 => 'ὄ', 8013 => 'ὅ', 8025 => 'ὑ', 8027 => 'ὓ', 8029 => 'ὕ', 8031 => 'ὗ', 8040 => 'ὠ', 8041 => 'ὡ', 8042 => 'ὢ', 8043 => 'ὣ', 8044 => 'ὤ', 8045 => 'ὥ', 8046 => 'ὦ', 8047 => 'ὧ', 8049 => 'ά', 8051 => 'έ', 8053 => 'ή', 8055 => 'ί', 8057 => 'ό', 8059 => 'ύ', 8061 => 'ώ', 8064 => 'ἀι', 8065 => 'ἁι', 8066 => 'ἂι', 8067 => 'ἃι', 8068 => 'ἄι', 8069 => 'ἅι', 8070 => 'ἆι', 8071 => 'ἇι', 8072 => 'ἀι', 8073 => 'ἁι', 8074 => 'ἂι', 8075 => 'ἃι', 8076 => 'ἄι', 8077 => 'ἅι', 8078 => 'ἆι', 8079 => 'ἇι', 8080 => 'ἠι', 8081 => 'ἡι', 8082 => 'ἢι', 8083 => 'ἣι', 8084 => 'ἤι', 8085 => 'ἥι', 8086 => 'ἦι', 8087 => 'ἧι', 8088 => 'ἠι', 8089 => 'ἡι', 8090 => 'ἢι', 8091 => 'ἣι', 8092 => 'ἤι', 8093 => 'ἥι', 8094 => 'ἦι', 8095 => 'ἧι', 8096 => 'ὠι', 8097 => 'ὡι', 8098 => 'ὢι', 8099 => 'ὣι', 8100 => 'ὤι', 8101 => 'ὥι', 8102 => 'ὦι', 8103 => 'ὧι', 8104 => 'ὠι', 8105 => 'ὡι', 8106 => 'ὢι', 8107 => 'ὣι', 8108 => 'ὤι', 8109 => 'ὥι', 8110 => 'ὦι', 8111 => 'ὧι', 8114 => 'ὰι', 8115 => 'αι', 8116 => 'άι', 8119 => 'ᾶι', 8120 => 'ᾰ', 8121 => 'ᾱ', 8122 => 'ὰ', 8123 => 'ά', 8124 => 'αι', 8126 => 'ι', 8130 => 'ὴι', 8131 => 'ηι', 8132 => 'ήι', 8135 => 'ῆι', 8136 => 'ὲ', 8137 => 'έ', 8138 => 'ὴ', 8139 => 'ή', 8140 => 'ηι', 8147 => 'ΐ', 8152 => 'ῐ', 8153 => 'ῑ', 8154 => 'ὶ', 8155 => 'ί', 8163 => 'ΰ', 8168 => 'ῠ', 8169 => 'ῡ', 8170 => 'ὺ', 8171 => 'ύ', 8172 => 'ῥ', 8178 => 'ὼι', 8179 => 'ωι', 8180 => 'ώι', 8183 => 'ῶι', 8184 => 'ὸ', 8185 => 'ό', 8186 => 'ὼ', 8187 => 'ώ', 8188 => 'ωι', 8209 => '‐', 8243 => '′′', 8244 => '′′′', 8246 => '‵‵', 8247 => '‵‵‵', 8279 => '′′′′', 8304 => '0', 8305 => 'i', 8308 => '4', 8309 => '5', 8310 => '6', 8311 => '7', 8312 => '8', 8313 => '9', 8315 => '−', 8319 => 'n', 8320 => '0', 8321 => '1', 8322 => '2', 8323 => '3', 8324 => '4', 8325 => '5', 8326 => '6', 8327 => '7', 8328 => '8', 8329 => '9', 8331 => '−', 8336 => 'a', 8337 => 'e', 8338 => 'o', 8339 => 'x', 8340 => 'ə', 8341 => 'h', 8342 => 'k', 8343 => 'l', 8344 => 'm', 8345 => 'n', 8346 => 'p', 8347 => 's', 8348 => 't', 8360 => 'rs', 8450 => 'c', 8451 => '°c', 8455 => 'ɛ', 8457 => '°f', 8458 => 'g', 8459 => 'h', 8460 => 'h', 8461 => 'h', 8462 => 'h', 8463 => 'ħ', 8464 => 'i', 8465 => 'i', 8466 => 'l', 8467 => 'l', 8469 => 'n', 8470 => 'no', 8473 => 'p', 8474 => 'q', 8475 => 'r', 8476 => 'r', 8477 => 'r', 8480 => 'sm', 8481 => 'tel', 8482 => 'tm', 8484 => 'z', 8486 => 'ω', 8488 => 'z', 8490 => 'k', 8491 => 'å', 8492 => 'b', 8493 => 'c', 8495 => 'e', 8496 => 'e', 8497 => 'f', 8499 => 'm', 8500 => 'o', 8501 => 'א', 8502 => 'ב', 8503 => 'ג', 8504 => 'ד', 8505 => 'i', 8507 => 'fax', 8508 => 'π', 8509 => 'γ', 8510 => 'γ', 8511 => 'π', 8512 => '∑', 8517 => 'd', 8518 => 'd', 8519 => 'e', 8520 => 'i', 8521 => 'j', 8528 => '1⁄7', 8529 => '1⁄9', 8530 => '1⁄10', 8531 => '1⁄3', 8532 => '2⁄3', 8533 => '1⁄5', 8534 => '2⁄5', 8535 => '3⁄5', 8536 => '4⁄5', 8537 => '1⁄6', 8538 => '5⁄6', 8539 => '1⁄8', 8540 => '3⁄8', 8541 => '5⁄8', 8542 => '7⁄8', 8543 => '1⁄', 8544 => 'i', 8545 => 'ii', 8546 => 'iii', 8547 => 'iv', 8548 => 'v', 8549 => 'vi', 8550 => 'vii', 8551 => 'viii', 8552 => 'ix', 8553 => 'x', 8554 => 'xi', 8555 => 'xii', 8556 => 'l', 8557 => 'c', 8558 => 'd', 8559 => 'm', 8560 => 'i', 8561 => 'ii', 8562 => 'iii', 8563 => 'iv', 8564 => 'v', 8565 => 'vi', 8566 => 'vii', 8567 => 'viii', 8568 => 'ix', 8569 => 'x', 8570 => 'xi', 8571 => 'xii', 8572 => 'l', 8573 => 'c', 8574 => 'd', 8575 => 'm', 8585 => '0⁄3', 8748 => '∫∫', 8749 => '∫∫∫', 8751 => '∮∮', 8752 => '∮∮∮', 9001 => '〈', 9002 => '〉', 9312 => '1', 9313 => '2', 9314 => '3', 9315 => '4', 9316 => '5', 9317 => '6', 9318 => '7', 9319 => '8', 9320 => '9', 9321 => '10', 9322 => '11', 9323 => '12', 9324 => '13', 9325 => '14', 9326 => '15', 9327 => '16', 9328 => '17', 9329 => '18', 9330 => '19', 9331 => '20', 9398 => 'a', 9399 => 'b', 9400 => 'c', 9401 => 'd', 9402 => 'e', 9403 => 'f', 9404 => 'g', 9405 => 'h', 9406 => 'i', 9407 => 'j', 9408 => 'k', 9409 => 'l', 9410 => 'm', 9411 => 'n', 9412 => 'o', 9413 => 'p', 9414 => 'q', 9415 => 'r', 9416 => 's', 9417 => 't', 9418 => 'u', 9419 => 'v', 9420 => 'w', 9421 => 'x', 9422 => 'y', 9423 => 'z', 9424 => 'a', 9425 => 'b', 9426 => 'c', 9427 => 'd', 9428 => 'e', 9429 => 'f', 9430 => 'g', 9431 => 'h', 9432 => 'i', 9433 => 'j', 9434 => 'k', 9435 => 'l', 9436 => 'm', 9437 => 'n', 9438 => 'o', 9439 => 'p', 9440 => 'q', 9441 => 'r', 9442 => 's', 9443 => 't', 9444 => 'u', 9445 => 'v', 9446 => 'w', 9447 => 'x', 9448 => 'y', 9449 => 'z', 9450 => '0', 10764 => '∫∫∫∫', 10972 => '⫝̸', 11264 => 'ⰰ', 11265 => 'ⰱ', 11266 => 'ⰲ', 11267 => 'ⰳ', 11268 => 'ⰴ', 11269 => 'ⰵ', 11270 => 'ⰶ', 11271 => 'ⰷ', 11272 => 'ⰸ', 11273 => 'ⰹ', 11274 => 'ⰺ', 11275 => 'ⰻ', 11276 => 'ⰼ', 11277 => 'ⰽ', 11278 => 'ⰾ', 11279 => 'ⰿ', 11280 => 'ⱀ', 11281 => 'ⱁ', 11282 => 'ⱂ', 11283 => 'ⱃ', 11284 => 'ⱄ', 11285 => 'ⱅ', 11286 => 'ⱆ', 11287 => 'ⱇ', 11288 => 'ⱈ', 11289 => 'ⱉ', 11290 => 'ⱊ', 11291 => 'ⱋ', 11292 => 'ⱌ', 11293 => 'ⱍ', 11294 => 'ⱎ', 11295 => 'ⱏ', 11296 => 'ⱐ', 11297 => 'ⱑ', 11298 => 'ⱒ', 11299 => 'ⱓ', 11300 => 'ⱔ', 11301 => 'ⱕ', 11302 => 'ⱖ', 11303 => 'ⱗ', 11304 => 'ⱘ', 11305 => 'ⱙ', 11306 => 'ⱚ', 11307 => 'ⱛ', 11308 => 'ⱜ', 11309 => 'ⱝ', 11310 => 'ⱞ', 11360 => 'ⱡ', 11362 => 'ɫ', 11363 => 'ᵽ', 11364 => 'ɽ', 11367 => 'ⱨ', 11369 => 'ⱪ', 11371 => 'ⱬ', 11373 => 'ɑ', 11374 => 'ɱ', 11375 => 'ɐ', 11376 => 'ɒ', 11378 => 'ⱳ', 11381 => 'ⱶ', 11388 => 'j', 11389 => 'v', 11390 => 'ȿ', 11391 => 'ɀ', 11392 => 'ⲁ', 11394 => 'ⲃ', 11396 => 'ⲅ', 11398 => 'ⲇ', 11400 => 'ⲉ', 11402 => 'ⲋ', 11404 => 'ⲍ', 11406 => 'ⲏ', 11408 => 'ⲑ', 11410 => 'ⲓ', 11412 => 'ⲕ', 11414 => 'ⲗ', 11416 => 'ⲙ', 11418 => 'ⲛ', 11420 => 'ⲝ', 11422 => 'ⲟ', 11424 => 'ⲡ', 11426 => 'ⲣ', 11428 => 'ⲥ', 11430 => 'ⲧ', 11432 => 'ⲩ', 11434 => 'ⲫ', 11436 => 'ⲭ', 11438 => 'ⲯ', 11440 => 'ⲱ', 11442 => 'ⲳ', 11444 => 'ⲵ', 11446 => 'ⲷ', 11448 => 'ⲹ', 11450 => 'ⲻ', 11452 => 'ⲽ', 11454 => 'ⲿ', 11456 => 'ⳁ', 11458 => 'ⳃ', 11460 => 'ⳅ', 11462 => 'ⳇ', 11464 => 'ⳉ', 11466 => 'ⳋ', 11468 => 'ⳍ', 11470 => 'ⳏ', 11472 => 'ⳑ', 11474 => 'ⳓ', 11476 => 'ⳕ', 11478 => 'ⳗ', 11480 => 'ⳙ', 11482 => 'ⳛ', 11484 => 'ⳝ', 11486 => 'ⳟ', 11488 => 'ⳡ', 11490 => 'ⳣ', 11499 => 'ⳬ', 11501 => 'ⳮ', 11506 => 'ⳳ', 11631 => 'ⵡ', 11935 => '母', 12019 => '龟', 12032 => '一', 12033 => '丨', 12034 => '丶', 12035 => '丿', 12036 => '乙', 12037 => '亅', 12038 => '二', 12039 => '亠', 12040 => '人', 12041 => '儿', 12042 => '入', 12043 => '八', 12044 => '冂', 12045 => '冖', 12046 => '冫', 12047 => '几', 12048 => '凵', 12049 => '刀', 12050 => '力', 12051 => '勹', 12052 => '匕', 12053 => '匚', 12054 => '匸', 12055 => '十', 12056 => '卜', 12057 => '卩', 12058 => '厂', 12059 => '厶', 12060 => '又', 12061 => '口', 12062 => '囗', 12063 => '土', 12064 => '士', 12065 => '夂', 12066 => '夊', 12067 => '夕', 12068 => '大', 12069 => '女', 12070 => '子', 12071 => '宀', 12072 => '寸', 12073 => '小', 12074 => '尢', 12075 => '尸', 12076 => '屮', 12077 => '山', 12078 => '巛', 12079 => '工', 12080 => '己', 12081 => '巾', 12082 => '干', 12083 => '幺', 12084 => '广', 12085 => '廴', 12086 => '廾', 12087 => '弋', 12088 => '弓', 12089 => '彐', 12090 => '彡', 12091 => '彳', 12092 => '心', 12093 => '戈', 12094 => '戶', 12095 => '手', 12096 => '支', 12097 => '攴', 12098 => '文', 12099 => '斗', 12100 => '斤', 12101 => '方', 12102 => '无', 12103 => '日', 12104 => '曰', 12105 => '月', 12106 => '木', 12107 => '欠', 12108 => '止', 12109 => '歹', 12110 => '殳', 12111 => '毋', 12112 => '比', 12113 => '毛', 12114 => '氏', 12115 => '气', 12116 => '水', 12117 => '火', 12118 => '爪', 12119 => '父', 12120 => '爻', 12121 => '爿', 12122 => '片', 12123 => '牙', 12124 => '牛', 12125 => '犬', 12126 => '玄', 12127 => '玉', 12128 => '瓜', 12129 => '瓦', 12130 => '甘', 12131 => '生', 12132 => '用', 12133 => '田', 12134 => '疋', 12135 => '疒', 12136 => '癶', 12137 => '白', 12138 => '皮', 12139 => '皿', 12140 => '目', 12141 => '矛', 12142 => '矢', 12143 => '石', 12144 => '示', 12145 => '禸', 12146 => '禾', 12147 => '穴', 12148 => '立', 12149 => '竹', 12150 => '米', 12151 => '糸', 12152 => '缶', 12153 => '网', 12154 => '羊', 12155 => '羽', 12156 => '老', 12157 => '而', 12158 => '耒', 12159 => '耳', 12160 => '聿', 12161 => '肉', 12162 => '臣', 12163 => '自', 12164 => '至', 12165 => '臼', 12166 => '舌', 12167 => '舛', 12168 => '舟', 12169 => '艮', 12170 => '色', 12171 => '艸', 12172 => '虍', 12173 => '虫', 12174 => '血', 12175 => '行', 12176 => '衣', 12177 => '襾', 12178 => '見', 12179 => '角', 12180 => '言', 12181 => '谷', 12182 => '豆', 12183 => '豕', 12184 => '豸', 12185 => '貝', 12186 => '赤', 12187 => '走', 12188 => '足', 12189 => '身', 12190 => '車', 12191 => '辛', 12192 => '辰', 12193 => '辵', 12194 => '邑', 12195 => '酉', 12196 => '釆', 12197 => '里', 12198 => '金', 12199 => '長', 12200 => '門', 12201 => '阜', 12202 => '隶', 12203 => '隹', 12204 => '雨', 12205 => '靑', 12206 => '非', 12207 => '面', 12208 => '革', 12209 => '韋', 12210 => '韭', 12211 => '音', 12212 => '頁', 12213 => '風', 12214 => '飛', 12215 => '食', 12216 => '首', 12217 => '香', 12218 => '馬', 12219 => '骨', 12220 => '高', 12221 => '髟', 12222 => '鬥', 12223 => '鬯', 12224 => '鬲', 12225 => '鬼', 12226 => '魚', 12227 => '鳥', 12228 => '鹵', 12229 => '鹿', 12230 => '麥', 12231 => '麻', 12232 => '黃', 12233 => '黍', 12234 => '黑', 12235 => '黹', 12236 => '黽', 12237 => '鼎', 12238 => '鼓', 12239 => '鼠', 12240 => '鼻', 12241 => '齊', 12242 => '齒', 12243 => '龍', 12244 => '龜', 12245 => '龠', 12290 => '.', 12342 => '〒', 12344 => '十', 12345 => '卄', 12346 => '卅', 12447 => 'より', 12543 => 'コト', 12593 => 'ᄀ', 12594 => 'ᄁ', 12595 => 'ᆪ', 12596 => 'ᄂ', 12597 => 'ᆬ', 12598 => 'ᆭ', 12599 => 'ᄃ', 12600 => 'ᄄ', 12601 => 'ᄅ', 12602 => 'ᆰ', 12603 => 'ᆱ', 12604 => 'ᆲ', 12605 => 'ᆳ', 12606 => 'ᆴ', 12607 => 'ᆵ', 12608 => 'ᄚ', 12609 => 'ᄆ', 12610 => 'ᄇ', 12611 => 'ᄈ', 12612 => 'ᄡ', 12613 => 'ᄉ', 12614 => 'ᄊ', 12615 => 'ᄋ', 12616 => 'ᄌ', 12617 => 'ᄍ', 12618 => 'ᄎ', 12619 => 'ᄏ', 12620 => 'ᄐ', 12621 => 'ᄑ', 12622 => 'ᄒ', 12623 => 'ᅡ', 12624 => 'ᅢ', 12625 => 'ᅣ', 12626 => 'ᅤ', 12627 => 'ᅥ', 12628 => 'ᅦ', 12629 => 'ᅧ', 12630 => 'ᅨ', 12631 => 'ᅩ', 12632 => 'ᅪ', 12633 => 'ᅫ', 12634 => 'ᅬ', 12635 => 'ᅭ', 12636 => 'ᅮ', 12637 => 'ᅯ', 12638 => 'ᅰ', 12639 => 'ᅱ', 12640 => 'ᅲ', 12641 => 'ᅳ', 12642 => 'ᅴ', 12643 => 'ᅵ', 12645 => 'ᄔ', 12646 => 'ᄕ', 12647 => 'ᇇ', 12648 => 'ᇈ', 12649 => 'ᇌ', 12650 => 'ᇎ', 12651 => 'ᇓ', 12652 => 'ᇗ', 12653 => 'ᇙ', 12654 => 'ᄜ', 12655 => 'ᇝ', 12656 => 'ᇟ', 12657 => 'ᄝ', 12658 => 'ᄞ', 12659 => 'ᄠ', 12660 => 'ᄢ', 12661 => 'ᄣ', 12662 => 'ᄧ', 12663 => 'ᄩ', 12664 => 'ᄫ', 12665 => 'ᄬ', 12666 => 'ᄭ', 12667 => 'ᄮ', 12668 => 'ᄯ', 12669 => 'ᄲ', 12670 => 'ᄶ', 12671 => 'ᅀ', 12672 => 'ᅇ', 12673 => 'ᅌ', 12674 => 'ᇱ', 12675 => 'ᇲ', 12676 => 'ᅗ', 12677 => 'ᅘ', 12678 => 'ᅙ', 12679 => 'ᆄ', 12680 => 'ᆅ', 12681 => 'ᆈ', 12682 => 'ᆑ', 12683 => 'ᆒ', 12684 => 'ᆔ', 12685 => 'ᆞ', 12686 => 'ᆡ', 12690 => '一', 12691 => '二', 12692 => '三', 12693 => '四', 12694 => '上', 12695 => '中', 12696 => '下', 12697 => '甲', 12698 => '乙', 12699 => '丙', 12700 => '丁', 12701 => '天', 12702 => '地', 12703 => '人', 12868 => '問', 12869 => '幼', 12870 => '文', 12871 => '箏', 12880 => 'pte', 12881 => '21', 12882 => '22', 12883 => '23', 12884 => '24', 12885 => '25', 12886 => '26', 12887 => '27', 12888 => '28', 12889 => '29', 12890 => '30', 12891 => '31', 12892 => '32', 12893 => '33', 12894 => '34', 12895 => '35', 12896 => 'ᄀ', 12897 => 'ᄂ', 12898 => 'ᄃ', 12899 => 'ᄅ', 12900 => 'ᄆ', 12901 => 'ᄇ', 12902 => 'ᄉ', 12903 => 'ᄋ', 12904 => 'ᄌ', 12905 => 'ᄎ', 12906 => 'ᄏ', 12907 => 'ᄐ', 12908 => 'ᄑ', 12909 => 'ᄒ', 12910 => '가', 12911 => '나', 12912 => '다', 12913 => '라', 12914 => '마', 12915 => '바', 12916 => '사', 12917 => '아', 12918 => '자', 12919 => '차', 12920 => '카', 12921 => '타', 12922 => '파', 12923 => '하', 12924 => '참고', 12925 => '주의', 12926 => '우', 12928 => '一', 12929 => '二', 12930 => '三', 12931 => '四', 12932 => '五', 12933 => '六', 12934 => '七', 12935 => '八', 12936 => '九', 12937 => '十', 12938 => '月', 12939 => '火', 12940 => '水', 12941 => '木', 12942 => '金', 12943 => '土', 12944 => '日', 12945 => '株', 12946 => '有', 12947 => '社', 12948 => '名', 12949 => '特', 12950 => '財', 12951 => '祝', 12952 => '労', 12953 => '秘', 12954 => '男', 12955 => '女', 12956 => '適', 12957 => '優', 12958 => '印', 12959 => '注', 12960 => '項', 12961 => '休', 12962 => '写', 12963 => '正', 12964 => '上', 12965 => '中', 12966 => '下', 12967 => '左', 12968 => '右', 12969 => '医', 12970 => '宗', 12971 => '学', 12972 => '監', 12973 => '企', 12974 => '資', 12975 => '協', 12976 => '夜', 12977 => '36', 12978 => '37', 12979 => '38', 12980 => '39', 12981 => '40', 12982 => '41', 12983 => '42', 12984 => '43', 12985 => '44', 12986 => '45', 12987 => '46', 12988 => '47', 12989 => '48', 12990 => '49', 12991 => '50', 12992 => '1月', 12993 => '2月', 12994 => '3月', 12995 => '4月', 12996 => '5月', 12997 => '6月', 12998 => '7月', 12999 => '8月', 13000 => '9月', 13001 => '10月', 13002 => '11月', 13003 => '12月', 13004 => 'hg', 13005 => 'erg', 13006 => 'ev', 13007 => 'ltd', 13008 => 'ア', 13009 => 'イ', 13010 => 'ウ', 13011 => 'エ', 13012 => 'オ', 13013 => 'カ', 13014 => 'キ', 13015 => 'ク', 13016 => 'ケ', 13017 => 'コ', 13018 => 'サ', 13019 => 'シ', 13020 => 'ス', 13021 => 'セ', 13022 => 'ソ', 13023 => 'タ', 13024 => 'チ', 13025 => 'ツ', 13026 => 'テ', 13027 => 'ト', 13028 => 'ナ', 13029 => 'ニ', 13030 => 'ヌ', 13031 => 'ネ', 13032 => 'ノ', 13033 => 'ハ', 13034 => 'ヒ', 13035 => 'フ', 13036 => 'ヘ', 13037 => 'ホ', 13038 => 'マ', 13039 => 'ミ', 13040 => 'ム', 13041 => 'メ', 13042 => 'モ', 13043 => 'ヤ', 13044 => 'ユ', 13045 => 'ヨ', 13046 => 'ラ', 13047 => 'リ', 13048 => 'ル', 13049 => 'レ', 13050 => 'ロ', 13051 => 'ワ', 13052 => 'ヰ', 13053 => 'ヱ', 13054 => 'ヲ', 13055 => '令和', 13056 => 'アパート', 13057 => 'アルファ', 13058 => 'アンペア', 13059 => 'アール', 13060 => 'イニング', 13061 => 'インチ', 13062 => 'ウォン', 13063 => 'エスクード', 13064 => 'エーカー', 13065 => 'オンス', 13066 => 'オーム', 13067 => 'カイリ', 13068 => 'カラット', 13069 => 'カロリー', 13070 => 'ガロン', 13071 => 'ガンマ', 13072 => 'ギガ', 13073 => 'ギニー', 13074 => 'キュリー', 13075 => 'ギルダー', 13076 => 'キロ', 13077 => 'キログラム', 13078 => 'キロメートル', 13079 => 'キロワット', 13080 => 'グラム', 13081 => 'グラムトン', 13082 => 'クルゼイロ', 13083 => 'クローネ', 13084 => 'ケース', 13085 => 'コルナ', 13086 => 'コーポ', 13087 => 'サイクル', 13088 => 'サンチーム', 13089 => 'シリング', 13090 => 'センチ', 13091 => 'セント', 13092 => 'ダース', 13093 => 'デシ', 13094 => 'ドル', 13095 => 'トン', 13096 => 'ナノ', 13097 => 'ノット', 13098 => 'ハイツ', 13099 => 'パーセント', 13100 => 'パーツ', 13101 => 'バーレル', 13102 => 'ピアストル', 13103 => 'ピクル', 13104 => 'ピコ', 13105 => 'ビル', 13106 => 'ファラッド', 13107 => 'フィート', 13108 => 'ブッシェル', 13109 => 'フラン', 13110 => 'ヘクタール', 13111 => 'ペソ', 13112 => 'ペニヒ', 13113 => 'ヘルツ', 13114 => 'ペンス', 13115 => 'ページ', 13116 => 'ベータ', 13117 => 'ポイント', 13118 => 'ボルト', 13119 => 'ホン', 13120 => 'ポンド', 13121 => 'ホール', 13122 => 'ホーン', 13123 => 'マイクロ', 13124 => 'マイル', 13125 => 'マッハ', 13126 => 'マルク', 13127 => 'マンション', 13128 => 'ミクロン', 13129 => 'ミリ', 13130 => 'ミリバール', 13131 => 'メガ', 13132 => 'メガトン', 13133 => 'メートル', 13134 => 'ヤード', 13135 => 'ヤール', 13136 => 'ユアン', 13137 => 'リットル', 13138 => 'リラ', 13139 => 'ルピー', 13140 => 'ルーブル', 13141 => 'レム', 13142 => 'レントゲン', 13143 => 'ワット', 13144 => '0点', 13145 => '1点', 13146 => '2点', 13147 => '3点', 13148 => '4点', 13149 => '5点', 13150 => '6点', 13151 => '7点', 13152 => '8点', 13153 => '9点', 13154 => '10点', 13155 => '11点', 13156 => '12点', 13157 => '13点', 13158 => '14点', 13159 => '15点', 13160 => '16点', 13161 => '17点', 13162 => '18点', 13163 => '19点', 13164 => '20点', 13165 => '21点', 13166 => '22点', 13167 => '23点', 13168 => '24点', 13169 => 'hpa', 13170 => 'da', 13171 => 'au', 13172 => 'bar', 13173 => 'ov', 13174 => 'pc', 13175 => 'dm', 13176 => 'dm2', 13177 => 'dm3', 13178 => 'iu', 13179 => '平成', 13180 => '昭和', 13181 => '大正', 13182 => '明治', 13183 => '株式会社', 13184 => 'pa', 13185 => 'na', 13186 => 'μa', 13187 => 'ma', 13188 => 'ka', 13189 => 'kb', 13190 => 'mb', 13191 => 'gb', 13192 => 'cal', 13193 => 'kcal', 13194 => 'pf', 13195 => 'nf', 13196 => 'μf', 13197 => 'μg', 13198 => 'mg', 13199 => 'kg', 13200 => 'hz', 13201 => 'khz', 13202 => 'mhz', 13203 => 'ghz', 13204 => 'thz', 13205 => 'μl', 13206 => 'ml', 13207 => 'dl', 13208 => 'kl', 13209 => 'fm', 13210 => 'nm', 13211 => 'μm', 13212 => 'mm', 13213 => 'cm', 13214 => 'km', 13215 => 'mm2', 13216 => 'cm2', 13217 => 'm2', 13218 => 'km2', 13219 => 'mm3', 13220 => 'cm3', 13221 => 'm3', 13222 => 'km3', 13223 => 'm∕s', 13224 => 'm∕s2', 13225 => 'pa', 13226 => 'kpa', 13227 => 'mpa', 13228 => 'gpa', 13229 => 'rad', 13230 => 'rad∕s', 13231 => 'rad∕s2', 13232 => 'ps', 13233 => 'ns', 13234 => 'μs', 13235 => 'ms', 13236 => 'pv', 13237 => 'nv', 13238 => 'μv', 13239 => 'mv', 13240 => 'kv', 13241 => 'mv', 13242 => 'pw', 13243 => 'nw', 13244 => 'μw', 13245 => 'mw', 13246 => 'kw', 13247 => 'mw', 13248 => 'kω', 13249 => 'mω', 13251 => 'bq', 13252 => 'cc', 13253 => 'cd', 13254 => 'c∕kg', 13256 => 'db', 13257 => 'gy', 13258 => 'ha', 13259 => 'hp', 13260 => 'in', 13261 => 'kk', 13262 => 'km', 13263 => 'kt', 13264 => 'lm', 13265 => 'ln', 13266 => 'log', 13267 => 'lx', 13268 => 'mb', 13269 => 'mil', 13270 => 'mol', 13271 => 'ph', 13273 => 'ppm', 13274 => 'pr', 13275 => 'sr', 13276 => 'sv', 13277 => 'wb', 13278 => 'v∕m', 13279 => 'a∕m', 13280 => '1日', 13281 => '2日', 13282 => '3日', 13283 => '4日', 13284 => '5日', 13285 => '6日', 13286 => '7日', 13287 => '8日', 13288 => '9日', 13289 => '10日', 13290 => '11日', 13291 => '12日', 13292 => '13日', 13293 => '14日', 13294 => '15日', 13295 => '16日', 13296 => '17日', 13297 => '18日', 13298 => '19日', 13299 => '20日', 13300 => '21日', 13301 => '22日', 13302 => '23日', 13303 => '24日', 13304 => '25日', 13305 => '26日', 13306 => '27日', 13307 => '28日', 13308 => '29日', 13309 => '30日', 13310 => '31日', 13311 => 'gal', 42560 => 'ꙁ', 42562 => 'ꙃ', 42564 => 'ꙅ', 42566 => 'ꙇ', 42568 => 'ꙉ', 42570 => 'ꙋ', 42572 => 'ꙍ', 42574 => 'ꙏ', 42576 => 'ꙑ', 42578 => 'ꙓ', 42580 => 'ꙕ', 42582 => 'ꙗ', 42584 => 'ꙙ', 42586 => 'ꙛ', 42588 => 'ꙝ', 42590 => 'ꙟ', 42592 => 'ꙡ', 42594 => 'ꙣ', 42596 => 'ꙥ', 42598 => 'ꙧ', 42600 => 'ꙩ', 42602 => 'ꙫ', 42604 => 'ꙭ', 42624 => 'ꚁ', 42626 => 'ꚃ', 42628 => 'ꚅ', 42630 => 'ꚇ', 42632 => 'ꚉ', 42634 => 'ꚋ', 42636 => 'ꚍ', 42638 => 'ꚏ', 42640 => 'ꚑ', 42642 => 'ꚓ', 42644 => 'ꚕ', 42646 => 'ꚗ', 42648 => 'ꚙ', 42650 => 'ꚛ', 42652 => 'ъ', 42653 => 'ь', 42786 => 'ꜣ', 42788 => 'ꜥ', 42790 => 'ꜧ', 42792 => 'ꜩ', 42794 => 'ꜫ', 42796 => 'ꜭ', 42798 => 'ꜯ', 42802 => 'ꜳ', 42804 => 'ꜵ', 42806 => 'ꜷ', 42808 => 'ꜹ', 42810 => 'ꜻ', 42812 => 'ꜽ', 42814 => 'ꜿ', 42816 => 'ꝁ', 42818 => 'ꝃ', 42820 => 'ꝅ', 42822 => 'ꝇ', 42824 => 'ꝉ', 42826 => 'ꝋ', 42828 => 'ꝍ', 42830 => 'ꝏ', 42832 => 'ꝑ', 42834 => 'ꝓ', 42836 => 'ꝕ', 42838 => 'ꝗ', 42840 => 'ꝙ', 42842 => 'ꝛ', 42844 => 'ꝝ', 42846 => 'ꝟ', 42848 => 'ꝡ', 42850 => 'ꝣ', 42852 => 'ꝥ', 42854 => 'ꝧ', 42856 => 'ꝩ', 42858 => 'ꝫ', 42860 => 'ꝭ', 42862 => 'ꝯ', 42864 => 'ꝯ', 42873 => 'ꝺ', 42875 => 'ꝼ', 42877 => 'ᵹ', 42878 => 'ꝿ', 42880 => 'ꞁ', 42882 => 'ꞃ', 42884 => 'ꞅ', 42886 => 'ꞇ', 42891 => 'ꞌ', 42893 => 'ɥ', 42896 => 'ꞑ', 42898 => 'ꞓ', 42902 => 'ꞗ', 42904 => 'ꞙ', 42906 => 'ꞛ', 42908 => 'ꞝ', 42910 => 'ꞟ', 42912 => 'ꞡ', 42914 => 'ꞣ', 42916 => 'ꞥ', 42918 => 'ꞧ', 42920 => 'ꞩ', 42922 => 'ɦ', 42923 => 'ɜ', 42924 => 'ɡ', 42925 => 'ɬ', 42926 => 'ɪ', 42928 => 'ʞ', 42929 => 'ʇ', 42930 => 'ʝ', 42931 => 'ꭓ', 42932 => 'ꞵ', 42934 => 'ꞷ', 42936 => 'ꞹ', 42938 => 'ꞻ', 42940 => 'ꞽ', 42942 => 'ꞿ', 42946 => 'ꟃ', 42948 => 'ꞔ', 42949 => 'ʂ', 42950 => 'ᶎ', 42951 => 'ꟈ', 42953 => 'ꟊ', 42997 => 'ꟶ', 43000 => 'ħ', 43001 => 'œ', 43868 => 'ꜧ', 43869 => 'ꬷ', 43870 => 'ɫ', 43871 => 'ꭒ', 43881 => 'ʍ', 43888 => 'Ꭰ', 43889 => 'Ꭱ', 43890 => 'Ꭲ', 43891 => 'Ꭳ', 43892 => 'Ꭴ', 43893 => 'Ꭵ', 43894 => 'Ꭶ', 43895 => 'Ꭷ', 43896 => 'Ꭸ', 43897 => 'Ꭹ', 43898 => 'Ꭺ', 43899 => 'Ꭻ', 43900 => 'Ꭼ', 43901 => 'Ꭽ', 43902 => 'Ꭾ', 43903 => 'Ꭿ', 43904 => 'Ꮀ', 43905 => 'Ꮁ', 43906 => 'Ꮂ', 43907 => 'Ꮃ', 43908 => 'Ꮄ', 43909 => 'Ꮅ', 43910 => 'Ꮆ', 43911 => 'Ꮇ', 43912 => 'Ꮈ', 43913 => 'Ꮉ', 43914 => 'Ꮊ', 43915 => 'Ꮋ', 43916 => 'Ꮌ', 43917 => 'Ꮍ', 43918 => 'Ꮎ', 43919 => 'Ꮏ', 43920 => 'Ꮐ', 43921 => 'Ꮑ', 43922 => 'Ꮒ', 43923 => 'Ꮓ', 43924 => 'Ꮔ', 43925 => 'Ꮕ', 43926 => 'Ꮖ', 43927 => 'Ꮗ', 43928 => 'Ꮘ', 43929 => 'Ꮙ', 43930 => 'Ꮚ', 43931 => 'Ꮛ', 43932 => 'Ꮜ', 43933 => 'Ꮝ', 43934 => 'Ꮞ', 43935 => 'Ꮟ', 43936 => 'Ꮠ', 43937 => 'Ꮡ', 43938 => 'Ꮢ', 43939 => 'Ꮣ', 43940 => 'Ꮤ', 43941 => 'Ꮥ', 43942 => 'Ꮦ', 43943 => 'Ꮧ', 43944 => 'Ꮨ', 43945 => 'Ꮩ', 43946 => 'Ꮪ', 43947 => 'Ꮫ', 43948 => 'Ꮬ', 43949 => 'Ꮭ', 43950 => 'Ꮮ', 43951 => 'Ꮯ', 43952 => 'Ꮰ', 43953 => 'Ꮱ', 43954 => 'Ꮲ', 43955 => 'Ꮳ', 43956 => 'Ꮴ', 43957 => 'Ꮵ', 43958 => 'Ꮶ', 43959 => 'Ꮷ', 43960 => 'Ꮸ', 43961 => 'Ꮹ', 43962 => 'Ꮺ', 43963 => 'Ꮻ', 43964 => 'Ꮼ', 43965 => 'Ꮽ', 43966 => 'Ꮾ', 43967 => 'Ꮿ', 63744 => '豈', 63745 => '更', 63746 => '車', 63747 => '賈', 63748 => '滑', 63749 => '串', 63750 => '句', 63751 => '龜', 63752 => '龜', 63753 => '契', 63754 => '金', 63755 => '喇', 63756 => '奈', 63757 => '懶', 63758 => '癩', 63759 => '羅', 63760 => '蘿', 63761 => '螺', 63762 => '裸', 63763 => '邏', 63764 => '樂', 63765 => '洛', 63766 => '烙', 63767 => '珞', 63768 => '落', 63769 => '酪', 63770 => '駱', 63771 => '亂', 63772 => '卵', 63773 => '欄', 63774 => '爛', 63775 => '蘭', 63776 => '鸞', 63777 => '嵐', 63778 => '濫', 63779 => '藍', 63780 => '襤', 63781 => '拉', 63782 => '臘', 63783 => '蠟', 63784 => '廊', 63785 => '朗', 63786 => '浪', 63787 => '狼', 63788 => '郎', 63789 => '來', 63790 => '冷', 63791 => '勞', 63792 => '擄', 63793 => '櫓', 63794 => '爐', 63795 => '盧', 63796 => '老', 63797 => '蘆', 63798 => '虜', 63799 => '路', 63800 => '露', 63801 => '魯', 63802 => '鷺', 63803 => '碌', 63804 => '祿', 63805 => '綠', 63806 => '菉', 63807 => '錄', 63808 => '鹿', 63809 => '論', 63810 => '壟', 63811 => '弄', 63812 => '籠', 63813 => '聾', 63814 => '牢', 63815 => '磊', 63816 => '賂', 63817 => '雷', 63818 => '壘', 63819 => '屢', 63820 => '樓', 63821 => '淚', 63822 => '漏', 63823 => '累', 63824 => '縷', 63825 => '陋', 63826 => '勒', 63827 => '肋', 63828 => '凜', 63829 => '凌', 63830 => '稜', 63831 => '綾', 63832 => '菱', 63833 => '陵', 63834 => '讀', 63835 => '拏', 63836 => '樂', 63837 => '諾', 63838 => '丹', 63839 => '寧', 63840 => '怒', 63841 => '率', 63842 => '異', 63843 => '北', 63844 => '磻', 63845 => '便', 63846 => '復', 63847 => '不', 63848 => '泌', 63849 => '數', 63850 => '索', 63851 => '參', 63852 => '塞', 63853 => '省', 63854 => '葉', 63855 => '說', 63856 => '殺', 63857 => '辰', 63858 => '沈', 63859 => '拾', 63860 => '若', 63861 => '掠', 63862 => '略', 63863 => '亮', 63864 => '兩', 63865 => '凉', 63866 => '梁', 63867 => '糧', 63868 => '良', 63869 => '諒', 63870 => '量', 63871 => '勵', 63872 => '呂', 63873 => '女', 63874 => '廬', 63875 => '旅', 63876 => '濾', 63877 => '礪', 63878 => '閭', 63879 => '驪', 63880 => '麗', 63881 => '黎', 63882 => '力', 63883 => '曆', 63884 => '歷', 63885 => '轢', 63886 => '年', 63887 => '憐', 63888 => '戀', 63889 => '撚', 63890 => '漣', 63891 => '煉', 63892 => '璉', 63893 => '秊', 63894 => '練', 63895 => '聯', 63896 => '輦', 63897 => '蓮', 63898 => '連', 63899 => '鍊', 63900 => '列', 63901 => '劣', 63902 => '咽', 63903 => '烈', 63904 => '裂', 63905 => '說', 63906 => '廉', 63907 => '念', 63908 => '捻', 63909 => '殮', 63910 => '簾', 63911 => '獵', 63912 => '令', 63913 => '囹', 63914 => '寧', 63915 => '嶺', 63916 => '怜', 63917 => '玲', 63918 => '瑩', 63919 => '羚', 63920 => '聆', 63921 => '鈴', 63922 => '零', 63923 => '靈', 63924 => '領', 63925 => '例', 63926 => '禮', 63927 => '醴', 63928 => '隸', 63929 => '惡', 63930 => '了', 63931 => '僚', 63932 => '寮', 63933 => '尿', 63934 => '料', 63935 => '樂', 63936 => '燎', 63937 => '療', 63938 => '蓼', 63939 => '遼', 63940 => '龍', 63941 => '暈', 63942 => '阮', 63943 => '劉', 63944 => '杻', 63945 => '柳', 63946 => '流', 63947 => '溜', 63948 => '琉', 63949 => '留', 63950 => '硫', 63951 => '紐', 63952 => '類', 63953 => '六', 63954 => '戮', 63955 => '陸', 63956 => '倫', 63957 => '崙', 63958 => '淪', 63959 => '輪', 63960 => '律', 63961 => '慄', 63962 => '栗', 63963 => '率', 63964 => '隆', 63965 => '利', 63966 => '吏', 63967 => '履', 63968 => '易', 63969 => '李', 63970 => '梨', 63971 => '泥', 63972 => '理', 63973 => '痢', 63974 => '罹', 63975 => '裏', 63976 => '裡', 63977 => '里', 63978 => '離', 63979 => '匿', 63980 => '溺', 63981 => '吝', 63982 => '燐', 63983 => '璘', 63984 => '藺', 63985 => '隣', 63986 => '鱗', 63987 => '麟', 63988 => '林', 63989 => '淋', 63990 => '臨', 63991 => '立', 63992 => '笠', 63993 => '粒', 63994 => '狀', 63995 => '炙', 63996 => '識', 63997 => '什', 63998 => '茶', 63999 => '刺', 64000 => '切', 64001 => '度', 64002 => '拓', 64003 => '糖', 64004 => '宅', 64005 => '洞', 64006 => '暴', 64007 => '輻', 64008 => '行', 64009 => '降', 64010 => '見', 64011 => '廓', 64012 => '兀', 64013 => '嗀', 64016 => '塚', 64018 => '晴', 64021 => '凞', 64022 => '猪', 64023 => '益', 64024 => '礼', 64025 => '神', 64026 => '祥', 64027 => '福', 64028 => '靖', 64029 => '精', 64030 => '羽', 64032 => '蘒', 64034 => '諸', 64037 => '逸', 64038 => '都', 64042 => '飯', 64043 => '飼', 64044 => '館', 64045 => '鶴', 64046 => '郞', 64047 => '隷', 64048 => '侮', 64049 => '僧', 64050 => '免', 64051 => '勉', 64052 => '勤', 64053 => '卑', 64054 => '喝', 64055 => '嘆', 64056 => '器', 64057 => '塀', 64058 => '墨', 64059 => '層', 64060 => '屮', 64061 => '悔', 64062 => '慨', 64063 => '憎', 64064 => '懲', 64065 => '敏', 64066 => '既', 64067 => '暑', 64068 => '梅', 64069 => '海', 64070 => '渚', 64071 => '漢', 64072 => '煮', 64073 => '爫', 64074 => '琢', 64075 => '碑', 64076 => '社', 64077 => '祉', 64078 => '祈', 64079 => '祐', 64080 => '祖', 64081 => '祝', 64082 => '禍', 64083 => '禎', 64084 => '穀', 64085 => '突', 64086 => '節', 64087 => '練', 64088 => '縉', 64089 => '繁', 64090 => '署', 64091 => '者', 64092 => '臭', 64093 => '艹', 64094 => '艹', 64095 => '著', 64096 => '褐', 64097 => '視', 64098 => '謁', 64099 => '謹', 64100 => '賓', 64101 => '贈', 64102 => '辶', 64103 => '逸', 64104 => '難', 64105 => '響', 64106 => '頻', 64107 => '恵', 64108 => '𤋮', 64109 => '舘', 64112 => '並', 64113 => '况', 64114 => '全', 64115 => '侀', 64116 => '充', 64117 => '冀', 64118 => '勇', 64119 => '勺', 64120 => '喝', 64121 => '啕', 64122 => '喙', 64123 => '嗢', 64124 => '塚', 64125 => '墳', 64126 => '奄', 64127 => '奔', 64128 => '婢', 64129 => '嬨', 64130 => '廒', 64131 => '廙', 64132 => '彩', 64133 => '徭', 64134 => '惘', 64135 => '慎', 64136 => '愈', 64137 => '憎', 64138 => '慠', 64139 => '懲', 64140 => '戴', 64141 => '揄', 64142 => '搜', 64143 => '摒', 64144 => '敖', 64145 => '晴', 64146 => '朗', 64147 => '望', 64148 => '杖', 64149 => '歹', 64150 => '殺', 64151 => '流', 64152 => '滛', 64153 => '滋', 64154 => '漢', 64155 => '瀞', 64156 => '煮', 64157 => '瞧', 64158 => '爵', 64159 => '犯', 64160 => '猪', 64161 => '瑱', 64162 => '甆', 64163 => '画', 64164 => '瘝', 64165 => '瘟', 64166 => '益', 64167 => '盛', 64168 => '直', 64169 => '睊', 64170 => '着', 64171 => '磌', 64172 => '窱', 64173 => '節', 64174 => '类', 64175 => '絛', 64176 => '練', 64177 => '缾', 64178 => '者', 64179 => '荒', 64180 => '華', 64181 => '蝹', 64182 => '襁', 64183 => '覆', 64184 => '視', 64185 => '調', 64186 => '諸', 64187 => '請', 64188 => '謁', 64189 => '諾', 64190 => '諭', 64191 => '謹', 64192 => '變', 64193 => '贈', 64194 => '輸', 64195 => '遲', 64196 => '醙', 64197 => '鉶', 64198 => '陼', 64199 => '難', 64200 => '靖', 64201 => '韛', 64202 => '響', 64203 => '頋', 64204 => '頻', 64205 => '鬒', 64206 => '龜', 64207 => '𢡊', 64208 => '𢡄', 64209 => '𣏕', 64210 => '㮝', 64211 => '䀘', 64212 => '䀹', 64213 => '𥉉', 64214 => '𥳐', 64215 => '𧻓', 64216 => '齃', 64217 => '龎', 64256 => 'ff', 64257 => 'fi', 64258 => 'fl', 64259 => 'ffi', 64260 => 'ffl', 64261 => 'st', 64262 => 'st', 64275 => 'մն', 64276 => 'մե', 64277 => 'մի', 64278 => 'վն', 64279 => 'մխ', 64285 => 'יִ', 64287 => 'ײַ', 64288 => 'ע', 64289 => 'א', 64290 => 'ד', 64291 => 'ה', 64292 => 'כ', 64293 => 'ל', 64294 => 'ם', 64295 => 'ר', 64296 => 'ת', 64298 => 'שׁ', 64299 => 'שׂ', 64300 => 'שּׁ', 64301 => 'שּׂ', 64302 => 'אַ', 64303 => 'אָ', 64304 => 'אּ', 64305 => 'בּ', 64306 => 'גּ', 64307 => 'דּ', 64308 => 'הּ', 64309 => 'וּ', 64310 => 'זּ', 64312 => 'טּ', 64313 => 'יּ', 64314 => 'ךּ', 64315 => 'כּ', 64316 => 'לּ', 64318 => 'מּ', 64320 => 'נּ', 64321 => 'סּ', 64323 => 'ףּ', 64324 => 'פּ', 64326 => 'צּ', 64327 => 'קּ', 64328 => 'רּ', 64329 => 'שּ', 64330 => 'תּ', 64331 => 'וֹ', 64332 => 'בֿ', 64333 => 'כֿ', 64334 => 'פֿ', 64335 => 'אל', 64336 => 'ٱ', 64337 => 'ٱ', 64338 => 'ٻ', 64339 => 'ٻ', 64340 => 'ٻ', 64341 => 'ٻ', 64342 => 'پ', 64343 => 'پ', 64344 => 'پ', 64345 => 'پ', 64346 => 'ڀ', 64347 => 'ڀ', 64348 => 'ڀ', 64349 => 'ڀ', 64350 => 'ٺ', 64351 => 'ٺ', 64352 => 'ٺ', 64353 => 'ٺ', 64354 => 'ٿ', 64355 => 'ٿ', 64356 => 'ٿ', 64357 => 'ٿ', 64358 => 'ٹ', 64359 => 'ٹ', 64360 => 'ٹ', 64361 => 'ٹ', 64362 => 'ڤ', 64363 => 'ڤ', 64364 => 'ڤ', 64365 => 'ڤ', 64366 => 'ڦ', 64367 => 'ڦ', 64368 => 'ڦ', 64369 => 'ڦ', 64370 => 'ڄ', 64371 => 'ڄ', 64372 => 'ڄ', 64373 => 'ڄ', 64374 => 'ڃ', 64375 => 'ڃ', 64376 => 'ڃ', 64377 => 'ڃ', 64378 => 'چ', 64379 => 'چ', 64380 => 'چ', 64381 => 'چ', 64382 => 'ڇ', 64383 => 'ڇ', 64384 => 'ڇ', 64385 => 'ڇ', 64386 => 'ڍ', 64387 => 'ڍ', 64388 => 'ڌ', 64389 => 'ڌ', 64390 => 'ڎ', 64391 => 'ڎ', 64392 => 'ڈ', 64393 => 'ڈ', 64394 => 'ژ', 64395 => 'ژ', 64396 => 'ڑ', 64397 => 'ڑ', 64398 => 'ک', 64399 => 'ک', 64400 => 'ک', 64401 => 'ک', 64402 => 'گ', 64403 => 'گ', 64404 => 'گ', 64405 => 'گ', 64406 => 'ڳ', 64407 => 'ڳ', 64408 => 'ڳ', 64409 => 'ڳ', 64410 => 'ڱ', 64411 => 'ڱ', 64412 => 'ڱ', 64413 => 'ڱ', 64414 => 'ں', 64415 => 'ں', 64416 => 'ڻ', 64417 => 'ڻ', 64418 => 'ڻ', 64419 => 'ڻ', 64420 => 'ۀ', 64421 => 'ۀ', 64422 => 'ہ', 64423 => 'ہ', 64424 => 'ہ', 64425 => 'ہ', 64426 => 'ھ', 64427 => 'ھ', 64428 => 'ھ', 64429 => 'ھ', 64430 => 'ے', 64431 => 'ے', 64432 => 'ۓ', 64433 => 'ۓ', 64467 => 'ڭ', 64468 => 'ڭ', 64469 => 'ڭ', 64470 => 'ڭ', 64471 => 'ۇ', 64472 => 'ۇ', 64473 => 'ۆ', 64474 => 'ۆ', 64475 => 'ۈ', 64476 => 'ۈ', 64477 => 'ۇٴ', 64478 => 'ۋ', 64479 => 'ۋ', 64480 => 'ۅ', 64481 => 'ۅ', 64482 => 'ۉ', 64483 => 'ۉ', 64484 => 'ې', 64485 => 'ې', 64486 => 'ې', 64487 => 'ې', 64488 => 'ى', 64489 => 'ى', 64490 => 'ئا', 64491 => 'ئا', 64492 => 'ئە', 64493 => 'ئە', 64494 => 'ئو', 64495 => 'ئو', 64496 => 'ئۇ', 64497 => 'ئۇ', 64498 => 'ئۆ', 64499 => 'ئۆ', 64500 => 'ئۈ', 64501 => 'ئۈ', 64502 => 'ئې', 64503 => 'ئې', 64504 => 'ئې', 64505 => 'ئى', 64506 => 'ئى', 64507 => 'ئى', 64508 => 'ی', 64509 => 'ی', 64510 => 'ی', 64511 => 'ی', 64512 => 'ئج', 64513 => 'ئح', 64514 => 'ئم', 64515 => 'ئى', 64516 => 'ئي', 64517 => 'بج', 64518 => 'بح', 64519 => 'بخ', 64520 => 'بم', 64521 => 'بى', 64522 => 'بي', 64523 => 'تج', 64524 => 'تح', 64525 => 'تخ', 64526 => 'تم', 64527 => 'تى', 64528 => 'تي', 64529 => 'ثج', 64530 => 'ثم', 64531 => 'ثى', 64532 => 'ثي', 64533 => 'جح', 64534 => 'جم', 64535 => 'حج', 64536 => 'حم', 64537 => 'خج', 64538 => 'خح', 64539 => 'خم', 64540 => 'سج', 64541 => 'سح', 64542 => 'سخ', 64543 => 'سم', 64544 => 'صح', 64545 => 'صم', 64546 => 'ضج', 64547 => 'ضح', 64548 => 'ضخ', 64549 => 'ضم', 64550 => 'طح', 64551 => 'طم', 64552 => 'ظم', 64553 => 'عج', 64554 => 'عم', 64555 => 'غج', 64556 => 'غم', 64557 => 'فج', 64558 => 'فح', 64559 => 'فخ', 64560 => 'فم', 64561 => 'فى', 64562 => 'في', 64563 => 'قح', 64564 => 'قم', 64565 => 'قى', 64566 => 'قي', 64567 => 'كا', 64568 => 'كج', 64569 => 'كح', 64570 => 'كخ', 64571 => 'كل', 64572 => 'كم', 64573 => 'كى', 64574 => 'كي', 64575 => 'لج', 64576 => 'لح', 64577 => 'لخ', 64578 => 'لم', 64579 => 'لى', 64580 => 'لي', 64581 => 'مج', 64582 => 'مح', 64583 => 'مخ', 64584 => 'مم', 64585 => 'مى', 64586 => 'مي', 64587 => 'نج', 64588 => 'نح', 64589 => 'نخ', 64590 => 'نم', 64591 => 'نى', 64592 => 'ني', 64593 => 'هج', 64594 => 'هم', 64595 => 'هى', 64596 => 'هي', 64597 => 'يج', 64598 => 'يح', 64599 => 'يخ', 64600 => 'يم', 64601 => 'يى', 64602 => 'يي', 64603 => 'ذٰ', 64604 => 'رٰ', 64605 => 'ىٰ', 64612 => 'ئر', 64613 => 'ئز', 64614 => 'ئم', 64615 => 'ئن', 64616 => 'ئى', 64617 => 'ئي', 64618 => 'بر', 64619 => 'بز', 64620 => 'بم', 64621 => 'بن', 64622 => 'بى', 64623 => 'بي', 64624 => 'تر', 64625 => 'تز', 64626 => 'تم', 64627 => 'تن', 64628 => 'تى', 64629 => 'تي', 64630 => 'ثر', 64631 => 'ثز', 64632 => 'ثم', 64633 => 'ثن', 64634 => 'ثى', 64635 => 'ثي', 64636 => 'فى', 64637 => 'في', 64638 => 'قى', 64639 => 'قي', 64640 => 'كا', 64641 => 'كل', 64642 => 'كم', 64643 => 'كى', 64644 => 'كي', 64645 => 'لم', 64646 => 'لى', 64647 => 'لي', 64648 => 'ما', 64649 => 'مم', 64650 => 'نر', 64651 => 'نز', 64652 => 'نم', 64653 => 'نن', 64654 => 'نى', 64655 => 'ني', 64656 => 'ىٰ', 64657 => 'ير', 64658 => 'يز', 64659 => 'يم', 64660 => 'ين', 64661 => 'يى', 64662 => 'يي', 64663 => 'ئج', 64664 => 'ئح', 64665 => 'ئخ', 64666 => 'ئم', 64667 => 'ئه', 64668 => 'بج', 64669 => 'بح', 64670 => 'بخ', 64671 => 'بم', 64672 => 'به', 64673 => 'تج', 64674 => 'تح', 64675 => 'تخ', 64676 => 'تم', 64677 => 'ته', 64678 => 'ثم', 64679 => 'جح', 64680 => 'جم', 64681 => 'حج', 64682 => 'حم', 64683 => 'خج', 64684 => 'خم', 64685 => 'سج', 64686 => 'سح', 64687 => 'سخ', 64688 => 'سم', 64689 => 'صح', 64690 => 'صخ', 64691 => 'صم', 64692 => 'ضج', 64693 => 'ضح', 64694 => 'ضخ', 64695 => 'ضم', 64696 => 'طح', 64697 => 'ظم', 64698 => 'عج', 64699 => 'عم', 64700 => 'غج', 64701 => 'غم', 64702 => 'فج', 64703 => 'فح', 64704 => 'فخ', 64705 => 'فم', 64706 => 'قح', 64707 => 'قم', 64708 => 'كج', 64709 => 'كح', 64710 => 'كخ', 64711 => 'كل', 64712 => 'كم', 64713 => 'لج', 64714 => 'لح', 64715 => 'لخ', 64716 => 'لم', 64717 => 'له', 64718 => 'مج', 64719 => 'مح', 64720 => 'مخ', 64721 => 'مم', 64722 => 'نج', 64723 => 'نح', 64724 => 'نخ', 64725 => 'نم', 64726 => 'نه', 64727 => 'هج', 64728 => 'هم', 64729 => 'هٰ', 64730 => 'يج', 64731 => 'يح', 64732 => 'يخ', 64733 => 'يم', 64734 => 'يه', 64735 => 'ئم', 64736 => 'ئه', 64737 => 'بم', 64738 => 'به', 64739 => 'تم', 64740 => 'ته', 64741 => 'ثم', 64742 => 'ثه', 64743 => 'سم', 64744 => 'سه', 64745 => 'شم', 64746 => 'شه', 64747 => 'كل', 64748 => 'كم', 64749 => 'لم', 64750 => 'نم', 64751 => 'نه', 64752 => 'يم', 64753 => 'يه', 64754 => 'ـَّ', 64755 => 'ـُّ', 64756 => 'ـِّ', 64757 => 'طى', 64758 => 'طي', 64759 => 'عى', 64760 => 'عي', 64761 => 'غى', 64762 => 'غي', 64763 => 'سى', 64764 => 'سي', 64765 => 'شى', 64766 => 'شي', 64767 => 'حى', 64768 => 'حي', 64769 => 'جى', 64770 => 'جي', 64771 => 'خى', 64772 => 'خي', 64773 => 'صى', 64774 => 'صي', 64775 => 'ضى', 64776 => 'ضي', 64777 => 'شج', 64778 => 'شح', 64779 => 'شخ', 64780 => 'شم', 64781 => 'شر', 64782 => 'سر', 64783 => 'صر', 64784 => 'ضر', 64785 => 'طى', 64786 => 'طي', 64787 => 'عى', 64788 => 'عي', 64789 => 'غى', 64790 => 'غي', 64791 => 'سى', 64792 => 'سي', 64793 => 'شى', 64794 => 'شي', 64795 => 'حى', 64796 => 'حي', 64797 => 'جى', 64798 => 'جي', 64799 => 'خى', 64800 => 'خي', 64801 => 'صى', 64802 => 'صي', 64803 => 'ضى', 64804 => 'ضي', 64805 => 'شج', 64806 => 'شح', 64807 => 'شخ', 64808 => 'شم', 64809 => 'شر', 64810 => 'سر', 64811 => 'صر', 64812 => 'ضر', 64813 => 'شج', 64814 => 'شح', 64815 => 'شخ', 64816 => 'شم', 64817 => 'سه', 64818 => 'شه', 64819 => 'طم', 64820 => 'سج', 64821 => 'سح', 64822 => 'سخ', 64823 => 'شج', 64824 => 'شح', 64825 => 'شخ', 64826 => 'طم', 64827 => 'ظم', 64828 => 'اً', 64829 => 'اً', 64848 => 'تجم', 64849 => 'تحج', 64850 => 'تحج', 64851 => 'تحم', 64852 => 'تخم', 64853 => 'تمج', 64854 => 'تمح', 64855 => 'تمخ', 64856 => 'جمح', 64857 => 'جمح', 64858 => 'حمي', 64859 => 'حمى', 64860 => 'سحج', 64861 => 'سجح', 64862 => 'سجى', 64863 => 'سمح', 64864 => 'سمح', 64865 => 'سمج', 64866 => 'سمم', 64867 => 'سمم', 64868 => 'صحح', 64869 => 'صحح', 64870 => 'صمم', 64871 => 'شحم', 64872 => 'شحم', 64873 => 'شجي', 64874 => 'شمخ', 64875 => 'شمخ', 64876 => 'شمم', 64877 => 'شمم', 64878 => 'ضحى', 64879 => 'ضخم', 64880 => 'ضخم', 64881 => 'طمح', 64882 => 'طمح', 64883 => 'طمم', 64884 => 'طمي', 64885 => 'عجم', 64886 => 'عمم', 64887 => 'عمم', 64888 => 'عمى', 64889 => 'غمم', 64890 => 'غمي', 64891 => 'غمى', 64892 => 'فخم', 64893 => 'فخم', 64894 => 'قمح', 64895 => 'قمم', 64896 => 'لحم', 64897 => 'لحي', 64898 => 'لحى', 64899 => 'لجج', 64900 => 'لجج', 64901 => 'لخم', 64902 => 'لخم', 64903 => 'لمح', 64904 => 'لمح', 64905 => 'محج', 64906 => 'محم', 64907 => 'محي', 64908 => 'مجح', 64909 => 'مجم', 64910 => 'مخج', 64911 => 'مخم', 64914 => 'مجخ', 64915 => 'همج', 64916 => 'همم', 64917 => 'نحم', 64918 => 'نحى', 64919 => 'نجم', 64920 => 'نجم', 64921 => 'نجى', 64922 => 'نمي', 64923 => 'نمى', 64924 => 'يمم', 64925 => 'يمم', 64926 => 'بخي', 64927 => 'تجي', 64928 => 'تجى', 64929 => 'تخي', 64930 => 'تخى', 64931 => 'تمي', 64932 => 'تمى', 64933 => 'جمي', 64934 => 'جحى', 64935 => 'جمى', 64936 => 'سخى', 64937 => 'صحي', 64938 => 'شحي', 64939 => 'ضحي', 64940 => 'لجي', 64941 => 'لمي', 64942 => 'يحي', 64943 => 'يجي', 64944 => 'يمي', 64945 => 'ممي', 64946 => 'قمي', 64947 => 'نحي', 64948 => 'قمح', 64949 => 'لحم', 64950 => 'عمي', 64951 => 'كمي', 64952 => 'نجح', 64953 => 'مخي', 64954 => 'لجم', 64955 => 'كمم', 64956 => 'لجم', 64957 => 'نجح', 64958 => 'جحي', 64959 => 'حجي', 64960 => 'مجي', 64961 => 'فمي', 64962 => 'بحي', 64963 => 'كمم', 64964 => 'عجم', 64965 => 'صمم', 64966 => 'سخي', 64967 => 'نجي', 65008 => 'صلے', 65009 => 'قلے', 65010 => 'الله', 65011 => 'اكبر', 65012 => 'محمد', 65013 => 'صلعم', 65014 => 'رسول', 65015 => 'عليه', 65016 => 'وسلم', 65017 => 'صلى', 65020 => 'ریال', 65041 => '、', 65047 => '〖', 65048 => '〗', 65073 => '—', 65074 => '–', 65081 => '〔', 65082 => '〕', 65083 => '【', 65084 => '】', 65085 => '《', 65086 => '》', 65087 => '〈', 65088 => '〉', 65089 => '「', 65090 => '」', 65091 => '『', 65092 => '』', 65105 => '、', 65112 => '—', 65117 => '〔', 65118 => '〕', 65123 => '-', 65137 => 'ـً', 65143 => 'ـَ', 65145 => 'ـُ', 65147 => 'ـِ', 65149 => 'ـّ', 65151 => 'ـْ', 65152 => 'ء', 65153 => 'آ', 65154 => 'آ', 65155 => 'أ', 65156 => 'أ', 65157 => 'ؤ', 65158 => 'ؤ', 65159 => 'إ', 65160 => 'إ', 65161 => 'ئ', 65162 => 'ئ', 65163 => 'ئ', 65164 => 'ئ', 65165 => 'ا', 65166 => 'ا', 65167 => 'ب', 65168 => 'ب', 65169 => 'ب', 65170 => 'ب', 65171 => 'ة', 65172 => 'ة', 65173 => 'ت', 65174 => 'ت', 65175 => 'ت', 65176 => 'ت', 65177 => 'ث', 65178 => 'ث', 65179 => 'ث', 65180 => 'ث', 65181 => 'ج', 65182 => 'ج', 65183 => 'ج', 65184 => 'ج', 65185 => 'ح', 65186 => 'ح', 65187 => 'ح', 65188 => 'ح', 65189 => 'خ', 65190 => 'خ', 65191 => 'خ', 65192 => 'خ', 65193 => 'د', 65194 => 'د', 65195 => 'ذ', 65196 => 'ذ', 65197 => 'ر', 65198 => 'ر', 65199 => 'ز', 65200 => 'ز', 65201 => 'س', 65202 => 'س', 65203 => 'س', 65204 => 'س', 65205 => 'ش', 65206 => 'ش', 65207 => 'ش', 65208 => 'ش', 65209 => 'ص', 65210 => 'ص', 65211 => 'ص', 65212 => 'ص', 65213 => 'ض', 65214 => 'ض', 65215 => 'ض', 65216 => 'ض', 65217 => 'ط', 65218 => 'ط', 65219 => 'ط', 65220 => 'ط', 65221 => 'ظ', 65222 => 'ظ', 65223 => 'ظ', 65224 => 'ظ', 65225 => 'ع', 65226 => 'ع', 65227 => 'ع', 65228 => 'ع', 65229 => 'غ', 65230 => 'غ', 65231 => 'غ', 65232 => 'غ', 65233 => 'ف', 65234 => 'ف', 65235 => 'ف', 65236 => 'ف', 65237 => 'ق', 65238 => 'ق', 65239 => 'ق', 65240 => 'ق', 65241 => 'ك', 65242 => 'ك', 65243 => 'ك', 65244 => 'ك', 65245 => 'ل', 65246 => 'ل', 65247 => 'ل', 65248 => 'ل', 65249 => 'م', 65250 => 'م', 65251 => 'م', 65252 => 'م', 65253 => 'ن', 65254 => 'ن', 65255 => 'ن', 65256 => 'ن', 65257 => 'ه', 65258 => 'ه', 65259 => 'ه', 65260 => 'ه', 65261 => 'و', 65262 => 'و', 65263 => 'ى', 65264 => 'ى', 65265 => 'ي', 65266 => 'ي', 65267 => 'ي', 65268 => 'ي', 65269 => 'لآ', 65270 => 'لآ', 65271 => 'لأ', 65272 => 'لأ', 65273 => 'لإ', 65274 => 'لإ', 65275 => 'لا', 65276 => 'لا', 65293 => '-', 65294 => '.', 65296 => '0', 65297 => '1', 65298 => '2', 65299 => '3', 65300 => '4', 65301 => '5', 65302 => '6', 65303 => '7', 65304 => '8', 65305 => '9', 65313 => 'a', 65314 => 'b', 65315 => 'c', 65316 => 'd', 65317 => 'e', 65318 => 'f', 65319 => 'g', 65320 => 'h', 65321 => 'i', 65322 => 'j', 65323 => 'k', 65324 => 'l', 65325 => 'm', 65326 => 'n', 65327 => 'o', 65328 => 'p', 65329 => 'q', 65330 => 'r', 65331 => 's', 65332 => 't', 65333 => 'u', 65334 => 'v', 65335 => 'w', 65336 => 'x', 65337 => 'y', 65338 => 'z', 65345 => 'a', 65346 => 'b', 65347 => 'c', 65348 => 'd', 65349 => 'e', 65350 => 'f', 65351 => 'g', 65352 => 'h', 65353 => 'i', 65354 => 'j', 65355 => 'k', 65356 => 'l', 65357 => 'm', 65358 => 'n', 65359 => 'o', 65360 => 'p', 65361 => 'q', 65362 => 'r', 65363 => 's', 65364 => 't', 65365 => 'u', 65366 => 'v', 65367 => 'w', 65368 => 'x', 65369 => 'y', 65370 => 'z', 65375 => '⦅', 65376 => '⦆', 65377 => '.', 65378 => '「', 65379 => '」', 65380 => '、', 65381 => '・', 65382 => 'ヲ', 65383 => 'ァ', 65384 => 'ィ', 65385 => 'ゥ', 65386 => 'ェ', 65387 => 'ォ', 65388 => 'ャ', 65389 => 'ュ', 65390 => 'ョ', 65391 => 'ッ', 65392 => 'ー', 65393 => 'ア', 65394 => 'イ', 65395 => 'ウ', 65396 => 'エ', 65397 => 'オ', 65398 => 'カ', 65399 => 'キ', 65400 => 'ク', 65401 => 'ケ', 65402 => 'コ', 65403 => 'サ', 65404 => 'シ', 65405 => 'ス', 65406 => 'セ', 65407 => 'ソ', 65408 => 'タ', 65409 => 'チ', 65410 => 'ツ', 65411 => 'テ', 65412 => 'ト', 65413 => 'ナ', 65414 => 'ニ', 65415 => 'ヌ', 65416 => 'ネ', 65417 => 'ノ', 65418 => 'ハ', 65419 => 'ヒ', 65420 => 'フ', 65421 => 'ヘ', 65422 => 'ホ', 65423 => 'マ', 65424 => 'ミ', 65425 => 'ム', 65426 => 'メ', 65427 => 'モ', 65428 => 'ヤ', 65429 => 'ユ', 65430 => 'ヨ', 65431 => 'ラ', 65432 => 'リ', 65433 => 'ル', 65434 => 'レ', 65435 => 'ロ', 65436 => 'ワ', 65437 => 'ン', 65438 => '゙', 65439 => '゚', 65441 => 'ᄀ', 65442 => 'ᄁ', 65443 => 'ᆪ', 65444 => 'ᄂ', 65445 => 'ᆬ', 65446 => 'ᆭ', 65447 => 'ᄃ', 65448 => 'ᄄ', 65449 => 'ᄅ', 65450 => 'ᆰ', 65451 => 'ᆱ', 65452 => 'ᆲ', 65453 => 'ᆳ', 65454 => 'ᆴ', 65455 => 'ᆵ', 65456 => 'ᄚ', 65457 => 'ᄆ', 65458 => 'ᄇ', 65459 => 'ᄈ', 65460 => 'ᄡ', 65461 => 'ᄉ', 65462 => 'ᄊ', 65463 => 'ᄋ', 65464 => 'ᄌ', 65465 => 'ᄍ', 65466 => 'ᄎ', 65467 => 'ᄏ', 65468 => 'ᄐ', 65469 => 'ᄑ', 65470 => 'ᄒ', 65474 => 'ᅡ', 65475 => 'ᅢ', 65476 => 'ᅣ', 65477 => 'ᅤ', 65478 => 'ᅥ', 65479 => 'ᅦ', 65482 => 'ᅧ', 65483 => 'ᅨ', 65484 => 'ᅩ', 65485 => 'ᅪ', 65486 => 'ᅫ', 65487 => 'ᅬ', 65490 => 'ᅭ', 65491 => 'ᅮ', 65492 => 'ᅯ', 65493 => 'ᅰ', 65494 => 'ᅱ', 65495 => 'ᅲ', 65498 => 'ᅳ', 65499 => 'ᅴ', 65500 => 'ᅵ', 65504 => '¢', 65505 => '£', 65506 => '¬', 65508 => '¦', 65509 => '¥', 65510 => '₩', 65512 => '│', 65513 => '←', 65514 => '↑', 65515 => '→', 65516 => '↓', 65517 => '■', 65518 => '○', 66560 => '𐐨', 66561 => '𐐩', 66562 => '𐐪', 66563 => '𐐫', 66564 => '𐐬', 66565 => '𐐭', 66566 => '𐐮', 66567 => '𐐯', 66568 => '𐐰', 66569 => '𐐱', 66570 => '𐐲', 66571 => '𐐳', 66572 => '𐐴', 66573 => '𐐵', 66574 => '𐐶', 66575 => '𐐷', 66576 => '𐐸', 66577 => '𐐹', 66578 => '𐐺', 66579 => '𐐻', 66580 => '𐐼', 66581 => '𐐽', 66582 => '𐐾', 66583 => '𐐿', 66584 => '𐑀', 66585 => '𐑁', 66586 => '𐑂', 66587 => '𐑃', 66588 => '𐑄', 66589 => '𐑅', 66590 => '𐑆', 66591 => '𐑇', 66592 => '𐑈', 66593 => '𐑉', 66594 => '𐑊', 66595 => '𐑋', 66596 => '𐑌', 66597 => '𐑍', 66598 => '𐑎', 66599 => '𐑏', 66736 => '𐓘', 66737 => '𐓙', 66738 => '𐓚', 66739 => '𐓛', 66740 => '𐓜', 66741 => '𐓝', 66742 => '𐓞', 66743 => '𐓟', 66744 => '𐓠', 66745 => '𐓡', 66746 => '𐓢', 66747 => '𐓣', 66748 => '𐓤', 66749 => '𐓥', 66750 => '𐓦', 66751 => '𐓧', 66752 => '𐓨', 66753 => '𐓩', 66754 => '𐓪', 66755 => '𐓫', 66756 => '𐓬', 66757 => '𐓭', 66758 => '𐓮', 66759 => '𐓯', 66760 => '𐓰', 66761 => '𐓱', 66762 => '𐓲', 66763 => '𐓳', 66764 => '𐓴', 66765 => '𐓵', 66766 => '𐓶', 66767 => '𐓷', 66768 => '𐓸', 66769 => '𐓹', 66770 => '𐓺', 66771 => '𐓻', 68736 => '𐳀', 68737 => '𐳁', 68738 => '𐳂', 68739 => '𐳃', 68740 => '𐳄', 68741 => '𐳅', 68742 => '𐳆', 68743 => '𐳇', 68744 => '𐳈', 68745 => '𐳉', 68746 => '𐳊', 68747 => '𐳋', 68748 => '𐳌', 68749 => '𐳍', 68750 => '𐳎', 68751 => '𐳏', 68752 => '𐳐', 68753 => '𐳑', 68754 => '𐳒', 68755 => '𐳓', 68756 => '𐳔', 68757 => '𐳕', 68758 => '𐳖', 68759 => '𐳗', 68760 => '𐳘', 68761 => '𐳙', 68762 => '𐳚', 68763 => '𐳛', 68764 => '𐳜', 68765 => '𐳝', 68766 => '𐳞', 68767 => '𐳟', 68768 => '𐳠', 68769 => '𐳡', 68770 => '𐳢', 68771 => '𐳣', 68772 => '𐳤', 68773 => '𐳥', 68774 => '𐳦', 68775 => '𐳧', 68776 => '𐳨', 68777 => '𐳩', 68778 => '𐳪', 68779 => '𐳫', 68780 => '𐳬', 68781 => '𐳭', 68782 => '𐳮', 68783 => '𐳯', 68784 => '𐳰', 68785 => '𐳱', 68786 => '𐳲', 71840 => '𑣀', 71841 => '𑣁', 71842 => '𑣂', 71843 => '𑣃', 71844 => '𑣄', 71845 => '𑣅', 71846 => '𑣆', 71847 => '𑣇', 71848 => '𑣈', 71849 => '𑣉', 71850 => '𑣊', 71851 => '𑣋', 71852 => '𑣌', 71853 => '𑣍', 71854 => '𑣎', 71855 => '𑣏', 71856 => '𑣐', 71857 => '𑣑', 71858 => '𑣒', 71859 => '𑣓', 71860 => '𑣔', 71861 => '𑣕', 71862 => '𑣖', 71863 => '𑣗', 71864 => '𑣘', 71865 => '𑣙', 71866 => '𑣚', 71867 => '𑣛', 71868 => '𑣜', 71869 => '𑣝', 71870 => '𑣞', 71871 => '𑣟', 93760 => '𖹠', 93761 => '𖹡', 93762 => '𖹢', 93763 => '𖹣', 93764 => '𖹤', 93765 => '𖹥', 93766 => '𖹦', 93767 => '𖹧', 93768 => '𖹨', 93769 => '𖹩', 93770 => '𖹪', 93771 => '𖹫', 93772 => '𖹬', 93773 => '𖹭', 93774 => '𖹮', 93775 => '𖹯', 93776 => '𖹰', 93777 => '𖹱', 93778 => '𖹲', 93779 => '𖹳', 93780 => '𖹴', 93781 => '𖹵', 93782 => '𖹶', 93783 => '𖹷', 93784 => '𖹸', 93785 => '𖹹', 93786 => '𖹺', 93787 => '𖹻', 93788 => '𖹼', 93789 => '𖹽', 93790 => '𖹾', 93791 => '𖹿', 119134 => '𝅗𝅥', 119135 => '𝅘𝅥', 119136 => '𝅘𝅥𝅮', 119137 => '𝅘𝅥𝅯', 119138 => '𝅘𝅥𝅰', 119139 => '𝅘𝅥𝅱', 119140 => '𝅘𝅥𝅲', 119227 => '𝆹𝅥', 119228 => '𝆺𝅥', 119229 => '𝆹𝅥𝅮', 119230 => '𝆺𝅥𝅮', 119231 => '𝆹𝅥𝅯', 119232 => '𝆺𝅥𝅯', 119808 => 'a', 119809 => 'b', 119810 => 'c', 119811 => 'd', 119812 => 'e', 119813 => 'f', 119814 => 'g', 119815 => 'h', 119816 => 'i', 119817 => 'j', 119818 => 'k', 119819 => 'l', 119820 => 'm', 119821 => 'n', 119822 => 'o', 119823 => 'p', 119824 => 'q', 119825 => 'r', 119826 => 's', 119827 => 't', 119828 => 'u', 119829 => 'v', 119830 => 'w', 119831 => 'x', 119832 => 'y', 119833 => 'z', 119834 => 'a', 119835 => 'b', 119836 => 'c', 119837 => 'd', 119838 => 'e', 119839 => 'f', 119840 => 'g', 119841 => 'h', 119842 => 'i', 119843 => 'j', 119844 => 'k', 119845 => 'l', 119846 => 'm', 119847 => 'n', 119848 => 'o', 119849 => 'p', 119850 => 'q', 119851 => 'r', 119852 => 's', 119853 => 't', 119854 => 'u', 119855 => 'v', 119856 => 'w', 119857 => 'x', 119858 => 'y', 119859 => 'z', 119860 => 'a', 119861 => 'b', 119862 => 'c', 119863 => 'd', 119864 => 'e', 119865 => 'f', 119866 => 'g', 119867 => 'h', 119868 => 'i', 119869 => 'j', 119870 => 'k', 119871 => 'l', 119872 => 'm', 119873 => 'n', 119874 => 'o', 119875 => 'p', 119876 => 'q', 119877 => 'r', 119878 => 's', 119879 => 't', 119880 => 'u', 119881 => 'v', 119882 => 'w', 119883 => 'x', 119884 => 'y', 119885 => 'z', 119886 => 'a', 119887 => 'b', 119888 => 'c', 119889 => 'd', 119890 => 'e', 119891 => 'f', 119892 => 'g', 119894 => 'i', 119895 => 'j', 119896 => 'k', 119897 => 'l', 119898 => 'm', 119899 => 'n', 119900 => 'o', 119901 => 'p', 119902 => 'q', 119903 => 'r', 119904 => 's', 119905 => 't', 119906 => 'u', 119907 => 'v', 119908 => 'w', 119909 => 'x', 119910 => 'y', 119911 => 'z', 119912 => 'a', 119913 => 'b', 119914 => 'c', 119915 => 'd', 119916 => 'e', 119917 => 'f', 119918 => 'g', 119919 => 'h', 119920 => 'i', 119921 => 'j', 119922 => 'k', 119923 => 'l', 119924 => 'm', 119925 => 'n', 119926 => 'o', 119927 => 'p', 119928 => 'q', 119929 => 'r', 119930 => 's', 119931 => 't', 119932 => 'u', 119933 => 'v', 119934 => 'w', 119935 => 'x', 119936 => 'y', 119937 => 'z', 119938 => 'a', 119939 => 'b', 119940 => 'c', 119941 => 'd', 119942 => 'e', 119943 => 'f', 119944 => 'g', 119945 => 'h', 119946 => 'i', 119947 => 'j', 119948 => 'k', 119949 => 'l', 119950 => 'm', 119951 => 'n', 119952 => 'o', 119953 => 'p', 119954 => 'q', 119955 => 'r', 119956 => 's', 119957 => 't', 119958 => 'u', 119959 => 'v', 119960 => 'w', 119961 => 'x', 119962 => 'y', 119963 => 'z', 119964 => 'a', 119966 => 'c', 119967 => 'd', 119970 => 'g', 119973 => 'j', 119974 => 'k', 119977 => 'n', 119978 => 'o', 119979 => 'p', 119980 => 'q', 119982 => 's', 119983 => 't', 119984 => 'u', 119985 => 'v', 119986 => 'w', 119987 => 'x', 119988 => 'y', 119989 => 'z', 119990 => 'a', 119991 => 'b', 119992 => 'c', 119993 => 'd', 119995 => 'f', 119997 => 'h', 119998 => 'i', 119999 => 'j', 120000 => 'k', 120001 => 'l', 120002 => 'm', 120003 => 'n', 120005 => 'p', 120006 => 'q', 120007 => 'r', 120008 => 's', 120009 => 't', 120010 => 'u', 120011 => 'v', 120012 => 'w', 120013 => 'x', 120014 => 'y', 120015 => 'z', 120016 => 'a', 120017 => 'b', 120018 => 'c', 120019 => 'd', 120020 => 'e', 120021 => 'f', 120022 => 'g', 120023 => 'h', 120024 => 'i', 120025 => 'j', 120026 => 'k', 120027 => 'l', 120028 => 'm', 120029 => 'n', 120030 => 'o', 120031 => 'p', 120032 => 'q', 120033 => 'r', 120034 => 's', 120035 => 't', 120036 => 'u', 120037 => 'v', 120038 => 'w', 120039 => 'x', 120040 => 'y', 120041 => 'z', 120042 => 'a', 120043 => 'b', 120044 => 'c', 120045 => 'd', 120046 => 'e', 120047 => 'f', 120048 => 'g', 120049 => 'h', 120050 => 'i', 120051 => 'j', 120052 => 'k', 120053 => 'l', 120054 => 'm', 120055 => 'n', 120056 => 'o', 120057 => 'p', 120058 => 'q', 120059 => 'r', 120060 => 's', 120061 => 't', 120062 => 'u', 120063 => 'v', 120064 => 'w', 120065 => 'x', 120066 => 'y', 120067 => 'z', 120068 => 'a', 120069 => 'b', 120071 => 'd', 120072 => 'e', 120073 => 'f', 120074 => 'g', 120077 => 'j', 120078 => 'k', 120079 => 'l', 120080 => 'm', 120081 => 'n', 120082 => 'o', 120083 => 'p', 120084 => 'q', 120086 => 's', 120087 => 't', 120088 => 'u', 120089 => 'v', 120090 => 'w', 120091 => 'x', 120092 => 'y', 120094 => 'a', 120095 => 'b', 120096 => 'c', 120097 => 'd', 120098 => 'e', 120099 => 'f', 120100 => 'g', 120101 => 'h', 120102 => 'i', 120103 => 'j', 120104 => 'k', 120105 => 'l', 120106 => 'm', 120107 => 'n', 120108 => 'o', 120109 => 'p', 120110 => 'q', 120111 => 'r', 120112 => 's', 120113 => 't', 120114 => 'u', 120115 => 'v', 120116 => 'w', 120117 => 'x', 120118 => 'y', 120119 => 'z', 120120 => 'a', 120121 => 'b', 120123 => 'd', 120124 => 'e', 120125 => 'f', 120126 => 'g', 120128 => 'i', 120129 => 'j', 120130 => 'k', 120131 => 'l', 120132 => 'm', 120134 => 'o', 120138 => 's', 120139 => 't', 120140 => 'u', 120141 => 'v', 120142 => 'w', 120143 => 'x', 120144 => 'y', 120146 => 'a', 120147 => 'b', 120148 => 'c', 120149 => 'd', 120150 => 'e', 120151 => 'f', 120152 => 'g', 120153 => 'h', 120154 => 'i', 120155 => 'j', 120156 => 'k', 120157 => 'l', 120158 => 'm', 120159 => 'n', 120160 => 'o', 120161 => 'p', 120162 => 'q', 120163 => 'r', 120164 => 's', 120165 => 't', 120166 => 'u', 120167 => 'v', 120168 => 'w', 120169 => 'x', 120170 => 'y', 120171 => 'z', 120172 => 'a', 120173 => 'b', 120174 => 'c', 120175 => 'd', 120176 => 'e', 120177 => 'f', 120178 => 'g', 120179 => 'h', 120180 => 'i', 120181 => 'j', 120182 => 'k', 120183 => 'l', 120184 => 'm', 120185 => 'n', 120186 => 'o', 120187 => 'p', 120188 => 'q', 120189 => 'r', 120190 => 's', 120191 => 't', 120192 => 'u', 120193 => 'v', 120194 => 'w', 120195 => 'x', 120196 => 'y', 120197 => 'z', 120198 => 'a', 120199 => 'b', 120200 => 'c', 120201 => 'd', 120202 => 'e', 120203 => 'f', 120204 => 'g', 120205 => 'h', 120206 => 'i', 120207 => 'j', 120208 => 'k', 120209 => 'l', 120210 => 'm', 120211 => 'n', 120212 => 'o', 120213 => 'p', 120214 => 'q', 120215 => 'r', 120216 => 's', 120217 => 't', 120218 => 'u', 120219 => 'v', 120220 => 'w', 120221 => 'x', 120222 => 'y', 120223 => 'z', 120224 => 'a', 120225 => 'b', 120226 => 'c', 120227 => 'd', 120228 => 'e', 120229 => 'f', 120230 => 'g', 120231 => 'h', 120232 => 'i', 120233 => 'j', 120234 => 'k', 120235 => 'l', 120236 => 'm', 120237 => 'n', 120238 => 'o', 120239 => 'p', 120240 => 'q', 120241 => 'r', 120242 => 's', 120243 => 't', 120244 => 'u', 120245 => 'v', 120246 => 'w', 120247 => 'x', 120248 => 'y', 120249 => 'z', 120250 => 'a', 120251 => 'b', 120252 => 'c', 120253 => 'd', 120254 => 'e', 120255 => 'f', 120256 => 'g', 120257 => 'h', 120258 => 'i', 120259 => 'j', 120260 => 'k', 120261 => 'l', 120262 => 'm', 120263 => 'n', 120264 => 'o', 120265 => 'p', 120266 => 'q', 120267 => 'r', 120268 => 's', 120269 => 't', 120270 => 'u', 120271 => 'v', 120272 => 'w', 120273 => 'x', 120274 => 'y', 120275 => 'z', 120276 => 'a', 120277 => 'b', 120278 => 'c', 120279 => 'd', 120280 => 'e', 120281 => 'f', 120282 => 'g', 120283 => 'h', 120284 => 'i', 120285 => 'j', 120286 => 'k', 120287 => 'l', 120288 => 'm', 120289 => 'n', 120290 => 'o', 120291 => 'p', 120292 => 'q', 120293 => 'r', 120294 => 's', 120295 => 't', 120296 => 'u', 120297 => 'v', 120298 => 'w', 120299 => 'x', 120300 => 'y', 120301 => 'z', 120302 => 'a', 120303 => 'b', 120304 => 'c', 120305 => 'd', 120306 => 'e', 120307 => 'f', 120308 => 'g', 120309 => 'h', 120310 => 'i', 120311 => 'j', 120312 => 'k', 120313 => 'l', 120314 => 'm', 120315 => 'n', 120316 => 'o', 120317 => 'p', 120318 => 'q', 120319 => 'r', 120320 => 's', 120321 => 't', 120322 => 'u', 120323 => 'v', 120324 => 'w', 120325 => 'x', 120326 => 'y', 120327 => 'z', 120328 => 'a', 120329 => 'b', 120330 => 'c', 120331 => 'd', 120332 => 'e', 120333 => 'f', 120334 => 'g', 120335 => 'h', 120336 => 'i', 120337 => 'j', 120338 => 'k', 120339 => 'l', 120340 => 'm', 120341 => 'n', 120342 => 'o', 120343 => 'p', 120344 => 'q', 120345 => 'r', 120346 => 's', 120347 => 't', 120348 => 'u', 120349 => 'v', 120350 => 'w', 120351 => 'x', 120352 => 'y', 120353 => 'z', 120354 => 'a', 120355 => 'b', 120356 => 'c', 120357 => 'd', 120358 => 'e', 120359 => 'f', 120360 => 'g', 120361 => 'h', 120362 => 'i', 120363 => 'j', 120364 => 'k', 120365 => 'l', 120366 => 'm', 120367 => 'n', 120368 => 'o', 120369 => 'p', 120370 => 'q', 120371 => 'r', 120372 => 's', 120373 => 't', 120374 => 'u', 120375 => 'v', 120376 => 'w', 120377 => 'x', 120378 => 'y', 120379 => 'z', 120380 => 'a', 120381 => 'b', 120382 => 'c', 120383 => 'd', 120384 => 'e', 120385 => 'f', 120386 => 'g', 120387 => 'h', 120388 => 'i', 120389 => 'j', 120390 => 'k', 120391 => 'l', 120392 => 'm', 120393 => 'n', 120394 => 'o', 120395 => 'p', 120396 => 'q', 120397 => 'r', 120398 => 's', 120399 => 't', 120400 => 'u', 120401 => 'v', 120402 => 'w', 120403 => 'x', 120404 => 'y', 120405 => 'z', 120406 => 'a', 120407 => 'b', 120408 => 'c', 120409 => 'd', 120410 => 'e', 120411 => 'f', 120412 => 'g', 120413 => 'h', 120414 => 'i', 120415 => 'j', 120416 => 'k', 120417 => 'l', 120418 => 'm', 120419 => 'n', 120420 => 'o', 120421 => 'p', 120422 => 'q', 120423 => 'r', 120424 => 's', 120425 => 't', 120426 => 'u', 120427 => 'v', 120428 => 'w', 120429 => 'x', 120430 => 'y', 120431 => 'z', 120432 => 'a', 120433 => 'b', 120434 => 'c', 120435 => 'd', 120436 => 'e', 120437 => 'f', 120438 => 'g', 120439 => 'h', 120440 => 'i', 120441 => 'j', 120442 => 'k', 120443 => 'l', 120444 => 'm', 120445 => 'n', 120446 => 'o', 120447 => 'p', 120448 => 'q', 120449 => 'r', 120450 => 's', 120451 => 't', 120452 => 'u', 120453 => 'v', 120454 => 'w', 120455 => 'x', 120456 => 'y', 120457 => 'z', 120458 => 'a', 120459 => 'b', 120460 => 'c', 120461 => 'd', 120462 => 'e', 120463 => 'f', 120464 => 'g', 120465 => 'h', 120466 => 'i', 120467 => 'j', 120468 => 'k', 120469 => 'l', 120470 => 'm', 120471 => 'n', 120472 => 'o', 120473 => 'p', 120474 => 'q', 120475 => 'r', 120476 => 's', 120477 => 't', 120478 => 'u', 120479 => 'v', 120480 => 'w', 120481 => 'x', 120482 => 'y', 120483 => 'z', 120484 => 'ı', 120485 => 'ȷ', 120488 => 'α', 120489 => 'β', 120490 => 'γ', 120491 => 'δ', 120492 => 'ε', 120493 => 'ζ', 120494 => 'η', 120495 => 'θ', 120496 => 'ι', 120497 => 'κ', 120498 => 'λ', 120499 => 'μ', 120500 => 'ν', 120501 => 'ξ', 120502 => 'ο', 120503 => 'π', 120504 => 'ρ', 120505 => 'θ', 120506 => 'σ', 120507 => 'τ', 120508 => 'υ', 120509 => 'φ', 120510 => 'χ', 120511 => 'ψ', 120512 => 'ω', 120513 => '∇', 120514 => 'α', 120515 => 'β', 120516 => 'γ', 120517 => 'δ', 120518 => 'ε', 120519 => 'ζ', 120520 => 'η', 120521 => 'θ', 120522 => 'ι', 120523 => 'κ', 120524 => 'λ', 120525 => 'μ', 120526 => 'ν', 120527 => 'ξ', 120528 => 'ο', 120529 => 'π', 120530 => 'ρ', 120531 => 'σ', 120532 => 'σ', 120533 => 'τ', 120534 => 'υ', 120535 => 'φ', 120536 => 'χ', 120537 => 'ψ', 120538 => 'ω', 120539 => '∂', 120540 => 'ε', 120541 => 'θ', 120542 => 'κ', 120543 => 'φ', 120544 => 'ρ', 120545 => 'π', 120546 => 'α', 120547 => 'β', 120548 => 'γ', 120549 => 'δ', 120550 => 'ε', 120551 => 'ζ', 120552 => 'η', 120553 => 'θ', 120554 => 'ι', 120555 => 'κ', 120556 => 'λ', 120557 => 'μ', 120558 => 'ν', 120559 => 'ξ', 120560 => 'ο', 120561 => 'π', 120562 => 'ρ', 120563 => 'θ', 120564 => 'σ', 120565 => 'τ', 120566 => 'υ', 120567 => 'φ', 120568 => 'χ', 120569 => 'ψ', 120570 => 'ω', 120571 => '∇', 120572 => 'α', 120573 => 'β', 120574 => 'γ', 120575 => 'δ', 120576 => 'ε', 120577 => 'ζ', 120578 => 'η', 120579 => 'θ', 120580 => 'ι', 120581 => 'κ', 120582 => 'λ', 120583 => 'μ', 120584 => 'ν', 120585 => 'ξ', 120586 => 'ο', 120587 => 'π', 120588 => 'ρ', 120589 => 'σ', 120590 => 'σ', 120591 => 'τ', 120592 => 'υ', 120593 => 'φ', 120594 => 'χ', 120595 => 'ψ', 120596 => 'ω', 120597 => '∂', 120598 => 'ε', 120599 => 'θ', 120600 => 'κ', 120601 => 'φ', 120602 => 'ρ', 120603 => 'π', 120604 => 'α', 120605 => 'β', 120606 => 'γ', 120607 => 'δ', 120608 => 'ε', 120609 => 'ζ', 120610 => 'η', 120611 => 'θ', 120612 => 'ι', 120613 => 'κ', 120614 => 'λ', 120615 => 'μ', 120616 => 'ν', 120617 => 'ξ', 120618 => 'ο', 120619 => 'π', 120620 => 'ρ', 120621 => 'θ', 120622 => 'σ', 120623 => 'τ', 120624 => 'υ', 120625 => 'φ', 120626 => 'χ', 120627 => 'ψ', 120628 => 'ω', 120629 => '∇', 120630 => 'α', 120631 => 'β', 120632 => 'γ', 120633 => 'δ', 120634 => 'ε', 120635 => 'ζ', 120636 => 'η', 120637 => 'θ', 120638 => 'ι', 120639 => 'κ', 120640 => 'λ', 120641 => 'μ', 120642 => 'ν', 120643 => 'ξ', 120644 => 'ο', 120645 => 'π', 120646 => 'ρ', 120647 => 'σ', 120648 => 'σ', 120649 => 'τ', 120650 => 'υ', 120651 => 'φ', 120652 => 'χ', 120653 => 'ψ', 120654 => 'ω', 120655 => '∂', 120656 => 'ε', 120657 => 'θ', 120658 => 'κ', 120659 => 'φ', 120660 => 'ρ', 120661 => 'π', 120662 => 'α', 120663 => 'β', 120664 => 'γ', 120665 => 'δ', 120666 => 'ε', 120667 => 'ζ', 120668 => 'η', 120669 => 'θ', 120670 => 'ι', 120671 => 'κ', 120672 => 'λ', 120673 => 'μ', 120674 => 'ν', 120675 => 'ξ', 120676 => 'ο', 120677 => 'π', 120678 => 'ρ', 120679 => 'θ', 120680 => 'σ', 120681 => 'τ', 120682 => 'υ', 120683 => 'φ', 120684 => 'χ', 120685 => 'ψ', 120686 => 'ω', 120687 => '∇', 120688 => 'α', 120689 => 'β', 120690 => 'γ', 120691 => 'δ', 120692 => 'ε', 120693 => 'ζ', 120694 => 'η', 120695 => 'θ', 120696 => 'ι', 120697 => 'κ', 120698 => 'λ', 120699 => 'μ', 120700 => 'ν', 120701 => 'ξ', 120702 => 'ο', 120703 => 'π', 120704 => 'ρ', 120705 => 'σ', 120706 => 'σ', 120707 => 'τ', 120708 => 'υ', 120709 => 'φ', 120710 => 'χ', 120711 => 'ψ', 120712 => 'ω', 120713 => '∂', 120714 => 'ε', 120715 => 'θ', 120716 => 'κ', 120717 => 'φ', 120718 => 'ρ', 120719 => 'π', 120720 => 'α', 120721 => 'β', 120722 => 'γ', 120723 => 'δ', 120724 => 'ε', 120725 => 'ζ', 120726 => 'η', 120727 => 'θ', 120728 => 'ι', 120729 => 'κ', 120730 => 'λ', 120731 => 'μ', 120732 => 'ν', 120733 => 'ξ', 120734 => 'ο', 120735 => 'π', 120736 => 'ρ', 120737 => 'θ', 120738 => 'σ', 120739 => 'τ', 120740 => 'υ', 120741 => 'φ', 120742 => 'χ', 120743 => 'ψ', 120744 => 'ω', 120745 => '∇', 120746 => 'α', 120747 => 'β', 120748 => 'γ', 120749 => 'δ', 120750 => 'ε', 120751 => 'ζ', 120752 => 'η', 120753 => 'θ', 120754 => 'ι', 120755 => 'κ', 120756 => 'λ', 120757 => 'μ', 120758 => 'ν', 120759 => 'ξ', 120760 => 'ο', 120761 => 'π', 120762 => 'ρ', 120763 => 'σ', 120764 => 'σ', 120765 => 'τ', 120766 => 'υ', 120767 => 'φ', 120768 => 'χ', 120769 => 'ψ', 120770 => 'ω', 120771 => '∂', 120772 => 'ε', 120773 => 'θ', 120774 => 'κ', 120775 => 'φ', 120776 => 'ρ', 120777 => 'π', 120778 => 'ϝ', 120779 => 'ϝ', 120782 => '0', 120783 => '1', 120784 => '2', 120785 => '3', 120786 => '4', 120787 => '5', 120788 => '6', 120789 => '7', 120790 => '8', 120791 => '9', 120792 => '0', 120793 => '1', 120794 => '2', 120795 => '3', 120796 => '4', 120797 => '5', 120798 => '6', 120799 => '7', 120800 => '8', 120801 => '9', 120802 => '0', 120803 => '1', 120804 => '2', 120805 => '3', 120806 => '4', 120807 => '5', 120808 => '6', 120809 => '7', 120810 => '8', 120811 => '9', 120812 => '0', 120813 => '1', 120814 => '2', 120815 => '3', 120816 => '4', 120817 => '5', 120818 => '6', 120819 => '7', 120820 => '8', 120821 => '9', 120822 => '0', 120823 => '1', 120824 => '2', 120825 => '3', 120826 => '4', 120827 => '5', 120828 => '6', 120829 => '7', 120830 => '8', 120831 => '9', 125184 => '𞤢', 125185 => '𞤣', 125186 => '𞤤', 125187 => '𞤥', 125188 => '𞤦', 125189 => '𞤧', 125190 => '𞤨', 125191 => '𞤩', 125192 => '𞤪', 125193 => '𞤫', 125194 => '𞤬', 125195 => '𞤭', 125196 => '𞤮', 125197 => '𞤯', 125198 => '𞤰', 125199 => '𞤱', 125200 => '𞤲', 125201 => '𞤳', 125202 => '𞤴', 125203 => '𞤵', 125204 => '𞤶', 125205 => '𞤷', 125206 => '𞤸', 125207 => '𞤹', 125208 => '𞤺', 125209 => '𞤻', 125210 => '𞤼', 125211 => '𞤽', 125212 => '𞤾', 125213 => '𞤿', 125214 => '𞥀', 125215 => '𞥁', 125216 => '𞥂', 125217 => '𞥃', 126464 => 'ا', 126465 => 'ب', 126466 => 'ج', 126467 => 'د', 126469 => 'و', 126470 => 'ز', 126471 => 'ح', 126472 => 'ط', 126473 => 'ي', 126474 => 'ك', 126475 => 'ل', 126476 => 'م', 126477 => 'ن', 126478 => 'س', 126479 => 'ع', 126480 => 'ف', 126481 => 'ص', 126482 => 'ق', 126483 => 'ر', 126484 => 'ش', 126485 => 'ت', 126486 => 'ث', 126487 => 'خ', 126488 => 'ذ', 126489 => 'ض', 126490 => 'ظ', 126491 => 'غ', 126492 => 'ٮ', 126493 => 'ں', 126494 => 'ڡ', 126495 => 'ٯ', 126497 => 'ب', 126498 => 'ج', 126500 => 'ه', 126503 => 'ح', 126505 => 'ي', 126506 => 'ك', 126507 => 'ل', 126508 => 'م', 126509 => 'ن', 126510 => 'س', 126511 => 'ع', 126512 => 'ف', 126513 => 'ص', 126514 => 'ق', 126516 => 'ش', 126517 => 'ت', 126518 => 'ث', 126519 => 'خ', 126521 => 'ض', 126523 => 'غ', 126530 => 'ج', 126535 => 'ح', 126537 => 'ي', 126539 => 'ل', 126541 => 'ن', 126542 => 'س', 126543 => 'ع', 126545 => 'ص', 126546 => 'ق', 126548 => 'ش', 126551 => 'خ', 126553 => 'ض', 126555 => 'غ', 126557 => 'ں', 126559 => 'ٯ', 126561 => 'ب', 126562 => 'ج', 126564 => 'ه', 126567 => 'ح', 126568 => 'ط', 126569 => 'ي', 126570 => 'ك', 126572 => 'م', 126573 => 'ن', 126574 => 'س', 126575 => 'ع', 126576 => 'ف', 126577 => 'ص', 126578 => 'ق', 126580 => 'ش', 126581 => 'ت', 126582 => 'ث', 126583 => 'خ', 126585 => 'ض', 126586 => 'ظ', 126587 => 'غ', 126588 => 'ٮ', 126590 => 'ڡ', 126592 => 'ا', 126593 => 'ب', 126594 => 'ج', 126595 => 'د', 126596 => 'ه', 126597 => 'و', 126598 => 'ز', 126599 => 'ح', 126600 => 'ط', 126601 => 'ي', 126603 => 'ل', 126604 => 'م', 126605 => 'ن', 126606 => 'س', 126607 => 'ع', 126608 => 'ف', 126609 => 'ص', 126610 => 'ق', 126611 => 'ر', 126612 => 'ش', 126613 => 'ت', 126614 => 'ث', 126615 => 'خ', 126616 => 'ذ', 126617 => 'ض', 126618 => 'ظ', 126619 => 'غ', 126625 => 'ب', 126626 => 'ج', 126627 => 'د', 126629 => 'و', 126630 => 'ز', 126631 => 'ح', 126632 => 'ط', 126633 => 'ي', 126635 => 'ل', 126636 => 'م', 126637 => 'ن', 126638 => 'س', 126639 => 'ع', 126640 => 'ف', 126641 => 'ص', 126642 => 'ق', 126643 => 'ر', 126644 => 'ش', 126645 => 'ت', 126646 => 'ث', 126647 => 'خ', 126648 => 'ذ', 126649 => 'ض', 126650 => 'ظ', 126651 => 'غ', 127274 => '〔s〕', 127275 => 'c', 127276 => 'r', 127277 => 'cd', 127278 => 'wz', 127280 => 'a', 127281 => 'b', 127282 => 'c', 127283 => 'd', 127284 => 'e', 127285 => 'f', 127286 => 'g', 127287 => 'h', 127288 => 'i', 127289 => 'j', 127290 => 'k', 127291 => 'l', 127292 => 'm', 127293 => 'n', 127294 => 'o', 127295 => 'p', 127296 => 'q', 127297 => 'r', 127298 => 's', 127299 => 't', 127300 => 'u', 127301 => 'v', 127302 => 'w', 127303 => 'x', 127304 => 'y', 127305 => 'z', 127306 => 'hv', 127307 => 'mv', 127308 => 'sd', 127309 => 'ss', 127310 => 'ppv', 127311 => 'wc', 127338 => 'mc', 127339 => 'md', 127340 => 'mr', 127376 => 'dj', 127488 => 'ほか', 127489 => 'ココ', 127490 => 'サ', 127504 => '手', 127505 => '字', 127506 => '双', 127507 => 'デ', 127508 => '二', 127509 => '多', 127510 => '解', 127511 => '天', 127512 => '交', 127513 => '映', 127514 => '無', 127515 => '料', 127516 => '前', 127517 => '後', 127518 => '再', 127519 => '新', 127520 => '初', 127521 => '終', 127522 => '生', 127523 => '販', 127524 => '声', 127525 => '吹', 127526 => '演', 127527 => '投', 127528 => '捕', 127529 => '一', 127530 => '三', 127531 => '遊', 127532 => '左', 127533 => '中', 127534 => '右', 127535 => '指', 127536 => '走', 127537 => '打', 127538 => '禁', 127539 => '空', 127540 => '合', 127541 => '満', 127542 => '有', 127543 => '月', 127544 => '申', 127545 => '割', 127546 => '営', 127547 => '配', 127552 => '〔本〕', 127553 => '〔三〕', 127554 => '〔二〕', 127555 => '〔安〕', 127556 => '〔点〕', 127557 => '〔打〕', 127558 => '〔盗〕', 127559 => '〔勝〕', 127560 => '〔敗〕', 127568 => '得', 127569 => '可', 130032 => '0', 130033 => '1', 130034 => '2', 130035 => '3', 130036 => '4', 130037 => '5', 130038 => '6', 130039 => '7', 130040 => '8', 130041 => '9', 194560 => '丽', 194561 => '丸', 194562 => '乁', 194563 => '𠄢', 194564 => '你', 194565 => '侮', 194566 => '侻', 194567 => '倂', 194568 => '偺', 194569 => '備', 194570 => '僧', 194571 => '像', 194572 => '㒞', 194573 => '𠘺', 194574 => '免', 194575 => '兔', 194576 => '兤', 194577 => '具', 194578 => '𠔜', 194579 => '㒹', 194580 => '內', 194581 => '再', 194582 => '𠕋', 194583 => '冗', 194584 => '冤', 194585 => '仌', 194586 => '冬', 194587 => '况', 194588 => '𩇟', 194589 => '凵', 194590 => '刃', 194591 => '㓟', 194592 => '刻', 194593 => '剆', 194594 => '割', 194595 => '剷', 194596 => '㔕', 194597 => '勇', 194598 => '勉', 194599 => '勤', 194600 => '勺', 194601 => '包', 194602 => '匆', 194603 => '北', 194604 => '卉', 194605 => '卑', 194606 => '博', 194607 => '即', 194608 => '卽', 194609 => '卿', 194610 => '卿', 194611 => '卿', 194612 => '𠨬', 194613 => '灰', 194614 => '及', 194615 => '叟', 194616 => '𠭣', 194617 => '叫', 194618 => '叱', 194619 => '吆', 194620 => '咞', 194621 => '吸', 194622 => '呈', 194623 => '周', 194624 => '咢', 194625 => '哶', 194626 => '唐', 194627 => '啓', 194628 => '啣', 194629 => '善', 194630 => '善', 194631 => '喙', 194632 => '喫', 194633 => '喳', 194634 => '嗂', 194635 => '圖', 194636 => '嘆', 194637 => '圗', 194638 => '噑', 194639 => '噴', 194640 => '切', 194641 => '壮', 194642 => '城', 194643 => '埴', 194644 => '堍', 194645 => '型', 194646 => '堲', 194647 => '報', 194648 => '墬', 194649 => '𡓤', 194650 => '売', 194651 => '壷', 194652 => '夆', 194653 => '多', 194654 => '夢', 194655 => '奢', 194656 => '𡚨', 194657 => '𡛪', 194658 => '姬', 194659 => '娛', 194660 => '娧', 194661 => '姘', 194662 => '婦', 194663 => '㛮', 194665 => '嬈', 194666 => '嬾', 194667 => '嬾', 194668 => '𡧈', 194669 => '寃', 194670 => '寘', 194671 => '寧', 194672 => '寳', 194673 => '𡬘', 194674 => '寿', 194675 => '将', 194677 => '尢', 194678 => '㞁', 194679 => '屠', 194680 => '屮', 194681 => '峀', 194682 => '岍', 194683 => '𡷤', 194684 => '嵃', 194685 => '𡷦', 194686 => '嵮', 194687 => '嵫', 194688 => '嵼', 194689 => '巡', 194690 => '巢', 194691 => '㠯', 194692 => '巽', 194693 => '帨', 194694 => '帽', 194695 => '幩', 194696 => '㡢', 194697 => '𢆃', 194698 => '㡼', 194699 => '庰', 194700 => '庳', 194701 => '庶', 194702 => '廊', 194703 => '𪎒', 194704 => '廾', 194705 => '𢌱', 194706 => '𢌱', 194707 => '舁', 194708 => '弢', 194709 => '弢', 194710 => '㣇', 194711 => '𣊸', 194712 => '𦇚', 194713 => '形', 194714 => '彫', 194715 => '㣣', 194716 => '徚', 194717 => '忍', 194718 => '志', 194719 => '忹', 194720 => '悁', 194721 => '㤺', 194722 => '㤜', 194723 => '悔', 194724 => '𢛔', 194725 => '惇', 194726 => '慈', 194727 => '慌', 194728 => '慎', 194729 => '慌', 194730 => '慺', 194731 => '憎', 194732 => '憲', 194733 => '憤', 194734 => '憯', 194735 => '懞', 194736 => '懲', 194737 => '懶', 194738 => '成', 194739 => '戛', 194740 => '扝', 194741 => '抱', 194742 => '拔', 194743 => '捐', 194744 => '𢬌', 194745 => '挽', 194746 => '拼', 194747 => '捨', 194748 => '掃', 194749 => '揤', 194750 => '𢯱', 194751 => '搢', 194752 => '揅', 194753 => '掩', 194754 => '㨮', 194755 => '摩', 194756 => '摾', 194757 => '撝', 194758 => '摷', 194759 => '㩬', 194760 => '敏', 194761 => '敬', 194762 => '𣀊', 194763 => '旣', 194764 => '書', 194765 => '晉', 194766 => '㬙', 194767 => '暑', 194768 => '㬈', 194769 => '㫤', 194770 => '冒', 194771 => '冕', 194772 => '最', 194773 => '暜', 194774 => '肭', 194775 => '䏙', 194776 => '朗', 194777 => '望', 194778 => '朡', 194779 => '杞', 194780 => '杓', 194781 => '𣏃', 194782 => '㭉', 194783 => '柺', 194784 => '枅', 194785 => '桒', 194786 => '梅', 194787 => '𣑭', 194788 => '梎', 194789 => '栟', 194790 => '椔', 194791 => '㮝', 194792 => '楂', 194793 => '榣', 194794 => '槪', 194795 => '檨', 194796 => '𣚣', 194797 => '櫛', 194798 => '㰘', 194799 => '次', 194800 => '𣢧', 194801 => '歔', 194802 => '㱎', 194803 => '歲', 194804 => '殟', 194805 => '殺', 194806 => '殻', 194807 => '𣪍', 194808 => '𡴋', 194809 => '𣫺', 194810 => '汎', 194811 => '𣲼', 194812 => '沿', 194813 => '泍', 194814 => '汧', 194815 => '洖', 194816 => '派', 194817 => '海', 194818 => '流', 194819 => '浩', 194820 => '浸', 194821 => '涅', 194822 => '𣴞', 194823 => '洴', 194824 => '港', 194825 => '湮', 194826 => '㴳', 194827 => '滋', 194828 => '滇', 194829 => '𣻑', 194830 => '淹', 194831 => '潮', 194832 => '𣽞', 194833 => '𣾎', 194834 => '濆', 194835 => '瀹', 194836 => '瀞', 194837 => '瀛', 194838 => '㶖', 194839 => '灊', 194840 => '災', 194841 => '灷', 194842 => '炭', 194843 => '𠔥', 194844 => '煅', 194845 => '𤉣', 194846 => '熜', 194848 => '爨', 194849 => '爵', 194850 => '牐', 194851 => '𤘈', 194852 => '犀', 194853 => '犕', 194854 => '𤜵', 194855 => '𤠔', 194856 => '獺', 194857 => '王', 194858 => '㺬', 194859 => '玥', 194860 => '㺸', 194861 => '㺸', 194862 => '瑇', 194863 => '瑜', 194864 => '瑱', 194865 => '璅', 194866 => '瓊', 194867 => '㼛', 194868 => '甤', 194869 => '𤰶', 194870 => '甾', 194871 => '𤲒', 194872 => '異', 194873 => '𢆟', 194874 => '瘐', 194875 => '𤾡', 194876 => '𤾸', 194877 => '𥁄', 194878 => '㿼', 194879 => '䀈', 194880 => '直', 194881 => '𥃳', 194882 => '𥃲', 194883 => '𥄙', 194884 => '𥄳', 194885 => '眞', 194886 => '真', 194887 => '真', 194888 => '睊', 194889 => '䀹', 194890 => '瞋', 194891 => '䁆', 194892 => '䂖', 194893 => '𥐝', 194894 => '硎', 194895 => '碌', 194896 => '磌', 194897 => '䃣', 194898 => '𥘦', 194899 => '祖', 194900 => '𥚚', 194901 => '𥛅', 194902 => '福', 194903 => '秫', 194904 => '䄯', 194905 => '穀', 194906 => '穊', 194907 => '穏', 194908 => '𥥼', 194909 => '𥪧', 194910 => '𥪧', 194912 => '䈂', 194913 => '𥮫', 194914 => '篆', 194915 => '築', 194916 => '䈧', 194917 => '𥲀', 194918 => '糒', 194919 => '䊠', 194920 => '糨', 194921 => '糣', 194922 => '紀', 194923 => '𥾆', 194924 => '絣', 194925 => '䌁', 194926 => '緇', 194927 => '縂', 194928 => '繅', 194929 => '䌴', 194930 => '𦈨', 194931 => '𦉇', 194932 => '䍙', 194933 => '𦋙', 194934 => '罺', 194935 => '𦌾', 194936 => '羕', 194937 => '翺', 194938 => '者', 194939 => '𦓚', 194940 => '𦔣', 194941 => '聠', 194942 => '𦖨', 194943 => '聰', 194944 => '𣍟', 194945 => '䏕', 194946 => '育', 194947 => '脃', 194948 => '䐋', 194949 => '脾', 194950 => '媵', 194951 => '𦞧', 194952 => '𦞵', 194953 => '𣎓', 194954 => '𣎜', 194955 => '舁', 194956 => '舄', 194957 => '辞', 194958 => '䑫', 194959 => '芑', 194960 => '芋', 194961 => '芝', 194962 => '劳', 194963 => '花', 194964 => '芳', 194965 => '芽', 194966 => '苦', 194967 => '𦬼', 194968 => '若', 194969 => '茝', 194970 => '荣', 194971 => '莭', 194972 => '茣', 194973 => '莽', 194974 => '菧', 194975 => '著', 194976 => '荓', 194977 => '菊', 194978 => '菌', 194979 => '菜', 194980 => '𦰶', 194981 => '𦵫', 194982 => '𦳕', 194983 => '䔫', 194984 => '蓱', 194985 => '蓳', 194986 => '蔖', 194987 => '𧏊', 194988 => '蕤', 194989 => '𦼬', 194990 => '䕝', 194991 => '䕡', 194992 => '𦾱', 194993 => '𧃒', 194994 => '䕫', 194995 => '虐', 194996 => '虜', 194997 => '虧', 194998 => '虩', 194999 => '蚩', 195000 => '蚈', 195001 => '蜎', 195002 => '蛢', 195003 => '蝹', 195004 => '蜨', 195005 => '蝫', 195006 => '螆', 195008 => '蟡', 195009 => '蠁', 195010 => '䗹', 195011 => '衠', 195012 => '衣', 195013 => '𧙧', 195014 => '裗', 195015 => '裞', 195016 => '䘵', 195017 => '裺', 195018 => '㒻', 195019 => '𧢮', 195020 => '𧥦', 195021 => '䚾', 195022 => '䛇', 195023 => '誠', 195024 => '諭', 195025 => '變', 195026 => '豕', 195027 => '𧲨', 195028 => '貫', 195029 => '賁', 195030 => '贛', 195031 => '起', 195032 => '𧼯', 195033 => '𠠄', 195034 => '跋', 195035 => '趼', 195036 => '跰', 195037 => '𠣞', 195038 => '軔', 195039 => '輸', 195040 => '𨗒', 195041 => '𨗭', 195042 => '邔', 195043 => '郱', 195044 => '鄑', 195045 => '𨜮', 195046 => '鄛', 195047 => '鈸', 195048 => '鋗', 195049 => '鋘', 195050 => '鉼', 195051 => '鏹', 195052 => '鐕', 195053 => '𨯺', 195054 => '開', 195055 => '䦕', 195056 => '閷', 195057 => '𨵷', 195058 => '䧦', 195059 => '雃', 195060 => '嶲', 195061 => '霣', 195062 => '𩅅', 195063 => '𩈚', 195064 => '䩮', 195065 => '䩶', 195066 => '韠', 195067 => '𩐊', 195068 => '䪲', 195069 => '𩒖', 195070 => '頋', 195071 => '頋', 195072 => '頩', 195073 => '𩖶', 195074 => '飢', 195075 => '䬳', 195076 => '餩', 195077 => '馧', 195078 => '駂', 195079 => '駾', 195080 => '䯎', 195081 => '𩬰', 195082 => '鬒', 195083 => '鱀', 195084 => '鳽', 195085 => '䳎', 195086 => '䳭', 195087 => '鵧', 195088 => '𪃎', 195089 => '䳸', 195090 => '𪄅', 195091 => '𪈎', 195092 => '𪊑', 195093 => '麻', 195094 => '䵖', 195095 => '黹', 195096 => '黾', 195097 => '鼅', 195098 => '鼏', 195099 => '鼖', 195100 => '鼻', 195101 => '𪘀'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/virama.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/virama.php new file mode 100644 index 00000000..60b49c77 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/Resources/unidata/virama.php @@ -0,0 +1,5 @@ + 9, 2509 => 9, 2637 => 9, 2765 => 9, 2893 => 9, 3021 => 9, 3149 => 9, 3277 => 9, 3387 => 9, 3388 => 9, 3405 => 9, 3530 => 9, 3642 => 9, 3770 => 9, 3972 => 9, 4153 => 9, 4154 => 9, 5908 => 9, 5940 => 9, 6098 => 9, 6752 => 9, 6980 => 9, 7082 => 9, 7083 => 9, 7154 => 9, 7155 => 9, 11647 => 9, 43014 => 9, 43052 => 9, 43204 => 9, 43347 => 9, 43456 => 9, 43766 => 9, 44013 => 9, 68159 => 9, 69702 => 9, 69759 => 9, 69817 => 9, 69939 => 9, 69940 => 9, 70080 => 9, 70197 => 9, 70378 => 9, 70477 => 9, 70722 => 9, 70850 => 9, 71103 => 9, 71231 => 9, 71350 => 9, 71467 => 9, 71737 => 9, 71997 => 9, 71998 => 9, 72160 => 9, 72244 => 9, 72263 => 9, 72345 => 9, 72767 => 9, 73028 => 9, 73029 => 9, 73111 => 9); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap.php new file mode 100644 index 00000000..9cadd8ef --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn as p; + +if (extension_loaded('intl')) { + return; +} + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_2003')) { + define('INTL_IDNA_VARIANT_2003', 0); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (\PHP_VERSION_ID < 70400) { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_2003, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} else { + if (!function_exists('idn_to_ascii')) { + function idn_to_ascii($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_ascii($domain, $flags, $variant, $idna_info); } + } + if (!function_exists('idn_to_utf8')) { + function idn_to_utf8($domain, $flags = 0, $variant = \INTL_IDNA_VARIANT_UTS46, &$idna_info = null) { return p\Idn::idn_to_utf8($domain, $flags, $variant, $idna_info); } + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap80.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap80.php new file mode 100644 index 00000000..40cba8b2 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-intl-idn/bootstrap80.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use WPMailSMTP\Vendor\Symfony\Polyfill\Intl\Idn as p; + +if (!defined('U_IDNA_PROHIBITED_ERROR')) { + define('U_IDNA_PROHIBITED_ERROR', 66560); +} +if (!defined('U_IDNA_ERROR_START')) { + define('U_IDNA_ERROR_START', 66560); +} +if (!defined('U_IDNA_UNASSIGNED_ERROR')) { + define('U_IDNA_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_IDNA_CHECK_BIDI_ERROR')) { + define('U_IDNA_CHECK_BIDI_ERROR', 66562); +} +if (!defined('U_IDNA_STD3_ASCII_RULES_ERROR')) { + define('U_IDNA_STD3_ASCII_RULES_ERROR', 66563); +} +if (!defined('U_IDNA_ACE_PREFIX_ERROR')) { + define('U_IDNA_ACE_PREFIX_ERROR', 66564); +} +if (!defined('U_IDNA_VERIFICATION_ERROR')) { + define('U_IDNA_VERIFICATION_ERROR', 66565); +} +if (!defined('U_IDNA_LABEL_TOO_LONG_ERROR')) { + define('U_IDNA_LABEL_TOO_LONG_ERROR', 66566); +} +if (!defined('U_IDNA_ZERO_LENGTH_LABEL_ERROR')) { + define('U_IDNA_ZERO_LENGTH_LABEL_ERROR', 66567); +} +if (!defined('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR')) { + define('U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR', 66568); +} +if (!defined('U_IDNA_ERROR_LIMIT')) { + define('U_IDNA_ERROR_LIMIT', 66569); +} +if (!defined('U_STRINGPREP_PROHIBITED_ERROR')) { + define('U_STRINGPREP_PROHIBITED_ERROR', 66560); +} +if (!defined('U_STRINGPREP_UNASSIGNED_ERROR')) { + define('U_STRINGPREP_UNASSIGNED_ERROR', 66561); +} +if (!defined('U_STRINGPREP_CHECK_BIDI_ERROR')) { + define('U_STRINGPREP_CHECK_BIDI_ERROR', 66562); +} +if (!defined('IDNA_DEFAULT')) { + define('IDNA_DEFAULT', 0); +} +if (!defined('IDNA_ALLOW_UNASSIGNED')) { + define('IDNA_ALLOW_UNASSIGNED', 1); +} +if (!defined('IDNA_USE_STD3_RULES')) { + define('IDNA_USE_STD3_RULES', 2); +} +if (!defined('IDNA_CHECK_BIDI')) { + define('IDNA_CHECK_BIDI', 4); +} +if (!defined('IDNA_CHECK_CONTEXTJ')) { + define('IDNA_CHECK_CONTEXTJ', 8); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_ASCII')) { + define('IDNA_NONTRANSITIONAL_TO_ASCII', 16); +} +if (!defined('IDNA_NONTRANSITIONAL_TO_UNICODE')) { + define('IDNA_NONTRANSITIONAL_TO_UNICODE', 32); +} +if (!defined('INTL_IDNA_VARIANT_UTS46')) { + define('INTL_IDNA_VARIANT_UTS46', 1); +} +if (!defined('IDNA_ERROR_EMPTY_LABEL')) { + define('IDNA_ERROR_EMPTY_LABEL', 1); +} +if (!defined('IDNA_ERROR_LABEL_TOO_LONG')) { + define('IDNA_ERROR_LABEL_TOO_LONG', 2); +} +if (!defined('IDNA_ERROR_DOMAIN_NAME_TOO_LONG')) { + define('IDNA_ERROR_DOMAIN_NAME_TOO_LONG', 4); +} +if (!defined('IDNA_ERROR_LEADING_HYPHEN')) { + define('IDNA_ERROR_LEADING_HYPHEN', 8); +} +if (!defined('IDNA_ERROR_TRAILING_HYPHEN')) { + define('IDNA_ERROR_TRAILING_HYPHEN', 16); +} +if (!defined('IDNA_ERROR_HYPHEN_3_4')) { + define('IDNA_ERROR_HYPHEN_3_4', 32); +} +if (!defined('IDNA_ERROR_LEADING_COMBINING_MARK')) { + define('IDNA_ERROR_LEADING_COMBINING_MARK', 64); +} +if (!defined('IDNA_ERROR_DISALLOWED')) { + define('IDNA_ERROR_DISALLOWED', 128); +} +if (!defined('IDNA_ERROR_PUNYCODE')) { + define('IDNA_ERROR_PUNYCODE', 256); +} +if (!defined('IDNA_ERROR_LABEL_HAS_DOT')) { + define('IDNA_ERROR_LABEL_HAS_DOT', 512); +} +if (!defined('IDNA_ERROR_INVALID_ACE_LABEL')) { + define('IDNA_ERROR_INVALID_ACE_LABEL', 1024); +} +if (!defined('IDNA_ERROR_BIDI')) { + define('IDNA_ERROR_BIDI', 2048); +} +if (!defined('IDNA_ERROR_CONTEXTJ')) { + define('IDNA_ERROR_CONTEXTJ', 4096); +} + +if (!function_exists('idn_to_ascii')) { + function idn_to_ascii(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_ascii((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} +if (!function_exists('idn_to_utf8')) { + function idn_to_utf8(?string $domain, ?int $flags = IDNA_DEFAULT, ?int $variant = INTL_IDNA_VARIANT_UTS46, &$idna_info = null): string|false { return p\Idn::idn_to_utf8((string) $domain, (int) $flags, (int) $variant, $idna_info); } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/LICENSE b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 00000000..6e3afce6 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Mbstring.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 00000000..4768b337 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,910 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +namespace WPMailSMTP\Vendor\Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * - mb_ucfirst - Make a string's first character uppercase + * - mb_lcfirst - Make a string's first character lowercase + * - mb_trim - Strip whitespace (or other characters) from the beginning and end of a string + * - mb_ltrim - Strip whitespace (or other characters) from the beginning of a string + * - mb_rtrim - Strip whitespace (or other characters) from the end of a string + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + private const SIMPLE_CASE_FOLD = [['µ', 'ſ', "ͅ", 'ς', "ϐ", "ϑ", "ϕ", "ϖ", "ϰ", "ϱ", "ϵ", "ẛ", "ι"], ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "ṡ", 'ι']]; + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $iconvSupportsIgnore; + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($s)) { + $r = []; + foreach ($s as $str) { + $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding); + } + return $r; + } + if (\is_array($fromEncoding) || null !== $fromEncoding && \false !== \strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + $toEncoding = self::getEncoding($toEncoding); + if ('BASE64' === $fromEncoding) { + $s = \base64_decode($s); + $fromEncoding = $toEncoding; + } + if ('BASE64' === $toEncoding) { + return \base64_encode($s); + } + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = self::iconv($fromEncoding, 'UTF-8', $s); + } + return \preg_replace_callback('/[\\x80-\\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + if ('HTML-ENTITIES' === $fromEncoding) { + $decodeControlChars = static function ($m) { + $code = '' !== ($m[2] ?? '') ? \hexdec($m[2]) : (int) $m[1]; + if ($code < 32 || 127 === $code) { + return \chr($code); + } + if (128 <= $code && $code <= 159) { + return "\xc2" . \chr(0x80 | $code & 0x3f); + } + return $m[0]; + }; + if (\PHP_VERSION_ID >= 70400) { + $s = \html_entity_decode($s, \ENT_QUOTES, 'UTF-8'); + // html_entity_decode() leaves numeric entities for C0/C1 control + // characters as-is (HTML spec), but mb_convert_encoding() decodes + // them. Catch what html_entity_decode() missed. + if (\false !== \strpos($s, '&#')) { + $s = \preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); + } + } else { + // PHP < 7.4: html_entity_decode() truncates strings at NUL bytes, + // so decode the control character entities first then call + // html_entity_decode() on each NUL-delimited chunk independently. + $s = \preg_replace_callback('/&#(?:0*([0-9]++)|[xX]0*([0-9a-fA-F]++));/', $decodeControlChars, $s); + $s = \implode("\x00", \array_map(static function ($chunk) { + return \html_entity_decode($chunk, \ENT_QUOTES, 'UTF-8'); + }, \explode("\x00", $s))); + } + $fromEncoding = 'UTF-8'; + } + return self::iconv($fromEncoding, $toEncoding, $s); + } + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = \true; + \array_walk_recursive($vars, static function (&$v) use(&$ok, $toEncoding, $fromEncoding) { + if (\false === ($v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding))) { + $ok = \false; + } + }); + return $ok ? $fromEncoding : \false; + } + public static function mb_decode_mimeheader($s) + { + return \iconv_mime_decode($s, 2, self::$internalEncoding); + } + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + \trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + \trigger_error('mb_decode_numericentity() expects parameter 1 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); + return null; + } + if (!\is_array($convmap) || 80000 > \PHP_VERSION_ID && !$convmap) { + return \false; + } + if (null !== $encoding && !\is_scalar($encoding)) { + \trigger_error('mb_decode_numericentity() expects parameter 3 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); + return ''; + // Instead of null (cf. mb_encode_numericentity). + } + $s = (string) $s; + if ('' === $s) { + return ''; + } + $encoding = self::getEncoding($encoding); + if ('UTF-8' === $encoding) { + $encoding = null; + if (!\preg_match('//u', $s)) { + $s = @self::iconv('UTF-8', 'UTF-8', $s); + } + } else { + $s = self::iconv($encoding, 'UTF-8', $s); + } + $cnt = \floor(\count($convmap) / 4) * 4; + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + $s = \preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))' . (\PHP_VERSION_ID >= 80200 ? '' : '(?!&)') . ';?/', static function (array $m) use($cnt, $convmap) { + $c = isset($m[2]) ? (int) \hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + return $m[0]; + }, $s); + if (null === $encoding) { + return $s; + } + return self::iconv('UTF-8', $encoding, $s); + } + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = \false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + \trigger_error('mb_encode_numericentity() expects parameter 1 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); + return null; + } + if (!\is_array($convmap) || 80000 > \PHP_VERSION_ID && !$convmap) { + return \false; + } + if (null !== $encoding && !\is_scalar($encoding)) { + \trigger_error('mb_encode_numericentity() expects parameter 3 to be string, ' . \gettype($s) . ' given', \E_USER_WARNING); + return null; + // Instead of '' (cf. mb_decode_numericentity). + } + if (null !== $is_hex && !\is_scalar($is_hex)) { + \trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, ' . \gettype($s) . ' given', \E_USER_WARNING); + return null; + } + $s = (string) $s; + if ('' === $s) { + return ''; + } + $encoding = self::getEncoding($encoding); + if ('UTF-8' === $encoding) { + $encoding = null; + if (!\preg_match('//u', $s)) { + $s = @self::iconv('UTF-8', 'UTF-8', $s); + } + } else { + $s = self::iconv($encoding, 'UTF-8', $s); + } + static $ulenMask = ["\xc0" => 2, "\xd0" => 2, "\xe0" => 3, "\xf0" => 4]; + $cnt = \floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xf0"]; + $uchr = \substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = $c + $convmap[$j + 2] & $convmap[$j + 3]; + $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#' . $cOffset . ';'; + continue 2; + } + } + $result .= $uchr; + } + if (null === $encoding) { + return $result; + } + return self::iconv('UTF-8', $encoding, $result); + } + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + $encoding = self::getEncoding($encoding); + if ('UTF-8' === $encoding) { + $encoding = null; + if (!\preg_match('//u', $s)) { + $s = @self::iconv('UTF-8', 'UTF-8', $s); + } + } else { + $s = self::iconv($encoding, 'UTF-8', $s); + } + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = \preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + static $caseFolding = null; + if (null === $caseFolding) { + $caseFolding = self::getData('caseFolding'); + } + $s = \strtr($s, $caseFolding); + } + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + static $ulenMask = ["\xc0" => 2, "\xd0" => 2, "\xe0" => 3, "\xf0" => 4]; + $i = 0; + $len = \strlen($s); + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xf0"]; + $uchr = \substr($s, $i, $ulen); + $i += $ulen; + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = \substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + if (null === $encoding) { + return $s; + } + return self::iconv('UTF-8', $encoding, $s); + } + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + $normalizedEncoding = self::getEncoding($encoding); + if ('UTF-8' === $normalizedEncoding || \false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + return \true; + } + if (80000 > \PHP_VERSION_ID) { + return \false; + } + throw new \ValueError(\sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + switch ($normalizedLang = \strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + return \true; + } + if (80000 > \PHP_VERSION_ID) { + return \false; + } + throw new \ValueError(\sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + public static function mb_list_encodings() + { + return ['UTF-8']; + } + public static function mb_encoding_aliases($encoding) + { + switch (\strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + return \false; + } + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return \false; + } + $encoding = self::$internalEncoding; + } + if (!\is_array($var)) { + return self::mb_detect_encoding($var, [$encoding]) || \false !== @\iconv($encoding, $encoding, $var); + } + foreach ($var as $key => $value) { + if (!self::mb_check_encoding($key, $encoding)) { + return \false; + } + if (!self::mb_check_encoding($value, $encoding)) { + return \false; + } + } + return \true; + } + public static function mb_detect_encoding($str, $encodingList = null, $strict = \false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = \array_map('trim', \explode(',', $encodingList)); + } + $encodingList = \array_map('strtoupper', $encodingList); + } + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!\preg_match('/[\\x80-\\xFF]/', $str)) { + return $enc; + } + break; + case 'UTF8': + case 'UTF-8': + if (\preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + default: + if (0 === \strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + return \false; + } + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + if (!\is_array($encodingList)) { + $encodingList = \array_map('trim', \explode(',', $encodingList)); + } + $encodingList = \array_map('strtoupper', $encodingList); + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (\strncmp($enc, 'ISO-8859-', 9)) { + return \false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + self::$encodingList = $encodingList; + return \true; + } + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + if (\false !== ($len = @\iconv_strlen($s, $encoding))) { + return $len; + } + if ('UTF-8' !== $encoding) { + return $len; + } + return \preg_match_all('/[\\x00-\\x7F]|[\\xC0-\\xDF][\\x80-\\xBF]?|[\\xE0-\\xEF][\\x80-\\xBF]{0,2}|[\\xF0-\\xF7][\\x80-\\xBF]{0,3}|[\\xF8-\\xFB][\\x80-\\xBF]{0,4}|[\\xFC-\\xFD][\\x80-\\xBF]{0,5}|[\\x80-\\xBF\\xFE\\xFF]/s', $s); + } + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strpos($haystack, $needle, $offset); + } + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + \trigger_error(__METHOD__ . ': Empty delimiter', \E_USER_WARNING); + return \false; + } + return 0; + } + return \iconv_strpos($haystack, $needle, $offset, $encoding); + } + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strrpos($haystack, $needle, $offset); + } + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > ($offset += self::mb_strlen($needle))) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID ? \iconv_strrpos($haystack, $needle, $encoding) : self::mb_strlen($haystack, $encoding); + return \false !== $pos ? $offset + $pos : \false; + } + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { + \trigger_error('mb_str_split() expects parameter 1 to be string, ' . \gettype($string) . ' given', \E_USER_WARNING); + return null; + } + if (1 > ($split_length = (int) $split_length)) { + if (80000 > \PHP_VERSION_ID) { + \trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + return \false; + } + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + if (null === $encoding) { + $encoding = \mb_internal_encoding(); + } + if ('UTF-8' === ($encoding = self::getEncoding($encoding))) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{' . $split_length . '})/us'; + return \preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + $result = []; + $length = \mb_strlen($string, $encoding); + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = \mb_substr($string, $i, $split_length, $encoding); + } + return $result; + } + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === \strcasecmp($c, 'none')) { + return \true; + } + if (80000 > \PHP_VERSION_ID) { + return \false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return \false; + } + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) \substr($s, $start, null === $length ? 2147483647 : $length); + } + if ($start < 0) { + $start = \iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = \iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + return (string) \iconv_substr($s, $start, $length, $encoding); + } + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + [$haystack, $needle] = \str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding), self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding)]); + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + public static function mb_stristr($haystack, $needle, $part = \false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + return self::getSubpart($pos, $part, $haystack, $encoding); + } + public static function mb_strrchr($haystack, $needle, $part = \false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = \strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = \iconv_strrpos($haystack, $needle, $encoding); + } + return self::getSubpart($pos, $part, $haystack, $encoding); + } + public static function mb_strrichr($haystack, $needle, $part = \false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + return self::getSubpart($pos, $part, $haystack, $encoding); + } + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding); + $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding); + $haystack = \str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack); + $needle = \str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle); + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + public static function mb_strstr($haystack, $needle, $part = \false, $encoding = null) + { + $pos = \strpos($haystack, $needle); + if (\false === $pos) { + return \false; + } + if ($part) { + return \substr($haystack, 0, $pos); + } + return \substr($haystack, $pos); + } + public static function mb_get_info($type = 'all') + { + $info = ['internal_encoding' => self::$internalEncoding, 'http_output' => 'pass', 'http_output_conv_mimetypes' => '^(text/|application/xhtml\\+xml)', 'func_overload' => 0, 'func_overload_list' => 'no overload', 'mail_charset' => 'UTF-8', 'mail_header_encoding' => 'BASE64', 'mail_body_encoding' => 'BASE64', 'illegal_chars' => 0, 'encoding_translation' => 'Off', 'language' => self::$language, 'detect_order' => self::$encodingList, 'substitute_character' => 'none', 'strict_detection' => 'Off']; + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + return \false; + } + public static function mb_http_input($type = '') + { + return \false; + } + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('UTF-8' !== $encoding) { + $s = self::iconv($encoding, 'UTF-8', $s); + } + $s = \preg_replace('/[\\x{1100}-\\x{115F}\\x{2329}\\x{232A}\\x{2E80}-\\x{303E}\\x{3040}-\\x{A4CF}\\x{AC00}-\\x{D7A3}\\x{F900}-\\x{FAFF}\\x{FE10}-\\x{FE19}\\x{FE30}-\\x{FE6F}\\x{FF00}-\\x{FF60}\\x{FFE0}-\\x{FFE6}\\x{20000}-\\x{2FFFD}\\x{30000}-\\x{3FFFD}]/u', '', $s, -1, $wide); + return ($wide << 1) + \iconv_strlen($s, 'UTF-8'); + } + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return \substr_count($haystack, $needle); + } + public static function mb_output_handler($contents, $status) + { + return $contents; + } + public static function mb_chr($code, $encoding = null) + { + if (0x80 > ($code %= 0x200000)) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xc0 | $code >> 6) . \chr(0x80 | $code & 0x3f); + } elseif (0x10000 > $code) { + $s = \chr(0xe0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f); + } else { + $s = \chr(0xf0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3f) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f); + } + if ('UTF-8' !== ($encoding = self::getEncoding($encoding))) { + $s = \mb_convert_encoding($s, $encoding, 'UTF-8'); + } + return $s; + } + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== ($encoding = self::getEncoding($encoding))) { + $s = \mb_convert_encoding($s, 'UTF-8', $encoding); + } + if (1 === \strlen($s)) { + return \ord($s); + } + $code = ($s = \unpack('C*', \substr($s, 0, 4))) ? $s[1] : 0; + if (0xf0 <= $code) { + return ($code - 0xf0 << 18) + ($s[2] - 0x80 << 12) + ($s[3] - 0x80 << 6) + $s[4] - 0x80; + } + if (0xe0 <= $code) { + return ($code - 0xe0 << 12) + ($s[2] - 0x80 << 6) + $s[3] - 0x80; + } + if (0xc0 <= $code) { + return ($code - 0xc0 << 6) + $s[2] - 0x80; + } + return $code; + } + /** @return string|false */ + public static function mb_scrub(?string $string, ?string $encoding = null) : string + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_scrub(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return \false; + } + return self::mb_convert_encoding((string) $string, $encoding, $encoding); + } + /** @return string|false */ + public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) { + return \false; + } + if (self::mb_strlen($pad_string, $encoding) <= 0) { + if (\PHP_VERSION_ID < 80000) { + \trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING); + return \false; + } + throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string'); + } + if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], \true)) { + if (\PHP_VERSION_ID < 80000) { + \trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING); + return \false; + } + throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH'); + } + $paddingRequired = $length - self::mb_strlen($string, $encoding); + if ($paddingRequired < 1) { + return $string; + } + switch ($pad_type) { + case \STR_PAD_LEFT: + return self::mb_substr(\str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding) . $string; + case \STR_PAD_RIGHT: + return $string . self::mb_substr(\str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding); + default: + $leftPaddingLength = \floor($paddingRequired / 2); + $rightPaddingLength = $paddingRequired - $leftPaddingLength; + return self::mb_substr(\str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding) . $string . self::mb_substr(\str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding); + } + } + /** @return string|false */ + public static function mb_ucfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return \false; + } + $firstChar = \mb_substr($string, 0, 1, $encoding); + $firstChar = \mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding); + return $firstChar . \mb_substr($string, 1, null, $encoding); + } + /** @return string|false */ + public static function mb_lcfirst(string $string, ?string $encoding = null) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) { + return \false; + } + $firstChar = \mb_substr($string, 0, 1, $encoding); + $firstChar = \mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding); + return $firstChar . \mb_substr($string, 1, null, $encoding); + } + /** @return string|false */ + public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + /** @return string|false */ + public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__); + } + /** @return string|false */ + public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) + { + return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__); + } + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (\false === $pos) { + return \false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + return self::mb_substr($haystack, $pos, null, $encoding); + } + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = \unpack('C*', \htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xf0 <= $m[$i]) { + $c = ($m[$i++] - 0xf0 << 18) + ($m[$i++] - 0x80 << 12) + ($m[$i++] - 0x80 << 6) + $m[$i++] - 0x80; + } elseif (0xe0 <= $m[$i]) { + $c = ($m[$i++] - 0xe0 << 12) + ($m[$i++] - 0x80 << 6) + $m[$i++] - 0x80; + } else { + $c = ($m[$i++] - 0xc0 << 6) + $m[$i++] - 0x80; + } + $entities .= '&#' . $c . ';'; + } + return $entities; + } + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8') . self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + private static function getData($file) + { + if (\file_exists($file = __DIR__ . '/Resources/unidata/' . $file . '.php')) { + return require $file; + } + return \false; + } + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + $encoding = \strtoupper($encoding); + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + if ('UTF-32' === $encoding) { + return 'UTF-32BE'; + } + if ('UTF-16' === $encoding) { + return 'UTF-16BE'; + } + return $encoding; + } + private static function iconv($fromEncoding, $toEncoding, $s) + { + if (null === self::$iconvSupportsIgnore) { + self::$iconvSupportsIgnore = \false !== @\iconv('UTF-8', 'UTF-8//IGNORE', ''); + } + return self::$iconvSupportsIgnore ? \iconv($fromEncoding, $toEncoding . '//IGNORE', $s) : \iconv($fromEncoding, $toEncoding, $s); + } + /** @return string|false */ + private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function) + { + if (null === $encoding) { + $encoding = self::mb_internal_encoding(); + } elseif (!self::assertEncoding($encoding, $function . '(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) { + return \false; + } + if ('' === $characters) { + return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding); + } + if ('UTF-8' === $encoding) { + $encoding = null; + if (!\preg_match('//u', $string)) { + $string = @self::iconv('UTF-8', 'UTF-8', $string); + } + if (null !== $characters && !\preg_match('//u', $characters)) { + $characters = @self::iconv('UTF-8', 'UTF-8', $characters); + } + } else { + $string = self::iconv($encoding, 'UTF-8', $string); + if (null !== $characters) { + $characters = self::iconv($encoding, 'UTF-8', $characters); + } + } + if (null === $characters) { + $characters = "\\0 \f\n\r\t\v             

   …᠎"; + } else { + $characters = \preg_quote($characters); + } + $string = \preg_replace(\sprintf($regex, $characters), '', $string); + if (null === $encoding) { + return $string; + } + return self::iconv('UTF-8', $encoding, $string); + } + private static function assertEncoding(string $encoding, string $errorFormat) : bool + { + try { + $validEncoding = @self::mb_check_encoding('', $encoding); + } catch (\ValueError $e) { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + if (!$validEncoding) { + if (80000 > \PHP_VERSION_ID) { + \trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING); + } else { + throw new \ValueError(\sprintf($errorFormat, $encoding)); + } + } + return $validEncoding; + } +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php new file mode 100644 index 00000000..65748d21 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php @@ -0,0 +1,5 @@ + 'i̇', 'µ' => 'μ', 'ſ' => 's', 'ͅ' => 'ι', 'ς' => 'σ', 'ϐ' => 'β', 'ϑ' => 'θ', 'ϕ' => 'φ', 'ϖ' => 'π', 'ϰ' => 'κ', 'ϱ' => 'ρ', 'ϵ' => 'ε', 'ẛ' => 'ṡ', 'ι' => 'ι', 'ß' => 'ss', 'ʼn' => 'ʼn', 'ǰ' => 'ǰ', 'ΐ' => 'ΐ', 'ΰ' => 'ΰ', 'և' => 'եւ', 'ẖ' => 'ẖ', 'ẗ' => 'ẗ', 'ẘ' => 'ẘ', 'ẙ' => 'ẙ', 'ẚ' => 'aʾ', 'ẞ' => 'ss', 'ὐ' => 'ὐ', 'ὒ' => 'ὒ', 'ὔ' => 'ὔ', 'ὖ' => 'ὖ', 'ᾀ' => 'ἀι', 'ᾁ' => 'ἁι', 'ᾂ' => 'ἂι', 'ᾃ' => 'ἃι', 'ᾄ' => 'ἄι', 'ᾅ' => 'ἅι', 'ᾆ' => 'ἆι', 'ᾇ' => 'ἇι', 'ᾈ' => 'ἀι', 'ᾉ' => 'ἁι', 'ᾊ' => 'ἂι', 'ᾋ' => 'ἃι', 'ᾌ' => 'ἄι', 'ᾍ' => 'ἅι', 'ᾎ' => 'ἆι', 'ᾏ' => 'ἇι', 'ᾐ' => 'ἠι', 'ᾑ' => 'ἡι', 'ᾒ' => 'ἢι', 'ᾓ' => 'ἣι', 'ᾔ' => 'ἤι', 'ᾕ' => 'ἥι', 'ᾖ' => 'ἦι', 'ᾗ' => 'ἧι', 'ᾘ' => 'ἠι', 'ᾙ' => 'ἡι', 'ᾚ' => 'ἢι', 'ᾛ' => 'ἣι', 'ᾜ' => 'ἤι', 'ᾝ' => 'ἥι', 'ᾞ' => 'ἦι', 'ᾟ' => 'ἧι', 'ᾠ' => 'ὠι', 'ᾡ' => 'ὡι', 'ᾢ' => 'ὢι', 'ᾣ' => 'ὣι', 'ᾤ' => 'ὤι', 'ᾥ' => 'ὥι', 'ᾦ' => 'ὦι', 'ᾧ' => 'ὧι', 'ᾨ' => 'ὠι', 'ᾩ' => 'ὡι', 'ᾪ' => 'ὢι', 'ᾫ' => 'ὣι', 'ᾬ' => 'ὤι', 'ᾭ' => 'ὥι', 'ᾮ' => 'ὦι', 'ᾯ' => 'ὧι', 'ᾲ' => 'ὰι', 'ᾳ' => 'αι', 'ᾴ' => 'άι', 'ᾶ' => 'ᾶ', 'ᾷ' => 'ᾶι', 'ᾼ' => 'αι', 'ῂ' => 'ὴι', 'ῃ' => 'ηι', 'ῄ' => 'ήι', 'ῆ' => 'ῆ', 'ῇ' => 'ῆι', 'ῌ' => 'ηι', 'ῒ' => 'ῒ', 'ῖ' => 'ῖ', 'ῗ' => 'ῗ', 'ῢ' => 'ῢ', 'ῤ' => 'ῤ', 'ῦ' => 'ῦ', 'ῧ' => 'ῧ', 'ῲ' => 'ὼι', 'ῳ' => 'ωι', 'ῴ' => 'ώι', 'ῶ' => 'ῶ', 'ῷ' => 'ῶι', 'ῼ' => 'ωι', 'ff' => 'ff', 'fi' => 'fi', 'fl' => 'fl', 'ffi' => 'ffi', 'ffl' => 'ffl', 'ſt' => 'st', 'st' => 'st', 'ﬓ' => 'մն', 'ﬔ' => 'մե', 'ﬕ' => 'մի', 'ﬖ' => 'վն', 'ﬗ' => 'մխ']; diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 00000000..82869255 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,5 @@ + 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', 'À' => 'à', 'Á' => 'á', 'Â' => 'â', 'Ã' => 'ã', 'Ä' => 'ä', 'Å' => 'å', 'Æ' => 'æ', 'Ç' => 'ç', 'È' => 'è', 'É' => 'é', 'Ê' => 'ê', 'Ë' => 'ë', 'Ì' => 'ì', 'Í' => 'í', 'Î' => 'î', 'Ï' => 'ï', 'Ð' => 'ð', 'Ñ' => 'ñ', 'Ò' => 'ò', 'Ó' => 'ó', 'Ô' => 'ô', 'Õ' => 'õ', 'Ö' => 'ö', 'Ø' => 'ø', 'Ù' => 'ù', 'Ú' => 'ú', 'Û' => 'û', 'Ü' => 'ü', 'Ý' => 'ý', 'Þ' => 'þ', 'Ā' => 'ā', 'Ă' => 'ă', 'Ą' => 'ą', 'Ć' => 'ć', 'Ĉ' => 'ĉ', 'Ċ' => 'ċ', 'Č' => 'č', 'Ď' => 'ď', 'Đ' => 'đ', 'Ē' => 'ē', 'Ĕ' => 'ĕ', 'Ė' => 'ė', 'Ę' => 'ę', 'Ě' => 'ě', 'Ĝ' => 'ĝ', 'Ğ' => 'ğ', 'Ġ' => 'ġ', 'Ģ' => 'ģ', 'Ĥ' => 'ĥ', 'Ħ' => 'ħ', 'Ĩ' => 'ĩ', 'Ī' => 'ī', 'Ĭ' => 'ĭ', 'Į' => 'į', 'İ' => 'i̇', 'IJ' => 'ij', 'Ĵ' => 'ĵ', 'Ķ' => 'ķ', 'Ĺ' => 'ĺ', 'Ļ' => 'ļ', 'Ľ' => 'ľ', 'Ŀ' => 'ŀ', 'Ł' => 'ł', 'Ń' => 'ń', 'Ņ' => 'ņ', 'Ň' => 'ň', 'Ŋ' => 'ŋ', 'Ō' => 'ō', 'Ŏ' => 'ŏ', 'Ő' => 'ő', 'Œ' => 'œ', 'Ŕ' => 'ŕ', 'Ŗ' => 'ŗ', 'Ř' => 'ř', 'Ś' => 'ś', 'Ŝ' => 'ŝ', 'Ş' => 'ş', 'Š' => 'š', 'Ţ' => 'ţ', 'Ť' => 'ť', 'Ŧ' => 'ŧ', 'Ũ' => 'ũ', 'Ū' => 'ū', 'Ŭ' => 'ŭ', 'Ů' => 'ů', 'Ű' => 'ű', 'Ų' => 'ų', 'Ŵ' => 'ŵ', 'Ŷ' => 'ŷ', 'Ÿ' => 'ÿ', 'Ź' => 'ź', 'Ż' => 'ż', 'Ž' => 'ž', 'Ɓ' => 'ɓ', 'Ƃ' => 'ƃ', 'Ƅ' => 'ƅ', 'Ɔ' => 'ɔ', 'Ƈ' => 'ƈ', 'Ɖ' => 'ɖ', 'Ɗ' => 'ɗ', 'Ƌ' => 'ƌ', 'Ǝ' => 'ǝ', 'Ə' => 'ə', 'Ɛ' => 'ɛ', 'Ƒ' => 'ƒ', 'Ɠ' => 'ɠ', 'Ɣ' => 'ɣ', 'Ɩ' => 'ɩ', 'Ɨ' => 'ɨ', 'Ƙ' => 'ƙ', 'Ɯ' => 'ɯ', 'Ɲ' => 'ɲ', 'Ɵ' => 'ɵ', 'Ơ' => 'ơ', 'Ƣ' => 'ƣ', 'Ƥ' => 'ƥ', 'Ʀ' => 'ʀ', 'Ƨ' => 'ƨ', 'Ʃ' => 'ʃ', 'Ƭ' => 'ƭ', 'Ʈ' => 'ʈ', 'Ư' => 'ư', 'Ʊ' => 'ʊ', 'Ʋ' => 'ʋ', 'Ƴ' => 'ƴ', 'Ƶ' => 'ƶ', 'Ʒ' => 'ʒ', 'Ƹ' => 'ƹ', 'Ƽ' => 'ƽ', 'DŽ' => 'dž', 'Dž' => 'dž', 'LJ' => 'lj', 'Lj' => 'lj', 'NJ' => 'nj', 'Nj' => 'nj', 'Ǎ' => 'ǎ', 'Ǐ' => 'ǐ', 'Ǒ' => 'ǒ', 'Ǔ' => 'ǔ', 'Ǖ' => 'ǖ', 'Ǘ' => 'ǘ', 'Ǚ' => 'ǚ', 'Ǜ' => 'ǜ', 'Ǟ' => 'ǟ', 'Ǡ' => 'ǡ', 'Ǣ' => 'ǣ', 'Ǥ' => 'ǥ', 'Ǧ' => 'ǧ', 'Ǩ' => 'ǩ', 'Ǫ' => 'ǫ', 'Ǭ' => 'ǭ', 'Ǯ' => 'ǯ', 'DZ' => 'dz', 'Dz' => 'dz', 'Ǵ' => 'ǵ', 'Ƕ' => 'ƕ', 'Ƿ' => 'ƿ', 'Ǹ' => 'ǹ', 'Ǻ' => 'ǻ', 'Ǽ' => 'ǽ', 'Ǿ' => 'ǿ', 'Ȁ' => 'ȁ', 'Ȃ' => 'ȃ', 'Ȅ' => 'ȅ', 'Ȇ' => 'ȇ', 'Ȉ' => 'ȉ', 'Ȋ' => 'ȋ', 'Ȍ' => 'ȍ', 'Ȏ' => 'ȏ', 'Ȑ' => 'ȑ', 'Ȓ' => 'ȓ', 'Ȕ' => 'ȕ', 'Ȗ' => 'ȗ', 'Ș' => 'ș', 'Ț' => 'ț', 'Ȝ' => 'ȝ', 'Ȟ' => 'ȟ', 'Ƞ' => 'ƞ', 'Ȣ' => 'ȣ', 'Ȥ' => 'ȥ', 'Ȧ' => 'ȧ', 'Ȩ' => 'ȩ', 'Ȫ' => 'ȫ', 'Ȭ' => 'ȭ', 'Ȯ' => 'ȯ', 'Ȱ' => 'ȱ', 'Ȳ' => 'ȳ', 'Ⱥ' => 'ⱥ', 'Ȼ' => 'ȼ', 'Ƚ' => 'ƚ', 'Ⱦ' => 'ⱦ', 'Ɂ' => 'ɂ', 'Ƀ' => 'ƀ', 'Ʉ' => 'ʉ', 'Ʌ' => 'ʌ', 'Ɇ' => 'ɇ', 'Ɉ' => 'ɉ', 'Ɋ' => 'ɋ', 'Ɍ' => 'ɍ', 'Ɏ' => 'ɏ', 'Ͱ' => 'ͱ', 'Ͳ' => 'ͳ', 'Ͷ' => 'ͷ', 'Ϳ' => 'ϳ', 'Ά' => 'ά', 'Έ' => 'έ', 'Ή' => 'ή', 'Ί' => 'ί', 'Ό' => 'ό', 'Ύ' => 'ύ', 'Ώ' => 'ώ', 'Α' => 'α', 'Β' => 'β', 'Γ' => 'γ', 'Δ' => 'δ', 'Ε' => 'ε', 'Ζ' => 'ζ', 'Η' => 'η', 'Θ' => 'θ', 'Ι' => 'ι', 'Κ' => 'κ', 'Λ' => 'λ', 'Μ' => 'μ', 'Ν' => 'ν', 'Ξ' => 'ξ', 'Ο' => 'ο', 'Π' => 'π', 'Ρ' => 'ρ', 'Σ' => 'σ', 'Τ' => 'τ', 'Υ' => 'υ', 'Φ' => 'φ', 'Χ' => 'χ', 'Ψ' => 'ψ', 'Ω' => 'ω', 'Ϊ' => 'ϊ', 'Ϋ' => 'ϋ', 'Ϗ' => 'ϗ', 'Ϙ' => 'ϙ', 'Ϛ' => 'ϛ', 'Ϝ' => 'ϝ', 'Ϟ' => 'ϟ', 'Ϡ' => 'ϡ', 'Ϣ' => 'ϣ', 'Ϥ' => 'ϥ', 'Ϧ' => 'ϧ', 'Ϩ' => 'ϩ', 'Ϫ' => 'ϫ', 'Ϭ' => 'ϭ', 'Ϯ' => 'ϯ', 'ϴ' => 'θ', 'Ϸ' => 'ϸ', 'Ϲ' => 'ϲ', 'Ϻ' => 'ϻ', 'Ͻ' => 'ͻ', 'Ͼ' => 'ͼ', 'Ͽ' => 'ͽ', 'Ѐ' => 'ѐ', 'Ё' => 'ё', 'Ђ' => 'ђ', 'Ѓ' => 'ѓ', 'Є' => 'є', 'Ѕ' => 'ѕ', 'І' => 'і', 'Ї' => 'ї', 'Ј' => 'ј', 'Љ' => 'љ', 'Њ' => 'њ', 'Ћ' => 'ћ', 'Ќ' => 'ќ', 'Ѝ' => 'ѝ', 'Ў' => 'ў', 'Џ' => 'џ', 'А' => 'а', 'Б' => 'б', 'В' => 'в', 'Г' => 'г', 'Д' => 'д', 'Е' => 'е', 'Ж' => 'ж', 'З' => 'з', 'И' => 'и', 'Й' => 'й', 'К' => 'к', 'Л' => 'л', 'М' => 'м', 'Н' => 'н', 'О' => 'о', 'П' => 'п', 'Р' => 'р', 'С' => 'с', 'Т' => 'т', 'У' => 'у', 'Ф' => 'ф', 'Х' => 'х', 'Ц' => 'ц', 'Ч' => 'ч', 'Ш' => 'ш', 'Щ' => 'щ', 'Ъ' => 'ъ', 'Ы' => 'ы', 'Ь' => 'ь', 'Э' => 'э', 'Ю' => 'ю', 'Я' => 'я', 'Ѡ' => 'ѡ', 'Ѣ' => 'ѣ', 'Ѥ' => 'ѥ', 'Ѧ' => 'ѧ', 'Ѩ' => 'ѩ', 'Ѫ' => 'ѫ', 'Ѭ' => 'ѭ', 'Ѯ' => 'ѯ', 'Ѱ' => 'ѱ', 'Ѳ' => 'ѳ', 'Ѵ' => 'ѵ', 'Ѷ' => 'ѷ', 'Ѹ' => 'ѹ', 'Ѻ' => 'ѻ', 'Ѽ' => 'ѽ', 'Ѿ' => 'ѿ', 'Ҁ' => 'ҁ', 'Ҋ' => 'ҋ', 'Ҍ' => 'ҍ', 'Ҏ' => 'ҏ', 'Ґ' => 'ґ', 'Ғ' => 'ғ', 'Ҕ' => 'ҕ', 'Җ' => 'җ', 'Ҙ' => 'ҙ', 'Қ' => 'қ', 'Ҝ' => 'ҝ', 'Ҟ' => 'ҟ', 'Ҡ' => 'ҡ', 'Ң' => 'ң', 'Ҥ' => 'ҥ', 'Ҧ' => 'ҧ', 'Ҩ' => 'ҩ', 'Ҫ' => 'ҫ', 'Ҭ' => 'ҭ', 'Ү' => 'ү', 'Ұ' => 'ұ', 'Ҳ' => 'ҳ', 'Ҵ' => 'ҵ', 'Ҷ' => 'ҷ', 'Ҹ' => 'ҹ', 'Һ' => 'һ', 'Ҽ' => 'ҽ', 'Ҿ' => 'ҿ', 'Ӏ' => 'ӏ', 'Ӂ' => 'ӂ', 'Ӄ' => 'ӄ', 'Ӆ' => 'ӆ', 'Ӈ' => 'ӈ', 'Ӊ' => 'ӊ', 'Ӌ' => 'ӌ', 'Ӎ' => 'ӎ', 'Ӑ' => 'ӑ', 'Ӓ' => 'ӓ', 'Ӕ' => 'ӕ', 'Ӗ' => 'ӗ', 'Ә' => 'ә', 'Ӛ' => 'ӛ', 'Ӝ' => 'ӝ', 'Ӟ' => 'ӟ', 'Ӡ' => 'ӡ', 'Ӣ' => 'ӣ', 'Ӥ' => 'ӥ', 'Ӧ' => 'ӧ', 'Ө' => 'ө', 'Ӫ' => 'ӫ', 'Ӭ' => 'ӭ', 'Ӯ' => 'ӯ', 'Ӱ' => 'ӱ', 'Ӳ' => 'ӳ', 'Ӵ' => 'ӵ', 'Ӷ' => 'ӷ', 'Ӹ' => 'ӹ', 'Ӻ' => 'ӻ', 'Ӽ' => 'ӽ', 'Ӿ' => 'ӿ', 'Ԁ' => 'ԁ', 'Ԃ' => 'ԃ', 'Ԅ' => 'ԅ', 'Ԇ' => 'ԇ', 'Ԉ' => 'ԉ', 'Ԋ' => 'ԋ', 'Ԍ' => 'ԍ', 'Ԏ' => 'ԏ', 'Ԑ' => 'ԑ', 'Ԓ' => 'ԓ', 'Ԕ' => 'ԕ', 'Ԗ' => 'ԗ', 'Ԙ' => 'ԙ', 'Ԛ' => 'ԛ', 'Ԝ' => 'ԝ', 'Ԟ' => 'ԟ', 'Ԡ' => 'ԡ', 'Ԣ' => 'ԣ', 'Ԥ' => 'ԥ', 'Ԧ' => 'ԧ', 'Ԩ' => 'ԩ', 'Ԫ' => 'ԫ', 'Ԭ' => 'ԭ', 'Ԯ' => 'ԯ', 'Ա' => 'ա', 'Բ' => 'բ', 'Գ' => 'գ', 'Դ' => 'դ', 'Ե' => 'ե', 'Զ' => 'զ', 'Է' => 'է', 'Ը' => 'ը', 'Թ' => 'թ', 'Ժ' => 'ժ', 'Ի' => 'ի', 'Լ' => 'լ', 'Խ' => 'խ', 'Ծ' => 'ծ', 'Կ' => 'կ', 'Հ' => 'հ', 'Ձ' => 'ձ', 'Ղ' => 'ղ', 'Ճ' => 'ճ', 'Մ' => 'մ', 'Յ' => 'յ', 'Ն' => 'ն', 'Շ' => 'շ', 'Ո' => 'ո', 'Չ' => 'չ', 'Պ' => 'պ', 'Ջ' => 'ջ', 'Ռ' => 'ռ', 'Ս' => 'ս', 'Վ' => 'վ', 'Տ' => 'տ', 'Ր' => 'ր', 'Ց' => 'ց', 'Ւ' => 'ւ', 'Փ' => 'փ', 'Ք' => 'ք', 'Օ' => 'օ', 'Ֆ' => 'ֆ', 'Ⴀ' => 'ⴀ', 'Ⴁ' => 'ⴁ', 'Ⴂ' => 'ⴂ', 'Ⴃ' => 'ⴃ', 'Ⴄ' => 'ⴄ', 'Ⴅ' => 'ⴅ', 'Ⴆ' => 'ⴆ', 'Ⴇ' => 'ⴇ', 'Ⴈ' => 'ⴈ', 'Ⴉ' => 'ⴉ', 'Ⴊ' => 'ⴊ', 'Ⴋ' => 'ⴋ', 'Ⴌ' => 'ⴌ', 'Ⴍ' => 'ⴍ', 'Ⴎ' => 'ⴎ', 'Ⴏ' => 'ⴏ', 'Ⴐ' => 'ⴐ', 'Ⴑ' => 'ⴑ', 'Ⴒ' => 'ⴒ', 'Ⴓ' => 'ⴓ', 'Ⴔ' => 'ⴔ', 'Ⴕ' => 'ⴕ', 'Ⴖ' => 'ⴖ', 'Ⴗ' => 'ⴗ', 'Ⴘ' => 'ⴘ', 'Ⴙ' => 'ⴙ', 'Ⴚ' => 'ⴚ', 'Ⴛ' => 'ⴛ', 'Ⴜ' => 'ⴜ', 'Ⴝ' => 'ⴝ', 'Ⴞ' => 'ⴞ', 'Ⴟ' => 'ⴟ', 'Ⴠ' => 'ⴠ', 'Ⴡ' => 'ⴡ', 'Ⴢ' => 'ⴢ', 'Ⴣ' => 'ⴣ', 'Ⴤ' => 'ⴤ', 'Ⴥ' => 'ⴥ', 'Ⴧ' => 'ⴧ', 'Ⴭ' => 'ⴭ', 'Ꭰ' => 'ꭰ', 'Ꭱ' => 'ꭱ', 'Ꭲ' => 'ꭲ', 'Ꭳ' => 'ꭳ', 'Ꭴ' => 'ꭴ', 'Ꭵ' => 'ꭵ', 'Ꭶ' => 'ꭶ', 'Ꭷ' => 'ꭷ', 'Ꭸ' => 'ꭸ', 'Ꭹ' => 'ꭹ', 'Ꭺ' => 'ꭺ', 'Ꭻ' => 'ꭻ', 'Ꭼ' => 'ꭼ', 'Ꭽ' => 'ꭽ', 'Ꭾ' => 'ꭾ', 'Ꭿ' => 'ꭿ', 'Ꮀ' => 'ꮀ', 'Ꮁ' => 'ꮁ', 'Ꮂ' => 'ꮂ', 'Ꮃ' => 'ꮃ', 'Ꮄ' => 'ꮄ', 'Ꮅ' => 'ꮅ', 'Ꮆ' => 'ꮆ', 'Ꮇ' => 'ꮇ', 'Ꮈ' => 'ꮈ', 'Ꮉ' => 'ꮉ', 'Ꮊ' => 'ꮊ', 'Ꮋ' => 'ꮋ', 'Ꮌ' => 'ꮌ', 'Ꮍ' => 'ꮍ', 'Ꮎ' => 'ꮎ', 'Ꮏ' => 'ꮏ', 'Ꮐ' => 'ꮐ', 'Ꮑ' => 'ꮑ', 'Ꮒ' => 'ꮒ', 'Ꮓ' => 'ꮓ', 'Ꮔ' => 'ꮔ', 'Ꮕ' => 'ꮕ', 'Ꮖ' => 'ꮖ', 'Ꮗ' => 'ꮗ', 'Ꮘ' => 'ꮘ', 'Ꮙ' => 'ꮙ', 'Ꮚ' => 'ꮚ', 'Ꮛ' => 'ꮛ', 'Ꮜ' => 'ꮜ', 'Ꮝ' => 'ꮝ', 'Ꮞ' => 'ꮞ', 'Ꮟ' => 'ꮟ', 'Ꮠ' => 'ꮠ', 'Ꮡ' => 'ꮡ', 'Ꮢ' => 'ꮢ', 'Ꮣ' => 'ꮣ', 'Ꮤ' => 'ꮤ', 'Ꮥ' => 'ꮥ', 'Ꮦ' => 'ꮦ', 'Ꮧ' => 'ꮧ', 'Ꮨ' => 'ꮨ', 'Ꮩ' => 'ꮩ', 'Ꮪ' => 'ꮪ', 'Ꮫ' => 'ꮫ', 'Ꮬ' => 'ꮬ', 'Ꮭ' => 'ꮭ', 'Ꮮ' => 'ꮮ', 'Ꮯ' => 'ꮯ', 'Ꮰ' => 'ꮰ', 'Ꮱ' => 'ꮱ', 'Ꮲ' => 'ꮲ', 'Ꮳ' => 'ꮳ', 'Ꮴ' => 'ꮴ', 'Ꮵ' => 'ꮵ', 'Ꮶ' => 'ꮶ', 'Ꮷ' => 'ꮷ', 'Ꮸ' => 'ꮸ', 'Ꮹ' => 'ꮹ', 'Ꮺ' => 'ꮺ', 'Ꮻ' => 'ꮻ', 'Ꮼ' => 'ꮼ', 'Ꮽ' => 'ꮽ', 'Ꮾ' => 'ꮾ', 'Ꮿ' => 'ꮿ', 'Ᏸ' => 'ᏸ', 'Ᏹ' => 'ᏹ', 'Ᏺ' => 'ᏺ', 'Ᏻ' => 'ᏻ', 'Ᏼ' => 'ᏼ', 'Ᏽ' => 'ᏽ', 'Ა' => 'ა', 'Ბ' => 'ბ', 'Გ' => 'გ', 'Დ' => 'დ', 'Ე' => 'ე', 'Ვ' => 'ვ', 'Ზ' => 'ზ', 'Თ' => 'თ', 'Ი' => 'ი', 'Კ' => 'კ', 'Ლ' => 'ლ', 'Მ' => 'მ', 'Ნ' => 'ნ', 'Ო' => 'ო', 'Პ' => 'პ', 'Ჟ' => 'ჟ', 'Რ' => 'რ', 'Ს' => 'ს', 'Ტ' => 'ტ', 'Უ' => 'უ', 'Ფ' => 'ფ', 'Ქ' => 'ქ', 'Ღ' => 'ღ', 'Ყ' => 'ყ', 'Შ' => 'შ', 'Ჩ' => 'ჩ', 'Ც' => 'ც', 'Ძ' => 'ძ', 'Წ' => 'წ', 'Ჭ' => 'ჭ', 'Ხ' => 'ხ', 'Ჯ' => 'ჯ', 'Ჰ' => 'ჰ', 'Ჱ' => 'ჱ', 'Ჲ' => 'ჲ', 'Ჳ' => 'ჳ', 'Ჴ' => 'ჴ', 'Ჵ' => 'ჵ', 'Ჶ' => 'ჶ', 'Ჷ' => 'ჷ', 'Ჸ' => 'ჸ', 'Ჹ' => 'ჹ', 'Ჺ' => 'ჺ', 'Ჽ' => 'ჽ', 'Ჾ' => 'ჾ', 'Ჿ' => 'ჿ', 'Ḁ' => 'ḁ', 'Ḃ' => 'ḃ', 'Ḅ' => 'ḅ', 'Ḇ' => 'ḇ', 'Ḉ' => 'ḉ', 'Ḋ' => 'ḋ', 'Ḍ' => 'ḍ', 'Ḏ' => 'ḏ', 'Ḑ' => 'ḑ', 'Ḓ' => 'ḓ', 'Ḕ' => 'ḕ', 'Ḗ' => 'ḗ', 'Ḙ' => 'ḙ', 'Ḛ' => 'ḛ', 'Ḝ' => 'ḝ', 'Ḟ' => 'ḟ', 'Ḡ' => 'ḡ', 'Ḣ' => 'ḣ', 'Ḥ' => 'ḥ', 'Ḧ' => 'ḧ', 'Ḩ' => 'ḩ', 'Ḫ' => 'ḫ', 'Ḭ' => 'ḭ', 'Ḯ' => 'ḯ', 'Ḱ' => 'ḱ', 'Ḳ' => 'ḳ', 'Ḵ' => 'ḵ', 'Ḷ' => 'ḷ', 'Ḹ' => 'ḹ', 'Ḻ' => 'ḻ', 'Ḽ' => 'ḽ', 'Ḿ' => 'ḿ', 'Ṁ' => 'ṁ', 'Ṃ' => 'ṃ', 'Ṅ' => 'ṅ', 'Ṇ' => 'ṇ', 'Ṉ' => 'ṉ', 'Ṋ' => 'ṋ', 'Ṍ' => 'ṍ', 'Ṏ' => 'ṏ', 'Ṑ' => 'ṑ', 'Ṓ' => 'ṓ', 'Ṕ' => 'ṕ', 'Ṗ' => 'ṗ', 'Ṙ' => 'ṙ', 'Ṛ' => 'ṛ', 'Ṝ' => 'ṝ', 'Ṟ' => 'ṟ', 'Ṡ' => 'ṡ', 'Ṣ' => 'ṣ', 'Ṥ' => 'ṥ', 'Ṧ' => 'ṧ', 'Ṩ' => 'ṩ', 'Ṫ' => 'ṫ', 'Ṭ' => 'ṭ', 'Ṯ' => 'ṯ', 'Ṱ' => 'ṱ', 'Ṳ' => 'ṳ', 'Ṵ' => 'ṵ', 'Ṷ' => 'ṷ', 'Ṹ' => 'ṹ', 'Ṻ' => 'ṻ', 'Ṽ' => 'ṽ', 'Ṿ' => 'ṿ', 'Ẁ' => 'ẁ', 'Ẃ' => 'ẃ', 'Ẅ' => 'ẅ', 'Ẇ' => 'ẇ', 'Ẉ' => 'ẉ', 'Ẋ' => 'ẋ', 'Ẍ' => 'ẍ', 'Ẏ' => 'ẏ', 'Ẑ' => 'ẑ', 'Ẓ' => 'ẓ', 'Ẕ' => 'ẕ', 'ẞ' => 'ß', 'Ạ' => 'ạ', 'Ả' => 'ả', 'Ấ' => 'ấ', 'Ầ' => 'ầ', 'Ẩ' => 'ẩ', 'Ẫ' => 'ẫ', 'Ậ' => 'ậ', 'Ắ' => 'ắ', 'Ằ' => 'ằ', 'Ẳ' => 'ẳ', 'Ẵ' => 'ẵ', 'Ặ' => 'ặ', 'Ẹ' => 'ẹ', 'Ẻ' => 'ẻ', 'Ẽ' => 'ẽ', 'Ế' => 'ế', 'Ề' => 'ề', 'Ể' => 'ể', 'Ễ' => 'ễ', 'Ệ' => 'ệ', 'Ỉ' => 'ỉ', 'Ị' => 'ị', 'Ọ' => 'ọ', 'Ỏ' => 'ỏ', 'Ố' => 'ố', 'Ồ' => 'ồ', 'Ổ' => 'ổ', 'Ỗ' => 'ỗ', 'Ộ' => 'ộ', 'Ớ' => 'ớ', 'Ờ' => 'ờ', 'Ở' => 'ở', 'Ỡ' => 'ỡ', 'Ợ' => 'ợ', 'Ụ' => 'ụ', 'Ủ' => 'ủ', 'Ứ' => 'ứ', 'Ừ' => 'ừ', 'Ử' => 'ử', 'Ữ' => 'ữ', 'Ự' => 'ự', 'Ỳ' => 'ỳ', 'Ỵ' => 'ỵ', 'Ỷ' => 'ỷ', 'Ỹ' => 'ỹ', 'Ỻ' => 'ỻ', 'Ỽ' => 'ỽ', 'Ỿ' => 'ỿ', 'Ἀ' => 'ἀ', 'Ἁ' => 'ἁ', 'Ἂ' => 'ἂ', 'Ἃ' => 'ἃ', 'Ἄ' => 'ἄ', 'Ἅ' => 'ἅ', 'Ἆ' => 'ἆ', 'Ἇ' => 'ἇ', 'Ἐ' => 'ἐ', 'Ἑ' => 'ἑ', 'Ἒ' => 'ἒ', 'Ἓ' => 'ἓ', 'Ἔ' => 'ἔ', 'Ἕ' => 'ἕ', 'Ἠ' => 'ἠ', 'Ἡ' => 'ἡ', 'Ἢ' => 'ἢ', 'Ἣ' => 'ἣ', 'Ἤ' => 'ἤ', 'Ἥ' => 'ἥ', 'Ἦ' => 'ἦ', 'Ἧ' => 'ἧ', 'Ἰ' => 'ἰ', 'Ἱ' => 'ἱ', 'Ἲ' => 'ἲ', 'Ἳ' => 'ἳ', 'Ἴ' => 'ἴ', 'Ἵ' => 'ἵ', 'Ἶ' => 'ἶ', 'Ἷ' => 'ἷ', 'Ὀ' => 'ὀ', 'Ὁ' => 'ὁ', 'Ὂ' => 'ὂ', 'Ὃ' => 'ὃ', 'Ὄ' => 'ὄ', 'Ὅ' => 'ὅ', 'Ὑ' => 'ὑ', 'Ὓ' => 'ὓ', 'Ὕ' => 'ὕ', 'Ὗ' => 'ὗ', 'Ὠ' => 'ὠ', 'Ὡ' => 'ὡ', 'Ὢ' => 'ὢ', 'Ὣ' => 'ὣ', 'Ὤ' => 'ὤ', 'Ὥ' => 'ὥ', 'Ὦ' => 'ὦ', 'Ὧ' => 'ὧ', 'ᾈ' => 'ᾀ', 'ᾉ' => 'ᾁ', 'ᾊ' => 'ᾂ', 'ᾋ' => 'ᾃ', 'ᾌ' => 'ᾄ', 'ᾍ' => 'ᾅ', 'ᾎ' => 'ᾆ', 'ᾏ' => 'ᾇ', 'ᾘ' => 'ᾐ', 'ᾙ' => 'ᾑ', 'ᾚ' => 'ᾒ', 'ᾛ' => 'ᾓ', 'ᾜ' => 'ᾔ', 'ᾝ' => 'ᾕ', 'ᾞ' => 'ᾖ', 'ᾟ' => 'ᾗ', 'ᾨ' => 'ᾠ', 'ᾩ' => 'ᾡ', 'ᾪ' => 'ᾢ', 'ᾫ' => 'ᾣ', 'ᾬ' => 'ᾤ', 'ᾭ' => 'ᾥ', 'ᾮ' => 'ᾦ', 'ᾯ' => 'ᾧ', 'Ᾰ' => 'ᾰ', 'Ᾱ' => 'ᾱ', 'Ὰ' => 'ὰ', 'Ά' => 'ά', 'ᾼ' => 'ᾳ', 'Ὲ' => 'ὲ', 'Έ' => 'έ', 'Ὴ' => 'ὴ', 'Ή' => 'ή', 'ῌ' => 'ῃ', 'Ῐ' => 'ῐ', 'Ῑ' => 'ῑ', 'Ὶ' => 'ὶ', 'Ί' => 'ί', 'Ῠ' => 'ῠ', 'Ῡ' => 'ῡ', 'Ὺ' => 'ὺ', 'Ύ' => 'ύ', 'Ῥ' => 'ῥ', 'Ὸ' => 'ὸ', 'Ό' => 'ό', 'Ὼ' => 'ὼ', 'Ώ' => 'ώ', 'ῼ' => 'ῳ', 'Ω' => 'ω', 'K' => 'k', 'Å' => 'å', 'Ⅎ' => 'ⅎ', 'Ⅰ' => 'ⅰ', 'Ⅱ' => 'ⅱ', 'Ⅲ' => 'ⅲ', 'Ⅳ' => 'ⅳ', 'Ⅴ' => 'ⅴ', 'Ⅵ' => 'ⅵ', 'Ⅶ' => 'ⅶ', 'Ⅷ' => 'ⅷ', 'Ⅸ' => 'ⅸ', 'Ⅹ' => 'ⅹ', 'Ⅺ' => 'ⅺ', 'Ⅻ' => 'ⅻ', 'Ⅼ' => 'ⅼ', 'Ⅽ' => 'ⅽ', 'Ⅾ' => 'ⅾ', 'Ⅿ' => 'ⅿ', 'Ↄ' => 'ↄ', 'Ⓐ' => 'ⓐ', 'Ⓑ' => 'ⓑ', 'Ⓒ' => 'ⓒ', 'Ⓓ' => 'ⓓ', 'Ⓔ' => 'ⓔ', 'Ⓕ' => 'ⓕ', 'Ⓖ' => 'ⓖ', 'Ⓗ' => 'ⓗ', 'Ⓘ' => 'ⓘ', 'Ⓙ' => 'ⓙ', 'Ⓚ' => 'ⓚ', 'Ⓛ' => 'ⓛ', 'Ⓜ' => 'ⓜ', 'Ⓝ' => 'ⓝ', 'Ⓞ' => 'ⓞ', 'Ⓟ' => 'ⓟ', 'Ⓠ' => 'ⓠ', 'Ⓡ' => 'ⓡ', 'Ⓢ' => 'ⓢ', 'Ⓣ' => 'ⓣ', 'Ⓤ' => 'ⓤ', 'Ⓥ' => 'ⓥ', 'Ⓦ' => 'ⓦ', 'Ⓧ' => 'ⓧ', 'Ⓨ' => 'ⓨ', 'Ⓩ' => 'ⓩ', 'Ⰰ' => 'ⰰ', 'Ⰱ' => 'ⰱ', 'Ⰲ' => 'ⰲ', 'Ⰳ' => 'ⰳ', 'Ⰴ' => 'ⰴ', 'Ⰵ' => 'ⰵ', 'Ⰶ' => 'ⰶ', 'Ⰷ' => 'ⰷ', 'Ⰸ' => 'ⰸ', 'Ⰹ' => 'ⰹ', 'Ⰺ' => 'ⰺ', 'Ⰻ' => 'ⰻ', 'Ⰼ' => 'ⰼ', 'Ⰽ' => 'ⰽ', 'Ⰾ' => 'ⰾ', 'Ⰿ' => 'ⰿ', 'Ⱀ' => 'ⱀ', 'Ⱁ' => 'ⱁ', 'Ⱂ' => 'ⱂ', 'Ⱃ' => 'ⱃ', 'Ⱄ' => 'ⱄ', 'Ⱅ' => 'ⱅ', 'Ⱆ' => 'ⱆ', 'Ⱇ' => 'ⱇ', 'Ⱈ' => 'ⱈ', 'Ⱉ' => 'ⱉ', 'Ⱊ' => 'ⱊ', 'Ⱋ' => 'ⱋ', 'Ⱌ' => 'ⱌ', 'Ⱍ' => 'ⱍ', 'Ⱎ' => 'ⱎ', 'Ⱏ' => 'ⱏ', 'Ⱐ' => 'ⱐ', 'Ⱑ' => 'ⱑ', 'Ⱒ' => 'ⱒ', 'Ⱓ' => 'ⱓ', 'Ⱔ' => 'ⱔ', 'Ⱕ' => 'ⱕ', 'Ⱖ' => 'ⱖ', 'Ⱗ' => 'ⱗ', 'Ⱘ' => 'ⱘ', 'Ⱙ' => 'ⱙ', 'Ⱚ' => 'ⱚ', 'Ⱛ' => 'ⱛ', 'Ⱜ' => 'ⱜ', 'Ⱝ' => 'ⱝ', 'Ⱞ' => 'ⱞ', 'Ⱡ' => 'ⱡ', 'Ɫ' => 'ɫ', 'Ᵽ' => 'ᵽ', 'Ɽ' => 'ɽ', 'Ⱨ' => 'ⱨ', 'Ⱪ' => 'ⱪ', 'Ⱬ' => 'ⱬ', 'Ɑ' => 'ɑ', 'Ɱ' => 'ɱ', 'Ɐ' => 'ɐ', 'Ɒ' => 'ɒ', 'Ⱳ' => 'ⱳ', 'Ⱶ' => 'ⱶ', 'Ȿ' => 'ȿ', 'Ɀ' => 'ɀ', 'Ⲁ' => 'ⲁ', 'Ⲃ' => 'ⲃ', 'Ⲅ' => 'ⲅ', 'Ⲇ' => 'ⲇ', 'Ⲉ' => 'ⲉ', 'Ⲋ' => 'ⲋ', 'Ⲍ' => 'ⲍ', 'Ⲏ' => 'ⲏ', 'Ⲑ' => 'ⲑ', 'Ⲓ' => 'ⲓ', 'Ⲕ' => 'ⲕ', 'Ⲗ' => 'ⲗ', 'Ⲙ' => 'ⲙ', 'Ⲛ' => 'ⲛ', 'Ⲝ' => 'ⲝ', 'Ⲟ' => 'ⲟ', 'Ⲡ' => 'ⲡ', 'Ⲣ' => 'ⲣ', 'Ⲥ' => 'ⲥ', 'Ⲧ' => 'ⲧ', 'Ⲩ' => 'ⲩ', 'Ⲫ' => 'ⲫ', 'Ⲭ' => 'ⲭ', 'Ⲯ' => 'ⲯ', 'Ⲱ' => 'ⲱ', 'Ⲳ' => 'ⲳ', 'Ⲵ' => 'ⲵ', 'Ⲷ' => 'ⲷ', 'Ⲹ' => 'ⲹ', 'Ⲻ' => 'ⲻ', 'Ⲽ' => 'ⲽ', 'Ⲿ' => 'ⲿ', 'Ⳁ' => 'ⳁ', 'Ⳃ' => 'ⳃ', 'Ⳅ' => 'ⳅ', 'Ⳇ' => 'ⳇ', 'Ⳉ' => 'ⳉ', 'Ⳋ' => 'ⳋ', 'Ⳍ' => 'ⳍ', 'Ⳏ' => 'ⳏ', 'Ⳑ' => 'ⳑ', 'Ⳓ' => 'ⳓ', 'Ⳕ' => 'ⳕ', 'Ⳗ' => 'ⳗ', 'Ⳙ' => 'ⳙ', 'Ⳛ' => 'ⳛ', 'Ⳝ' => 'ⳝ', 'Ⳟ' => 'ⳟ', 'Ⳡ' => 'ⳡ', 'Ⳣ' => 'ⳣ', 'Ⳬ' => 'ⳬ', 'Ⳮ' => 'ⳮ', 'Ⳳ' => 'ⳳ', 'Ꙁ' => 'ꙁ', 'Ꙃ' => 'ꙃ', 'Ꙅ' => 'ꙅ', 'Ꙇ' => 'ꙇ', 'Ꙉ' => 'ꙉ', 'Ꙋ' => 'ꙋ', 'Ꙍ' => 'ꙍ', 'Ꙏ' => 'ꙏ', 'Ꙑ' => 'ꙑ', 'Ꙓ' => 'ꙓ', 'Ꙕ' => 'ꙕ', 'Ꙗ' => 'ꙗ', 'Ꙙ' => 'ꙙ', 'Ꙛ' => 'ꙛ', 'Ꙝ' => 'ꙝ', 'Ꙟ' => 'ꙟ', 'Ꙡ' => 'ꙡ', 'Ꙣ' => 'ꙣ', 'Ꙥ' => 'ꙥ', 'Ꙧ' => 'ꙧ', 'Ꙩ' => 'ꙩ', 'Ꙫ' => 'ꙫ', 'Ꙭ' => 'ꙭ', 'Ꚁ' => 'ꚁ', 'Ꚃ' => 'ꚃ', 'Ꚅ' => 'ꚅ', 'Ꚇ' => 'ꚇ', 'Ꚉ' => 'ꚉ', 'Ꚋ' => 'ꚋ', 'Ꚍ' => 'ꚍ', 'Ꚏ' => 'ꚏ', 'Ꚑ' => 'ꚑ', 'Ꚓ' => 'ꚓ', 'Ꚕ' => 'ꚕ', 'Ꚗ' => 'ꚗ', 'Ꚙ' => 'ꚙ', 'Ꚛ' => 'ꚛ', 'Ꜣ' => 'ꜣ', 'Ꜥ' => 'ꜥ', 'Ꜧ' => 'ꜧ', 'Ꜩ' => 'ꜩ', 'Ꜫ' => 'ꜫ', 'Ꜭ' => 'ꜭ', 'Ꜯ' => 'ꜯ', 'Ꜳ' => 'ꜳ', 'Ꜵ' => 'ꜵ', 'Ꜷ' => 'ꜷ', 'Ꜹ' => 'ꜹ', 'Ꜻ' => 'ꜻ', 'Ꜽ' => 'ꜽ', 'Ꜿ' => 'ꜿ', 'Ꝁ' => 'ꝁ', 'Ꝃ' => 'ꝃ', 'Ꝅ' => 'ꝅ', 'Ꝇ' => 'ꝇ', 'Ꝉ' => 'ꝉ', 'Ꝋ' => 'ꝋ', 'Ꝍ' => 'ꝍ', 'Ꝏ' => 'ꝏ', 'Ꝑ' => 'ꝑ', 'Ꝓ' => 'ꝓ', 'Ꝕ' => 'ꝕ', 'Ꝗ' => 'ꝗ', 'Ꝙ' => 'ꝙ', 'Ꝛ' => 'ꝛ', 'Ꝝ' => 'ꝝ', 'Ꝟ' => 'ꝟ', 'Ꝡ' => 'ꝡ', 'Ꝣ' => 'ꝣ', 'Ꝥ' => 'ꝥ', 'Ꝧ' => 'ꝧ', 'Ꝩ' => 'ꝩ', 'Ꝫ' => 'ꝫ', 'Ꝭ' => 'ꝭ', 'Ꝯ' => 'ꝯ', 'Ꝺ' => 'ꝺ', 'Ꝼ' => 'ꝼ', 'Ᵹ' => 'ᵹ', 'Ꝿ' => 'ꝿ', 'Ꞁ' => 'ꞁ', 'Ꞃ' => 'ꞃ', 'Ꞅ' => 'ꞅ', 'Ꞇ' => 'ꞇ', 'Ꞌ' => 'ꞌ', 'Ɥ' => 'ɥ', 'Ꞑ' => 'ꞑ', 'Ꞓ' => 'ꞓ', 'Ꞗ' => 'ꞗ', 'Ꞙ' => 'ꞙ', 'Ꞛ' => 'ꞛ', 'Ꞝ' => 'ꞝ', 'Ꞟ' => 'ꞟ', 'Ꞡ' => 'ꞡ', 'Ꞣ' => 'ꞣ', 'Ꞥ' => 'ꞥ', 'Ꞧ' => 'ꞧ', 'Ꞩ' => 'ꞩ', 'Ɦ' => 'ɦ', 'Ɜ' => 'ɜ', 'Ɡ' => 'ɡ', 'Ɬ' => 'ɬ', 'Ɪ' => 'ɪ', 'Ʞ' => 'ʞ', 'Ʇ' => 'ʇ', 'Ʝ' => 'ʝ', 'Ꭓ' => 'ꭓ', 'Ꞵ' => 'ꞵ', 'Ꞷ' => 'ꞷ', 'Ꞹ' => 'ꞹ', 'Ꞻ' => 'ꞻ', 'Ꞽ' => 'ꞽ', 'Ꞿ' => 'ꞿ', 'Ꟃ' => 'ꟃ', 'Ꞔ' => 'ꞔ', 'Ʂ' => 'ʂ', 'Ᶎ' => 'ᶎ', 'Ꟈ' => 'ꟈ', 'Ꟊ' => 'ꟊ', 'Ꟶ' => 'ꟶ', 'A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e', 'F' => 'f', 'G' => 'g', 'H' => 'h', 'I' => 'i', 'J' => 'j', 'K' => 'k', 'L' => 'l', 'M' => 'm', 'N' => 'n', 'O' => 'o', 'P' => 'p', 'Q' => 'q', 'R' => 'r', 'S' => 's', 'T' => 't', 'U' => 'u', 'V' => 'v', 'W' => 'w', 'X' => 'x', 'Y' => 'y', 'Z' => 'z', '𐐀' => '𐐨', '𐐁' => '𐐩', '𐐂' => '𐐪', '𐐃' => '𐐫', '𐐄' => '𐐬', '𐐅' => '𐐭', '𐐆' => '𐐮', '𐐇' => '𐐯', '𐐈' => '𐐰', '𐐉' => '𐐱', '𐐊' => '𐐲', '𐐋' => '𐐳', '𐐌' => '𐐴', '𐐍' => '𐐵', '𐐎' => '𐐶', '𐐏' => '𐐷', '𐐐' => '𐐸', '𐐑' => '𐐹', '𐐒' => '𐐺', '𐐓' => '𐐻', '𐐔' => '𐐼', '𐐕' => '𐐽', '𐐖' => '𐐾', '𐐗' => '𐐿', '𐐘' => '𐑀', '𐐙' => '𐑁', '𐐚' => '𐑂', '𐐛' => '𐑃', '𐐜' => '𐑄', '𐐝' => '𐑅', '𐐞' => '𐑆', '𐐟' => '𐑇', '𐐠' => '𐑈', '𐐡' => '𐑉', '𐐢' => '𐑊', '𐐣' => '𐑋', '𐐤' => '𐑌', '𐐥' => '𐑍', '𐐦' => '𐑎', '𐐧' => '𐑏', '𐒰' => '𐓘', '𐒱' => '𐓙', '𐒲' => '𐓚', '𐒳' => '𐓛', '𐒴' => '𐓜', '𐒵' => '𐓝', '𐒶' => '𐓞', '𐒷' => '𐓟', '𐒸' => '𐓠', '𐒹' => '𐓡', '𐒺' => '𐓢', '𐒻' => '𐓣', '𐒼' => '𐓤', '𐒽' => '𐓥', '𐒾' => '𐓦', '𐒿' => '𐓧', '𐓀' => '𐓨', '𐓁' => '𐓩', '𐓂' => '𐓪', '𐓃' => '𐓫', '𐓄' => '𐓬', '𐓅' => '𐓭', '𐓆' => '𐓮', '𐓇' => '𐓯', '𐓈' => '𐓰', '𐓉' => '𐓱', '𐓊' => '𐓲', '𐓋' => '𐓳', '𐓌' => '𐓴', '𐓍' => '𐓵', '𐓎' => '𐓶', '𐓏' => '𐓷', '𐓐' => '𐓸', '𐓑' => '𐓹', '𐓒' => '𐓺', '𐓓' => '𐓻', '𐲀' => '𐳀', '𐲁' => '𐳁', '𐲂' => '𐳂', '𐲃' => '𐳃', '𐲄' => '𐳄', '𐲅' => '𐳅', '𐲆' => '𐳆', '𐲇' => '𐳇', '𐲈' => '𐳈', '𐲉' => '𐳉', '𐲊' => '𐳊', '𐲋' => '𐳋', '𐲌' => '𐳌', '𐲍' => '𐳍', '𐲎' => '𐳎', '𐲏' => '𐳏', '𐲐' => '𐳐', '𐲑' => '𐳑', '𐲒' => '𐳒', '𐲓' => '𐳓', '𐲔' => '𐳔', '𐲕' => '𐳕', '𐲖' => '𐳖', '𐲗' => '𐳗', '𐲘' => '𐳘', '𐲙' => '𐳙', '𐲚' => '𐳚', '𐲛' => '𐳛', '𐲜' => '𐳜', '𐲝' => '𐳝', '𐲞' => '𐳞', '𐲟' => '𐳟', '𐲠' => '𐳠', '𐲡' => '𐳡', '𐲢' => '𐳢', '𐲣' => '𐳣', '𐲤' => '𐳤', '𐲥' => '𐳥', '𐲦' => '𐳦', '𐲧' => '𐳧', '𐲨' => '𐳨', '𐲩' => '𐳩', '𐲪' => '𐳪', '𐲫' => '𐳫', '𐲬' => '𐳬', '𐲭' => '𐳭', '𐲮' => '𐳮', '𐲯' => '𐳯', '𐲰' => '𐳰', '𐲱' => '𐳱', '𐲲' => '𐳲', '𑢠' => '𑣀', '𑢡' => '𑣁', '𑢢' => '𑣂', '𑢣' => '𑣃', '𑢤' => '𑣄', '𑢥' => '𑣅', '𑢦' => '𑣆', '𑢧' => '𑣇', '𑢨' => '𑣈', '𑢩' => '𑣉', '𑢪' => '𑣊', '𑢫' => '𑣋', '𑢬' => '𑣌', '𑢭' => '𑣍', '𑢮' => '𑣎', '𑢯' => '𑣏', '𑢰' => '𑣐', '𑢱' => '𑣑', '𑢲' => '𑣒', '𑢳' => '𑣓', '𑢴' => '𑣔', '𑢵' => '𑣕', '𑢶' => '𑣖', '𑢷' => '𑣗', '𑢸' => '𑣘', '𑢹' => '𑣙', '𑢺' => '𑣚', '𑢻' => '𑣛', '𑢼' => '𑣜', '𑢽' => '𑣝', '𑢾' => '𑣞', '𑢿' => '𑣟', '𖹀' => '𖹠', '𖹁' => '𖹡', '𖹂' => '𖹢', '𖹃' => '𖹣', '𖹄' => '𖹤', '𖹅' => '𖹥', '𖹆' => '𖹦', '𖹇' => '𖹧', '𖹈' => '𖹨', '𖹉' => '𖹩', '𖹊' => '𖹪', '𖹋' => '𖹫', '𖹌' => '𖹬', '𖹍' => '𖹭', '𖹎' => '𖹮', '𖹏' => '𖹯', '𖹐' => '𖹰', '𖹑' => '𖹱', '𖹒' => '𖹲', '𖹓' => '𖹳', '𖹔' => '𖹴', '𖹕' => '𖹵', '𖹖' => '𖹶', '𖹗' => '𖹷', '𖹘' => '𖹸', '𖹙' => '𖹹', '𖹚' => '𖹺', '𖹛' => '𖹻', '𖹜' => '𖹼', '𖹝' => '𖹽', '𖹞' => '𖹾', '𖹟' => '𖹿', '𞤀' => '𞤢', '𞤁' => '𞤣', '𞤂' => '𞤤', '𞤃' => '𞤥', '𞤄' => '𞤦', '𞤅' => '𞤧', '𞤆' => '𞤨', '𞤇' => '𞤩', '𞤈' => '𞤪', '𞤉' => '𞤫', '𞤊' => '𞤬', '𞤋' => '𞤭', '𞤌' => '𞤮', '𞤍' => '𞤯', '𞤎' => '𞤰', '𞤏' => '𞤱', '𞤐' => '𞤲', '𞤑' => '𞤳', '𞤒' => '𞤴', '𞤓' => '𞤵', '𞤔' => '𞤶', '𞤕' => '𞤷', '𞤖' => '𞤸', '𞤗' => '𞤹', '𞤘' => '𞤺', '𞤙' => '𞤻', '𞤚' => '𞤼', '𞤛' => '𞤽', '𞤜' => '𞤾', '𞤝' => '𞤿', '𞤞' => '𞥀', '𞤟' => '𞥁', '𞤠' => '𞥂', '𞤡' => '𞥃'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 00000000..92d2cdac --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,6 @@ + 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', 'µ' => 'Μ', 'à' => 'À', 'á' => 'Á', 'â' => 'Â', 'ã' => 'Ã', 'ä' => 'Ä', 'å' => 'Å', 'æ' => 'Æ', 'ç' => 'Ç', 'è' => 'È', 'é' => 'É', 'ê' => 'Ê', 'ë' => 'Ë', 'ì' => 'Ì', 'í' => 'Í', 'î' => 'Î', 'ï' => 'Ï', 'ð' => 'Ð', 'ñ' => 'Ñ', 'ò' => 'Ò', 'ó' => 'Ó', 'ô' => 'Ô', 'õ' => 'Õ', 'ö' => 'Ö', 'ø' => 'Ø', 'ù' => 'Ù', 'ú' => 'Ú', 'û' => 'Û', 'ü' => 'Ü', 'ý' => 'Ý', 'þ' => 'Þ', 'ÿ' => 'Ÿ', 'ā' => 'Ā', 'ă' => 'Ă', 'ą' => 'Ą', 'ć' => 'Ć', 'ĉ' => 'Ĉ', 'ċ' => 'Ċ', 'č' => 'Č', 'ď' => 'Ď', 'đ' => 'Đ', 'ē' => 'Ē', 'ĕ' => 'Ĕ', 'ė' => 'Ė', 'ę' => 'Ę', 'ě' => 'Ě', 'ĝ' => 'Ĝ', 'ğ' => 'Ğ', 'ġ' => 'Ġ', 'ģ' => 'Ģ', 'ĥ' => 'Ĥ', 'ħ' => 'Ħ', 'ĩ' => 'Ĩ', 'ī' => 'Ī', 'ĭ' => 'Ĭ', 'į' => 'Į', 'ı' => 'I', 'ij' => 'IJ', 'ĵ' => 'Ĵ', 'ķ' => 'Ķ', 'ĺ' => 'Ĺ', 'ļ' => 'Ļ', 'ľ' => 'Ľ', 'ŀ' => 'Ŀ', 'ł' => 'Ł', 'ń' => 'Ń', 'ņ' => 'Ņ', 'ň' => 'Ň', 'ŋ' => 'Ŋ', 'ō' => 'Ō', 'ŏ' => 'Ŏ', 'ő' => 'Ő', 'œ' => 'Œ', 'ŕ' => 'Ŕ', 'ŗ' => 'Ŗ', 'ř' => 'Ř', 'ś' => 'Ś', 'ŝ' => 'Ŝ', 'ş' => 'Ş', 'š' => 'Š', 'ţ' => 'Ţ', 'ť' => 'Ť', 'ŧ' => 'Ŧ', 'ũ' => 'Ũ', 'ū' => 'Ū', 'ŭ' => 'Ŭ', 'ů' => 'Ů', 'ű' => 'Ű', 'ų' => 'Ų', 'ŵ' => 'Ŵ', 'ŷ' => 'Ŷ', 'ź' => 'Ź', 'ż' => 'Ż', 'ž' => 'Ž', 'ſ' => 'S', 'ƀ' => 'Ƀ', 'ƃ' => 'Ƃ', 'ƅ' => 'Ƅ', 'ƈ' => 'Ƈ', 'ƌ' => 'Ƌ', 'ƒ' => 'Ƒ', 'ƕ' => 'Ƕ', 'ƙ' => 'Ƙ', 'ƚ' => 'Ƚ', 'ƞ' => 'Ƞ', 'ơ' => 'Ơ', 'ƣ' => 'Ƣ', 'ƥ' => 'Ƥ', 'ƨ' => 'Ƨ', 'ƭ' => 'Ƭ', 'ư' => 'Ư', 'ƴ' => 'Ƴ', 'ƶ' => 'Ƶ', 'ƹ' => 'Ƹ', 'ƽ' => 'Ƽ', 'ƿ' => 'Ƿ', 'Dž' => 'DŽ', 'dž' => 'DŽ', 'Lj' => 'LJ', 'lj' => 'LJ', 'Nj' => 'NJ', 'nj' => 'NJ', 'ǎ' => 'Ǎ', 'ǐ' => 'Ǐ', 'ǒ' => 'Ǒ', 'ǔ' => 'Ǔ', 'ǖ' => 'Ǖ', 'ǘ' => 'Ǘ', 'ǚ' => 'Ǚ', 'ǜ' => 'Ǜ', 'ǝ' => 'Ǝ', 'ǟ' => 'Ǟ', 'ǡ' => 'Ǡ', 'ǣ' => 'Ǣ', 'ǥ' => 'Ǥ', 'ǧ' => 'Ǧ', 'ǩ' => 'Ǩ', 'ǫ' => 'Ǫ', 'ǭ' => 'Ǭ', 'ǯ' => 'Ǯ', 'Dz' => 'DZ', 'dz' => 'DZ', 'ǵ' => 'Ǵ', 'ǹ' => 'Ǹ', 'ǻ' => 'Ǻ', 'ǽ' => 'Ǽ', 'ǿ' => 'Ǿ', 'ȁ' => 'Ȁ', 'ȃ' => 'Ȃ', 'ȅ' => 'Ȅ', 'ȇ' => 'Ȇ', 'ȉ' => 'Ȉ', 'ȋ' => 'Ȋ', 'ȍ' => 'Ȍ', 'ȏ' => 'Ȏ', 'ȑ' => 'Ȑ', 'ȓ' => 'Ȓ', 'ȕ' => 'Ȕ', 'ȗ' => 'Ȗ', 'ș' => 'Ș', 'ț' => 'Ț', 'ȝ' => 'Ȝ', 'ȟ' => 'Ȟ', 'ȣ' => 'Ȣ', 'ȥ' => 'Ȥ', 'ȧ' => 'Ȧ', 'ȩ' => 'Ȩ', 'ȫ' => 'Ȫ', 'ȭ' => 'Ȭ', 'ȯ' => 'Ȯ', 'ȱ' => 'Ȱ', 'ȳ' => 'Ȳ', 'ȼ' => 'Ȼ', 'ȿ' => 'Ȿ', 'ɀ' => 'Ɀ', 'ɂ' => 'Ɂ', 'ɇ' => 'Ɇ', 'ɉ' => 'Ɉ', 'ɋ' => 'Ɋ', 'ɍ' => 'Ɍ', 'ɏ' => 'Ɏ', 'ɐ' => 'Ɐ', 'ɑ' => 'Ɑ', 'ɒ' => 'Ɒ', 'ɓ' => 'Ɓ', 'ɔ' => 'Ɔ', 'ɖ' => 'Ɖ', 'ɗ' => 'Ɗ', 'ə' => 'Ə', 'ɛ' => 'Ɛ', 'ɜ' => 'Ɜ', 'ɠ' => 'Ɠ', 'ɡ' => 'Ɡ', 'ɣ' => 'Ɣ', 'ɥ' => 'Ɥ', 'ɦ' => 'Ɦ', 'ɨ' => 'Ɨ', 'ɩ' => 'Ɩ', 'ɪ' => 'Ɪ', 'ɫ' => 'Ɫ', 'ɬ' => 'Ɬ', 'ɯ' => 'Ɯ', 'ɱ' => 'Ɱ', 'ɲ' => 'Ɲ', 'ɵ' => 'Ɵ', 'ɽ' => 'Ɽ', 'ʀ' => 'Ʀ', 'ʂ' => 'Ʂ', 'ʃ' => 'Ʃ', 'ʇ' => 'Ʇ', 'ʈ' => 'Ʈ', 'ʉ' => 'Ʉ', 'ʊ' => 'Ʊ', 'ʋ' => 'Ʋ', 'ʌ' => 'Ʌ', 'ʒ' => 'Ʒ', 'ʝ' => 'Ʝ', 'ʞ' => 'Ʞ', 'ͅ' => 'Ι', 'ͱ' => 'Ͱ', 'ͳ' => 'Ͳ', 'ͷ' => 'Ͷ', 'ͻ' => 'Ͻ', 'ͼ' => 'Ͼ', 'ͽ' => 'Ͽ', 'ά' => 'Ά', 'έ' => 'Έ', 'ή' => 'Ή', 'ί' => 'Ί', 'α' => 'Α', 'β' => 'Β', 'γ' => 'Γ', 'δ' => 'Δ', 'ε' => 'Ε', 'ζ' => 'Ζ', 'η' => 'Η', 'θ' => 'Θ', 'ι' => 'Ι', 'κ' => 'Κ', 'λ' => 'Λ', 'μ' => 'Μ', 'ν' => 'Ν', 'ξ' => 'Ξ', 'ο' => 'Ο', 'π' => 'Π', 'ρ' => 'Ρ', 'ς' => 'Σ', 'σ' => 'Σ', 'τ' => 'Τ', 'υ' => 'Υ', 'φ' => 'Φ', 'χ' => 'Χ', 'ψ' => 'Ψ', 'ω' => 'Ω', 'ϊ' => 'Ϊ', 'ϋ' => 'Ϋ', 'ό' => 'Ό', 'ύ' => 'Ύ', 'ώ' => 'Ώ', 'ϐ' => 'Β', 'ϑ' => 'Θ', 'ϕ' => 'Φ', 'ϖ' => 'Π', 'ϗ' => 'Ϗ', 'ϙ' => 'Ϙ', 'ϛ' => 'Ϛ', 'ϝ' => 'Ϝ', 'ϟ' => 'Ϟ', 'ϡ' => 'Ϡ', 'ϣ' => 'Ϣ', 'ϥ' => 'Ϥ', 'ϧ' => 'Ϧ', 'ϩ' => 'Ϩ', 'ϫ' => 'Ϫ', 'ϭ' => 'Ϭ', 'ϯ' => 'Ϯ', 'ϰ' => 'Κ', 'ϱ' => 'Ρ', 'ϲ' => 'Ϲ', 'ϳ' => 'Ϳ', 'ϵ' => 'Ε', 'ϸ' => 'Ϸ', 'ϻ' => 'Ϻ', 'а' => 'А', 'б' => 'Б', 'в' => 'В', 'г' => 'Г', 'д' => 'Д', 'е' => 'Е', 'ж' => 'Ж', 'з' => 'З', 'и' => 'И', 'й' => 'Й', 'к' => 'К', 'л' => 'Л', 'м' => 'М', 'н' => 'Н', 'о' => 'О', 'п' => 'П', 'р' => 'Р', 'с' => 'С', 'т' => 'Т', 'у' => 'У', 'ф' => 'Ф', 'х' => 'Х', 'ц' => 'Ц', 'ч' => 'Ч', 'ш' => 'Ш', 'щ' => 'Щ', 'ъ' => 'Ъ', 'ы' => 'Ы', 'ь' => 'Ь', 'э' => 'Э', 'ю' => 'Ю', 'я' => 'Я', 'ѐ' => 'Ѐ', 'ё' => 'Ё', 'ђ' => 'Ђ', 'ѓ' => 'Ѓ', 'є' => 'Є', 'ѕ' => 'Ѕ', 'і' => 'І', 'ї' => 'Ї', 'ј' => 'Ј', 'љ' => 'Љ', 'њ' => 'Њ', 'ћ' => 'Ћ', 'ќ' => 'Ќ', 'ѝ' => 'Ѝ', 'ў' => 'Ў', 'џ' => 'Џ', 'ѡ' => 'Ѡ', 'ѣ' => 'Ѣ', 'ѥ' => 'Ѥ', 'ѧ' => 'Ѧ', 'ѩ' => 'Ѩ', 'ѫ' => 'Ѫ', 'ѭ' => 'Ѭ', 'ѯ' => 'Ѯ', 'ѱ' => 'Ѱ', 'ѳ' => 'Ѳ', 'ѵ' => 'Ѵ', 'ѷ' => 'Ѷ', 'ѹ' => 'Ѹ', 'ѻ' => 'Ѻ', 'ѽ' => 'Ѽ', 'ѿ' => 'Ѿ', 'ҁ' => 'Ҁ', 'ҋ' => 'Ҋ', 'ҍ' => 'Ҍ', 'ҏ' => 'Ҏ', 'ґ' => 'Ґ', 'ғ' => 'Ғ', 'ҕ' => 'Ҕ', 'җ' => 'Җ', 'ҙ' => 'Ҙ', 'қ' => 'Қ', 'ҝ' => 'Ҝ', 'ҟ' => 'Ҟ', 'ҡ' => 'Ҡ', 'ң' => 'Ң', 'ҥ' => 'Ҥ', 'ҧ' => 'Ҧ', 'ҩ' => 'Ҩ', 'ҫ' => 'Ҫ', 'ҭ' => 'Ҭ', 'ү' => 'Ү', 'ұ' => 'Ұ', 'ҳ' => 'Ҳ', 'ҵ' => 'Ҵ', 'ҷ' => 'Ҷ', 'ҹ' => 'Ҹ', 'һ' => 'Һ', 'ҽ' => 'Ҽ', 'ҿ' => 'Ҿ', 'ӂ' => 'Ӂ', 'ӄ' => 'Ӄ', 'ӆ' => 'Ӆ', 'ӈ' => 'Ӈ', 'ӊ' => 'Ӊ', 'ӌ' => 'Ӌ', 'ӎ' => 'Ӎ', 'ӏ' => 'Ӏ', 'ӑ' => 'Ӑ', 'ӓ' => 'Ӓ', 'ӕ' => 'Ӕ', 'ӗ' => 'Ӗ', 'ә' => 'Ә', 'ӛ' => 'Ӛ', 'ӝ' => 'Ӝ', 'ӟ' => 'Ӟ', 'ӡ' => 'Ӡ', 'ӣ' => 'Ӣ', 'ӥ' => 'Ӥ', 'ӧ' => 'Ӧ', 'ө' => 'Ө', 'ӫ' => 'Ӫ', 'ӭ' => 'Ӭ', 'ӯ' => 'Ӯ', 'ӱ' => 'Ӱ', 'ӳ' => 'Ӳ', 'ӵ' => 'Ӵ', 'ӷ' => 'Ӷ', 'ӹ' => 'Ӹ', 'ӻ' => 'Ӻ', 'ӽ' => 'Ӽ', 'ӿ' => 'Ӿ', 'ԁ' => 'Ԁ', 'ԃ' => 'Ԃ', 'ԅ' => 'Ԅ', 'ԇ' => 'Ԇ', 'ԉ' => 'Ԉ', 'ԋ' => 'Ԋ', 'ԍ' => 'Ԍ', 'ԏ' => 'Ԏ', 'ԑ' => 'Ԑ', 'ԓ' => 'Ԓ', 'ԕ' => 'Ԕ', 'ԗ' => 'Ԗ', 'ԙ' => 'Ԙ', 'ԛ' => 'Ԛ', 'ԝ' => 'Ԝ', 'ԟ' => 'Ԟ', 'ԡ' => 'Ԡ', 'ԣ' => 'Ԣ', 'ԥ' => 'Ԥ', 'ԧ' => 'Ԧ', 'ԩ' => 'Ԩ', 'ԫ' => 'Ԫ', 'ԭ' => 'Ԭ', 'ԯ' => 'Ԯ', 'ա' => 'Ա', 'բ' => 'Բ', 'գ' => 'Գ', 'դ' => 'Դ', 'ե' => 'Ե', 'զ' => 'Զ', 'է' => 'Է', 'ը' => 'Ը', 'թ' => 'Թ', 'ժ' => 'Ժ', 'ի' => 'Ի', 'լ' => 'Լ', 'խ' => 'Խ', 'ծ' => 'Ծ', 'կ' => 'Կ', 'հ' => 'Հ', 'ձ' => 'Ձ', 'ղ' => 'Ղ', 'ճ' => 'Ճ', 'մ' => 'Մ', 'յ' => 'Յ', 'ն' => 'Ն', 'շ' => 'Շ', 'ո' => 'Ո', 'չ' => 'Չ', 'պ' => 'Պ', 'ջ' => 'Ջ', 'ռ' => 'Ռ', 'ս' => 'Ս', 'վ' => 'Վ', 'տ' => 'Տ', 'ր' => 'Ր', 'ց' => 'Ց', 'ւ' => 'Ւ', 'փ' => 'Փ', 'ք' => 'Ք', 'օ' => 'Օ', 'ֆ' => 'Ֆ', 'ა' => 'Ა', 'ბ' => 'Ბ', 'გ' => 'Გ', 'დ' => 'Დ', 'ე' => 'Ე', 'ვ' => 'Ვ', 'ზ' => 'Ზ', 'თ' => 'Თ', 'ი' => 'Ი', 'კ' => 'Კ', 'ლ' => 'Ლ', 'მ' => 'Მ', 'ნ' => 'Ნ', 'ო' => 'Ო', 'პ' => 'Პ', 'ჟ' => 'Ჟ', 'რ' => 'Რ', 'ს' => 'Ს', 'ტ' => 'Ტ', 'უ' => 'Უ', 'ფ' => 'Ფ', 'ქ' => 'Ქ', 'ღ' => 'Ღ', 'ყ' => 'Ყ', 'შ' => 'Შ', 'ჩ' => 'Ჩ', 'ც' => 'Ც', 'ძ' => 'Ძ', 'წ' => 'Წ', 'ჭ' => 'Ჭ', 'ხ' => 'Ხ', 'ჯ' => 'Ჯ', 'ჰ' => 'Ჰ', 'ჱ' => 'Ჱ', 'ჲ' => 'Ჲ', 'ჳ' => 'Ჳ', 'ჴ' => 'Ჴ', 'ჵ' => 'Ჵ', 'ჶ' => 'Ჶ', 'ჷ' => 'Ჷ', 'ჸ' => 'Ჸ', 'ჹ' => 'Ჹ', 'ჺ' => 'Ჺ', 'ჽ' => 'Ჽ', 'ჾ' => 'Ჾ', 'ჿ' => 'Ჿ', 'ᏸ' => 'Ᏸ', 'ᏹ' => 'Ᏹ', 'ᏺ' => 'Ᏺ', 'ᏻ' => 'Ᏻ', 'ᏼ' => 'Ᏼ', 'ᏽ' => 'Ᏽ', 'ᲀ' => 'В', 'ᲁ' => 'Д', 'ᲂ' => 'О', 'ᲃ' => 'С', 'ᲄ' => 'Т', 'ᲅ' => 'Т', 'ᲆ' => 'Ъ', 'ᲇ' => 'Ѣ', 'ᲈ' => 'Ꙋ', 'ᵹ' => 'Ᵹ', 'ᵽ' => 'Ᵽ', 'ᶎ' => 'Ᶎ', 'ḁ' => 'Ḁ', 'ḃ' => 'Ḃ', 'ḅ' => 'Ḅ', 'ḇ' => 'Ḇ', 'ḉ' => 'Ḉ', 'ḋ' => 'Ḋ', 'ḍ' => 'Ḍ', 'ḏ' => 'Ḏ', 'ḑ' => 'Ḑ', 'ḓ' => 'Ḓ', 'ḕ' => 'Ḕ', 'ḗ' => 'Ḗ', 'ḙ' => 'Ḙ', 'ḛ' => 'Ḛ', 'ḝ' => 'Ḝ', 'ḟ' => 'Ḟ', 'ḡ' => 'Ḡ', 'ḣ' => 'Ḣ', 'ḥ' => 'Ḥ', 'ḧ' => 'Ḧ', 'ḩ' => 'Ḩ', 'ḫ' => 'Ḫ', 'ḭ' => 'Ḭ', 'ḯ' => 'Ḯ', 'ḱ' => 'Ḱ', 'ḳ' => 'Ḳ', 'ḵ' => 'Ḵ', 'ḷ' => 'Ḷ', 'ḹ' => 'Ḹ', 'ḻ' => 'Ḻ', 'ḽ' => 'Ḽ', 'ḿ' => 'Ḿ', 'ṁ' => 'Ṁ', 'ṃ' => 'Ṃ', 'ṅ' => 'Ṅ', 'ṇ' => 'Ṇ', 'ṉ' => 'Ṉ', 'ṋ' => 'Ṋ', 'ṍ' => 'Ṍ', 'ṏ' => 'Ṏ', 'ṑ' => 'Ṑ', 'ṓ' => 'Ṓ', 'ṕ' => 'Ṕ', 'ṗ' => 'Ṗ', 'ṙ' => 'Ṙ', 'ṛ' => 'Ṛ', 'ṝ' => 'Ṝ', 'ṟ' => 'Ṟ', 'ṡ' => 'Ṡ', 'ṣ' => 'Ṣ', 'ṥ' => 'Ṥ', 'ṧ' => 'Ṧ', 'ṩ' => 'Ṩ', 'ṫ' => 'Ṫ', 'ṭ' => 'Ṭ', 'ṯ' => 'Ṯ', 'ṱ' => 'Ṱ', 'ṳ' => 'Ṳ', 'ṵ' => 'Ṵ', 'ṷ' => 'Ṷ', 'ṹ' => 'Ṹ', 'ṻ' => 'Ṻ', 'ṽ' => 'Ṽ', 'ṿ' => 'Ṿ', 'ẁ' => 'Ẁ', 'ẃ' => 'Ẃ', 'ẅ' => 'Ẅ', 'ẇ' => 'Ẇ', 'ẉ' => 'Ẉ', 'ẋ' => 'Ẋ', 'ẍ' => 'Ẍ', 'ẏ' => 'Ẏ', 'ẑ' => 'Ẑ', 'ẓ' => 'Ẓ', 'ẕ' => 'Ẕ', 'ẛ' => 'Ṡ', 'ạ' => 'Ạ', 'ả' => 'Ả', 'ấ' => 'Ấ', 'ầ' => 'Ầ', 'ẩ' => 'Ẩ', 'ẫ' => 'Ẫ', 'ậ' => 'Ậ', 'ắ' => 'Ắ', 'ằ' => 'Ằ', 'ẳ' => 'Ẳ', 'ẵ' => 'Ẵ', 'ặ' => 'Ặ', 'ẹ' => 'Ẹ', 'ẻ' => 'Ẻ', 'ẽ' => 'Ẽ', 'ế' => 'Ế', 'ề' => 'Ề', 'ể' => 'Ể', 'ễ' => 'Ễ', 'ệ' => 'Ệ', 'ỉ' => 'Ỉ', 'ị' => 'Ị', 'ọ' => 'Ọ', 'ỏ' => 'Ỏ', 'ố' => 'Ố', 'ồ' => 'Ồ', 'ổ' => 'Ổ', 'ỗ' => 'Ỗ', 'ộ' => 'Ộ', 'ớ' => 'Ớ', 'ờ' => 'Ờ', 'ở' => 'Ở', 'ỡ' => 'Ỡ', 'ợ' => 'Ợ', 'ụ' => 'Ụ', 'ủ' => 'Ủ', 'ứ' => 'Ứ', 'ừ' => 'Ừ', 'ử' => 'Ử', 'ữ' => 'Ữ', 'ự' => 'Ự', 'ỳ' => 'Ỳ', 'ỵ' => 'Ỵ', 'ỷ' => 'Ỷ', 'ỹ' => 'Ỹ', 'ỻ' => 'Ỻ', 'ỽ' => 'Ỽ', 'ỿ' => 'Ỿ', 'ἀ' => 'Ἀ', 'ἁ' => 'Ἁ', 'ἂ' => 'Ἂ', 'ἃ' => 'Ἃ', 'ἄ' => 'Ἄ', 'ἅ' => 'Ἅ', 'ἆ' => 'Ἆ', 'ἇ' => 'Ἇ', 'ἐ' => 'Ἐ', 'ἑ' => 'Ἑ', 'ἒ' => 'Ἒ', 'ἓ' => 'Ἓ', 'ἔ' => 'Ἔ', 'ἕ' => 'Ἕ', 'ἠ' => 'Ἠ', 'ἡ' => 'Ἡ', 'ἢ' => 'Ἢ', 'ἣ' => 'Ἣ', 'ἤ' => 'Ἤ', 'ἥ' => 'Ἥ', 'ἦ' => 'Ἦ', 'ἧ' => 'Ἧ', 'ἰ' => 'Ἰ', 'ἱ' => 'Ἱ', 'ἲ' => 'Ἲ', 'ἳ' => 'Ἳ', 'ἴ' => 'Ἴ', 'ἵ' => 'Ἵ', 'ἶ' => 'Ἶ', 'ἷ' => 'Ἷ', 'ὀ' => 'Ὀ', 'ὁ' => 'Ὁ', 'ὂ' => 'Ὂ', 'ὃ' => 'Ὃ', 'ὄ' => 'Ὄ', 'ὅ' => 'Ὅ', 'ὑ' => 'Ὑ', 'ὓ' => 'Ὓ', 'ὕ' => 'Ὕ', 'ὗ' => 'Ὗ', 'ὠ' => 'Ὠ', 'ὡ' => 'Ὡ', 'ὢ' => 'Ὢ', 'ὣ' => 'Ὣ', 'ὤ' => 'Ὤ', 'ὥ' => 'Ὥ', 'ὦ' => 'Ὦ', 'ὧ' => 'Ὧ', 'ὰ' => 'Ὰ', 'ά' => 'Ά', 'ὲ' => 'Ὲ', 'έ' => 'Έ', 'ὴ' => 'Ὴ', 'ή' => 'Ή', 'ὶ' => 'Ὶ', 'ί' => 'Ί', 'ὸ' => 'Ὸ', 'ό' => 'Ό', 'ὺ' => 'Ὺ', 'ύ' => 'Ύ', 'ὼ' => 'Ὼ', 'ώ' => 'Ώ', 'ᾀ' => 'ἈΙ', 'ᾁ' => 'ἉΙ', 'ᾂ' => 'ἊΙ', 'ᾃ' => 'ἋΙ', 'ᾄ' => 'ἌΙ', 'ᾅ' => 'ἍΙ', 'ᾆ' => 'ἎΙ', 'ᾇ' => 'ἏΙ', 'ᾐ' => 'ἨΙ', 'ᾑ' => 'ἩΙ', 'ᾒ' => 'ἪΙ', 'ᾓ' => 'ἫΙ', 'ᾔ' => 'ἬΙ', 'ᾕ' => 'ἭΙ', 'ᾖ' => 'ἮΙ', 'ᾗ' => 'ἯΙ', 'ᾠ' => 'ὨΙ', 'ᾡ' => 'ὩΙ', 'ᾢ' => 'ὪΙ', 'ᾣ' => 'ὫΙ', 'ᾤ' => 'ὬΙ', 'ᾥ' => 'ὭΙ', 'ᾦ' => 'ὮΙ', 'ᾧ' => 'ὯΙ', 'ᾰ' => 'Ᾰ', 'ᾱ' => 'Ᾱ', 'ᾳ' => 'ΑΙ', 'ι' => 'Ι', 'ῃ' => 'ΗΙ', 'ῐ' => 'Ῐ', 'ῑ' => 'Ῑ', 'ῠ' => 'Ῠ', 'ῡ' => 'Ῡ', 'ῥ' => 'Ῥ', 'ῳ' => 'ΩΙ', 'ⅎ' => 'Ⅎ', 'ⅰ' => 'Ⅰ', 'ⅱ' => 'Ⅱ', 'ⅲ' => 'Ⅲ', 'ⅳ' => 'Ⅳ', 'ⅴ' => 'Ⅴ', 'ⅵ' => 'Ⅵ', 'ⅶ' => 'Ⅶ', 'ⅷ' => 'Ⅷ', 'ⅸ' => 'Ⅸ', 'ⅹ' => 'Ⅹ', 'ⅺ' => 'Ⅺ', 'ⅻ' => 'Ⅻ', 'ⅼ' => 'Ⅼ', 'ⅽ' => 'Ⅽ', 'ⅾ' => 'Ⅾ', 'ⅿ' => 'Ⅿ', 'ↄ' => 'Ↄ', 'ⓐ' => 'Ⓐ', 'ⓑ' => 'Ⓑ', 'ⓒ' => 'Ⓒ', 'ⓓ' => 'Ⓓ', 'ⓔ' => 'Ⓔ', 'ⓕ' => 'Ⓕ', 'ⓖ' => 'Ⓖ', 'ⓗ' => 'Ⓗ', 'ⓘ' => 'Ⓘ', 'ⓙ' => 'Ⓙ', 'ⓚ' => 'Ⓚ', 'ⓛ' => 'Ⓛ', 'ⓜ' => 'Ⓜ', 'ⓝ' => 'Ⓝ', 'ⓞ' => 'Ⓞ', 'ⓟ' => 'Ⓟ', 'ⓠ' => 'Ⓠ', 'ⓡ' => 'Ⓡ', 'ⓢ' => 'Ⓢ', 'ⓣ' => 'Ⓣ', 'ⓤ' => 'Ⓤ', 'ⓥ' => 'Ⓥ', 'ⓦ' => 'Ⓦ', 'ⓧ' => 'Ⓧ', 'ⓨ' => 'Ⓨ', 'ⓩ' => 'Ⓩ', 'ⰰ' => 'Ⰰ', 'ⰱ' => 'Ⰱ', 'ⰲ' => 'Ⰲ', 'ⰳ' => 'Ⰳ', 'ⰴ' => 'Ⰴ', 'ⰵ' => 'Ⰵ', 'ⰶ' => 'Ⰶ', 'ⰷ' => 'Ⰷ', 'ⰸ' => 'Ⰸ', 'ⰹ' => 'Ⰹ', 'ⰺ' => 'Ⰺ', 'ⰻ' => 'Ⰻ', 'ⰼ' => 'Ⰼ', 'ⰽ' => 'Ⰽ', 'ⰾ' => 'Ⰾ', 'ⰿ' => 'Ⰿ', 'ⱀ' => 'Ⱀ', 'ⱁ' => 'Ⱁ', 'ⱂ' => 'Ⱂ', 'ⱃ' => 'Ⱃ', 'ⱄ' => 'Ⱄ', 'ⱅ' => 'Ⱅ', 'ⱆ' => 'Ⱆ', 'ⱇ' => 'Ⱇ', 'ⱈ' => 'Ⱈ', 'ⱉ' => 'Ⱉ', 'ⱊ' => 'Ⱊ', 'ⱋ' => 'Ⱋ', 'ⱌ' => 'Ⱌ', 'ⱍ' => 'Ⱍ', 'ⱎ' => 'Ⱎ', 'ⱏ' => 'Ⱏ', 'ⱐ' => 'Ⱐ', 'ⱑ' => 'Ⱑ', 'ⱒ' => 'Ⱒ', 'ⱓ' => 'Ⱓ', 'ⱔ' => 'Ⱔ', 'ⱕ' => 'Ⱕ', 'ⱖ' => 'Ⱖ', 'ⱗ' => 'Ⱗ', 'ⱘ' => 'Ⱘ', 'ⱙ' => 'Ⱙ', 'ⱚ' => 'Ⱚ', 'ⱛ' => 'Ⱛ', 'ⱜ' => 'Ⱜ', 'ⱝ' => 'Ⱝ', 'ⱞ' => 'Ⱞ', 'ⱡ' => 'Ⱡ', 'ⱥ' => 'Ⱥ', 'ⱦ' => 'Ⱦ', 'ⱨ' => 'Ⱨ', 'ⱪ' => 'Ⱪ', 'ⱬ' => 'Ⱬ', 'ⱳ' => 'Ⱳ', 'ⱶ' => 'Ⱶ', 'ⲁ' => 'Ⲁ', 'ⲃ' => 'Ⲃ', 'ⲅ' => 'Ⲅ', 'ⲇ' => 'Ⲇ', 'ⲉ' => 'Ⲉ', 'ⲋ' => 'Ⲋ', 'ⲍ' => 'Ⲍ', 'ⲏ' => 'Ⲏ', 'ⲑ' => 'Ⲑ', 'ⲓ' => 'Ⲓ', 'ⲕ' => 'Ⲕ', 'ⲗ' => 'Ⲗ', 'ⲙ' => 'Ⲙ', 'ⲛ' => 'Ⲛ', 'ⲝ' => 'Ⲝ', 'ⲟ' => 'Ⲟ', 'ⲡ' => 'Ⲡ', 'ⲣ' => 'Ⲣ', 'ⲥ' => 'Ⲥ', 'ⲧ' => 'Ⲧ', 'ⲩ' => 'Ⲩ', 'ⲫ' => 'Ⲫ', 'ⲭ' => 'Ⲭ', 'ⲯ' => 'Ⲯ', 'ⲱ' => 'Ⲱ', 'ⲳ' => 'Ⲳ', 'ⲵ' => 'Ⲵ', 'ⲷ' => 'Ⲷ', 'ⲹ' => 'Ⲹ', 'ⲻ' => 'Ⲻ', 'ⲽ' => 'Ⲽ', 'ⲿ' => 'Ⲿ', 'ⳁ' => 'Ⳁ', 'ⳃ' => 'Ⳃ', 'ⳅ' => 'Ⳅ', 'ⳇ' => 'Ⳇ', 'ⳉ' => 'Ⳉ', 'ⳋ' => 'Ⳋ', 'ⳍ' => 'Ⳍ', 'ⳏ' => 'Ⳏ', 'ⳑ' => 'Ⳑ', 'ⳓ' => 'Ⳓ', 'ⳕ' => 'Ⳕ', 'ⳗ' => 'Ⳗ', 'ⳙ' => 'Ⳙ', 'ⳛ' => 'Ⳛ', 'ⳝ' => 'Ⳝ', 'ⳟ' => 'Ⳟ', 'ⳡ' => 'Ⳡ', 'ⳣ' => 'Ⳣ', 'ⳬ' => 'Ⳬ', 'ⳮ' => 'Ⳮ', 'ⳳ' => 'Ⳳ', 'ⴀ' => 'Ⴀ', 'ⴁ' => 'Ⴁ', 'ⴂ' => 'Ⴂ', 'ⴃ' => 'Ⴃ', 'ⴄ' => 'Ⴄ', 'ⴅ' => 'Ⴅ', 'ⴆ' => 'Ⴆ', 'ⴇ' => 'Ⴇ', 'ⴈ' => 'Ⴈ', 'ⴉ' => 'Ⴉ', 'ⴊ' => 'Ⴊ', 'ⴋ' => 'Ⴋ', 'ⴌ' => 'Ⴌ', 'ⴍ' => 'Ⴍ', 'ⴎ' => 'Ⴎ', 'ⴏ' => 'Ⴏ', 'ⴐ' => 'Ⴐ', 'ⴑ' => 'Ⴑ', 'ⴒ' => 'Ⴒ', 'ⴓ' => 'Ⴓ', 'ⴔ' => 'Ⴔ', 'ⴕ' => 'Ⴕ', 'ⴖ' => 'Ⴖ', 'ⴗ' => 'Ⴗ', 'ⴘ' => 'Ⴘ', 'ⴙ' => 'Ⴙ', 'ⴚ' => 'Ⴚ', 'ⴛ' => 'Ⴛ', 'ⴜ' => 'Ⴜ', 'ⴝ' => 'Ⴝ', 'ⴞ' => 'Ⴞ', 'ⴟ' => 'Ⴟ', 'ⴠ' => 'Ⴠ', 'ⴡ' => 'Ⴡ', 'ⴢ' => 'Ⴢ', 'ⴣ' => 'Ⴣ', 'ⴤ' => 'Ⴤ', 'ⴥ' => 'Ⴥ', 'ⴧ' => 'Ⴧ', 'ⴭ' => 'Ⴭ', 'ꙁ' => 'Ꙁ', 'ꙃ' => 'Ꙃ', 'ꙅ' => 'Ꙅ', 'ꙇ' => 'Ꙇ', 'ꙉ' => 'Ꙉ', 'ꙋ' => 'Ꙋ', 'ꙍ' => 'Ꙍ', 'ꙏ' => 'Ꙏ', 'ꙑ' => 'Ꙑ', 'ꙓ' => 'Ꙓ', 'ꙕ' => 'Ꙕ', 'ꙗ' => 'Ꙗ', 'ꙙ' => 'Ꙙ', 'ꙛ' => 'Ꙛ', 'ꙝ' => 'Ꙝ', 'ꙟ' => 'Ꙟ', 'ꙡ' => 'Ꙡ', 'ꙣ' => 'Ꙣ', 'ꙥ' => 'Ꙥ', 'ꙧ' => 'Ꙧ', 'ꙩ' => 'Ꙩ', 'ꙫ' => 'Ꙫ', 'ꙭ' => 'Ꙭ', 'ꚁ' => 'Ꚁ', 'ꚃ' => 'Ꚃ', 'ꚅ' => 'Ꚅ', 'ꚇ' => 'Ꚇ', 'ꚉ' => 'Ꚉ', 'ꚋ' => 'Ꚋ', 'ꚍ' => 'Ꚍ', 'ꚏ' => 'Ꚏ', 'ꚑ' => 'Ꚑ', 'ꚓ' => 'Ꚓ', 'ꚕ' => 'Ꚕ', 'ꚗ' => 'Ꚗ', 'ꚙ' => 'Ꚙ', 'ꚛ' => 'Ꚛ', 'ꜣ' => 'Ꜣ', 'ꜥ' => 'Ꜥ', 'ꜧ' => 'Ꜧ', 'ꜩ' => 'Ꜩ', 'ꜫ' => 'Ꜫ', 'ꜭ' => 'Ꜭ', 'ꜯ' => 'Ꜯ', 'ꜳ' => 'Ꜳ', 'ꜵ' => 'Ꜵ', 'ꜷ' => 'Ꜷ', 'ꜹ' => 'Ꜹ', 'ꜻ' => 'Ꜻ', 'ꜽ' => 'Ꜽ', 'ꜿ' => 'Ꜿ', 'ꝁ' => 'Ꝁ', 'ꝃ' => 'Ꝃ', 'ꝅ' => 'Ꝅ', 'ꝇ' => 'Ꝇ', 'ꝉ' => 'Ꝉ', 'ꝋ' => 'Ꝋ', 'ꝍ' => 'Ꝍ', 'ꝏ' => 'Ꝏ', 'ꝑ' => 'Ꝑ', 'ꝓ' => 'Ꝓ', 'ꝕ' => 'Ꝕ', 'ꝗ' => 'Ꝗ', 'ꝙ' => 'Ꝙ', 'ꝛ' => 'Ꝛ', 'ꝝ' => 'Ꝝ', 'ꝟ' => 'Ꝟ', 'ꝡ' => 'Ꝡ', 'ꝣ' => 'Ꝣ', 'ꝥ' => 'Ꝥ', 'ꝧ' => 'Ꝧ', 'ꝩ' => 'Ꝩ', 'ꝫ' => 'Ꝫ', 'ꝭ' => 'Ꝭ', 'ꝯ' => 'Ꝯ', 'ꝺ' => 'Ꝺ', 'ꝼ' => 'Ꝼ', 'ꝿ' => 'Ꝿ', 'ꞁ' => 'Ꞁ', 'ꞃ' => 'Ꞃ', 'ꞅ' => 'Ꞅ', 'ꞇ' => 'Ꞇ', 'ꞌ' => 'Ꞌ', 'ꞑ' => 'Ꞑ', 'ꞓ' => 'Ꞓ', 'ꞔ' => 'Ꞔ', 'ꞗ' => 'Ꞗ', 'ꞙ' => 'Ꞙ', 'ꞛ' => 'Ꞛ', 'ꞝ' => 'Ꞝ', 'ꞟ' => 'Ꞟ', 'ꞡ' => 'Ꞡ', 'ꞣ' => 'Ꞣ', 'ꞥ' => 'Ꞥ', 'ꞧ' => 'Ꞧ', 'ꞩ' => 'Ꞩ', 'ꞵ' => 'Ꞵ', 'ꞷ' => 'Ꞷ', 'ꞹ' => 'Ꞹ', 'ꞻ' => 'Ꞻ', 'ꞽ' => 'Ꞽ', 'ꞿ' => 'Ꞿ', 'ꟃ' => 'Ꟃ', 'ꟈ' => 'Ꟈ', 'ꟊ' => 'Ꟊ', 'ꟶ' => 'Ꟶ', 'ꭓ' => 'Ꭓ', 'ꭰ' => 'Ꭰ', 'ꭱ' => 'Ꭱ', 'ꭲ' => 'Ꭲ', 'ꭳ' => 'Ꭳ', 'ꭴ' => 'Ꭴ', 'ꭵ' => 'Ꭵ', 'ꭶ' => 'Ꭶ', 'ꭷ' => 'Ꭷ', 'ꭸ' => 'Ꭸ', 'ꭹ' => 'Ꭹ', 'ꭺ' => 'Ꭺ', 'ꭻ' => 'Ꭻ', 'ꭼ' => 'Ꭼ', 'ꭽ' => 'Ꭽ', 'ꭾ' => 'Ꭾ', 'ꭿ' => 'Ꭿ', 'ꮀ' => 'Ꮀ', 'ꮁ' => 'Ꮁ', 'ꮂ' => 'Ꮂ', 'ꮃ' => 'Ꮃ', 'ꮄ' => 'Ꮄ', 'ꮅ' => 'Ꮅ', 'ꮆ' => 'Ꮆ', 'ꮇ' => 'Ꮇ', 'ꮈ' => 'Ꮈ', 'ꮉ' => 'Ꮉ', 'ꮊ' => 'Ꮊ', 'ꮋ' => 'Ꮋ', 'ꮌ' => 'Ꮌ', 'ꮍ' => 'Ꮍ', 'ꮎ' => 'Ꮎ', 'ꮏ' => 'Ꮏ', 'ꮐ' => 'Ꮐ', 'ꮑ' => 'Ꮑ', 'ꮒ' => 'Ꮒ', 'ꮓ' => 'Ꮓ', 'ꮔ' => 'Ꮔ', 'ꮕ' => 'Ꮕ', 'ꮖ' => 'Ꮖ', 'ꮗ' => 'Ꮗ', 'ꮘ' => 'Ꮘ', 'ꮙ' => 'Ꮙ', 'ꮚ' => 'Ꮚ', 'ꮛ' => 'Ꮛ', 'ꮜ' => 'Ꮜ', 'ꮝ' => 'Ꮝ', 'ꮞ' => 'Ꮞ', 'ꮟ' => 'Ꮟ', 'ꮠ' => 'Ꮠ', 'ꮡ' => 'Ꮡ', 'ꮢ' => 'Ꮢ', 'ꮣ' => 'Ꮣ', 'ꮤ' => 'Ꮤ', 'ꮥ' => 'Ꮥ', 'ꮦ' => 'Ꮦ', 'ꮧ' => 'Ꮧ', 'ꮨ' => 'Ꮨ', 'ꮩ' => 'Ꮩ', 'ꮪ' => 'Ꮪ', 'ꮫ' => 'Ꮫ', 'ꮬ' => 'Ꮬ', 'ꮭ' => 'Ꮭ', 'ꮮ' => 'Ꮮ', 'ꮯ' => 'Ꮯ', 'ꮰ' => 'Ꮰ', 'ꮱ' => 'Ꮱ', 'ꮲ' => 'Ꮲ', 'ꮳ' => 'Ꮳ', 'ꮴ' => 'Ꮴ', 'ꮵ' => 'Ꮵ', 'ꮶ' => 'Ꮶ', 'ꮷ' => 'Ꮷ', 'ꮸ' => 'Ꮸ', 'ꮹ' => 'Ꮹ', 'ꮺ' => 'Ꮺ', 'ꮻ' => 'Ꮻ', 'ꮼ' => 'Ꮼ', 'ꮽ' => 'Ꮽ', 'ꮾ' => 'Ꮾ', 'ꮿ' => 'Ꮿ', 'a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'e' => 'E', 'f' => 'F', 'g' => 'G', 'h' => 'H', 'i' => 'I', 'j' => 'J', 'k' => 'K', 'l' => 'L', 'm' => 'M', 'n' => 'N', 'o' => 'O', 'p' => 'P', 'q' => 'Q', 'r' => 'R', 's' => 'S', 't' => 'T', 'u' => 'U', 'v' => 'V', 'w' => 'W', 'x' => 'X', 'y' => 'Y', 'z' => 'Z', '𐐨' => '𐐀', '𐐩' => '𐐁', '𐐪' => '𐐂', '𐐫' => '𐐃', '𐐬' => '𐐄', '𐐭' => '𐐅', '𐐮' => '𐐆', '𐐯' => '𐐇', '𐐰' => '𐐈', '𐐱' => '𐐉', '𐐲' => '𐐊', '𐐳' => '𐐋', '𐐴' => '𐐌', '𐐵' => '𐐍', '𐐶' => '𐐎', '𐐷' => '𐐏', '𐐸' => '𐐐', '𐐹' => '𐐑', '𐐺' => '𐐒', '𐐻' => '𐐓', '𐐼' => '𐐔', '𐐽' => '𐐕', '𐐾' => '𐐖', '𐐿' => '𐐗', '𐑀' => '𐐘', '𐑁' => '𐐙', '𐑂' => '𐐚', '𐑃' => '𐐛', '𐑄' => '𐐜', '𐑅' => '𐐝', '𐑆' => '𐐞', '𐑇' => '𐐟', '𐑈' => '𐐠', '𐑉' => '𐐡', '𐑊' => '𐐢', '𐑋' => '𐐣', '𐑌' => '𐐤', '𐑍' => '𐐥', '𐑎' => '𐐦', '𐑏' => '𐐧', '𐓘' => '𐒰', '𐓙' => '𐒱', '𐓚' => '𐒲', '𐓛' => '𐒳', '𐓜' => '𐒴', '𐓝' => '𐒵', '𐓞' => '𐒶', '𐓟' => '𐒷', '𐓠' => '𐒸', '𐓡' => '𐒹', '𐓢' => '𐒺', '𐓣' => '𐒻', '𐓤' => '𐒼', '𐓥' => '𐒽', '𐓦' => '𐒾', '𐓧' => '𐒿', '𐓨' => '𐓀', '𐓩' => '𐓁', '𐓪' => '𐓂', '𐓫' => '𐓃', '𐓬' => '𐓄', '𐓭' => '𐓅', '𐓮' => '𐓆', '𐓯' => '𐓇', '𐓰' => '𐓈', '𐓱' => '𐓉', '𐓲' => '𐓊', '𐓳' => '𐓋', '𐓴' => '𐓌', '𐓵' => '𐓍', '𐓶' => '𐓎', '𐓷' => '𐓏', '𐓸' => '𐓐', '𐓹' => '𐓑', '𐓺' => '𐓒', '𐓻' => '𐓓', '𐳀' => '𐲀', '𐳁' => '𐲁', '𐳂' => '𐲂', '𐳃' => '𐲃', '𐳄' => '𐲄', '𐳅' => '𐲅', '𐳆' => '𐲆', '𐳇' => '𐲇', '𐳈' => '𐲈', '𐳉' => '𐲉', '𐳊' => '𐲊', '𐳋' => '𐲋', '𐳌' => '𐲌', '𐳍' => '𐲍', '𐳎' => '𐲎', '𐳏' => '𐲏', '𐳐' => '𐲐', '𐳑' => '𐲑', '𐳒' => '𐲒', '𐳓' => '𐲓', '𐳔' => '𐲔', '𐳕' => '𐲕', '𐳖' => '𐲖', '𐳗' => '𐲗', '𐳘' => '𐲘', '𐳙' => '𐲙', '𐳚' => '𐲚', '𐳛' => '𐲛', '𐳜' => '𐲜', '𐳝' => '𐲝', '𐳞' => '𐲞', '𐳟' => '𐲟', '𐳠' => '𐲠', '𐳡' => '𐲡', '𐳢' => '𐲢', '𐳣' => '𐲣', '𐳤' => '𐲤', '𐳥' => '𐲥', '𐳦' => '𐲦', '𐳧' => '𐲧', '𐳨' => '𐲨', '𐳩' => '𐲩', '𐳪' => '𐲪', '𐳫' => '𐲫', '𐳬' => '𐲬', '𐳭' => '𐲭', '𐳮' => '𐲮', '𐳯' => '𐲯', '𐳰' => '𐲰', '𐳱' => '𐲱', '𐳲' => '𐲲', '𑣀' => '𑢠', '𑣁' => '𑢡', '𑣂' => '𑢢', '𑣃' => '𑢣', '𑣄' => '𑢤', '𑣅' => '𑢥', '𑣆' => '𑢦', '𑣇' => '𑢧', '𑣈' => '𑢨', '𑣉' => '𑢩', '𑣊' => '𑢪', '𑣋' => '𑢫', '𑣌' => '𑢬', '𑣍' => '𑢭', '𑣎' => '𑢮', '𑣏' => '𑢯', '𑣐' => '𑢰', '𑣑' => '𑢱', '𑣒' => '𑢲', '𑣓' => '𑢳', '𑣔' => '𑢴', '𑣕' => '𑢵', '𑣖' => '𑢶', '𑣗' => '𑢷', '𑣘' => '𑢸', '𑣙' => '𑢹', '𑣚' => '𑢺', '𑣛' => '𑢻', '𑣜' => '𑢼', '𑣝' => '𑢽', '𑣞' => '𑢾', '𑣟' => '𑢿', '𖹠' => '𖹀', '𖹡' => '𖹁', '𖹢' => '𖹂', '𖹣' => '𖹃', '𖹤' => '𖹄', '𖹥' => '𖹅', '𖹦' => '𖹆', '𖹧' => '𖹇', '𖹨' => '𖹈', '𖹩' => '𖹉', '𖹪' => '𖹊', '𖹫' => '𖹋', '𖹬' => '𖹌', '𖹭' => '𖹍', '𖹮' => '𖹎', '𖹯' => '𖹏', '𖹰' => '𖹐', '𖹱' => '𖹑', '𖹲' => '𖹒', '𖹳' => '𖹓', '𖹴' => '𖹔', '𖹵' => '𖹕', '𖹶' => '𖹖', '𖹷' => '𖹗', '𖹸' => '𖹘', '𖹹' => '𖹙', '𖹺' => '𖹚', '𖹻' => '𖹛', '𖹼' => '𖹜', '𖹽' => '𖹝', '𖹾' => '𖹞', '𖹿' => '𖹟', '𞤢' => '𞤀', '𞤣' => '𞤁', '𞤤' => '𞤂', '𞤥' => '𞤃', '𞤦' => '𞤄', '𞤧' => '𞤅', '𞤨' => '𞤆', '𞤩' => '𞤇', '𞤪' => '𞤈', '𞤫' => '𞤉', '𞤬' => '𞤊', '𞤭' => '𞤋', '𞤮' => '𞤌', '𞤯' => '𞤍', '𞤰' => '𞤎', '𞤱' => '𞤏', '𞤲' => '𞤐', '𞤳' => '𞤑', '𞤴' => '𞤒', '𞤵' => '𞤓', '𞤶' => '𞤔', '𞤷' => '𞤕', '𞤸' => '𞤖', '𞤹' => '𞤗', '𞤺' => '𞤘', '𞤻' => '𞤙', '𞤼' => '𞤚', '𞤽' => '𞤛', '𞤾' => '𞤜', '𞤿' => '𞤝', '𞥀' => '𞤞', '𞥁' => '𞤟', '𞥂' => '𞤠', '𞥃' => '𞤡', 'ß' => 'SS', 'ff' => 'FF', 'fi' => 'FI', 'fl' => 'FL', 'ffi' => 'FFI', 'ffl' => 'FFL', 'ſt' => 'ST', 'st' => 'ST', 'և' => 'ԵՒ', 'ﬓ' => 'ՄՆ', 'ﬔ' => 'ՄԵ', 'ﬕ' => 'ՄԻ', 'ﬖ' => 'ՎՆ', 'ﬗ' => 'ՄԽ', 'ʼn' => 'ʼN', 'ΐ' => 'Ϊ́', 'ΰ' => 'Ϋ́', 'ǰ' => 'J̌', 'ẖ' => 'H̱', 'ẗ' => 'T̈', 'ẘ' => 'W̊', 'ẙ' => 'Y̊', 'ẚ' => 'Aʾ', 'ὐ' => 'Υ̓', 'ὒ' => 'Υ̓̀', 'ὔ' => 'Υ̓́', 'ὖ' => 'Υ̓͂', 'ᾶ' => 'Α͂', 'ῆ' => 'Η͂', 'ῒ' => 'Ϊ̀', 'ΐ' => 'Ϊ́', 'ῖ' => 'Ι͂', 'ῗ' => 'Ϊ͂', 'ῢ' => 'Ϋ̀', 'ΰ' => 'Ϋ́', 'ῤ' => 'Ρ̓', 'ῦ' => 'Υ͂', 'ῧ' => 'Ϋ͂', 'ῶ' => 'Ω͂', 'ᾈ' => 'ἈΙ', 'ᾉ' => 'ἉΙ', 'ᾊ' => 'ἊΙ', 'ᾋ' => 'ἋΙ', 'ᾌ' => 'ἌΙ', 'ᾍ' => 'ἍΙ', 'ᾎ' => 'ἎΙ', 'ᾏ' => 'ἏΙ', 'ᾘ' => 'ἨΙ', 'ᾙ' => 'ἩΙ', 'ᾚ' => 'ἪΙ', 'ᾛ' => 'ἫΙ', 'ᾜ' => 'ἬΙ', 'ᾝ' => 'ἭΙ', 'ᾞ' => 'ἮΙ', 'ᾟ' => 'ἯΙ', 'ᾨ' => 'ὨΙ', 'ᾩ' => 'ὩΙ', 'ᾪ' => 'ὪΙ', 'ᾫ' => 'ὫΙ', 'ᾬ' => 'ὬΙ', 'ᾭ' => 'ὭΙ', 'ᾮ' => 'ὮΙ', 'ᾯ' => 'ὯΙ', 'ᾼ' => 'ΑΙ', 'ῌ' => 'ΗΙ', 'ῼ' => 'ΩΙ', 'ᾲ' => 'ᾺΙ', 'ᾴ' => 'ΆΙ', 'ῂ' => 'ῊΙ', 'ῄ' => 'ΉΙ', 'ῲ' => 'ῺΙ', 'ῴ' => 'ΏΙ', 'ᾷ' => 'Α͂Ι', 'ῇ' => 'Η͂Ι', 'ῷ' => 'Ω͂Ι'); diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 00000000..0e934d7a --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +return require __DIR__.'/bootstrap72.php'; diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap72.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap72.php new file mode 100644 index 00000000..dd644fab --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap72.php @@ -0,0 +1,296 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +use WPMailSMTP\Vendor\Symfony\Polyfill\Mbstring as p; +if (!\function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) + { + return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); + } +} +if (!\function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) + { + return p\Mbstring::mb_decode_mimeheader($string); + } +} +if (!\function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) + { + return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); + } +} +if (!\function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) + { + return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); + } +} +if (!\function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = \false) + { + return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); + } +} +if (!\function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) + { + return p\Mbstring::mb_convert_case($string, $mode, $encoding); + } +} +if (!\function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) + { + return p\Mbstring::mb_internal_encoding($encoding); + } +} +if (!\function_exists('mb_language')) { + function mb_language($language = null) + { + return p\Mbstring::mb_language($language); + } +} +if (!\function_exists('mb_list_encodings')) { + function mb_list_encodings() + { + return p\Mbstring::mb_list_encodings(); + } +} +if (!\function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) + { + return p\Mbstring::mb_encoding_aliases($encoding); + } +} +if (!\function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) + { + return p\Mbstring::mb_check_encoding($value, $encoding); + } +} +if (!\function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = \false) + { + return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); + } +} +if (!\function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) + { + return p\Mbstring::mb_detect_order($encoding); + } +} +if (!\function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) + { + \parse_str($string, $result); + return (bool) $result; + } +} +if (!\function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) + { + return p\Mbstring::mb_strlen($string, $encoding); + } +} +if (!\function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); + } +} +if (!\function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) + { + return p\Mbstring::mb_strtolower($string, $encoding); + } +} +if (!\function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) + { + return p\Mbstring::mb_strtoupper($string, $encoding); + } +} +if (!\function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) + { + return p\Mbstring::mb_substitute_character($substitute_character); + } +} +if (!\function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) + { + return p\Mbstring::mb_substr($string, $start, $length, $encoding); + } +} +if (!\function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); + } +} +if (!\function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = \false, $encoding = null) + { + return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); + } +} +if (!\function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = \false, $encoding = null) + { + return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); + } +} +if (!\function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = \false, $encoding = null) + { + return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); + } +} +if (!\function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); + } +} +if (!\function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); + } +} +if (!\function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = \false, $encoding = null) + { + return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); + } +} +if (!\function_exists('mb_get_info')) { + function mb_get_info($type = 'all') + { + return p\Mbstring::mb_get_info($type); + } +} +if (!\function_exists('mb_http_output')) { + function mb_http_output($encoding = null) + { + return p\Mbstring::mb_http_output($encoding); + } +} +if (!\function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) + { + return p\Mbstring::mb_strwidth($string, $encoding); + } +} +if (!\function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) + { + return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); + } +} +if (!\function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) + { + return p\Mbstring::mb_output_handler($string, $status); + } +} +if (!\function_exists('mb_http_input')) { + function mb_http_input($type = null) + { + return p\Mbstring::mb_http_input($type); + } +} +if (!\function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) + { + return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); + } +} +if (!\function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) + { + return p\Mbstring::mb_ord($string, $encoding); + } +} +if (!\function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) + { + return p\Mbstring::mb_chr($codepoint, $encoding); + } +} +if (!\function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) + { + return p\Mbstring::mb_scrub($string, $encoding); + } +} +if (!\function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) + { + return p\Mbstring::mb_str_split($string, $length, $encoding); + } +} +if (!\function_exists('WPMailSMTP\\Vendor\\mb_str_pad')) { + /** @return string|false */ + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null) + { + return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); + } +} +if (!\function_exists('WPMailSMTP\\Vendor\\mb_ucfirst')) { + /** @return string|false */ + function mb_ucfirst(?string $string, ?string $encoding = null) + { + return p\Mbstring::mb_ucfirst((string) $string, $encoding); + } +} +if (!\function_exists('WPMailSMTP\\Vendor\\mb_lcfirst')) { + /** @return string|false */ + function mb_lcfirst(?string $string, ?string $encoding = null) + { + return p\Mbstring::mb_lcfirst((string) $string, $encoding); + } +} +if (!\function_exists('WPMailSMTP\\Vendor\\mb_trim')) { + /** @return string|false */ + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null) + { + return p\Mbstring::mb_trim((string) $string, $characters, $encoding); + } +} +if (!\function_exists('WPMailSMTP\\Vendor\\mb_ltrim')) { + /** @return string|false */ + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null) + { + return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); + } +} +if (!\function_exists('WPMailSMTP\\Vendor\\mb_rtrim')) { + /** @return string|false */ + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null) + { + return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); + } +} +if (\extension_loaded('mbstring')) { + return; +} +if (!\defined('MB_CASE_UPPER')) { + \define('MB_CASE_UPPER', 0); +} +if (!\defined('MB_CASE_LOWER')) { + \define('MB_CASE_LOWER', 1); +} +if (!\defined('MB_CASE_TITLE')) { + \define('MB_CASE_TITLE', 2); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap80.php b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 00000000..440198d9 --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/vendor_prefixed/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use WPMailSMTP\Vendor\Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false|null { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_scrub($string, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (!function_exists('mb_str_pad')) { + function mb_str_pad(?string $string, ?int $length, ?string $pad_string = ' ', ?int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad((string) $string, (int) $length, (string) $pad_string, (int) $pad_type, $encoding); } +} + +if (!function_exists('mb_ucfirst')) { + function mb_ucfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_lcfirst')) { + function mb_lcfirst(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst((string) $string, $encoding); } +} + +if (!function_exists('mb_trim')) { + function mb_trim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_ltrim')) { + function mb_ltrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim((string) $string, $characters, $encoding); } +} + +if (!function_exists('mb_rtrim')) { + function mb_rtrim(?string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim((string) $string, $characters, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/web/app/plugins/wp-mail-smtp-pro/wp-mail-smtp.php b/web/app/plugins/wp-mail-smtp-pro/wp-mail-smtp.php new file mode 100644 index 00000000..f95be13b --- /dev/null +++ b/web/app/plugins/wp-mail-smtp-pro/wp-mail-smtp.php @@ -0,0 +1,66 @@ +Sender if true, relevant only for Other SMTP mailer. + +// Recommended mailers. +define( 'WPMS_SMTPCOM_API_KEY', '' ); +define( 'WPMS_SMTPCOM_CHANNEL', '' ); +define( 'WPMS_SENDINBLUE_API_KEY', '' ); +define( 'WPMS_SENDINBLUE_DOMAIN', '' ); + +define( 'WPMS_ZOHO_DOMAIN', '' ); +define( 'WPMS_ZOHO_CLIENT_ID', '' ); +define( 'WPMS_ZOHO_CLIENT_SECRET', '' ); + +define( 'WPMS_PEPIPOST_API_KEY', '' ); + +define( 'WPMS_SENDINBLUE_API_KEY', '' ); + +define( 'WPMS_MAILGUN_API_KEY', '' ); +define( 'WPMS_MAILGUN_DOMAIN', '' ); +define( 'WPMS_MAILGUN_REGION', 'US' ); // or 'EU' for Europe. + +define( 'WPMS_SENDGRID_API_KEY', '' ); + +define( 'WPMS_GMAIL_CLIENT_ID', '' ); +define( 'WPMS_GMAIL_CLIENT_SECRET', '' ); + +define( 'WPMS_SMTP_HOST', 'localhost' ); // The SMTP mail host. +define( 'WPMS_SMTP_PORT', 25 ); // The SMTP server port number. +define( 'WPMS_SSL', '' ); // Possible values '', 'ssl', 'tls' - note TLS is not STARTTLS. +define( 'WPMS_SMTP_AUTH', true ); // True turns it on, false turns it off. +define( 'WPMS_SMTP_USER', 'username' ); // SMTP authentication username, only used if WPMS_SMTP_AUTH is true. +define( 'WPMS_SMTP_PASS', 'password' ); // SMTP authentication password, only used if WPMS_SMTP_AUTH is true. +define( 'WPMS_SMTP_AUTOTLS', true ); // True turns it on, false turns it off. +*/ + +/** + * Don't allow multiple versions of 1.5.x (Lite and Pro) and above to be active. + * + * @since 1.5.0 + */ +if ( function_exists( 'wp_mail_smtp' ) ) { + + if ( ! function_exists( 'wp_mail_smtp_deactivate' ) ) { + /** + * Deactivate if plugin already activated. + * Needed when transitioning from 1.5+ Lite to Pro. + * + * @since 1.5.0 + */ + function wp_mail_smtp_deactivate() { + /* + * Prevent issues of WP functions not being available for other plugins that hook into + * this early deactivation. GH issue #861. + */ + require_once ABSPATH . WPINC . '/pluggable.php'; + + deactivate_plugins( plugin_basename( __FILE__ ) ); + } + } + add_action( 'admin_init', 'wp_mail_smtp_deactivate' ); + + // Do not process the plugin code further. + return; +} + +if ( ! function_exists( 'wp_mail_smtp_check_pro_loading_allowed' ) ) { + /** + * Don't allow 1.4.x and below to break when 1.5+ Pro is activated. + * This will stop the current plugin from loading and display a message in admin area. + * + * @since 1.5.0 + */ + function wp_mail_smtp_check_pro_loading_allowed() { + + // Check for pro without using wp_mail_smtp()->is_pro(), because at this point it's too early. + if ( ! is_readable( rtrim( plugin_dir_path( __FILE__ ), '/\\' ) . '/src/Pro/Pro.php' ) ) { + // Currently, not a pro version of the plugin is loaded. + return false; + } + + if ( ! function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . '/wp-admin/includes/plugin.php'; + } + + $lite_plugin_slug = 'wp-mail-smtp/wp_mail_smtp.php'; + + // Search for old plugin name. + if ( is_plugin_active( $lite_plugin_slug ) ) { + /* + * Prevent issues of WP functions not being available for other plugins that hook into + * this early deactivation. GH issue #861. + */ + require_once ABSPATH . WPINC . '/pluggable.php'; + + if ( + is_multisite() && + is_plugin_active_for_network( plugin_basename( __FILE__ ) ) && + ! is_plugin_active_for_network( $lite_plugin_slug ) + ) { + // Deactivate Lite plugin if Pro activated on Network level. + deactivate_plugins( $lite_plugin_slug ); + } else { + // As Pro is loaded and Lite too - deactivate *silently* itself not to break older SMTP plugin. + deactivate_plugins( plugin_basename( __FILE__ ) ); + + if ( is_network_admin() ) { + add_action( 'network_admin_notices', 'wp_mail_smtp_lite_deactivation_notice' ); + } else { + add_action( 'admin_notices', 'wp_mail_smtp_lite_deactivation_notice' ); + } + + return true; + } + } + + return false; + } + + if ( ! function_exists( 'wp_mail_smtp_lite_deactivation_notice' ) ) { + /** + * Display the notice after deactivation. + * + * @since 1.5.0 + */ + function wp_mail_smtp_lite_deactivation_notice() { + + echo '

    ' . esc_html__( 'Please deactivate the free version of the WP Mail SMTP plugin before activating WP Mail SMTP Pro.', 'wp-mail-smtp' ) . '

    '; + + if ( isset( $_GET['activate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + } + } + + // Stop the plugin loading. + if ( wp_mail_smtp_check_pro_loading_allowed() === true ) { + return; + } +} + +if ( ! function_exists( 'wp_mail_smtp_insecure_php_version_notice' ) ) { + /** + * Display admin notice, if the server is using old/insecure PHP version. + * + * @since 2.0.0 + */ + function wp_mail_smtp_insecure_php_version_notice() { + + ?> +
    +

    + insecure version of PHP that is no longer supported. Please contact your web hosting provider to update your PHP version or switch to a recommended WordPress hosting company.', 'wp-mail-smtp' ), + array( + 'a' => array( + 'href' => array(), + 'target' => array(), + 'rel' => array(), + ), + 'strong' => array(), + ) + ), + 'https://www.wpbeginner.com/wordpress-hosting/' + ); + ?> +

    + 'WordPress', + 'utm_medium' => 'Admin Notice', + 'utm_campaign' => is_readable( rtrim( plugin_dir_path( __FILE__ ), '/\\' ) . '/src/Pro/Pro.php' ) ? 'plugin' : 'liteplugin', + 'utm_content' => 'Minimal Required PHP Version', + ], + 'https://wpmailsmtp.com/docs/supported-php-versions-for-wp-mail-smtp/' + ); + + printf( + wp_kses( /* translators: %s - WPMailSMTP.com docs URL with more details. */ + __( 'WP Mail SMTP plugin is disabled on your site until you fix the issue. Read more for additional information.', 'wp-mail-smtp' ), + array( + 'a' => array( + 'href' => array(), + 'target' => array(), + 'rel' => array(), + ), + 'strong' => array(), + ) + ), + esc_url( $doc_link ) + ); + ?> +

    +
    + + +
    +

    + old version of WordPress that is no longer supported by WP Mail SMTP. Please update your WordPress site to at least version %s.', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ), + esc_html( WPMS_WP_VER ) + ); + ?> +

    + WP Mail SMTP plugin is disabled on your site until WordPress is updated to the required version.', 'wp-mail-smtp' ), + [ + 'strong' => [], + ] + ); + ?> +

    +
    + + Date: Wed, 19 Aug 2026 21:08:47 -0400 Subject: [PATCH 4/8] chore: update Lando config - update PHP 8.3 => 8.5 - update MySQL 8.0 => 8.4 --- .lando.yml | 7 ++++--- mysql.cnf | 3 +++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 mysql.cnf 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/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 From 1ffcef22318a4c1d66bad4975d2d8f42cb71dd8b Mon Sep 17 00:00:00 2001 From: Genyus Date: Wed, 19 Aug 2026 21:30:41 -0400 Subject: [PATCH 5/8] ci: pin action refs - switch from version tags to SHAs --- .github/workflows/auto-merge.yml | 4 ++-- .github/workflows/deploy-application.yml | 4 ++-- .github/workflows/deploy-push.yml | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) 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..c049baf1 100644 --- a/.github/workflows/deploy-application.yml +++ b/.github/workflows/deploy-application.yml @@ -27,7 +27,7 @@ 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 }} @@ -54,7 +54,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 From 0925fe721fc357e5dbe016a9516205ee057408ee Mon Sep 17 00:00:00 2001 From: Genyus Date: Wed, 19 Aug 2026 21:46:00 -0400 Subject: [PATCH 6/8] ci: stash server changes before pull --- .github/workflows/deploy-application.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-application.yml b/.github/workflows/deploy-application.yml index c049baf1..6b08eb74 100644 --- a/.github/workflows/deploy-application.yml +++ b/.github/workflows/deploy-application.yml @@ -38,6 +38,7 @@ jobs: export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" cd ${{ secrets.SSH_PATH }} + git stash -m "Stashed by ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" git pull cd web/app/plugins/cbf-multisite npm ci && npm run build:assets From ec9db1f8d02b6f870059f142a968eaad89f68f2c Mon Sep 17 00:00:00 2001 From: Genyus Date: Wed, 19 Aug 2026 21:53:38 -0400 Subject: [PATCH 7/8] ci: fail step if SSH script raises error --- .github/workflows/deploy-application.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-application.yml b/.github/workflows/deploy-application.yml index 6b08eb74..0f635db3 100644 --- a/.github/workflows/deploy-application.yml +++ b/.github/workflows/deploy-application.yml @@ -34,9 +34,12 @@ jobs: 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 }}" git pull From 29f231cd0430e7e03832fa5946c1092f7451ca66 Mon Sep 17 00:00:00 2001 From: Genyus Date: Wed, 19 Aug 2026 21:59:56 -0400 Subject: [PATCH 8/8] ci: include job id in stash message URL --- .github/workflows/deploy-application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-application.yml b/.github/workflows/deploy-application.yml index 0f635db3..1a0a78d9 100644 --- a/.github/workflows/deploy-application.yml +++ b/.github/workflows/deploy-application.yml @@ -41,7 +41,7 @@ jobs: . "$NVM_DIR/nvm.sh" fi cd ${{ secrets.SSH_PATH }} - git stash -m "Stashed by ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + 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