From eb679385b52a9877552632541e434052dab49d91 Mon Sep 17 00:00:00 2001 From: limosh Date: Mon, 25 Jun 2018 12:15:35 +0100 Subject: [PATCH] Upgrade for Magento 2 --- Block/Product/View/Type/Configurable.php | 194 +++++++++++++++++ Helper/Data.php | 37 ++++ .../Attribute/InStockOptionSelectBuilder.php | 57 +++++ README.md | 5 + .../Block/Product/View/Type/Configurable.php | 205 ------------------ .../AllConfigurableOptions/etc/config.xml | 17 -- .../modules/Edge_AllConfigurableOptions.xml | 12 - composer.json | 22 +- etc/frontend/di.xml | 6 + etc/module.xml | 8 + registration.php | 6 + .../allconfigurableoptions/configurable.js | 42 ---- view/frontend/requirejs-config.js | 7 + view/frontend/web/js/configurable.js | 79 +++++++ 14 files changed, 408 insertions(+), 289 deletions(-) create mode 100644 Block/Product/View/Type/Configurable.php create mode 100644 Helper/Data.php create mode 100644 Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilder.php delete mode 100644 app/code/local/Edge/AllConfigurableOptions/Block/Product/View/Type/Configurable.php delete mode 100644 app/code/local/Edge/AllConfigurableOptions/etc/config.xml delete mode 100644 app/etc/modules/Edge_AllConfigurableOptions.xml create mode 100644 etc/frontend/di.xml create mode 100644 etc/module.xml create mode 100644 registration.php delete mode 100644 skin/frontend/base/default/edge/allconfigurableoptions/configurable.js create mode 100644 view/frontend/requirejs-config.js create mode 100644 view/frontend/web/js/configurable.js diff --git a/Block/Product/View/Type/Configurable.php b/Block/Product/View/Type/Configurable.php new file mode 100644 index 0000000..073079a --- /dev/null +++ b/Block/Product/View/Type/Configurable.php @@ -0,0 +1,194 @@ +swatchHelper = $swatchHelper; + $this->swatchMediaHelper = $swatchMediaHelper; + $this->_stockRepository = $stockItemInterfaceFactory->create(); + $this->swatchAttributesProvider = $swatchAttributesProvider + ?: ObjectManager::getInstance()->get(SwatchAttributesProvider::class); + $this->localeFormat = $localeFormat ?: ObjectManager::getInstance()->get(Format::class); + $this->jsonDecoder =$jsonDecoder; + + parent::__construct( + $context, + $arrayUtils, + $jsonEncoder, + $helper, + $catalogProduct, + $currentCustomer, + $priceCurrency, + $configurableAttributeData, + $swatchHelper, + $swatchMediaHelper, + $data + ); + $this->context = $context; + $this->stockItemInterfaceFactory = $stockItemInterfaceFactory; + } + + /** + * Sets product stock status. + * + * @param $productId + * + * @return array + */ + public function getStockItem($productId) + { + $stock_data = array(); + $stock_data['out_stock'] = 0 ; + + $stock = $this->_stockRepository->load($productId,'product_id'); + if (!$stock->getIsInStock()) { + $stock_data['out_stock'] = 1; + } + + return $stock_data; + } + + /** + * Get Product Stock + * + * @return array + */ + public function getProductStock() + { + $stock = []; + $skipSaleableCheck=true; + $allProducts = $this->getProduct()->getTypeInstance()->getUsedProducts($this->getProduct(), null); + + foreach ($allProducts as $product) { + if ($product->isSaleable() || $skipSaleableCheck) { + $stock[$product->getId()] = $this->getStockItem($product->getId()); + } + } + return $stock; + } + + /** + * Extend the configuration for js to add stock values. + * + * @return string + */ + public function getJsonConfig() + { + $config = $this->jsonDecoder->decode(parent::getJsonConfig()); + $currentProduct = $this->getProduct(); + $options = $this->helper->getOptions($currentProduct, $this->getAllowProducts(),$this->getProductStock()); + //Adding stock details to the config product options. + $config['stock']=isset($options['stock']) ? $options['stock'] : []; + return $this->jsonEncoder->encode($config); + } + + /** + * Get Allowed Products + * Modified to set $skipSaleableCheck to true + * + * @return \Magento\Catalog\Model\Product[] + */ + public function getAllowProducts() + { + if (!$this->hasAllowProducts()) { + $products = []; + //$skipSaleableCheck = $this->catalogProduct->getSkipSaleableCheck(); + //Setting $skipSaleableCheck true as it is hardcoded false in the parent. + $skipSaleableCheck=true; + $allProducts = $this->getProduct()->getTypeInstance()->getUsedProducts($this->getProduct(), null); + foreach ($allProducts as $product) { + if ($product->isSaleable() || $skipSaleableCheck) { + $products[] = $product; + } + } + $this->setAllowProducts($products); + } + return $this->getData('allow_products'); + } + +} \ No newline at end of file diff --git a/Helper/Data.php b/Helper/Data.php new file mode 100644 index 0000000..8fb5838 --- /dev/null +++ b/Helper/Data.php @@ -0,0 +1,37 @@ +getAllowAttributes($currentProduct); + + foreach ($allowedProducts as $product) { + $productId = $product->getId(); + foreach ($allowAttributes as $attribute) { + $productAttribute = $attribute->getProductAttribute(); + $productAttributeId = $productAttribute->getId(); + $attributeValue = $product->getData($productAttribute->getAttributeCode()); + + $options[$productAttributeId][$attributeValue][] = $productId; + $options['index'][$productId][$productAttributeId] = $attributeValue; + } + //Adding stock status in the option list. + $options['stock'][$productId][] = $stockdata[$productId]['out_stock']; + } + return $options; + } +} \ No newline at end of file diff --git a/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilder.php b/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilder.php new file mode 100644 index 0000000..b451bc7 --- /dev/null +++ b/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilder.php @@ -0,0 +1,57 @@ +stockStatusResource = $stockStatusResource; + $this->stockConfiguration = $stockConfiguration; + } + /** + * Add stock status filter to select. + * + * @param OptionSelectBuilderInterface $subject + * @param Select $select + * @return Select + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetSelect(OptionSelectBuilderInterface $subject, Select $select) + { + //Ignore the stock status in the configuration option, if the show out of stock is set. + if (!$this->stockConfiguration->isShowOutOfStock()) { + $select->joinInner( + ['stock' => $this->stockStatusResource->getMainTable()], + 'stock.product_id = entity.entity_id', + [] + )->where( + 'stock.stock_status = ?', + \Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK + ); + } + return $select; + } +} \ No newline at end of file diff --git a/README.md b/README.md index d15bf94..8e635a3 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,8 @@ All Configurable Options Module for Magento by outer/edge This module will show all available options that are available for a configurable product, regardless of stock levels for the associated products. For example, if you have a clothing item available in three sizes - **Small**, **Medium** and **Large**, and the **Small** size is out of stock, Magento would not show this option. However, with this module all options will be displayed, but out of stock items will be disabled. + +### Configuration + +To show out of stock options in the configurable product, enable(set to'yes') "Display Out of Stock Products" in +Stores->Settings->Configuration->Catalogue->Inventory-> Stock Options \ No newline at end of file diff --git a/app/code/local/Edge/AllConfigurableOptions/Block/Product/View/Type/Configurable.php b/app/code/local/Edge/AllConfigurableOptions/Block/Product/View/Type/Configurable.php deleted file mode 100644 index 5f12032..0000000 --- a/app/code/local/Edge/AllConfigurableOptions/Block/Product/View/Type/Configurable.php +++ /dev/null @@ -1,205 +0,0 @@ -getLayout()->getBlock('head'); - $head->addItem('skin_js', 'edge/allconfigurableoptions/configurable.js'); - } - - /** - * Get Allowed Products - * @return array - */ - public function getAllowProducts() - { - if (!$this->hasAllowProducts()) { - $products = $this->getProduct()->getTypeInstance(true)->getUsedProducts(null, $this->getProduct()); - $this->setAllowProducts($products); - } - return $this->getData('allow_products'); - } - - /** - * Composes configuration for js - * @return string - */ - public function getJsonConfig() - { - $attributes = array(); - $options = array(); - $store = $this->getCurrentStore(); - $taxHelper = Mage::helper('tax'); - $currentProduct = $this->getProduct(); - - $preconfiguredFlag = $currentProduct->hasPreconfiguredValues(); - if ($preconfiguredFlag) { - $preconfiguredValues = $currentProduct->getPreconfiguredValues(); - $defaultValues = array(); - } - - foreach ($this->getAllowProducts() as $product) { - $productId = $product->getId(); - - foreach ($this->getAllowAttributes() as $attribute) { - $productAttribute = $attribute->getProductAttribute(); - $productAttributeId = $productAttribute->getId(); - $attributeValue = $product->getData($productAttribute->getAttributeCode()); - - // Get stock of product - $options['qty'][$product->getAttributeText($productAttribute->getName())] = floor($product->getStockItem()->getQty()); - - if (!isset($options[$productAttributeId])) { - $options[$productAttributeId] = array(); - } - - if (!isset($options[$productAttributeId][$attributeValue])) { - $options[$productAttributeId][$attributeValue] = array(); - } - $options[$productAttributeId][$attributeValue][] = $productId; - } - } - - $this->_resPrices = array( - $this->_preparePrice($currentProduct->getFinalPrice()) - ); - - foreach ($this->getAllowAttributes() as $attribute) { - $productAttribute = $attribute->getProductAttribute(); - $attributeId = $productAttribute->getId(); - $info = array( - 'id' => $productAttribute->getId(), - 'code' => $productAttribute->getAttributeCode(), - 'label' => $attribute->getLabel(), - 'options' => array() - ); - - $optionPrices = array(); - $prices = $attribute->getPrices(); - if (is_array($prices)) { - $prices = $this->_sortOptions($prices, $attributeId); - foreach ($prices as $value) { - if(!$this->_validateAttributeValue($attributeId, $value, $options)) { - continue; - } - $currentProduct->setConfigurablePrice( - $this->_preparePrice($value['pricing_value'], $value['is_percent']) - ); - $currentProduct->setParentId(true); - Mage::dispatchEvent( - 'catalog_product_type_configurable_price', - array('product' => $currentProduct) - ); - $configurablePrice = $currentProduct->getConfigurablePrice(); - - if (isset($options[$attributeId][$value['value_index']])) { - $productsIndex = $options[$attributeId][$value['value_index']]; - } else { - $productsIndex = array(); - } - - $info['options'][] = array( - 'id' => $value['value_index'], - 'label' => ($options['qty'][$value['label']] <= 0) ? $this->__('Not available - %s', $value['label']) : $value['label'], - 'price' => $configurablePrice, - 'oldPrice' => $this->_prepareOldPrice($value['pricing_value'], $value['is_percent']), - 'products' => $productsIndex, - 'disabled' => $options['qty'][$value['label']] <= 0 - ); - $optionPrices[] = $configurablePrice; - } - } - /** - * Prepare formated values for options choose - */ - foreach ($optionPrices as $optionPrice) { - foreach ($optionPrices as $additional) { - $this->_preparePrice(abs($additional-$optionPrice)); - } - } - if($this->_validateAttributeInfo($info)) { - $attributes[$attributeId] = $info; - } - - // Add attribute default value (if set) - if ($preconfiguredFlag) { - $configValue = $preconfiguredValues->getData('super_attribute/' . $attributeId); - if ($configValue) { - $defaultValues[$attributeId] = $configValue; - } - } - } - - $taxCalculation = Mage::getSingleton('tax/calculation'); - if (!$taxCalculation->getCustomer() && Mage::registry('current_customer')) { - $taxCalculation->setCustomer(Mage::registry('current_customer')); - } - - $_request = $taxCalculation->getDefaultRateRequest(); - $_request->setProductClassId($currentProduct->getTaxClassId()); - $defaultTax = $taxCalculation->getRate($_request); - - $_request = $taxCalculation->getRateRequest(); - $_request->setProductClassId($currentProduct->getTaxClassId()); - $currentTax = $taxCalculation->getRate($_request); - - $taxConfig = array( - 'includeTax' => $taxHelper->priceIncludesTax(), - 'showIncludeTax' => $taxHelper->displayPriceIncludingTax(), - 'showBothPrices' => $taxHelper->displayBothPrices(), - 'defaultTax' => $defaultTax, - 'currentTax' => $currentTax, - 'inclTaxTitle' => Mage::helper('catalog')->__('Incl. Tax') - ); - - $config = array( - 'attributes' => $attributes, - 'template' => str_replace('%s', '#{price}', $store->getCurrentCurrency()->getOutputFormat()), - 'basePrice' => $this->_registerJsPrice($this->_convertPrice($currentProduct->getFinalPrice())), - 'oldPrice' => $this->_registerJsPrice($this->_convertPrice($currentProduct->getPrice())), - 'productId' => $currentProduct->getId(), - 'chooseText' => Mage::helper('catalog')->__('Choose an Option...'), - 'taxConfig' => $taxConfig - ); - - if ($preconfiguredFlag && !empty($defaultValues)) { - $config['defaultValues'] = $defaultValues; - } - - $config = array_merge($config, $this->_getAdditionalConfig()); - - return Mage::helper('core')->jsonEncode($config); - } - - protected function _getSorts($attributeId) - { - if (empty($this->_sorts[$attributeId])) { - $this->_sorts[$attributeId] = array_reduce( - Mage::getResourceModel('eav/entity_attribute_option_collection') - ->setAttributeFilter($attributeId) - ->getData(), - - function(&$result, $item){ - $result[$item['option_id']] = $item['sort_order']; - return $result; - } - ); - } - return $this->_sorts[$attributeId]; - } - - protected function _sortOptions($options, $attributeId) - { - $sorts = $this->_getSorts($attributeId); - $sorter = array(); - foreach ($options as $key => $option) { - $sorter[$key] = $sorts[$option['value_index']]; - } - array_multisort($sorter, SORT_ASC, $options); - return $options; - } -} diff --git a/app/code/local/Edge/AllConfigurableOptions/etc/config.xml b/app/code/local/Edge/AllConfigurableOptions/etc/config.xml deleted file mode 100644 index 1bd0501..0000000 --- a/app/code/local/Edge/AllConfigurableOptions/etc/config.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - 1.0.0 - - - - - - - Edge_AllConfigurableOptions_Block_Product_View_Type_Configurable - - - - - \ No newline at end of file diff --git a/app/etc/modules/Edge_AllConfigurableOptions.xml b/app/etc/modules/Edge_AllConfigurableOptions.xml deleted file mode 100644 index a50b468..0000000 --- a/app/etc/modules/Edge_AllConfigurableOptions.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - true - local - - - - - - \ No newline at end of file diff --git a/composer.json b/composer.json index c7f603a..de395f1 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { - "name": "outeredge/edge-magento-module-allconfigurableoptions", - "type": "magento-module", - "description": "All Configurable Options Module for Magento by outer/edge", + "name": "outeredge/magento-outofstockoptions-module", + "type": "magento2-module", + "description": "All Configurable Options on config product: Module for Magento 2 by outer/edge", "license": "MIT", "authors": [ { @@ -9,14 +9,10 @@ "email": "support@outeredgeuk.com" } ], - "require": { - "magento-hackathon/magento-composer-installer": "*" - }, - "extra": { - "map": [ - ["app/code/local/Edge/AllConfigurableOptions", "app/code/local/Edge/AllConfigurableOptions"], - ["app/etc/modules/Edge_AllConfigurableOptions.xml", "app/etc/modules/Edge_AllConfigurableOptions.xml"], - ["skin/frontend/base/default/edge/allconfigurableoptions", "skin/frontend/base/default/edge/allconfigurableoptions"] - ] + "autoload": { + "files": ["registration.php"], + "psr-4": { + "OuterEdge\\ConfigProduct\\": "" + } } -} \ No newline at end of file +} diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml new file mode 100644 index 0000000..4aef07b --- /dev/null +++ b/etc/frontend/di.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..442ed7f --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..7d7d707 --- /dev/null +++ b/registration.php @@ -0,0 +1,6 @@ +-1){ - allowedProducts.push(options[i].products[j]); - } - } - } else { - allowedProducts = options[i].products.clone(); - } - - if(allowedProducts.size()>0){ - options[i].allowedProducts = allowedProducts; - element.options[index] = new Option(this.getOptionLabel(options[i], options[i].price), options[i].id); - if (typeof options[i].price != 'undefined') { - element.options[index].setAttribute('price', options[i].price); - } - if (options[i].disabled) { - element.options[index].setAttribute('disabled', 'disabled'); - } - element.options[index].config = options[i]; - index++; - } - } - } -}; \ No newline at end of file diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js new file mode 100644 index 0000000..6f35856 --- /dev/null +++ b/view/frontend/requirejs-config.js @@ -0,0 +1,7 @@ +var config = { + map: { + '*': { + configurable: 'OuterEdge_ConfigProduct/js/configurable' + } + } +}; \ No newline at end of file diff --git a/view/frontend/web/js/configurable.js b/view/frontend/web/js/configurable.js new file mode 100644 index 0000000..3a946e7 --- /dev/null +++ b/view/frontend/web/js/configurable.js @@ -0,0 +1,79 @@ +define([ + 'jquery', + 'jquery/ui', + 'Magento_ConfigurableProduct/js/configurable' +], function ($) { + + $.widget('outeredge.configurable',$.mage.configurable,{ + + /** + * Extended to add stock status in the dropdown. + * Populates an option's selectable choices. + * + * @private + * @param {*} element - Element associated with a configurable option. + */ + _fillSelect: function (element) { + var attributeId = element.id.replace(/[a-z]*/, ''), + options = this._getAttributeOptions(attributeId), + prevConfig, + index = 1, + allowedProducts, + i, + j; + + this._clearSelect(element); + element.options[0] = new Option('', ''); + element.options[0].innerHTML = this.options.spConfig.chooseText; + prevConfig = false; + + if (element.prevSetting) { + prevConfig = element.prevSetting.options[element.prevSetting.selectedIndex]; + } + + if (options) { + for (i = 0; i < options.length; i++) { + allowedProducts = []; + + /* eslint-disable max-depth */ + if (prevConfig) { + for (j = 0; j < options[i].products.length; j++) { + // prevConfig.config can be undefined + if (prevConfig.config && + prevConfig.config.allowedProducts && + prevConfig.config.allowedProducts.indexOf(options[i].products[j]) > -1) { + allowedProducts.push(options[i].products[j]); + } + } + } else { + allowedProducts = options[i].products.slice(0); + } + + if (allowedProducts.length > 0) { + options[i].allowedProducts = allowedProducts; + element.options[index] = new Option(this._getOptionLabel(options[i]), options[i].id); + + //Adding out of stock to the dropdown option. + var optionItem= element.options[index].innerHTML; + if((typeof this.options.spConfig.stock !== 'undefined') && this.options.spConfig.stock[options[i].products]==1) { + optionItem = optionItem+' – out of stock'; + element.options[index].disabled = true; + } + element.options[index].innerHTML=optionItem; + + if (typeof options[i].price !== 'undefined') { + element.options[index].setAttribute('price', options[i].prices); + } + + element.options[index].config = options[i]; + index++; + } + + /* eslint-enable max-depth */ + } + } + }, + + }); + +}); \ No newline at end of file