Skip to content

Commit be3cb9c

Browse files
committed
MC-41400: Admin interface locale breaks "Expiration date" field
1 parent 912ceab commit be3cb9c

File tree

6 files changed

+250
-147
lines changed

6 files changed

+250
-147
lines changed

app/code/Magento/Security/Model/ResourceModel/UserExpiration.php

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,19 @@
77

88
namespace Magento\Security\Model\ResourceModel;
99

10+
use Magento\Framework\App\ObjectManager;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\Model\AbstractModel;
13+
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
14+
use Magento\Framework\Model\ResourceModel\Db\Context;
15+
use Magento\Framework\Stdlib\DateTime\Timezone\LocalizedDateToUtcConverterInterface;
16+
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
17+
use Magento\Security\Model\UserExpiration as UserExpirationModel;
18+
1019
/**
1120
* Admin User Expiration resource model
1221
*/
13-
class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
22+
class UserExpiration extends AbstractDb
1423
{
1524

1625
/**
@@ -21,24 +30,31 @@ class UserExpiration extends \Magento\Framework\Model\ResourceModel\Db\AbstractD
2130
protected $_isPkAutoIncrement = false;
2231

2332
/**
24-
* @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
33+
* @var TimezoneInterface
2534
*/
2635
private $timezone;
2736

2837
/**
29-
* UserExpiration constructor.
30-
*
31-
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
32-
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone
33-
* @param string $connectionName
38+
* @var LocalizedDateToUtcConverterInterface
39+
*/
40+
private $localizedDateToUtcConverter;
41+
42+
/**
43+
* @param Context $context
44+
* @param TimezoneInterface $timezone
45+
* @param string|null $connectionName
46+
* @param LocalizedDateToUtcConverterInterface|null $localizedDateToUtcConverter
3447
*/
3548
public function __construct(
36-
\Magento\Framework\Model\ResourceModel\Db\Context $context,
37-
\Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone,
38-
?string $connectionName = null
49+
Context $context,
50+
TimezoneInterface $timezone,
51+
?string $connectionName = null,
52+
?LocalizedDateToUtcConverterInterface $localizedDateToUtcConverter = null
3953
) {
4054
parent::__construct($context, $connectionName);
4155
$this->timezone = $timezone;
56+
$this->localizedDateToUtcConverter = $localizedDateToUtcConverter ?: ObjectManager::getInstance()
57+
->get(LocalizedDateToUtcConverterInterface::class);
4258
}
4359

4460
/**
@@ -54,15 +70,17 @@ protected function _construct()
5470
/**
5571
* Convert to UTC time.
5672
*
57-
* @param \Magento\Framework\Model\AbstractModel $userExpiration
73+
* @param AbstractModel $userExpiration
5874
* @return $this
59-
* @throws \Magento\Framework\Exception\LocalizedException
75+
* @throws LocalizedException
6076
*/
61-
protected function _beforeSave(\Magento\Framework\Model\AbstractModel $userExpiration)
77+
protected function _beforeSave(AbstractModel $userExpiration)
6278
{
63-
/** @var $userExpiration \Magento\Security\Model\UserExpiration */
79+
/** @var $userExpiration UserExpirationModel */
6480
$expiresAt = $userExpiration->getExpiresAt();
65-
$utcValue = $this->timezone->convertConfigTimeToUtc($expiresAt);
81+
$utcValue = strtotime($expiresAt)
82+
? $this->timezone->convertConfigTimeToUtc($expiresAt)
83+
: $this->localizedDateToUtcConverter->convertLocalizedDateToUtc($expiresAt);
6684
$userExpiration->setExpiresAt($utcValue);
6785

6886
return $this;
@@ -71,15 +89,16 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $userExpir
7189
/**
7290
* Convert to store time.
7391
*
74-
* @param \Magento\Framework\Model\AbstractModel $userExpiration
75-
* @return $this|\Magento\Framework\Model\ResourceModel\Db\AbstractDb
92+
* @param AbstractModel $userExpiration
93+
* @return $this|AbstractDb
7694
* @throws \Exception
7795
*/
78-
protected function _afterLoad(\Magento\Framework\Model\AbstractModel $userExpiration)
96+
protected function _afterLoad(AbstractModel $userExpiration)
7997
{
80-
/** @var $userExpiration \Magento\Security\Model\UserExpiration */
98+
/** @var $userExpiration UserExpirationModel */
8199
if ($userExpiration->getExpiresAt()) {
82-
$storeValue = $this->timezone->date($userExpiration->getExpiresAt());
100+
$date = new \DateTime($userExpiration->getExpiresAt());
101+
$storeValue = $this->timezone->date($date);
83102
$userExpiration->setExpiresAt($storeValue->format('Y-m-d H:i:s'));
84103
}
85104

app/code/Magento/Security/Model/UserExpiration/Validator.php

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
namespace Magento\Security\Model\UserExpiration;
99

10+
use Magento\Framework\App\ObjectManager;
1011
use Magento\Framework\Stdlib\DateTime\DateTime;
12+
use Magento\Framework\Stdlib\DateTime\Timezone\LocalizedDateToUtcConverterInterface;
1113
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
1214
use Magento\Framework\Validator\AbstractValidator;
1315

@@ -16,25 +18,35 @@
1618
*/
1719
class Validator extends AbstractValidator
1820
{
19-
20-
/**@var TimezoneInterface */
21+
/**
22+
* @var TimezoneInterface
23+
*/
2124
private $timezone;
2225

23-
/**@var DateTime */
26+
/**
27+
* @var DateTime
28+
*/
2429
private $dateTime;
2530

2631
/**
27-
* Validator constructor.
28-
*
32+
* @var LocalizedDateToUtcConverterInterface
33+
*/
34+
private $localizedDateToUtcConverter;
35+
36+
/**
2937
* @param TimezoneInterface $timezone
3038
* @param DateTime $dateTime
39+
* @param LocalizedDateToUtcConverterInterface|null $localizedDateToUtcConverter
3140
*/
3241
public function __construct(
3342
TimezoneInterface $timezone,
34-
DateTime $dateTime
43+
DateTime $dateTime,
44+
?LocalizedDateToUtcConverterInterface $localizedDateToUtcConverter = null
3545
) {
3646
$this->timezone = $timezone;
3747
$this->dateTime = $dateTime;
48+
$this->localizedDateToUtcConverter = $localizedDateToUtcConverter ?: ObjectManager::getInstance()
49+
->get(LocalizedDateToUtcConverterInterface::class);
3850
}
3951

4052
/**
@@ -48,18 +60,13 @@ public function isValid($value)
4860
{
4961
$this->_clearMessages();
5062
$messages = [];
51-
$expiresAt = $value;
5263
$label = 'Expiration date';
53-
if (\Zend_Validate::is($expiresAt, 'NotEmpty')) {
54-
if (strtotime($expiresAt)) {
55-
$currentTime = $this->dateTime->gmtTimestamp();
56-
$utcExpiresAt = $this->timezone->convertConfigTimeToUtc($expiresAt);
57-
$expiresAt = $this->timezone->date($utcExpiresAt)->getTimestamp();
58-
if ($expiresAt < $currentTime) {
59-
$messages['expires_at'] = __('"%1" must be later than the current date.', $label);
60-
}
61-
} else {
62-
$messages['expires_at'] = __('"%1" is not a valid date.', $label);
64+
if (\Zend_Validate::is($value, 'NotEmpty')) {
65+
$utcExpiresAt = $this->localizedDateToUtcConverter->convertLocalizedDateToUtc($value);
66+
$currentTime = $this->dateTime->gmtTimestamp();
67+
$expiresAt = $this->timezone->date($utcExpiresAt)->getTimestamp();
68+
if ($expiresAt < $currentTime) {
69+
$messages['expires_at'] = __('"%1" must be later than the current date.', $label);
6370
}
6471
}
6572
$this->_addMessages($messages);

app/code/Magento/Security/Test/Unit/Model/UserExpiration/ValidatorTest.php

Lines changed: 0 additions & 102 deletions
This file was deleted.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Security\Model\ResourceModel;
9+
10+
use Magento\Framework\Locale\ResolverInterface;
11+
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
12+
use Magento\Security\Model\UserExpirationFactory;
13+
use Magento\TestFramework\Helper\Bootstrap;
14+
use Magento\User\Model\ResourceModel\User as UserResource;
15+
use PHPUnit\Framework\TestCase;
16+
17+
/**
18+
* Verify user expiration resource model.
19+
*/
20+
class UserExpirationTest extends TestCase
21+
{
22+
/**
23+
* @var UserExpiration
24+
*/
25+
private $userExpirationResource;
26+
27+
/**
28+
* @inheritdoc
29+
*/
30+
protected function setUp(): void
31+
{
32+
$this->userExpirationResource = Bootstrap::getObjectManager()->get(UserExpiration::class);
33+
}
34+
35+
/**
36+
* Verify user expiration saved with correct date.
37+
*
38+
* @magentoDataFixture Magento/User/_files/dummy_user.php
39+
* @dataProvider userExpirationSaveDataProvider
40+
* @magentoAppArea adminhtml
41+
* @return void
42+
*/
43+
public function testUserExpirationSave(string $locale): void
44+
{
45+
$localeResolver = Bootstrap::getObjectManager()->get(ResolverInterface::class);
46+
$timeZone = Bootstrap::getObjectManager()->get(TimezoneInterface::class);
47+
$localeResolver->setLocale($locale);
48+
$initialExpirationDate = $timeZone->date()->modify('+10 day');
49+
$expireDate = $timeZone->formatDateTime(
50+
$initialExpirationDate,
51+
\IntlDateFormatter::MEDIUM,
52+
\IntlDateFormatter::MEDIUM
53+
);
54+
$userExpirationFactory = Bootstrap::getObjectManager()->get(UserExpirationFactory::class);
55+
$userExpiration = $userExpirationFactory->create();
56+
$userExpiration->setExpiresAt($expireDate);
57+
$userExpiration->setUserId($this->getUserId());
58+
$this->userExpirationResource->save($userExpiration);
59+
$loadedUserExpiration = $userExpirationFactory->create();
60+
$this->userExpirationResource->load($loadedUserExpiration, $userExpiration->getId());
61+
62+
self::assertEquals($initialExpirationDate->format('Y-m-d H:i:s'), $loadedUserExpiration->getExpiresAt());
63+
}
64+
65+
/**
66+
* Provides locale codes for validation test.
67+
*
68+
* @return array
69+
*/
70+
public function userExpirationSaveDataProvider(): array
71+
{
72+
return [
73+
'default' => [
74+
'locale_code' => 'en_US',
75+
],
76+
'non_default_english_textual' => [
77+
'locale_code' => 'de_DE',
78+
],
79+
'non_default_non_english_textual' => [
80+
'locale_code' => 'uk_UA',
81+
],
82+
];
83+
}
84+
85+
/**
86+
* Retrieve user id from db.
87+
*
88+
* @return int
89+
*/
90+
private function getUserId(): int
91+
{
92+
$userResource = Bootstrap::getObjectManager()->get(UserResource::class);
93+
$data = $userResource->loadByUsername('dummy_username');
94+
95+
return (int)$data['user_id'];
96+
}
97+
}

0 commit comments

Comments
 (0)