arch-8.x-1.x-dev/modules/cart/src/Cart/Cart.php

modules/cart/src/Cart/Cart.php
<?php

namespace Drupal\arch_cart\Cart;

use Drupal\arch_order\Entity\Order;
use Drupal\arch_price\Price\PriceFactoryInterface;
use Drupal\arch_price\Price\PriceInterface;
use Drupal\arch_shipping\ShippingMethodExtraLineItemInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\currency\Entity\CurrencyInterface;

/**
 * Default cart implementation.
 *
 * @package Drupal\arch_cart\Cart
 */
class Cart implements CartInterface {

  use DependencySerializationTrait;

  /**
   * Store.
   *
   * @var \Drupal\Core\TempStore\PrivateTempStore
   */
  protected $store;

  /**
   * Module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * Price factory.
   *
   * @var \Drupal\arch_price\Price\PriceFactoryInterface
   */
  protected $priceFactory;

  /**
   * Stored values.
   *
   * @var array
   */
  protected $values;

  /**
   * Shipping price.
   *
   * @var \Drupal\arch_price\Price\PriceInterface
   */
  protected $shippingPrice;

  /**
   * Shipping extra.
   *
   * @var \Drupal\arch_price\Price\PriceInterface
   */
  protected $shippingExtra;

  /**
   * Payment fee.
   *
   * @var \Drupal\arch_price\Price\PriceInterface
   */
  protected $paymentFee;

  /**
   * Order entity (Note: a non-saved order only).
   *
   * @var \Drupal\arch_order\Entity\OrderInterface
   */
  protected $order;

  /**
   * Default price values.
   *
   * @var array
   */
  protected $defaultPrice;

  /**
   * Total base values.
   *
   * @var array
   */
  protected $totalBaseValues = [
    'base' => 'net',
    'price_type' => 'default',
    'currency' => NULL,
    'net' => 0,
    'gross' => 0,
    'vat_category' => 'custom',
    'vat_rate' => 0,
    'vat_value' => 0,
    'date_from' => NULL,
    'date_to' => NULL,
  ];

  /**
   * Price type object.
   *
   * @var \Drupal\arch_price\Entity\PriceTypeInterface
   */
  protected $priceType;

  /**
   * Cart constructor.
   *
   * @param \Drupal\Core\TempStore\PrivateTempStore $store
   *   Store.
   */
  public function __construct(PrivateTempStore $store) {
    $this->store = $store;
    $this->readFromStore();
    $this->order = Order::createFromCart($this);
  }

  /**
   * {@inheritdoc}
   */
  public function setValues(array $values) {
    $this->values = $values + [
      'items' => [],
      'messages' => [],
    ];

    $items = [];
    foreach ($this->values['items'] as $item) {
      $key = $item['type'] . '::' . $item['id'];
      if (!isset($items[$key])) {
        $items[$key] = $item;
        continue;
      }

      $items[$key]['quantity'] += $item['quantity'];
    }

    if (count($items) !== count($this->values['items'])) {
      $this->values['items'] = array_values($items);
      $this->updateStore();
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getValues() {
    return $this->values;
  }

  /**
   * Update store.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  protected function updateStore() {
    $this->store->set('cart', $this->values);
  }

  /**
   * Read data from store.
   *
   * @return $this
   */
  protected function readFromStore() {
    $this->setValues((array) $this->store->get('cart'));
    return $this;
  }

  /**
   * Reset cart store.
   *
   * @return $this
   *   This cart instance.
   *
   * @throws \Drupal\Core\TempStore\TempStoreException
   */
  public function resetStore() {
    $this->setValues([]);
    $this->store->set('cart', $this->values);

    return $this;
  }

  /**
   * Build total values from given items.
   *
   * @param array $items
   *   Item list.
   * @param \Drupal\currency\Entity\CurrencyInterface|string $currency
   *   Currency.
   *
   * @return array
   *   Price values.
   */
  protected function buildTotal(array $items, $currency = NULL) {
    $values = $this->totalBaseValues;
    /** @var \Drupal\currency\Entity\CurrencyInterface $currency */
    if (!empty($currency) && is_string($currency)) {
      $currency = $this->loadCurrency($currency);
    }

    if (!($currency instanceof CurrencyInterface)) {
      $currency = NULL;
    }
    else {
      $values['currency'] = $currency->id();
    }

    foreach ($items as $item) {
      $price = NULL;
      if (
        $item['type'] == 'product'
        && ($product = $this->loadProduct($item['id']))
      ) {
        /** @var \Drupal\arch_price\Price\PriceInterface $price */
        $price = $product->getActivePrice();
      }

      if (empty($price)) {
        continue;
      }

      if (empty($currency)) {
        $currency = $price->getCurrency();
        $values['currency'] = $price->getCurrencyId();
      }
      elseif ($price->getCurrencyId() !== $values['currency']) {
        $price = $price->getExchangedPrice($currency);
      }

      $values['net'] += $price->getNetPrice() * $item['quantity'];
      $values['gross'] += $price->getGrossPrice() * $item['quantity'];
      $values['vat_value'] += $price->getVatValue();
    }

    $default_price_values = $this->getDefaultPriceValues();
    if (!isset($values['currency'])) {
      $values['currency'] = $default_price_values['currency'];
    }

    return $values;
  }

  /**
   * {@inheritdoc}
   */
  public function getTotal($currency = NULL) {
    return $this->buildTotal($this->getProducts(), $currency);
  }

  /**
   * {@inheritdoc}
   */
  public function getGrandTotal($currency = NULL) {
    $total = $this->buildTotal($this->getItems(), $currency);

    $shipping_price = $this->getShippingPrice();
    if ($shipping_price->getCurrencyId() !== $total['currency']) {
      $shipping_price = $shipping_price->getExchangedPrice($total['currency']);
    }
    $total['net'] += $shipping_price->getNetPrice();
    $total['gross'] += $shipping_price->getGrossPrice();
    $total['vat_value'] += $shipping_price->getVatValue();

    $shipping_extra = $this->getShippingExtra();
    if (!empty($shipping_extra)) {
      if ($shipping_extra->getCurrencyId() !== $total['currency']) {
        $shipping_extra = $shipping_extra->getExchangedPrice($total['currency']);
      }
      $total['net'] += $shipping_extra->getNetPrice();
      $total['gross'] += $shipping_extra->getGrossPrice();
      $total['vat_value'] += $shipping_extra->getVatValue();
    }

    $payment_fee = $this->getPaymentFee();
    if (!empty($payment_fee)) {
      if ($payment_fee->getCurrencyId() !== $total['currency']) {
        $payment_fee = $payment_fee->getExchangedPrice($total['currency']);
      }
      $total['net'] += $payment_fee->getNetPrice();
      $total['gross'] += $payment_fee->getGrossPrice();
      $total['vat_value'] += $payment_fee->getVatValue();
    }

    $this->moduleHandler->alter('cart_get_gran_total', $total, $this->order);

    return $total;
  }

  /**
   * {@inheritdoc}
   */
  public function getTotalPrice($currency = NULL) {
    return $this->buildPriceInstance($this->getTotal($currency));
  }

  /**
   * {@inheritdoc}
   */
  public function getGrandTotalPrice($currency = NULL) {
    return $this->buildPriceInstance($this->getGrandTotal($currency));
  }

  /**
   * Get values as PriceInterface instance.
   *
   * @param array $values
   *   Price values.
   *
   * @return \Drupal\arch_price\Price\PriceInterface
   *   Price instance.
   */
  protected function buildPriceInstance(array $values) {
    $values += [
      'base' => 'net',
      'vat_rate' => 0,
    ];
    // @todo If $values['vat_category'] == 'custom', then should we step over?
    // @todo Since different vat_categories can cause FAKE vat_rate.
    if (!empty($values['net'])) {
      $values['vat_rate'] = round(($values['gross'] / $values['net']) - 1, 4);
    }
    $values['vat_value'] = $values['gross'] - $values['net'];

    return $this->getPriceFactory()->getInstance($values);
  }

  /**
   * {@inheritdoc}
   */
  public function addMessage($message, $merge = TRUE) {
    $this->values['messages'][] = [
      'message' => $message,
      'merge' => $merge,
    ];
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getMessages() {
    return $this->values['messages'];
  }

  /**
   * {@inheritdoc}
   */
  public function displayMessages($clear = TRUE) {
    $messages = [];
    foreach ($this->values['messages'] as $msg) {
      $key = md5($msg['message']);
      if (!isset($messages[$key])) {
        $messages[$key] = (string) $msg['message'];
        continue;
      }

      if ($msg['merge']) {
        continue;
      }

      $key = Html::getUniqueId($key);
      $messages[$key] = (string) $msg['message'];
    }

    if ($clear) {
      $this->values['messages'] = [];
      $this->updateStore();
    }
    return $messages;
  }

  /**
   * {@inheritdoc}
   */
  public function addItem($item) {
    $key = NULL;
    $existing_quantity = NULL;
    foreach ($this->values['items'] as $i => $value) {
      if (
        $value['type'] == $item['type']
        && $value['id'] == $item['id']
      ) {
        $key = $i;
        $existing_quantity = $value['quantity'];
        break;
      }
    }

    if (isset($key)) {
      return $this->updateItemQuantity($key, $existing_quantity + $item['quantity']);
    }

    $this->values['items'][] = $item;
    $this->onCartUpdate(self::ITEM_NEW, $item, NULL);
    $this->updateStore();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getItems() {
    $items = (array) $this->values['items'];
    return $items;
  }

  /**
   * {@inheritdoc}
   */
  public function hasItem() {
    $items = $this->getItems();
    return !empty($items);
  }

  /**
   * {@inheritdoc}
   */
  public function getProducts() {
    return array_values(array_filter($this->getItems(), function ($item) {
      if (!empty($item['_removed'])) {
        return FALSE;
      }
      if (
        $item['type'] !== 'product'
      ) {
        return FALSE;
      }
      return !empty($item['quantity']) && $item['quantity'] > 0;
    }));
  }

  /**
   * {@inheritdoc}
   */
  public function getCount() {
    return count($this->getProducts());
  }

  /**
   * {@inheritdoc}
   */
  public function hasProduct() {
    $products = $this->getProducts();
    return !empty($products);
  }

  /**
   * {@inheritdoc}
   */
  public function getItem($index) {
    $items = $this->getItems();
    if (!isset($items[$index])) {
      return NULL;
    }
    return !empty($items[$index]['_removed']) ? NULL : $items[$index];
  }

  /**
   * {@inheritdoc}
   */
  public function getItemById($type, $id) {
    foreach ($this->getItems() as $item) {
      if ($item['type'] == $type && $item['id'] == $id) {
        return $item;
      }
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function updateItem($index, $item) {
    if (empty($this->values['items'][$index])) {
      throw new \Exception('Cannot update missing item');
    }

    if (
      empty($item['quantity'])
      || $item['quantity'] <= 0
    ) {
      return $this->removeItem($index);
    }

    $old_item = $this->values['items'][$index];
    $this->values['items'][$index] = $item;
    $this->onCartUpdate(self::ITEM_UPDATE, $item, $old_item);

    $this->updateStore();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function updateItemById($type, $id, $item) {
    $item['id'] = $id;
    $item['type'] = $type;

    $update_index = NULL;
    $old_item = NULL;
    foreach ($this->values['items'] as $index => $line_item) {
      if ($line_item['type'] == $type && $line_item['id'] == $id) {
        $update_index = $index;
        $old_item = $line_item;
        break;
      }
    }

    if (empty($old_item)) {
      throw new \Exception('Cannot update missing item');
    }

    return $this->updateItem($update_index, $item);
  }

  /**
   * {@inheritdoc}
   */
  public function updateItemQuantity($index, $quantity) {
    $old_item = $this->values['items'][$index];
    return $this->updateItemQuantityById($old_item['type'], $old_item['id'], $quantity);
  }

  /**
   * {@inheritdoc}
   */
  public function updateItemQuantityById($type, $id, $quantity) {
    $old_item = $this->getItemById($type, $id);
    if (empty($old_item)) {
      throw new \Exception('Cannot update missing item', 1001);
    }

    if (empty($quantity) || $quantity <= 0) {
      return $this->removeItemById($type, $id);
    }

    $new_item = NULL;
    foreach ($this->values['items'] as $index => $line_item) {
      if ($line_item['type'] == $type && $line_item['id'] == $id) {
        $this->values['items'][$index]['quantity'] = $quantity;
        $new_item = $this->values['items'][$index];
        break;
      }
    }
    $this->onCartUpdate(self::ITEM_UPDATE, $new_item, $old_item);
    $this->updateStore();
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function removeItem($index) {
    $old_item = $this->values['items'][$index];
    return $this->removeItemById($old_item['type'], $old_item['id']);
  }

  /**
   * {@inheritdoc}
   */
  public function removeItemById($type, $id) {
    $old_item = $this->getItemById($type, $id);
    if (empty($old_item)) {
      throw new \Exception('Cannot remove missing item');
    }
    $new_item_list = array_filter($this->values['items'], function ($item) use ($type, $id) {
      return !($item['type'] == $type && $item['id'] == $id);
    });
    $this->values['items'] = array_values($new_item_list);

    $this->onCartUpdate(self::ITEM_REMOVE, NULL, $old_item);
    $this->updateStore();
    return $this;

  }

  /**
   * {@inheritdoc}
   */
  public function &getOrder() {
    return $this->order;
  }

  /**
   * {@inheritdoc}
   */
  public function placeOrder() {
    return $this->order->save();
  }

  /**
   * Load product.
   *
   * @param int $pid
   *   Product ID.
   *
   * @return \Drupal\arch_product\Entity\ProductInterface|null
   *   Product entity.
   */
  protected function loadProduct($pid) {
    try {
      // @codingStandardsIgnoreStart
      $storage = \Drupal::entityTypeManager()->getStorage('product');
      // @codingStandardsIgnoreEnd

      return $storage->load($pid);
    }
    catch (\Exception $e) {
      // @todo handler error.
    }

    return NULL;
  }

  /**
   * Load currency.
   *
   * @param string $currency
   *   Currency ID.
   *
   * @return \Drupal\currency\Entity\CurrencyInterface|null
   *   Product entity.
   */
  protected function loadCurrency($currency) {
    try {
      // @codingStandardsIgnoreStart
      $storage = \Drupal::entityTypeManager()->getStorage('currency');
      // @codingStandardsIgnoreEnd

      return $storage->load($currency);
    }
    catch (\Exception $e) {
      // @todo handler error.
    }

    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function setModuleHandler(ModuleHandlerInterface $module_handler) {
    $this->moduleHandler = $module_handler;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getModuleHandler() {
    if (!$this->moduleHandler) {
      $this->moduleHandler = \Drupal::moduleHandler();
    }
    return $this->moduleHandler;
  }

  /**
   * {@inheritdoc}
   */
  public function setPriceFactory(PriceFactoryInterface $price_factory) {
    $this->priceFactory = $price_factory;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getPriceFactory() {
    if (!$this->priceFactory) {
      $this->priceFactory = \Drupal::service('price_factory');
    }
    return $this->priceFactory;
  }

  /**
   * On cart update.
   *
   * @param string $type
   *   Update type.
   * @param array|null $item
   *   New value.
   * @param array|null $old_item
   *   Old value.
   */
  protected function onCartUpdate($type, $item, $old_item) {
    $this->shippingPrice = NULL;
    $this->getModuleHandler()->invokeAllWith('arch_cart_change', function (
      callable $hook,
      string $module,
    ) use (
      &$type,
      &$item,
      &$old_item,
    ) {
      $hook($type, $item, $old_item, $this->values['items'], $this);
    });

    $hook = NULL;
    switch ($type) {
      case self::ITEM_NEW:
        $hook = 'arch_cart_item_new';
        break;

      case self::ITEM_UPDATE:
        $hook = 'arch_cart_item_update';
        break;

      case self::ITEM_REMOVE:
        $hook = 'arch_cart_item_remove';
        break;
    }

    if ($hook) {
      $this->getModuleHandler()->invokeAllWith($hook, function (
        callable $hook,
        string $module,
      ) use (
        &$item,
        &$old_item
      ) {
        $hook($item, $old_item, $this->values['items'], $this);
      });
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getShippingPrice($force_update = FALSE) {
    if ($this->shippingPrice && !$force_update) {
      return $this->shippingPrice;
    }

    $shipping_method = $this->order->getShippingMethod();
    $hook = ['shipping_price'];
    if (empty($shipping_method)) {
      $shipping_price = $this->getDefaultPriceValues();
    }
    else {
      $hook[] = 'shipping_price_' . $shipping_method->getPluginId();
      $shipping_price = $shipping_method->getShippingPrice($this->order);
    }

    if (is_array($shipping_price)) {
      $shipping_price = $this->getPriceFactory()->getInstance($shipping_price);
    }

    $this->getModuleHandler()->alter($hook, $shipping_price, $this, $this->order);
    if (
      !empty($shipping_price)
      && !($shipping_price instanceof PriceInterface)
    ) {
      throw new \TypeError('Shipping price should be PriceInterface instance!');
    }

    $this->shippingPrice = $shipping_price;
    return $shipping_price;
  }

  /**
   * {@inheritdoc}
   */
  public function getShippingExtra($force_update = FALSE) {
    if ($this->shippingExtra && !$force_update) {
      return $this->shippingExtra;
    }

    $shipping_price = NULL;
    $shipping_method = $this->order->getShippingMethod();
    $hook = ['shipping_extra_price'];
    if (empty($shipping_method)) {
      $shipping_price = $this->getDefaultPriceValues();
    }
    elseif ($shipping_method instanceof ShippingMethodExtraLineItemInterface) {
      $hook[] = 'shipping_extra_price_' . $shipping_method->getPluginId();
      $shipping_price = $shipping_method->getExtraPrice($this->order);
    }

    if (is_array($shipping_price)) {
      $shipping_price = $this->getPriceFactory()->getInstance($shipping_price);
    }

    $this->getModuleHandler()->alter($hook, $shipping_price, $this, $this->order);
    if (
      !empty($shipping_price)
      && !($shipping_price instanceof PriceInterface)
    ) {
      throw new \TypeError('Shipping price should be PriceInterface instance!');
    }

    $this->shippingExtra = $shipping_price;
    return $shipping_price;
  }

  /**
   * {@inheritdoc}
   */
  public function getPaymentFee($force_update = FALSE) {
    if ($this->paymentFee && !$force_update) {
      return $this->paymentFee;
    }

    $payment_method = $this->order->getPaymentMethod();
    if (empty($payment_method)) {
      return NULL;
    }

    $hook = ['payment_method_fee'];
    if (empty($payment_method)) {
      $payment_fee = $this->getDefaultPriceValues();
    }
    else {
      $hook[] = 'payment_method_fee_' . $payment_method->getPluginId();
      $payment_fee = $payment_method->getPaymentFee($this->order);
    }

    if (empty($payment_fee)) {
      $payment_fee = $this->getDefaultPriceValues();
    }

    if (is_array($payment_fee)) {
      $payment_fee = $this->getPriceFactory()->getInstance($payment_fee);
    }

    $payment_method_settings = $payment_method->getSettings();
    $this->getModuleHandler()->alter($hook, $this->order, $payment_fee, $payment_method_settings);
    if (
      !empty($payment_fee)
      && !($payment_fee instanceof PriceInterface)
    ) {
      throw new \TypeError('Payment fee should be PriceInterface instance!');
    }

    $this->paymentFee = $payment_fee;
    return $payment_fee;
  }

  /**
   * {@inheritdoc}
   */
  public function setDefaultPriceValues(array $default_price_values) {
    $this->defaultPrice = $default_price_values;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function setTotalBaseValues(array $total_base_values) {
    $this->totalBaseValues = $total_base_values;
    return $this;
  }

  /**
   * Get default price values.
   *
   * @return array
   *   Default price values.
   */
  protected function getDefaultPriceValues() {
    return ((array) $this->defaultPrice) + [
      'base' => 'gross',
      'price_type' => 'default',
      'currency' => 'EUR',
      'net' => 0,
      'gross' => 0,
      'vat_category' => 'custom',
      'vat_rate' => 0,
      'vat_value' => 0,
      'date_from' => NULL,
      'date_to' => NULL,
    ];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc