diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..61365be --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,38 @@ +name: Check +on: + schedule: + - cron: "5 15 * * *" + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + cs: + if: github.event_name != 'schedule' + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Run CS + uses: shopware/github-actions/extension-verifier@main + with: + action: format + + check: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + version-selection: [ 'lowest', 'highest'] + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Run Check + uses: shopware/github-actions/extension-verifier@main + with: + action: check + check-against: ${{ matrix.version-selection }} \ No newline at end of file diff --git a/README.md b/README.md index 41fcfd9..a20fff4 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,24 @@ -# FroshMailValidation +# FroshMailAddressTester -This plugin for Shopware 6 validates email addresses during customer registration and checkout processes to ensure the mailbox is accessible and potentially valid. +This plugin for Shopware 6 tests email address during customer registration and checkout processes to ensure the mailbox is accessible and potentially valid. ## Installation ### Via Composer ```bash -composer require frosh/mail-validation +composer require frosh/mail-address-tester ``` ```bash bin/console plugin:refresh -bin/console plugin:install --activate FroshMailValidation +bin/console plugin:install --activate FroshMailAddressTester bin/console cache:clear ``` ## Support -- **GitHub Issues**: [https://github.com/FriendsOfShopware/FroshMailValidation/issues](https://github.com/FriendsOfShopware/FroshMailValidation/issues) +- **GitHub Issues**: [https://github.com/FriendsOfShopware/FroshMailAddressTester/issues](https://github.com/FriendsOfShopware/FroshMailAddressTester/issues) ## License diff --git a/composer.json b/composer.json index 3041c7a..81bb8ca 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "frosh/mail-validation", + "name": "frosh/mail-address-tester", "description": "Validates email addresses by checking their actual existence on the target mail server. Reduces bounces, fake addresses, and sustainably improves deliverability.", "type": "shopware-platform-plugin", "version": "1.0.0", @@ -10,32 +10,32 @@ ], "require": { "shopware/core": "~6.7", - "shyim/check-if-email-exists": "^1.0" + "shyim/check-if-email-exists": "^1.0.2" }, "license": "MIT", "autoload": { "psr-4": { - "Frosh\\MailValidation\\": "src/" + "Frosh\\MailAddressTester\\": "src/" } }, "extra": { - "shopware-plugin-class": "Frosh\\MailValidation\\FroshMailValidation", + "shopware-plugin-class": "Frosh\\MailAddressTester\\FroshMailAddressTester", "copyright": "FriendsOfShopware", "label": { - "de-DE": "E-Mail-Validierung durch Überprüfung der Existenz", - "en-GB": "MailValidation by checking existence" + "de-DE": "E-Mail-Adressen-Tester", + "en-GB": "Mail address tester" }, "description": { "de-DE": "Validiert E-Mail-Adressen durch Prüfung der tatsächlichen Existenz beim Zielserver. Reduziert Bounces, Fake-Adressen und verbessert nachhaltig die Zustellbarkeit.", "en-GB": "Validates email addresses by checking their actual existence on the target mail server. Reduces bounces, fake addresses, and sustainably improves deliverability." }, "manufacturerLink": { - "de-DE": "https://github.com/FriendsOfShopware/FroshMailValidation/", - "en-GB": "https://github.com/FriendsOfShopware/FroshMailValidation/" + "de-DE": "https://github.com/FriendsOfShopware/FroshMailAddressTester/", + "en-GB": "https://github.com/FriendsOfShopware/FroshMailAddressTester/" }, "supportLink": { - "de-DE": "https://github.com/FriendsOfShopware/FroshMailValidation/", - "en-GB": "https://github.com/FriendsOfShopware/FroshMailValidation/" + "de-DE": "https://github.com/FriendsOfShopware/FroshMailAddressTester/", + "en-GB": "https://github.com/FriendsOfShopware/FroshMailAddressTester/" } }, "scripts": { diff --git a/src/Constraint/TestEmail.php b/src/Constraint/TestEmail.php index 1b0e732..013cb3d 100644 --- a/src/Constraint/TestEmail.php +++ b/src/Constraint/TestEmail.php @@ -1,6 +1,6 @@ 'frosh-mail-validation'])] +#[AutoconfigureTag('monolog.logger', ['channel' => 'frosh_mail_tester'])] #[AutoconfigureTag(name: 'validator.constraint_validator')] class TestEmailValidator extends ConstraintValidator { public function __construct( - private readonly Validator $emailValidator, + private readonly Tester $emailAddressTester, ) { } @@ -26,7 +26,7 @@ public function validate(mixed $value, Constraint $constraint): void return; } - if ($this->emailValidator->validateEmail($value)) { + if ($this->emailAddressTester->validateEmail($value)) { return; } diff --git a/src/FroshMailAddressTester.php b/src/FroshMailAddressTester.php new file mode 100644 index 0000000..afdb87e --- /dev/null +++ b/src/FroshMailAddressTester.php @@ -0,0 +1,39 @@ +getPath(), '/') . '/Resources/config'; + + $configLoader->load($confDir . '/{packages}/*.yaml', 'glob'); + } + + public function executeComposerCommands(): bool + { + return true; + } +} diff --git a/src/FroshMailValidation.php b/src/FroshMailValidation.php deleted file mode 100644 index cd58e19..0000000 --- a/src/FroshMailValidation.php +++ /dev/null @@ -1,13 +0,0 @@ -Basic Configuration Grundeinstellungen + + level + + + simple + simple: just checks if the domain has an announced mail server (MX record) +
+ SMTP: checks if an email would be deliverable by connecting to the mail server and simulating a mail delivery without really sending. Diese Prüfung dauert länger. Make sure the email address provided in the next field is valid, otherwise, some checks will be false-negative. + ]]>
+ Einfach: Es wird lediglich geprüft, ob für die Domain ein bekanntgegebener Mailserver (MX-Eintrag) existiert. +
+ SMTP: Diese Option prüft die Zustellbarkeit einer E-Mail, indem sie eine Verbindung zum Mailserver herstellt und die Zustellung simuliert, ohne die E-Mail tatsächlich zu senden. Diese Prüfung dauert länger. Stellen Sie sicher, dass die im nächsten Feld angegebene E-Mail-Adresse gültig ist, da es sonst zu Fehlalarmen kommen kann. + ]]>
+ + + + +
+ verifyEmail - + verify@example.com - This is important to be a valid mail domain. Otherwise, some checks will be false-positive. - Es ist wichtig, dass es sich um eine gültige E-Mail-Domain handelt. Andernfalls liefern einige Prüfungen falsch-positive Ergebnisse. + This is important to be a valid mail domain. Otherwise, some checks will be false-negative. + Es ist wichtig, dass es sich um eine gültige E-Mail-Domain handelt. Andernfalls liefern einige Prüfungen falsch-negative Ergebnisse. \ No newline at end of file diff --git a/src/Resources/config/packages/monolog.yaml b/src/Resources/config/packages/monolog.yaml new file mode 100644 index 0000000..71cec37 --- /dev/null +++ b/src/Resources/config/packages/monolog.yaml @@ -0,0 +1,10 @@ +monolog: + channels: ['frosh_mail_tester'] + + handlers: + FroshMailTesterHandler: + type: rotating_file + max_files: 14 + path: '%kernel.logs_dir%/frosh_mail_tester_%kernel.environment%.log' + level: error + channels: ['frosh_mail_tester'] diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 52c2d08..0ea7360 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -2,6 +2,6 @@ services: _defaults: autowire: true autoconfigure: true - Frosh\MailValidation\: + Frosh\MailAddressTester\: resource: '../../*' - exclude: '../../{Resources,FroshMailValidation.php}' + exclude: '../../{Resources,FroshMailAddressTester.php}' diff --git a/src/Service/Tester.php b/src/Service/Tester.php new file mode 100644 index 0000000..80b513e --- /dev/null +++ b/src/Service/Tester.php @@ -0,0 +1,111 @@ +getCacheItem($email); + $mailValidCacheResult = $mailValidCache->get(); + if (\is_bool($mailValidCacheResult)) { + return $mailValidCacheResult; + } + + $syntaxResult = new Syntax($email); + if ($syntaxResult->isValid() === false) { + return false; + } + + $domain = $syntaxResult->domain; + + $domainValidCache = $this->getCacheItem($domain); + // first check if the domain is already marked as invalid + if ($domainValidCache->get() === false) { + return false; + } + + $mxRecords = (new DNS())->getMxRecords($domain); + + if (empty($mxRecords)) { + $this->saveCache($domainValidCache, false); + + $this->froshMailAddressTesterLogger->error(\sprintf('Domain %s has no mx records', $domain)); + + return false; + } + + if ($this->systemConfigService->getString('FroshMailAddressTester.config.level') !== 'smtp') { + return true; + } + + $verifyEmail = $this->systemConfigService->getString('FroshMailAddressTester.config.verifyEmail'); + if ($verifyEmail !== '') { + $smtpCheck = new SMTP($verifyEmail); + } else { + $smtpCheck = new SMTP(); + } + + $smtpResult = $smtpCheck->check($domain, $mxRecords, $email); + $this->saveCache($domainValidCache, $smtpResult->canConnect); + + if ($smtpResult->canConnect === false) { + $this->froshMailAddressTesterLogger->error($smtpResult->error); + + return false; + } + + $isValid = $smtpResult->isDeliverable === true && $smtpResult->isDisabled === false && $smtpResult->hasFullInbox === false; + + $this->saveCache($mailValidCache, $isValid); + + if ($isValid === false) { + $this->froshMailAddressTesterLogger->error( + \sprintf('Email address "%s" test failed', $email), + json_decode(json_encode($smtpResult, \JSON_THROW_ON_ERROR), true, 1, \JSON_THROW_ON_ERROR) + ); + } + + return $isValid; + } + + private function getCacheItem(string $value): CacheItemInterface + { + $cacheKey = 'frosh_mail_tester_' . Hasher::hash($value); + + return $this->cache->getItem($cacheKey); + } + + private function saveCache(CacheItemInterface $item, bool $value): void + { + $cacheTime = 3600; + + if ($value === true) { + $cacheTime = 86400; // one day for valid emails + } + + $item->expiresAfter($cacheTime); + $item->set($value); + $this->cache->save($item); + } +} diff --git a/src/Service/Validator.php b/src/Service/Validator.php deleted file mode 100644 index 7e855d3..0000000 --- a/src/Service/Validator.php +++ /dev/null @@ -1,70 +0,0 @@ -systemConfigService->getString('FroshMailValidation.config.verifyEmail'); - - $syntax = new Syntax($email); - if ($syntax->isValid() === false) { - return false; - } - - $domain = $syntax->domain; - - $domainCacheKey = 'frosh_mail_validation_domain_' . Hasher::hash($domain); - - \assert($this->cache instanceof AdapterInterface); - $domainValidCache = $this->cache->getItem($domainCacheKey); - - // first check if the domain is already marked as invalid - if ($domainValidCache->get() === false) { - return false; - } - - $checker = new EmailChecker(smtp: new SMTP($verifyEmail)); - - $emailCacheKey = 'frosh_mail_validation_email_' . Hasher::hash($email); - - return $this->cache->get($emailCacheKey, function (ItemInterface $cacheItem) use ($email, $checker, $domainValidCache) { - $result = $checker->check($email); - - $domainValid = $result->hasMxRecords && $result->isReachable; - $domainValidCache->set($domainValid); - $domainValidCache->expiresAfter($domainValid ? 86400 : 3600); - $this->cache->save($domainValidCache); - - $cacheItem->expiresAfter(3600); - - if ($domainValid && $result->isDisabled === false && $result->hasFullInbox === false) { - return true; - } - - $this->froshMailValidationLogger->error('Email validation failed', $result->toArray()); - - return false; - }); - } -} diff --git a/src/Subscriber/BuildValidationEventListener.php b/src/Subscriber/BuildValidationEventListener.php index 15c0a5e..989a41a 100644 --- a/src/Subscriber/BuildValidationEventListener.php +++ b/src/Subscriber/BuildValidationEventListener.php @@ -1,8 +1,8 @@