From 28c79149760b76c5d4901b05dc36bd960e8c46d6 Mon Sep 17 00:00:00 2001 From: ashraf Date: Tue, 1 Oct 2024 15:11:53 +0200 Subject: [PATCH 1/3] OXDEV-7191 Redirect the user after resetting password --- CHANGELOG-7.2.md | 1 + .../Controller/ForgotPasswordController.php | 33 ++++++++----------- source/Application/translations/de/lang.php | 6 ++-- source/Application/translations/en/lang.php | 4 ++- .../Acceptance/UserAccountCest.php | 20 +++++------ 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/CHANGELOG-7.2.md b/CHANGELOG-7.2.md index 9f80a562f7..b3843b2943 100644 --- a/CHANGELOG-7.2.md +++ b/CHANGELOG-7.2.md @@ -15,6 +15,7 @@ - Multiple language creation [#0007683](https://bugs.oxid-esales.com/view.php?id=7683) - Remove unnecessary `` tags from CHF currency - Existing sessions should be destroyed on password change [#0007324](https://bugs.oxid-esales.com/view.php?id=7324) +- Forgot password message ### Changed - Changed the exported user file name from `Export_recipient_` to `Export_user_recipient_status_` to better reflect the content of the export. diff --git a/source/Application/Controller/ForgotPasswordController.php b/source/Application/Controller/ForgotPasswordController.php index 32d6454ed5..3e2fb9ea30 100644 --- a/source/Application/Controller/ForgotPasswordController.php +++ b/source/Application/Controller/ForgotPasswordController.php @@ -7,6 +7,7 @@ namespace OxidEsales\EshopCommunity\Application\Controller; +use OxidEsales\Eshop\Core\Email; use OxidEsales\Eshop\Core\Registry; /** @@ -30,7 +31,7 @@ class ForgotPasswordController extends \OxidEsales\Eshop\Application\Controller\ * * @var string */ - protected $_sForgotEmail = null; + protected $_sForgotEmail; /** * Current view search engine indexing state @@ -43,8 +44,10 @@ class ForgotPasswordController extends \OxidEsales\Eshop\Application\Controller\ * Update link expiration status * * @var bool + * + * @deprecated property will be removed in next major */ - protected $_blUpdateLinkStatus = null; + protected $_blUpdateLinkStatus; /** * Sign if to load and show bargain action @@ -54,27 +57,17 @@ class ForgotPasswordController extends \OxidEsales\Eshop\Application\Controller\ protected $_blBargainAction = true; /** - * Executes oxemail::SendForgotPwdEmail() and sends login - * password to user according to login name (email). - * - * Template variables: - * sendForgotMail + * Executes Email::sendForgotPwdEmail() to send "forgot password" email to user */ public function forgotPassword() { - $sEmail = Registry::getRequest()->getRequestEscapedParameter('lgn_usr'); - $this->_sForgotEmail = $sEmail; - $oEmail = oxNew(\OxidEsales\Eshop\Core\Email::class); - - // problems sending passwd reminder ? - $iSuccess = false; - if ($sEmail) { - $iSuccess = $oEmail->sendForgotPwdEmail($sEmail); - } - if ($iSuccess !== true) { - $sError = ($iSuccess === false) ? 'ERROR_MESSAGE_PASSWORD_EMAIL_INVALID' : 'MESSAGE_NOT_ABLE_TO_SEND_EMAIL'; - Registry::getUtilsView()->addErrorToDisplay($sError); - $this->_sForgotEmail = false; + $this->_sForgotEmail = Registry::getRequest()->getRequestEscapedParameter('lgn_usr'); + if ($this->_sForgotEmail) { + $result = oxNew(Email::class)->sendForgotPwdEmail($this->_sForgotEmail); + if ($result === -1) { + Registry::getUtilsView()->addErrorToDisplay('MESSAGE_NOT_ABLE_TO_SEND_EMAIL'); + $this->_sForgotEmail = false; + } } } diff --git a/source/Application/translations/de/lang.php b/source/Application/translations/de/lang.php index 441fbebdb3..b8dd03ffad 100644 --- a/source/Application/translations/de/lang.php +++ b/source/Application/translations/de/lang.php @@ -138,14 +138,16 @@ 'ERROR_MESSAGE_INPUT_NOVALIDEMAIL' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein', 'ERROR_MESSAGE_INVITE_INCORRECTEMAILADDRESS' => 'Ungültige E-Mail-Adresse. Bitte überprüfen Sie die E-Mail-Adressen.', 'ERROR_MESSAGE_MANDATES_EXCEEDED' => 'Die Anzahl der lizenzierten Mandanten ist überschritten. Tragen Sie bitte im Shop Admin einen gültigen Lizenzschlüssel ein oder kontaktieren Sie', -'FOR_MORE_INFORMATION' => 'für mehr Informationen.', +'FOR_MORE_INFORMATION' => 'für mehr Informationen.',// 'ERROR_MESSAGE_NOFILE' => 'Keine Datei hochgeladen', 'EXCEPTION_NOTALLOWEDTYPE' => 'Verbotener Dateityp. Bitte config.inc.php anpassen, um diesen Dateityp zu erlauben.', 'ERROR_MESSAGE_OUTOFSTOCK_OUTOFSTOCK' => 'Der Lagerbestand dieses Artikels ist nicht ausreichend! Verfügbar', 'ERROR_MESSAGE_OXID_ESALES' => 'OXID eSales', 'ERROR_MESSAGE_OXID_SHOP_ERROR' => 'OXID eShop Fehler', 'ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH' => 'Fehler: Die Passwörter stimmen nicht überein.', +// @deprecated will be removed in next major 'ERROR_MESSAGE_PASSWORD_EMAIL_INVALID' => 'Bitte geben Sie eine gültige E-Mail-Adresse ein!', +// END deprecated 'ERROR_MESSAGE_PASSWORD_LINK_EXPIRED' => 'Diese Seite ist nicht mehr gültig. Bitte benutzen Sie die Funktion "Passwort vergessen?" erneut.', 'ERROR_MESSAGE_PASSWORD_TOO_SHORT' => 'Fehler: Ihr Passwort ist zu kurz.', 'ERROR_REVIEW_AND_RATING_NOT_DELETED' => 'Bewertung und Sterne-Rating konnten nicht gelöscht werden', @@ -365,7 +367,7 @@ 'PAGE' => 'Seite', 'PASSWORD' => 'Passwort', 'PASSWORD_CHANGED' => 'Ihr Passwort wurde erfolgreich geändert.', -'PASSWORD_WAS_SEND_TO' => 'Passwort wurde verschickt an', +'PASSWORD_WAS_SEND_TO' => 'Bei bestehender Registrierung erhalten Sie eine E-Mail mit einem Link zur Passwortvergabe an', 'PAY' => 'Bezahlen', 'PAYMENT_INFORMATION' => 'Bezahlinformation', 'PAYMENT_METHOD' => 'Zahlungsart', diff --git a/source/Application/translations/en/lang.php b/source/Application/translations/en/lang.php index 9cb3a60743..d7996d1db0 100644 --- a/source/Application/translations/en/lang.php +++ b/source/Application/translations/en/lang.php @@ -145,7 +145,9 @@ 'ERROR_MESSAGE_OXID_ESALES' => 'OXID eSales', 'ERROR_MESSAGE_OXID_SHOP_ERROR' => 'OXID eShop error', 'ERROR_MESSAGE_PASSWORD_DO_NOT_MATCH' => 'Error: passwords don\'t match.', +// @deprecated will be removed in next major 'ERROR_MESSAGE_PASSWORD_EMAIL_INVALID' => 'Please enter a valid e-mail address!', +// END deprecated 'ERROR_MESSAGE_PASSWORD_LINK_EXPIRED' => 'This page is expired. Please use the function "Forgot password?" once again.', 'ERROR_MESSAGE_PASSWORD_TOO_SHORT' => 'Error: your password is too short.', 'ERROR_REVIEW_AND_RATING_NOT_DELETED' => 'The review and the star rating could not be deleted', @@ -365,7 +367,7 @@ 'PAGE' => 'Page', 'PASSWORD' => 'Password', 'PASSWORD_CHANGED' => 'Your password was changed successfully.', -'PASSWORD_WAS_SEND_TO' => 'Password was sent to', +'PASSWORD_WAS_SEND_TO' => 'If you have registered, you will receive an e-mail with a link to the password assignment to', 'PAY' => 'Pay', 'PAYMENT_INFORMATION' => 'Payment information', 'PAYMENT_METHOD' => 'Payment method', diff --git a/tests/Codeception/Acceptance/UserAccountCest.php b/tests/Codeception/Acceptance/UserAccountCest.php index c40db2fbbf..53574dd79d 100644 --- a/tests/Codeception/Acceptance/UserAccountCest.php +++ b/tests/Codeception/Acceptance/UserAccountCest.php @@ -100,24 +100,24 @@ public function sendUserPasswordReminder(AcceptanceTester $I): void $I->wantToTest('user password reminder in my account navigation'); $userData = $this->getExistingUserData(); - $startPage = $I->openShop(); - //open password reminder page in account menu popup $passwordReminderPage = $startPage->openUserPasswordReminderPage(); $I->see(Translator::translate('HAVE_YOU_FORGOTTEN_PASSWORD')); - //enter not existing email - $passwordReminderPage = $passwordReminderPage->resetPassword('not_existing_user@oxid-esales.dev'); - $I->see(Translator::translate('ERROR_MESSAGE_PASSWORD_EMAIL_INVALID')); + $I->amGoingTo('reset password with invalid email format'); + $passwordReminderPage->resetPassword('wrongEmail'); + $I->see(Translator::translate('DD_FORM_VALIDATION_VALIDEMAIL')); - //enter existing email - $passwordReminderPage = $passwordReminderPage->resetPassword($userData['userLoginName']); + $I->amGoingTo('reset password with existing user email'); + $passwordReminderPage->resetPassword($userData['userLoginName']); $I->see(Translator::translate('PASSWORD_WAS_SEND_TO') . ' ' . $userData['userLoginName']); - //open password reminder page in main user account page - $passwordReminderPage->openUserPasswordReminderPage(); - $I->see(Translator::translate('HAVE_YOU_FORGOTTEN_PASSWORD')); + $I->amGoingTo('reset password with non-existing user email'); + $nonExistingEmail = 'not_existing_user@oxid-esales.dev'; + $startPage->openUserPasswordReminderPage() + ->resetPassword($nonExistingEmail); + $I->see(Translator::translate('PASSWORD_WAS_SEND_TO') . ' ' . $nonExistingEmail); } /** From f80825860fbef7ee89130d416be0113ec377ea1c Mon Sep 17 00:00:00 2001 From: ashraf Date: Mon, 2 Sep 2024 13:57:13 +0200 Subject: [PATCH 2/3] OXDEV-6846 Sending mail optional --- CHANGELOG-7.2.md | 1 + source/Core/Email.php | 18 +++ source/Internal/Utility/Email/services.yaml | 5 +- tests/ContainerTrait.php | 40 +++++- tests/Integration/Core/EmailTest.php | 128 ++++++++++++++++++++ 5 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 tests/Integration/Core/EmailTest.php diff --git a/CHANGELOG-7.2.md b/CHANGELOG-7.2.md index b3843b2943..aad2ee04d9 100644 --- a/CHANGELOG-7.2.md +++ b/CHANGELOG-7.2.md @@ -4,6 +4,7 @@ ### Added - Translations for Change language and currency +- New parameter `oxid_esales.email.disable_order_emails` to enable and disable sending order emails ### Deprecated - Filesystem module cache related services and interface will be refactored and some of them will be removed diff --git a/source/Core/Email.php b/source/Core/Email.php index fcbb68a7c0..559b954017 100644 --- a/source/Core/Email.php +++ b/source/Core/Email.php @@ -13,12 +13,14 @@ use OxidEsales\Eshop\Core\Exception\SystemComponentException; use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\Str; +use OxidEsales\EshopCommunity\Core\Di\ContainerFacade; use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory; use OxidEsales\EshopCommunity\Internal\Domain\Admin\Event\AdminModeChangedEvent; use OxidEsales\EshopCommunity\Internal\Framework\Templating\TemplateRendererBridgeInterface; use OxidEsales\EshopCommunity\Internal\Framework\Templating\TemplateRendererInterface; use OxidEsales\EshopCommunity\Internal\Utility\Email\EmailValidatorServiceBridgeInterface; use PHPMailer\PHPMailer\PHPMailer; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -463,6 +465,11 @@ protected function isValidSmtpHost($smtpHost) */ public function sendOrderEmailToUser($order, $subject = null) { + if ($this->areOrderEmailsDisabled()) { + ContainerFacade::get(LoggerInterface::class) + ->notice('Order email not sent to user due to disabled configuration option'); + return true; + } // add user defined stuff if there is any $order = $this->addUserInfoOrderEMail($order); @@ -514,6 +521,11 @@ public function sendOrderEmailToUser($order, $subject = null) */ public function sendOrderEmailToOwner($order, $subject = null) { + if ($this->areOrderEmailsDisabled()) { + ContainerFacade::get(LoggerInterface::class) + ->notice('Order email not sent to owner due to disabled configuration option'); + return true; + } $config = Registry::getConfig(); $shop = $this->getShop(); @@ -2015,4 +2027,10 @@ private function dispatchAdminModeChangedEvent(): void ->get(EventDispatcherInterface::class) ->dispatch(new AdminModeChangedEvent()); } + + private function areOrderEmailsDisabled(): bool + { + return ContainerFacade::hasParameter('oxid_esales.email.disable_order_emails') + && ContainerFacade::getParameter('oxid_esales.email.disable_order_emails'); + } } diff --git a/source/Internal/Utility/Email/services.yaml b/source/Internal/Utility/Email/services.yaml index 172609a78d..042954eabb 100644 --- a/source/Internal/Utility/Email/services.yaml +++ b/source/Internal/Utility/Email/services.yaml @@ -1,3 +1,6 @@ +parameters: + oxid_esales.email.disable_order_emails: false + services: _defaults: autowire: true @@ -8,4 +11,4 @@ services: OxidEsales\EshopCommunity\Internal\Utility\Email\EmailValidatorServiceBridgeInterface: class: OxidEsales\EshopCommunity\Internal\Utility\Email\EmailValidatorServiceBridge - public: true \ No newline at end of file + public: true diff --git a/tests/ContainerTrait.php b/tests/ContainerTrait.php index 03dc98f518..078e5ebb8e 100644 --- a/tests/ContainerTrait.php +++ b/tests/ContainerTrait.php @@ -9,7 +9,10 @@ namespace OxidEsales\EshopCommunity\Tests; +use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory; +use ReflectionClass; use Symfony\Component\DependencyInjection\Container; +use UnitEnum; /** * @internal @@ -18,7 +21,7 @@ trait ContainerTrait { private $container; - + protected function get(string $serviceId) { $this->prepareContainer(); @@ -31,12 +34,43 @@ private function getParameter(string $name) return $this->container->getParameter($name); } + private function setParameter(string $name, array|bool|string|int|float|UnitEnum|null $value): void + { + if (!$this->container) { + $this->createContainer(); + } + $this->container->setParameter($name, $value); + } + private function prepareContainer(): void { if ($this->container === null) { - $this->container = (new TestContainerFactory())->create(); + $this->createContainer(); + $this->compileContainer(); + } + } + + private function createContainer(): void + { + $this->container = (new TestContainerFactory())->create(); + } + + private function compileContainer(): void + { + $this->container->compile(true); + $this->get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')->generate(); + } + + /** + * Run tests in a separate process if you use this function. + */ + private function attachContainerToContainerFactory(): void + { + if (!$this->container->isCompiled()) { $this->container->compile(); - $this->get('oxid_esales.module.install.service.launched_shop_project_configuration_generator')->generate(); } + $reflectionClass = new ReflectionClass(ContainerFactory::getInstance()); + $reflectionProperty = $reflectionClass->getProperty('symfonyContainer'); + $reflectionProperty->setValue(ContainerFactory::getInstance(), $this->container); } } diff --git a/tests/Integration/Core/EmailTest.php b/tests/Integration/Core/EmailTest.php new file mode 100644 index 0000000000..dbdf240aba --- /dev/null +++ b/tests/Integration/Core/EmailTest.php @@ -0,0 +1,128 @@ +getLoggerMock(); + $this->getEmailMock(); + $this->getOrderStub(); + $this->createContainer(); + $this->container->set(LoggerInterface::class, $this->logger); + $this->container->autowire(LoggerInterface::class, LoggerInterface::class); + } + + public function testSendOrderEmailToUserWithDefaultConfiguration(): void + { + $this->email->expects($this->once()) + ->method('sendMail'); + $this->email->expects($this->once()) + ->method('getRenderer'); + + $this->email->sendOrderEmailToUser($this->order); + } + + public function testSendOrderEmailToOwnerWithDefaultConfiguration(): void + { + $this->email->expects($this->once()) + ->method('sendMail'); + $this->email->expects($this->once()) + ->method('getRenderer'); + + $this->email->sendOrderEmailToOwner($this->order); + } + + public function testSendOrderEmailToUserWithDisabledEmails(): void + { + $this->setParameter('oxid_esales.email.disable_order_emails', true); + $this->attachContainerToContainerFactory(); + + $this->logger->expects($this->atLeastOnce()) + ->method('notice'); + $this->email->expects($this->never()) + ->method('sendMail'); + $this->email->expects($this->never()) + ->method('getRenderer'); + + $return = $this->email->sendOrderEmailToUser($this->order); + + $this->assertTrue($return); + } + + public function testSendOrderEmailToOwnerWithDisabledEmails(): void + { + $this->setParameter('oxid_esales.email.disable_order_emails', true); + $this->attachContainerToContainerFactory(); + + $this->logger->expects($this->atLeastOnce()) + ->method('notice'); + $this->email->expects($this->never()) + ->method('sendMail'); + $this->email->expects($this->never()) + ->method('getRenderer'); + + $return = $this->email->sendOrderEmailToOwner($this->order); + + $this->assertTrue($return); + } + + private function getOrderStub(): void + { + $user = new User(); + $user->oxuser__oxfname = new Field('user-first-name'); + $user->oxuser__oxlname = new Field('user-last-name'); + $user->oxuser__oxusername = new Field('test@example.com'); + $user->oxshops__oxorderemail = new Field('test@order.com'); + + $this->order = $this->createPartialMock(Order::class, ['getOrderUser']); + $this->order->oxorder__oxordernr = new Field('order-test-1'); + $this->order->method('getOrderUser') + ->willReturn($user); + } + + private function getEmailMock(): void + { + $templateRenderer = $this->createMock(TemplateRendererInterface::class); + $templateRenderer->method('renderTemplate') + ->willReturn('some-data'); + $this->email = $this->createPartialMock(Email::class, ['sendMail', 'getRenderer']); + $this->email->method('getRenderer') + ->willReturn($templateRenderer); + $this->email->method('sendMail') + ->willReturn(true); + } + + private function getLoggerMock(): void + { + $this->logger = $this->createMock(LoggerInterface::class); + } +} From d6ca04e684d0f1ff3bada04acd10f6167d978989 Mon Sep 17 00:00:00 2001 From: ashraf Date: Mon, 7 Oct 2024 15:11:16 +0200 Subject: [PATCH 3/3] OXDEV-6846 Exclude codeception underscore method declaration from code sniffer check --- phpcs.xml.dist | 3 +++ tests/phpcs.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 04edacb035..2d4e393554 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,4 +10,7 @@ + + *tests/Codeception/.*$ + diff --git a/tests/phpcs.xml b/tests/phpcs.xml index 2cce669711..9773144bbd 100644 --- a/tests/phpcs.xml +++ b/tests/phpcs.xml @@ -10,4 +10,7 @@ + + *tests/Codeception/.*$ +