vendor/isotope/isotope-core/system/modules/isotope/library/Isotope/Isotope.php line 79

Open in your IDE?
  1. <?php
  2. /*
  3.  * Isotope eCommerce for Contao Open Source CMS
  4.  *
  5.  * Copyright (C) 2009 - 2019 terminal42 gmbh & Isotope eCommerce Workgroup
  6.  *
  7.  * @link       https://isotopeecommerce.org
  8.  * @license    https://opensource.org/licenses/lgpl-3.0.html
  9.  */
  10. namespace Isotope;
  11. use Contao\Controller;
  12. use Contao\Environment;
  13. use Contao\Input;
  14. use Contao\System;
  15. use Contao\Widget;
  16. use Haste\Data\Plain;
  17. use Haste\Util\Format;
  18. use Isotope\Frontend\ProductAction\CartAction;
  19. use Isotope\Frontend\ProductAction\FavoriteAction;
  20. use Isotope\Frontend\ProductAction\ProductActionInterface;
  21. use Isotope\Frontend\ProductAction\UpdateAction;
  22. use Isotope\Interfaces\IsotopeAttributeWithOptions;
  23. use Isotope\Interfaces\IsotopeProduct;
  24. use Isotope\Model\Config;
  25. use Isotope\Model\Product;
  26. use Isotope\Model\ProductCollection\Cart;
  27. use Isotope\Model\ProductCollection\Favorites;
  28. use Isotope\Model\ProductPrice;
  29. use Isotope\Model\RequestCache;
  30. use Isotope\Model\TaxClass;
  31. /**
  32.  * The base class for all Isotope components.
  33.  */
  34. class Isotope extends Controller
  35. {
  36.     /**
  37.      * Isotope version
  38.      */
  39.     const VERSION '2.8.8';
  40.     /**
  41.      * True if the system has been initialized
  42.      * @var bool
  43.      */
  44.     protected static $blnInitialized false;
  45.     /**
  46.      * Current cart instance
  47.      * @var Cart
  48.      */
  49.     protected static $objCart;
  50.     /**
  51.      * Current config instance
  52.      * @var Config
  53.      */
  54.     protected static $objConfig;
  55.     /**
  56.      * Current request cache instance
  57.      * @var RequestCache
  58.      */
  59.     protected static $objRequestCache;
  60.     public static function initialize()
  61.     {
  62.         if (static::$blnInitialized === false) {
  63.             static::$blnInitialized true;
  64.             // Make sure field data is available
  65.             Controller::loadDataContainer('tl_iso_product');
  66.             System::loadLanguageFile('tl_iso_product');
  67.             // Initialize request cache for product list filters
  68.             if (Input::get('isorc') != '') {
  69.                 if (static::getRequestCache()->isEmpty()) {
  70.                     global $objPage;
  71.                     $objPage->noSearch 1;
  72.                 } elseif (static::getRequestCache()->id != Input::get('isorc')) {
  73.                     unset($_GET['isorc']);
  74.                     // Unset the language parameter
  75.                     if ($GLOBALS['TL_CONFIG']['addLanguageToUrl']) {
  76.                         unset($_GET['language']);
  77.                     }
  78.                     $strQuery http_build_query($_GET);
  79.                     Controller::redirect(
  80.                         preg_replace(
  81.                             '/\?.*$/i',
  82.                             '',
  83.                             Environment::get('request') . ($strQuery '?' $strQuery '')
  84.                         )
  85.                     );
  86.                 }
  87.             }
  88.         }
  89.     }
  90.     /**
  91.      * Get the currently active Isotope cart
  92.      *
  93.      * @return Cart|null
  94.      */
  95.     public static function getCart()
  96.     {
  97.         if (null === static::$objCart && 'FE' === TL_MODE) {
  98.             static::initialize();
  99.             if ((static::$objCart Cart::findForCurrentStore()) !== null) {
  100.                 static::$objCart->mergeGuestCart();
  101.             }
  102.         }
  103.         return static::$objCart;
  104.     }
  105.     /**
  106.      * Gets the favorites collection for the currently logged in user
  107.      *
  108.      * @return Favorites|null
  109.      */
  110.     public static function getFavorites()
  111.     {
  112.         return Favorites::findForCurrentStore();
  113.     }
  114.     /**
  115.      * Set the currently active Isotope cart
  116.      *
  117.      * @param Cart $objCart
  118.      */
  119.     public static function setCart(Cart $objCart)
  120.     {
  121.         static::$objCart $objCart;
  122.     }
  123.     /**
  124.      * Get the currently active Isotope configuration
  125.      *
  126.      * @return Config
  127.      */
  128.     public static function getConfig()
  129.     {
  130.         if (null === static::$objConfig) {
  131.             static::initialize();
  132.             if (($objCart = static::getCart()) !== null) {
  133.                 static::$objConfig Config::findByPk($objCart->config_id);
  134.             }
  135.             // If cart was null or still did not find a config
  136.             if (null === static::$objConfig) {
  137.                 global $objPage;
  138.                 static::$objConfig = ('FE' === TL_MODE Config::findByRootPageOrFallback($objPage->rootId) : Config::findByFallback());
  139.             }
  140.             // No config at all, create empty model as fallback
  141.             if (null === static::$objConfig) {
  142.                 static::$objConfig = new Config();
  143.                 trigger_error($GLOBALS['TL_LANG']['ERR']['noDefaultStoreConfiguration']);
  144.             }
  145.         }
  146.         return static::$objConfig;
  147.     }
  148.     /**
  149.      * Set the currently active Isotope configuration
  150.      *
  151.      * @param Config $objConfig
  152.      */
  153.     public static function setConfig(Config $objConfig null)
  154.     {
  155.         static::$objConfig $objConfig;
  156.     }
  157.     /**
  158.      * Get active request cache
  159.      *
  160.      * @return RequestCache
  161.      */
  162.     public static function getRequestCache()
  163.     {
  164.         $cart = static::getCart();
  165.         // The system has not been initialized yet, return a temporary empty object
  166.         if (null === $cart) {
  167.             return new RequestCache();
  168.         }
  169.         if (null === static::$objRequestCache) {
  170.             static::$objRequestCache RequestCache::findByIdAndStore(Input::get('isorc'), $cart->store_id);
  171.             if (null === static::$objRequestCache) {
  172.                 static::$objRequestCache = new RequestCache();
  173.                 static::$objRequestCache->store_id $cart->store_id;
  174.             }
  175.         }
  176.         return static::$objRequestCache;
  177.     }
  178.     /**
  179.      * Calculate price through hooks and foreign prices
  180.      *
  181.      * @param float  $fltPrice
  182.      * @param object $objSource
  183.      * @param string $strField
  184.      * @param int    $intTaxClass
  185.      * @param array  $arrAddresses
  186.      * @param array  $arrOptions
  187.      *
  188.      * @return float
  189.      */
  190.     public static function calculatePrice(
  191.         $fltPrice,
  192.         $objSource,
  193.         $strField,
  194.         $intTaxClass 0,
  195.         array $arrAddresses null,
  196.         array $arrOptions = array()
  197.     ) {
  198.         if (empty($fltPrice) || !is_numeric($fltPrice)) {
  199.             return 0;
  200.         }
  201.         // !HOOK: calculate price
  202.         if (isset($GLOBALS['ISO_HOOKS']['calculatePrice']) && \is_array($GLOBALS['ISO_HOOKS']['calculatePrice'])) {
  203.             foreach ($GLOBALS['ISO_HOOKS']['calculatePrice'] as $callback) {
  204.                 $fltPrice System::importStatic($callback[0])->{$callback[1]}(
  205.                     $fltPrice,
  206.                     $objSource,
  207.                     $strField,
  208.                     $intTaxClass,
  209.                     $arrOptions
  210.                 );
  211.             }
  212.         }
  213.         $objConfig = static::getConfig();
  214.         if ($objConfig->priceMultiplier != 1) {
  215.             switch ($objConfig->priceCalculateMode) {
  216.                 case 'mul':
  217.                     $fltPrice $fltPrice $objConfig->priceCalculateFactor;
  218.                     break;
  219.                 case 'div':
  220.                     $fltPrice $fltPrice $objConfig->priceCalculateFactor;
  221.                     break;
  222.             }
  223.         }
  224.         $sourceIsProduct $objSource instanceof IsotopeProduct;
  225.         $sourceIsPrice   $objSource instanceof ProductPrice;
  226.         if (!\is_array($arrAddresses) && ($sourceIsProduct || $sourceIsPrice)) {
  227.             $product $sourceIsPrice $objSource->getRelated('pid') : $objSource;
  228.             $arrAddresses = array(
  229.                 'billing'  => Isotope::getCart()->getBillingAddress(),
  230.                 'shipping' => $product->isExemptFromShipping() ? Isotope::getCart()->getBillingAddress() : Isotope::getCart()->getShippingAddress(),
  231.             );
  232.         }
  233.         // Possibly add/subtract tax
  234.         /** @var TaxClass $objTaxClass */
  235.         if ($intTaxClass && ($objTaxClass TaxClass::findByPk($intTaxClass)) !== null) {
  236.             $fltPrice $objTaxClass->calculatePrice($fltPrice$arrAddresses);
  237.         }
  238.         return static::roundPrice($fltPrice);
  239.     }
  240.     /**
  241.      * Rounds a price according to store config settings
  242.      *
  243.      * @param float $fltValue                  original value
  244.      * @param bool  $blnApplyRoundingIncrement apply rounding increment
  245.      *
  246.      * @return float rounded value
  247.      */
  248.     public static function roundPrice($fltValue$blnApplyRoundingIncrement true)
  249.     {
  250.         $objConfig = static::getConfig();
  251.         if ($blnApplyRoundingIncrement && '0.05' === $objConfig->priceRoundIncrement) {
  252.             $fltValue round(20 $fltValue) / 20;
  253.         }
  254.         return round($fltValue$objConfig->priceRoundPrecision);
  255.     }
  256.     /**
  257.      * Format given price according to store config settings
  258.      *
  259.      * @param float $fltPrice
  260.      * @param bool  $blnApplyRoundingIncrement
  261.      *
  262.      * @return float
  263.      */
  264.     public static function formatPrice($fltPrice$blnApplyRoundingIncrement true)
  265.     {
  266.         // If price or override price is a string
  267.         if (!is_numeric($fltPrice)) {
  268.             return $fltPrice;
  269.         }
  270.         $fltPrice  = static::roundPrice($fltPrice$blnApplyRoundingIncrement);
  271.         $arrFormat $GLOBALS['ISO_NUM'][static::getConfig()->currencyFormat];
  272.         if (!\is_array($arrFormat)) {
  273.             return $fltPrice;
  274.         }
  275.         return number_format($fltPrice$arrFormat[0], $arrFormat[1], $arrFormat[2]);
  276.     }
  277.     /**
  278.      * Format given price according to store config settings, including currency representation
  279.      *
  280.      * @param float  $fltPrice
  281.      * @param bool   $blnHtml
  282.      * @param string $strCurrencyCode
  283.      * @param bool   $blnApplyRoundingIncrement
  284.      *
  285.      * @return string
  286.      */
  287.     public static function formatPriceWithCurrency($fltPrice$blnHtml true$strCurrencyCode null$blnApplyRoundingIncrement true)
  288.     {
  289.         // If price or override price is a string
  290.         if (!is_numeric($fltPrice)) {
  291.             return $fltPrice;
  292.         }
  293.         $objConfig   = static::getConfig();
  294.         $strCurrency $strCurrencyCode ?: $objConfig->currency;
  295.         $strPrice    = static::formatPrice($fltPrice$blnApplyRoundingIncrement);
  296.         $space       $blnHtml '&nbsp;' ' ';
  297.         if ($objConfig->currencySymbol && $GLOBALS['TL_LANG']['CUR_SYMBOL'][$strCurrency] != '') {
  298.             $strCurrency $GLOBALS['TL_LANG']['CUR_SYMBOL'][$strCurrency];
  299.             if (!$objConfig->currencySpace) {
  300.                 $space '';
  301.             }
  302.         }
  303.         if ($blnHtml) {
  304.             $strCurrency '<span class="currency">' $strCurrency '</span>';
  305.         }
  306.         if ('right' === $objConfig->currencyPosition) {
  307.             return $strPrice $space $strCurrency;
  308.         }
  309.         return $strCurrency $space $strPrice;
  310.     }
  311.     /**
  312.      * Format the number of items and return the items string
  313.      *
  314.      * @param int $intItems
  315.      *
  316.      * @return string
  317.      */
  318.     public static function formatItemsString($intItems)
  319.     {
  320.         if ($intItems == 1) {
  321.             return $GLOBALS['TL_LANG']['MSC']['productSingle'];
  322.         }
  323.         $arrFormat $GLOBALS['ISO_NUM'][static::getConfig()->currencyFormat];
  324.         if (\is_array($arrFormat)) {
  325.             $intItems number_format($intItems0$arrFormat[1], $arrFormat[2]);
  326.         }
  327.         return sprintf($GLOBALS['TL_LANG']['MSC']['productMultiple'], $intItems);
  328.     }
  329.     /**
  330.      * Callback for isoButton Hook
  331.      *
  332.      * @param array          $arrButtons
  333.      * @param IsotopeProduct $objProduct
  334.      *
  335.      * @return array
  336.      *
  337.      * @deprecated Deprecated since Isotope 2.5
  338.      */
  339.     public static function defaultButtons($arrButtonsIsotopeProduct $objProduct null)
  340.     {
  341.         $actions = [
  342.             new UpdateAction(),
  343.             new CartAction(),
  344.         ];
  345.         if (true === FE_USER_LOGGED_IN) {
  346.             $actions[] = new FavoriteAction();
  347.         }
  348.         /** @var ProductActionInterface $action */
  349.         foreach ($actions as $action) {
  350.             $arrButtons[$action->getName()] = array(
  351.                 'label' => $action->getLabel($objProduct),
  352.                 'callback' => [\get_class($action), 'handleSubmit'],
  353.                 'class'    => ($objProduct instanceof IsotopeProduct && \is_callable([$action'getClasses']) ? $action->getClasses($objProduct) : '')
  354.             );
  355.         }
  356.         return $arrButtons;
  357.     }
  358.     /**
  359.      * Validate a custom regular expression
  360.      *
  361.      * @param string  $strRegexp
  362.      * @param mixed   $varValue
  363.      *
  364.      * @return bool
  365.      */
  366.     public static function validateRegexp($strRegexp$varValueWidget $objWidget)
  367.     {
  368.         switch ($strRegexp) {
  369.             case 'price':
  370.                 if (!preg_match('/^[\d .-]*$/'$varValue)) {
  371.                     $objWidget->addError(sprintf($GLOBALS['TL_LANG']['ERR']['digit'], $objWidget->label));
  372.                 }
  373.                 return true;
  374.             case 'discount':
  375.                 if (!preg_match('/^[-+]\d+(\.\d+)?%?$/'$varValue)) {
  376.                     $objWidget->addError(sprintf($GLOBALS['TL_LANG']['ERR']['discount'], $objWidget->label));
  377.                 }
  378.                 return true;
  379.             case 'surcharge':
  380.                 if (!preg_match('/^-?\d+(\.\d+)?%?$/'$varValue)) {
  381.                     $objWidget->addError(sprintf($GLOBALS['TL_LANG']['ERR']['surcharge'], $objWidget->label));
  382.                 }
  383.                 return true;
  384.         }
  385.         return false;
  386.     }
  387.     /**
  388.      * Format options label and value
  389.      *
  390.      * @param array  $arrData
  391.      * @param string $strTable
  392.      * @param bool   $blnSkipEmpty
  393.      *
  394.      * @return array
  395.      *
  396.      * @deprecated Deprecated since Isotope 2.4, to be removed in Isotope 3.0
  397.      */
  398.     public static function formatOptions(array $arrData$strTable 'tl_iso_product'$blnSkipEmpty true)
  399.     {
  400.         $arrOptions = array();
  401.         foreach ($arrData as $field => $value) {
  402.             if ($blnSkipEmpty && ($value == '' || $value == '-')) {
  403.                 continue;
  404.             }
  405.             $arrOptions[$field] = array
  406.             (
  407.                 'label' => Format::dcaLabel($strTable$field),
  408.                 'value' => Controller::replaceInsertTags(Format::dcaValue($strTable$field$value)),
  409.             );
  410.         }
  411.         return $arrOptions;
  412.     }
  413.     /**
  414.      * Format product configuration using \Haste\Data
  415.      *
  416.      * @param array                  $arrConfig
  417.      * @param IsotopeProduct|Product $objProduct
  418.      *
  419.      * @return array
  420.      *
  421.      * @deprecated Deprecated since Isotope 2.4, to be removed in Isotope 3.0
  422.      */
  423.     public static function formatProductConfiguration(array $arrConfigIsotopeProduct $objProduct)
  424.     {
  425.         Product::setActive($objProduct);
  426.         $strTable Product::getTable();
  427.         foreach ($arrConfig as $k => $v) {
  428.             /** @var \Isotope\Model\Attribute $objAttribute */
  429.             if (($objAttribute $GLOBALS['TL_DCA'][$strTable]['attributes'][$k]) !== null
  430.                 && $objAttribute instanceof IsotopeAttributeWithOptions
  431.             ) {
  432.                 /** @var Widget $strClass */
  433.                 $strClass $objAttribute->getFrontendWidget();
  434.                 $arrField $strClass::getAttributesFromDca(
  435.                     $GLOBALS['TL_DCA'][$strTable]['fields'][$k],
  436.                     $k,
  437.                     $v,
  438.                     $k,
  439.                     $strTable,
  440.                     $objProduct
  441.                 );
  442.                 $arrOptions = array();
  443.                 $values     $v;
  444.                 if (!empty($arrField['options']) && \is_array($arrField['options'])) {
  445.                     if (!\is_array($values)) {
  446.                         $values = array($values);
  447.                     }
  448.                     $arrOptions array_filter(
  449.                         $arrField['options'],
  450.                         function($option) use (&$values) {
  451.                             if (($pos array_search($option['value'], $values)) !== false) {
  452.                                 unset($values[$pos]);
  453.                                 return true;
  454.                             }
  455.                             return false;
  456.                         }
  457.                     );
  458.                     $arrOptions array_column($arrOptions'label');
  459.                     if (!empty($values)) {
  460.                         $arrOptions array_merge($arrOptions$values);
  461.                     }
  462.                 }
  463.                 $formatted implode(', '$arrOptions);
  464.             } else {
  465.                 $formatted Format::dcaValue($strTable$k$v);
  466.             }
  467.             $arrConfig[$k] = new Plain(
  468.                 $v,
  469.                 Format::dcaLabel($strTable$k),
  470.                 array (
  471.                     'formatted' => Controller::replaceInsertTags($formatted),
  472.                 )
  473.             );
  474.         }
  475.         Product::unsetActive();
  476.         return $arrConfig;
  477.     }
  478. }