JEMBOT MAWOT Bypass Shell
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
declare(strict_types=1);
namespace PrestaShop\PrestaShop\Adapter\Product\Update;
use PrestaShop\PrestaShop\Adapter\Product\Combination\Repository\CombinationRepository;
use PrestaShop\PrestaShop\Adapter\Product\Repository\ProductRepository;
use PrestaShop\PrestaShop\Adapter\Product\Repository\ProductSupplierRepository;
use PrestaShop\PrestaShop\Adapter\Supplier\Repository\SupplierRepository;
use PrestaShop\PrestaShop\Core\Domain\Product\Combination\Exception\CannotUpdateCombinationException;
use PrestaShop\PrestaShop\Core\Domain\Product\Combination\ValueObject\CombinationId;
use PrestaShop\PrestaShop\Core\Domain\Product\Combination\ValueObject\CombinationIdInterface;
use PrestaShop\PrestaShop\Core\Domain\Product\Combination\ValueObject\NoCombinationId;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\CannotUpdateProductException;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\InvalidProductTypeException;
use PrestaShop\PrestaShop\Core\Domain\Product\Supplier\Exception\ProductSupplierNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\Product\Supplier\ValueObject\ProductSupplierAssociation;
use PrestaShop\PrestaShop\Core\Domain\Product\Supplier\ValueObject\ProductSupplierId;
use PrestaShop\PrestaShop\Core\Domain\Product\ValueObject\ProductId;
use PrestaShop\PrestaShop\Core\Domain\Product\ValueObject\ProductType;
use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint;
use PrestaShop\PrestaShop\Core\Domain\Supplier\ValueObject\SupplierId;
use PrestaShop\PrestaShop\Core\Exception\InvalidArgumentException;
use Product;
use ProductSupplier;
/**
* Updates product supplier relation
*/
class ProductSupplierUpdater
{
/**
* @var ProductRepository
*/
private $productRepository;
/**
* @var CombinationRepository
*/
private $combinationRepository;
/**
* @var SupplierRepository
*/
private $supplierRepository;
/**
* @var ProductSupplierRepository
*/
private $productSupplierRepository;
/**
* @var int
*/
private $defaultCurrencyId;
/**
* @param ProductRepository $productRepository
* @param CombinationRepository $combinationRepository
* @param SupplierRepository $supplierRepository
* @param ProductSupplierRepository $productSupplierRepository
* @param int $defaultCurrencyId
*/
public function __construct(
ProductRepository $productRepository,
CombinationRepository $combinationRepository,
SupplierRepository $supplierRepository,
ProductSupplierRepository $productSupplierRepository,
int $defaultCurrencyId
) {
$this->supplierRepository = $supplierRepository;
$this->productSupplierRepository = $productSupplierRepository;
$this->combinationRepository = $combinationRepository;
$this->defaultCurrencyId = $defaultCurrencyId;
$this->productRepository = $productRepository;
}
/**
* Apply associations between a product and provided suppliers. If association that don't match the provided suppliers
* exist they are removed. If some associations are missing they are created with empty values. Existing and valid
* association are not modified. If the product has combination the association is created for all of them.
*
* @param ProductId $productId
* @param SupplierId[] $supplierIds
*
* @return ProductSupplierAssociation[]
*/
public function associateSuppliers(ProductId $productId, array $supplierIds): array
{
if (empty($supplierIds)) {
throw new InvalidArgumentException('Provided empty list of suppliers to associate');
}
// First check that all suppliers exist
foreach ($supplierIds as $supplierId) {
$this->supplierRepository->assertSupplierExists($supplierId);
}
// We get useless IDs and perform a bulk delete, we don't clean via a direct query even if it would be faster
//because we need hook executed on each deleted ProductSupplier instance
$uselessProductSupplierIds = $this->productSupplierRepository->getUselessProductSupplierIds($productId, $supplierIds);
$this->productSupplierRepository->bulkDelete($uselessProductSupplierIds);
// Get list of combinations for product, or NoCombination for products without association
$productType = $this->productRepository->getProductType($productId);
// We should always create an association not related to a combination
$combinationIds = [new NoCombinationId()];
if ($productType->getValue() === ProductType::TYPE_COMBINATIONS) {
$combinationIds = array_merge($combinationIds, $this->combinationRepository->getCombinationIds(
$productId,
ShopConstraint::allShops()
));
}
// Now we search for each associated supplier if some associations are missing
$allAssociations = [];
foreach ($supplierIds as $supplierId) {
$supplierAssociations = $this->productSupplierRepository->getAssociationsForSupplier($productId, $supplierId);
// Loop through all combinations to check if they have a matching association if not it will need to be created
foreach ($combinationIds as $combinationId) {
// Search matching association by combination, if none is found the association is missing
$matchingAssociations = array_filter($supplierAssociations, function (ProductSupplierAssociation $association) use ($combinationId) {
return $association->getCombinationId()->getValue() === $combinationId->getValue();
});
if (empty($matchingAssociations)) {
$productSupplier = new ProductSupplier();
$productSupplier->id_product = $productId->getValue();
$productSupplier->id_product_attribute = $combinationId->getValue();
$productSupplier->id_supplier = $supplierId->getValue();
$productSupplier->id_currency = $this->defaultCurrencyId;
$this->productSupplierRepository->add($productSupplier);
$allAssociations[] = new ProductSupplierAssociation(
$productId->getValue(),
$combinationId->getValue(),
$supplierId->getValue(),
(int) $productSupplier->id
);
} else {
// We must use reset as the returned array is a filtered one so the first index is not necessarily 0
$allAssociations[] = reset($matchingAssociations);
}
}
}
// Check if product has a default supplier if not we must define it
$defaultSupplierId = $this->productSupplierRepository->getDefaultSupplierId($productId);
if (null === $defaultSupplierId) {
// We use the first created association by default
$firstAssociation = $allAssociations[0];
// Update the default supplier for products, and potentially all its combinations
$this->updateProductDefaultSupplier($productId, $firstAssociation->getSupplierId());
}
return $allAssociations;
}
/**
* When new combinations are created some product supplier are absent, so we get associated suppliers and associate
* them again, it will only created the missing ones without modifying the existing ones.
*
* @param ProductId $productId
*
* @return ProductSupplierAssociation[]
*/
public function updateMissingProductSuppliers(ProductId $productId): array
{
$supplierIds = $this->productSupplierRepository->getAssociatedSupplierIds($productId);
if (empty($supplierIds)) {
return [];
}
return $this->associateSuppliers($productId, $supplierIds);
}
/**
* @param ProductId $productId
* @param array<int, ProductSupplier> $productSuppliers
*
* @return array<int, ProductSupplierAssociation>
*/
public function updateSuppliersForProduct(
ProductId $productId,
array $productSuppliers
): array {
$product = $this->productRepository->getProductByDefaultShop($productId);
if ($product->getProductType() === ProductType::TYPE_COMBINATIONS) {
$this->throwInvalidTypeException($productId, 'setCombinationSuppliers');
}
return $this->updateProductSuppliers($productId, $productSuppliers, new NoCombinationId());
}
/**
* @param ProductId $productId
* @param CombinationId $combinationId
* @param array<int, ProductSupplier> $productSuppliers
*
* @return array<int, ProductSupplierAssociation>
*/
public function updateSuppliersForCombination(
ProductId $productId,
CombinationId $combinationId,
array $productSuppliers
): array {
return $this->updateProductSuppliers($productId, $productSuppliers, $combinationId);
}
/**
* Removes all product suppliers associated to specified product without combinations
*
* @param ProductId $productId
*/
public function removeAllForProduct(ProductId $productId): void
{
$product = $this->productRepository->getProductByDefaultShop($productId);
$productSupplierIds = $this->getProductSupplierIds($productId);
$this->productSupplierRepository->bulkDelete($productSupplierIds);
$this->resetDefaultSupplier($product);
}
/**
* When the default supplier is changed we must update all the synced field for the product and all its combinations.
*
* @param ProductId $productId
* @param SupplierId $defaultSupplierId
*/
public function updateProductDefaultSupplier(ProductId $productId, SupplierId $defaultSupplierId): void
{
$productType = $this->productRepository->getProductType($productId);
if ($productType->getValue() === ProductType::TYPE_COMBINATIONS) {
// Product must always be updated even for product with combinations, we use the default combination as the reference
$defaultProductSupplier = $this->getDefaultCombinationProductSupplier($productId, $defaultSupplierId);
if (!$defaultProductSupplier) {
// When no combinations exist yet we use the default ProductSupplier as a fallback
$defaultProductSupplier = $this->getDefaultProductSupplier($productId, $defaultSupplierId);
}
$this->updateDefaultSupplierDataForProduct($defaultProductSupplier);
// Then each combination must be updated based on its data for default supplier (which may be different for each one)
$associations = $this->productSupplierRepository->getAssociationsForSupplier($productId, $defaultSupplierId);
foreach ($associations as $association) {
if ($association->getCombinationId() instanceof CombinationId) {
$defaultCombinationSupplier = $this->productSupplierRepository->getByAssociation($association);
$this->updateDefaultSupplierDataForCombination($defaultCombinationSupplier);
}
}
} else {
// For products without combinations only one association is possible
$defaultProductSupplier = $this->getDefaultProductSupplier($productId, $defaultSupplierId);
$this->updateDefaultSupplierDataForProduct($defaultProductSupplier);
}
}
/**
* @param ProductId[] $productIds
*/
public function resetSupplierAssociations(array $productIds): void
{
foreach ($productIds as $productId) {
$suppliers = $this->productSupplierRepository->getAssociatedSupplierIds($productId);
if (!empty($suppliers)) {
$this->associateSuppliers($productId, $suppliers);
} else {
$this->resetDefaultSupplier($this->productRepository->getProductByDefaultShop($productId));
}
}
}
/**
* @param ProductId $productId
* @param array<int, ProductSupplier> $productSuppliers
* @param CombinationIdInterface $combinationId
*
* @return ProductSupplierAssociation[]
*/
private function updateProductSuppliers(ProductId $productId, array $productSuppliers, CombinationIdInterface $combinationId): array
{
$updatedAssociations = [];
$defaultSupplierId = $this->productSupplierRepository->getDefaultSupplierId($productId);
/** @var ProductSupplier|null $updatedDefaultSupplier */
$updatedDefaultSupplier = null;
foreach ($productSuppliers as $productSupplier) {
if (!$productSupplier->id) {
throw new ProductSupplierNotFoundException(sprintf(
'Trying to update a nonexistent ProductSupplier for product #%d', $productId->getValue()
));
}
$this->productSupplierRepository->update($productSupplier);
if ($defaultSupplierId->getValue() === (int) $productSupplier->id_supplier) {
$updatedDefaultSupplier = $productSupplier;
}
$updatedAssociations[] = new ProductSupplierAssociation(
$productId->getValue(),
$combinationId->getValue(),
(int) $productSupplier->id_supplier,
(int) $productSupplier->id
);
}
// If product supplier associated to default supplier was updated we need to update the product's default supplier related data
if (null !== $updatedDefaultSupplier) {
// CombinationInterface is either a CombinationId (identified combination) or a NoCombinationId (no combination for standard product)
// So if $combinationId is a CombinationId we are updating a combination which also needs its default data to be updated
if ($combinationId instanceof CombinationId) {
$this->updateDefaultSupplierDataForCombination($updatedDefaultSupplier);
$this->updateDefaultSupplierDataForProduct($updatedDefaultSupplier);
} elseif ($combinationId instanceof NoCombinationId) {
$this->updateDefaultSupplierDataForProduct($updatedDefaultSupplier);
}
}
return $updatedAssociations;
}
/**
* Update the default data for combination since it has two fields that must be synced with the supplier
*
* @param ProductSupplier $defaultCombinationSupplier
*/
private function updateDefaultSupplierDataForCombination(ProductSupplier $defaultCombinationSupplier): void
{
$shopConstraint = ShopConstraint::allShops();
$combination = $this->combinationRepository->getByShopConstraint(
new CombinationId((int) $defaultCombinationSupplier->id_product_attribute),
$shopConstraint
);
$combination->supplier_reference = $defaultCombinationSupplier->product_supplier_reference;
$this->combinationRepository->partialUpdate(
$combination,
['supplier_reference'],
$shopConstraint,
CannotUpdateCombinationException::FAILED_UPDATE_DEFAULT_SUPPLIER_DATA
);
}
/**
* Update the default data for product since it has two fields that must be synced with the supplier, and most importantly
* it is the one saving the default supplier association
*
* @param ProductSupplier $defaultProductSupplier
*/
private function updateDefaultSupplierDataForProduct(ProductSupplier $defaultProductSupplier): void
{
$product = $this->productRepository->getProductByDefaultShop(new ProductId((int) $defaultProductSupplier->id_product));
$product->supplier_reference = $defaultProductSupplier->product_supplier_reference;
$product->id_supplier = (int) $defaultProductSupplier->id_supplier;
$this->productRepository->partialUpdate(
$product,
['supplier_reference', 'id_supplier'],
ShopConstraint::allShops(),
CannotUpdateProductException::FAILED_UPDATE_DEFAULT_SUPPLIER
);
}
/**
* Find the ProductSupplier associated to the default combination of a product.
*
* @param ProductId $productId
* @param SupplierId $supplierId
*
* @return ProductSupplier|null
*/
private function getDefaultCombinationProductSupplier(ProductId $productId, SupplierId $supplierId): ?ProductSupplier
{
$defaultCombinationId = $this->combinationRepository->findDefaultCombinationIdForShop(
$productId,
$this->productRepository->getProductDefaultShopId($productId)
);
if (!$defaultCombinationId) {
return null;
}
return $this->productSupplierRepository->getByAssociation(new ProductSupplierAssociation(
$productId->getValue(),
$defaultCombinationId->getValue(),
$supplierId->getValue()
));
}
/**
* Return the default ProductSupplier instance of a product, the one not associated to any combination.
*
* @param ProductId $productId
* @param SupplierId $supplierId
*
* @return ProductSupplier
*
* @throws ProductSupplierNotFoundException
*/
private function getDefaultProductSupplier(ProductId $productId, SupplierId $supplierId): ProductSupplier
{
return $this->productSupplierRepository->getByAssociation(new ProductSupplierAssociation(
$productId->getValue(),
NoCombinationId::NO_COMBINATION_ID,
$supplierId->getValue()
));
}
/**
* @param Product $product
*/
private function resetDefaultSupplier(Product $product): void
{
$product->supplier_reference = '';
$product->id_supplier = 0;
$this->productRepository->partialUpdate(
$product,
['supplier_reference', 'id_supplier'],
ShopConstraint::allShops(),
CannotUpdateProductException::FAILED_UPDATE_DEFAULT_SUPPLIER
);
}
/**
* @param ProductId $productId
* @param CombinationIdInterface|null $combinationId
*
* @return array<int, ProductSupplierId>
*/
private function getProductSupplierIds(ProductId $productId, ?CombinationIdInterface $combinationId = null): array
{
return array_map(function (array $currentSupplier): ProductSupplierId {
return new ProductSupplierId((int) $currentSupplier['id_product_supplier']);
}, $this->productSupplierRepository->getProductSuppliersInfo($productId, $combinationId));
}
/**
* @param ProductId $productId
*
* @throws InvalidProductTypeException
*/
private function throwInvalidTypeException(ProductId $productId, string $appropriateMethod): void
{
throw new InvalidProductTypeException(
InvalidProductTypeException::EXPECTED_NO_COMBINATIONS_TYPE,
sprintf(
'Product #%d has combinations. Use %s::%s to set product suppliers for specified combination',
$productId->getValue(),
self::class,
$appropriateMethod
));
}
}
xxxxx1.0, XXX xxxx