Skip to content

Commit 5d60850

Browse files
authored
Merge branch '2.4-develop' into ACPT-1493
2 parents 95a7eb4 + 39d54c2 commit 5d60850

File tree

53 files changed

+1649
-259
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1649
-259
lines changed

app/code/Magento/Catalog/Model/Product/Price/BasePriceStorage.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,11 @@ public function update(array $prices)
157157
{
158158
$prices = $this->retrieveValidPrices($prices);
159159
$formattedPrices = [];
160+
$productIds = [];
160161

161162
foreach ($prices as $price) {
162163
$ids = array_keys($this->productIdLocator->retrieveProductIdsBySkus([$price->getSku()])[$price->getSku()]);
164+
$productIds[] = $ids[key($ids)];
163165
foreach ($ids as $id) {
164166
$formattedPrices[] = [
165167
'store_id' => $price->getStoreId(),
@@ -182,6 +184,7 @@ public function update(array $prices)
182184
}
183185

184186
$this->getPricePersistence()->update($formattedPrices);
187+
$this->getPricePersistence()->updateLastUpdatedAt($productIds);
185188

186189
return $this->validationResult->getFailedItems();
187190
}

app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
1111
use Magento\Catalog\Model\ProductIdLocatorInterface;
1212
use Magento\Catalog\Model\ResourceModel\Attribute;
13+
use Magento\Framework\App\ObjectManager;
1314
use Magento\Framework\EntityManager\MetadataPool;
1415
use Magento\Framework\Exception\CouldNotDeleteException;
1516
use Magento\Framework\Exception\CouldNotSaveException;
17+
use Magento\Framework\Stdlib\DateTime\DateTime;
1618

1719
/**
1820
* Class responsibly for persistence of prices.
21+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1922
*/
2023
class PricePersistence
2124
{
@@ -42,33 +45,40 @@ class PricePersistence
4245
private $productIdLocator;
4346

4447
/**
45-
* Metadata pool.
48+
* Metadata pool property to get a metadata.
4649
*
4750
* @var MetadataPool
4851
*/
4952
private $metadataPool;
5053

5154
/**
52-
* Attribute code.
55+
* Attribute code attribute to get the attribute id.
5356
*
5457
* @var string
5558
*/
5659
private $attributeCode;
5760

5861
/**
59-
* Attribute ID.
62+
* Attribute ID property to store the attribute id.
6063
*
6164
* @var int
6265
*/
6366
private $attributeId;
6467

6568
/**
66-
* Items per operation.
69+
* Items per operation to chunk the array in a batch.
6770
*
6871
* @var int
6972
*/
7073
private $itemsPerOperation = 500;
7174

75+
/**
76+
* Date time property to get the gm date.
77+
*
78+
* @var DateTime
79+
*/
80+
private $dateTime;
81+
7282
/**
7383
* PricePersistence constructor.
7484
*
@@ -77,19 +87,23 @@ class PricePersistence
7787
* @param ProductIdLocatorInterface $productIdLocator
7888
* @param MetadataPool $metadataPool
7989
* @param string $attributeCode
90+
* @param DateTime|null $dateTime
8091
*/
8192
public function __construct(
8293
Attribute $attributeResource,
8394
ProductAttributeRepositoryInterface $attributeRepository,
8495
ProductIdLocatorInterface $productIdLocator,
8596
MetadataPool $metadataPool,
86-
$attributeCode = ''
97+
$attributeCode = '',
98+
?DateTime $dateTime = null
8799
) {
88100
$this->attributeResource = $attributeResource;
89101
$this->attributeRepository = $attributeRepository;
90102
$this->attributeCode = $attributeCode;
91103
$this->productIdLocator = $productIdLocator;
92104
$this->metadataPool = $metadataPool;
105+
$this->dateTime = $dateTime ?: ObjectManager::getInstance()
106+
->get(DateTime::class);
93107
}
94108

95109
/**
@@ -233,4 +247,27 @@ public function getEntityLinkField()
233247
return $this->metadataPool->getMetadata(ProductInterface::class)
234248
->getLinkField();
235249
}
250+
251+
/**
252+
* Update last updated date.
253+
*
254+
* @param array $productIds
255+
* @return void
256+
* @throws CouldNotSaveException
257+
*/
258+
public function updateLastUpdatedAt(array $productIds): void
259+
{
260+
try {
261+
$this->attributeResource->getConnection()->update(
262+
$this->attributeResource->getTable('catalog_product_entity'),
263+
[ProductInterface::UPDATED_AT => $this->dateTime->gmtDate()],
264+
[$this->getEntityLinkField(). ' IN(?)' => $productIds]
265+
);
266+
} catch (\Exception $e) {
267+
throw new CouldNotSaveException(
268+
__("The attribute can't be saved."),
269+
$e
270+
);
271+
}
272+
}
236273
}

app/code/Magento/Catalog/Model/Product/Price/Validation/TierPriceValidator.php

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
use Magento\Framework\Exception\LocalizedException;
1717
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
1818
use Magento\Store\Api\WebsiteRepositoryInterface;
19+
use Magento\Framework\App\ObjectManager;
20+
use Magento\Framework\App\Config\ScopeConfigInterface;
21+
use Magento\Catalog\Helper\Data;
22+
use Magento\Store\Model\ScopeInterface;
23+
use Magento\Framework\Exception\NoSuchEntityException;
1924

2025
/**
2126
* Validate Tier Price and check duplication
@@ -91,6 +96,11 @@ class TierPriceValidator implements ResetAfterRequestInterface
9196
*/
9297
private $productsCacheBySku = [];
9398

99+
/**
100+
* @var ScopeConfigInterface
101+
*/
102+
private $scopeConfig;
103+
94104
/**
95105
* TierPriceValidator constructor.
96106
*
@@ -103,17 +113,20 @@ class TierPriceValidator implements ResetAfterRequestInterface
103113
* @param InvalidSkuProcessor $invalidSkuProcessor
104114
* @param ProductRepositoryInterface $productRepository
105115
* @param array $allowedProductTypes [optional]
116+
* @param ScopeConfigInterface|null $scopeConfig
117+
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
106118
*/
107119
public function __construct(
108-
ProductIdLocatorInterface $productIdLocator,
109-
SearchCriteriaBuilder $searchCriteriaBuilder,
110-
FilterBuilder $filterBuilder,
111-
GroupRepositoryInterface $customerGroupRepository,
112-
WebsiteRepositoryInterface $websiteRepository,
113-
Result $validationResult,
114-
InvalidSkuProcessor $invalidSkuProcessor,
120+
ProductIdLocatorInterface $productIdLocator,
121+
SearchCriteriaBuilder $searchCriteriaBuilder,
122+
FilterBuilder $filterBuilder,
123+
GroupRepositoryInterface $customerGroupRepository,
124+
WebsiteRepositoryInterface $websiteRepository,
125+
Result $validationResult,
126+
InvalidSkuProcessor $invalidSkuProcessor,
115127
ProductRepositoryInterface $productRepository,
116-
array $allowedProductTypes = []
128+
array $allowedProductTypes = [],
129+
?ScopeConfigInterface $scopeConfig = null
117130
) {
118131
$this->productIdLocator = $productIdLocator;
119132
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
@@ -124,6 +137,7 @@ public function __construct(
124137
$this->invalidSkuProcessor = $invalidSkuProcessor;
125138
$this->productRepository = $productRepository;
126139
$this->allowedProductTypes = $allowedProductTypes;
140+
$this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class);
127141
}
128142

129143
/**
@@ -355,10 +369,19 @@ private function checkQuantity(TierPriceInterface $price, $key, Result $validati
355369
* @param Result $validationResult
356370
* @return void
357371
*/
358-
private function checkWebsite(TierPriceInterface $price, $key, Result $validationResult)
372+
private function checkWebsite(TierPriceInterface $price, $key, Result $validationResult): void
359373
{
360374
try {
361375
$this->websiteRepository->getById($price->getWebsiteId());
376+
$isWebsiteScope = $this->scopeConfig
377+
->isSetFlag(
378+
Data::XML_PATH_PRICE_SCOPE,
379+
ScopeInterface::SCOPE_STORE,
380+
ScopeConfigInterface::SCOPE_TYPE_DEFAULT
381+
);
382+
if (!$isWebsiteScope && (int) $this->allWebsitesValue !== $price->getWebsiteId()) {
383+
throw NoSuchEntityException::singleField('website_id', $price->getWebsiteId());
384+
}
362385
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
363386
$validationResult->addFailedItem(
364387
$key,

app/code/Magento/CatalogImportExport/Model/Export/Product.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,11 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
368368
*/
369369
private $stockConfiguration;
370370

371+
/**
372+
* @var array
373+
*/
374+
private array $attributeFrontendTypes = [];
375+
371376
/**
372377
* Product constructor.
373378
*
@@ -1062,7 +1067,7 @@ protected function collectRawData()
10621067

10631068
if ($this->_attributeTypes[$code] == 'datetime') {
10641069
if (in_array($code, $this->dateAttrCodes)
1065-
|| in_array($code, $this->userDefinedAttributes)
1070+
|| $this->attributeFrontendTypes[$code] === 'date'
10661071
) {
10671072
$attrValue = $this->_localeDate->formatDateTime(
10681073
new \DateTime($attrValue),
@@ -1657,6 +1662,7 @@ protected function initAttributes()
16571662
$this->_attributeValues[$attribute->getAttributeCode()] = $this->getAttributeOptions($attribute);
16581663
$this->_attributeTypes[$attribute->getAttributeCode()] =
16591664
\Magento\ImportExport\Model\Import::getAttributeType($attribute);
1665+
$this->attributeFrontendTypes[$attribute->getAttributeCode()] = $attribute->getFrontendInput();
16601666
if ($attribute->getIsUserDefined()) {
16611667
$this->userDefinedAttributes[] = $attribute->getAttributeCode();
16621668
}

app/code/Magento/CatalogImportExport/Model/Import/Product.php

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,18 +2055,26 @@ private function saveProductAttributesPhase(
20552055
$backModel = $attribute->getBackendModel();
20562056
$attrTable = $attribute->getBackend()->getTable();
20572057
$storeIds = [0];
2058-
if ('datetime' == $attribute->getBackendType()
2059-
&& (
2060-
in_array($attribute->getAttributeCode(), $this->dateAttrCodes)
2061-
|| $attribute->getIsUserDefined()
2062-
)
2063-
) {
2064-
$attrValue = $this->dateTime->formatDate($attrValue, false);
2065-
} elseif ('datetime' == $attribute->getBackendType() && strtotime($attrValue)) {
2066-
$attrValue = gmdate(
2067-
'Y-m-d H:i:s',
2068-
$this->_localeDate->date($attrValue)->getTimestamp()
2069-
);
2058+
if ('datetime' == $attribute->getBackendType()) {
2059+
$attrValue = trim((string) $attrValue);
2060+
if (!empty($attrValue)) {
2061+
$timezone = new \DateTimeZone($this->_localeDate->getConfigTimezone());
2062+
// Parse date from format Y-m-d[ H:i:s]
2063+
$date = date_create_from_format(DateTime::DATETIME_PHP_FORMAT, $attrValue, $timezone)
2064+
?: date_create_from_format(DateTime::DATE_PHP_FORMAT, $attrValue, $timezone);
2065+
// Perhaps, date is formatted according to user locale. For example, dates in exported csv file
2066+
$date = $date ?: $this->_localeDate->date($attrValue);
2067+
if ($attribute->getFrontendInput() === 'date'
2068+
|| in_array($attribute->getAttributeCode(), $this->dateAttrCodes)
2069+
) {
2070+
$date->setTime(0, 0);
2071+
} else {
2072+
$date->setTimezone(new \DateTimeZone($this->_localeDate->getDefaultTimezone()));
2073+
}
2074+
$attrValue = $date->format(DateTime::DATETIME_PHP_FORMAT);
2075+
} else {
2076+
$attrValue = null;
2077+
}
20702078
} elseif ($backModel) {
20712079
$attribute->getBackend()->beforeSave($product);
20722080
$attrValue = $product->getData($attribute->getAttributeCode());

app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
namespace Magento\CatalogImportExport\Model\Import\Product;
77

88
use Magento\CatalogImportExport\Model\Import\Product;
9+
use Magento\Framework\App\ObjectManager;
10+
use Magento\Framework\Stdlib\DateTime;
11+
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
912
use Magento\Framework\Validator\AbstractValidator;
1013
use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
1114

@@ -49,15 +52,24 @@ class Validator extends AbstractValidator implements RowValidatorInterface
4952
protected $invalidAttribute;
5053

5154
/**
52-
* @param \Magento\Framework\Stdlib\StringUtils $string
55+
* @var TimezoneInterface
56+
*/
57+
private $localeDate;
58+
59+
/**
60+
* @param StringUtils $string
5361
* @param RowValidatorInterface[] $validators
62+
* @param TimezoneInterface|null $localeDate
5463
*/
5564
public function __construct(
5665
\Magento\Framework\Stdlib\StringUtils $string,
57-
$validators = []
66+
$validators = [],
67+
?TimezoneInterface $localeDate = null
5868
) {
5969
$this->string = $string;
6070
$this->validators = $validators;
71+
$this->localeDate = $localeDate ?: ObjectManager::getInstance()
72+
->get(TimezoneInterface::class);
6173
}
6274

6375
/**
@@ -302,7 +314,16 @@ private function validateMultiselect(string $attrCode, array $options, array|str
302314
private function validateDateTime(string $rowData): bool
303315
{
304316
$val = trim($rowData);
305-
$valid = strtotime($val) !== false;
317+
try {
318+
if (!date_create_from_format(DateTime::DATETIME_PHP_FORMAT, $val)
319+
&& !date_create_from_format(DateTime::DATE_PHP_FORMAT, $val)
320+
) {
321+
$this->localeDate->date($val);
322+
}
323+
$valid = true;
324+
} catch (\Exception $e) {
325+
$valid = false;
326+
}
306327
if (!$valid) {
307328
$this->_addMessages([RowValidatorInterface::ERROR_INVALID_ATTRIBUTE_TYPE]);
308329
}

app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
use PHPUnit\Framework\MockObject\MockObject;
1919
use PHPUnit\Framework\TestCase;
2020

21+
/**
22+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
23+
*/
2124
class ValidatorTest extends TestCase
2225
{
2326
/** @var Validator */
@@ -62,13 +65,19 @@ protected function setUp(): void
6265
);
6366

6467
$this->validators = [$this->validatorOne, $this->validatorTwo];
65-
$this->objectManagerHelper = new ObjectManagerHelper($this);
66-
$this->validator = $this->objectManagerHelper->getObject(
67-
Validator::class,
68-
[
69-
'string' => new StringUtils(),
70-
'validators' => $this->validators
71-
]
68+
$timezone = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
69+
$timezone->expects($this->any())
70+
->method('date')
71+
->willReturnCallback(
72+
function ($date = null) {
73+
return new \DateTime($date);
74+
}
75+
);
76+
77+
$this->validator = new Validator(
78+
new StringUtils(),
79+
$this->validators,
80+
$timezone
7281
);
7382
$this->validator->init($this->context);
7483
}

0 commit comments

Comments
 (0)