arch-8.x-1.x-dev/modules/price/src/Negotiation/PriceNegotiation.php
modules/price/src/Negotiation/PriceNegotiation.php
<?php
namespace Drupal\arch_price\Negotiation;
use Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface;
use Drupal\arch_price\Price\PriceFactoryInterface;
use Drupal\arch_product\Entity\ProductInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Price negotiation.
*
* @package Drupal\arch_price\Negotiation
*/
class PriceNegotiation implements PriceNegotiationInterface, ContainerInjectionInterface {
/**
* Current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Price factory.
*
* @var \Drupal\arch_price\Price\PriceFactoryInterface
*/
protected $priceFactory;
/**
* PriceNegotiation constructor.
*
* @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
* @param \Drupal\arch_price\Price\PriceFactoryInterface $price_factory
* Price factory.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* Module handler.
*/
public function __construct(
AccountInterface $current_user,
PriceFactoryInterface $price_factory,
ModuleHandlerInterface $module_handler,
) {
$this->currentUser = $current_user;
$this->moduleHandler = $module_handler;
$this->priceFactory = $price_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('current_user'),
$container->get('price_factory'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getProductPrices(ProductInterface $product) {
/** @var \Drupal\arch_price\Plugin\Field\FieldType\PriceFieldItemList $price_field */
$price_field = $product->get('price');
return $price_field->getPriceList();
}
/**
* {@inheritdoc}
*/
public function getAvailablePrices(ProductInterface $product, ?AccountInterface $account = NULL) {
if (!isset($account)) {
$account = $this->currentUser;
}
$prices = array_filter($this->getProductPrices($product), function (PriceItemInterface $item) use ($product, $account) {
return $this->filterAvailablePrices($item, $product, $account);
});
$this->moduleHandler->alter('product_available_prices', $prices, $product, $account);
return $prices;
}
/**
* {@inheritdoc}
*/
public function getActivePrice(ProductInterface $product, ?AccountInterface $account = NULL) {
if (!isset($account)) {
$account = $this->currentUser;
}
$price_list = $this->getPriceList($product, $account);
$prices = array_map(function ($item) {
/** @var \Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface $item */
return $item->toPrice();
}, $price_list);
if (empty($prices)) {
$prices = $this->getDefaultPrices($product, $account);
}
/** @var \Drupal\arch_price\Price\PriceInterface $price */
$price = current($prices);
$context = [
'account' => $account,
'product' => $product,
'prices' => $prices,
];
$this->moduleHandler->alter('product_active_price', $price, $context);
return $price;
}
/**
* {@inheritdoc}
*/
public function getOriginalPrice(ProductInterface $product, ?AccountInterface $account = NULL) {
if (!isset($account)) {
$account = $this->currentUser;
}
$price_list = [];
$available_prices = array_filter($this->getProductPrices($product), function (PriceItemInterface $item) use ($product, $account) {
return $this->filterAvailablePrices($item, $product, $account);
});
$price_list[] = current($available_prices);
$prices = array_map(function ($item) {
/** @var \Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface $item */
return $item->toPrice();
}, $price_list);
if (empty($prices)) {
$prices = $this->getDefaultPrices($product, $account);
}
/** @var \Drupal\arch_price\Price\PriceInterface $price */
$price = current($prices);
$context = [
'account' => $account,
'product' => $product,
'prices' => $available_prices,
];
$this->moduleHandler->alter('product_original_price', $price, $context);
return $price;
}
/**
* Get price list.
*
* @param \Drupal\arch_product\Entity\ProductInterface $product
* Product entity.
* @param \Drupal\Core\Session\AccountInterface $account
* Customer.
*
* @return \Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface[]
* List of available prices.
*/
protected function getPriceList(ProductInterface $product, AccountInterface $account) {
// Group prices by type.
$prices_by_type = [];
$available_prices = $this->getAvailablePrices($product, $account);
foreach ($available_prices as $delta => $item) {
/** @var \Drupal\arch_price\Plugin\Field\FieldType\PriceItem $item */
$prices_by_type[$item->getPriceTypeId()][$delta] = $item;
}
// Filter multiple prices by type.
$price_list = [];
foreach ($prices_by_type as $price_items) {
if (count($price_items) > 1) {
uasort($price_items, [$this, 'comparePriceItems']);
reset($price_items);
}
$key = key($price_items);
$price_list[$key] = $price_items[$key];
}
ksort($price_list);
$this->moduleHandler->alter('price_negotiation_prices', $price_list, $product, $account);
return $price_list;
}
/**
* Get default prices.
*
* @param \Drupal\arch_product\Entity\ProductInterface $product
* Product entity.
* @param \Drupal\Core\Session\AccountInterface $account
* Account.
*
* @return \Drupal\arch_price\Price\PriceInterface[]
* Price list.
*/
protected function getDefaultPrices(ProductInterface $product, AccountInterface $account) {
$prices = [
$this->priceFactory->getMissingPriceInstance(),
];
$this->moduleHandler->alter('price_negotiation_empty_price_list', $prices, $product, $account);
return $prices;
}
/**
* List filter callback.
*
* @param \Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface $item
* Price item.
* @param \Drupal\arch_product\Entity\ProductInterface $product
* Product.
* @param \Drupal\Core\Session\AccountInterface $account
* User.
*
* @return bool
* Return TRUE if given price is available for given user.
*/
protected function filterAvailablePrices(PriceItemInterface $item, ProductInterface $product, AccountInterface $account) {
// Exclude if price is not available at this time.
if (!$item->isAvailable()) {
return FALSE;
}
$priceType = $item->getPriceType();
if (empty($priceType)) {
return FALSE;
}
// Exclude if type of price is not available.
$type_access = $priceType->access('view', $account, TRUE);
if ($type_access->isForbidden()) {
return FALSE;
}
// Get modules response.
$result = AccessResult::allowedIf($item->isAvailable());
$access_results = $this->moduleHandler->invokeAll('price_access', [
$item,
$product,
$account,
]);
foreach ($access_results as $access) {
$result->andIf($access);
}
// Allow only if not forbidden.
return !$result->isForbidden();
}
/**
* Uasort callback.
*
* @param \Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface $a
* Price A.
* @param \Drupal\arch_price\Plugin\Field\FieldType\PriceItemInterface $b
* Price B.
*
* @return int
* Compare result.
*/
protected static function comparePriceItems(PriceItemInterface $a, PriceItemInterface $b) {
$a_full_limit = $a->getAvailableFrom() && $a->getAvailableTo();
$b_full_limit = $b->getAvailableFrom() && $b->getAvailableTo();
if ($a_full_limit && !$b_full_limit) {
return -1;
}
if (!$a_full_limit && $b_full_limit) {
return 1;
}
$a_default = !$a->getAvailableFrom() && !$a->getAvailableTo();
$b_default = !$b->getAvailableFrom() && !$b->getAvailableTo();
if ($a_default && !$b_default) {
return 1;
}
if (!$a_default && $b_default) {
return -1;
}
if ($a->getAvailableFrom() !== $b->getAvailableFrom()) {
return $a->getAvailableFrom() < $b->getAvailableFrom() ? -1 : 1;
}
if ($a->getAvailableTo() !== $b->getAvailableTo()) {
return $a->getAvailableTo() < $b->getAvailableTo() ? -1 : 1;
}
return 0;
}
}
