I am calculating the tax for the entire order using a third party API.
How can set total tax rate to a quote ?
$quote->setTax($taxAmount); is not working.
can anybody please help ?
That's how you should do it:
Change the file app/code/Vendor/Package/etc/di.xml in your custom package by adding the line
<type name="Magento\Checkout\Model\Cart">
<plugin name="interceptAddingProductToCart" type="Vendor\Package\Model\Checkout\Cart\Plugin" sortOrder="10" disabled="false"/>
</type>
Create the plugin
app/code/Vendor/Package/Model/Checkout/Cart/Plugin.php
Now, add the content to the file above:
<?php
namespace Vendor\Package\Model\Checkout\Cart;
class Plugin
{
/**
* #var \Magento\Quote\Model\Quote
*/
protected $quote;
/**
* Plugin constructor.
*
* #param \Magento\Checkout\Model\Session $checkoutSession
*/
public function __construct(
\Magento\Checkout\Model\Session $checkoutSession
) {
$this->quote = $checkoutSession->getQuote();
}
public function beforeAddProduct($subject, $productInfo, $requestInfo = null)
{
# Do something, here for instance, it remove TAX for giftvouchers
if ($productInfo->getTypeId() == 'giftvoucher') {
$productInfo->setTaxClassId(0);
}
return [$productInfo, $requestInfo];
}
}
Related
I have a theme that uses Stripe, and when installing the plugin Dokan Pro it gives me fatal error, i looked after the name class in the workspace of the plugin Stripe/Stripe and change the name class for MyStripe, but when i do that it gives me error another fatal error, and i looked for those directions and i dont know what to do in there, this a fragment of the code Helps.php, I intuitively thought of changing Stripe / Stripe to Stripe / MyStripe but it doesn't fix it.
<?php
namespace WeDevs\DokanPro\Modules\Stripe;
use Stripe\Stripe;
use WeDevs\DokanPro\Modules\Stripe\Settings\RetrieveSettings;
defined( 'ABSPATH' ) || exit;
/**
* Stripe Helper class
*
* #since 3.0.3
*/
class Helper {
public static function get_settings() {
return RetrieveSettings::instance()->settings;
}
/**
* Check wheter the 3d secure is enabled or not
*
* #since 3.0.3
*
* #return bool
*/
public static function is_3d_secure_enabled() {
$settings = self::get_settings();
if ( empty( $settings['enable_3d_secure'] ) || 'yes' !== $settings['enable_3d_secure'] ) {
return false;
}
return true;
}
/**
* Check wheter we are paying with 3ds or non_3ds payment method
*
* #since 3.0.3
*
* #return string
*/
public static function payment_method() {
return self::is_3d_secure_enabled() ? '3ds' : 'non_3ds';
}
/**
* Check wheter the gateway in test mode or not
*
* #since 3.0.3
*
* #return bool
*/
public static function is_test_mode() {
$settings = self::get_settings();
if ( empty( $settings['testmode'] ) || 'yes' !== $settings['testmode'] ) {
return false;
}
return 'yes' === $settings['testmode'];
}
/**
* Check wheter subscription module is enabled or not
*
* #since 3.0.3
*
* #return bool
*/
public static function has_subscription_module() {
return dokan_pro()->module->is_active( 'product_subscription' );
}
/**
* Set stripe app info
*
* #since 3.0.3
*
* #return void
*/
public static function set_app_info() {
Stripe::setAppInfo(
'Dokan Stripe-Connect',
DOKAN_PRO_PLUGIN_VERSION,
'https://wedevs.com/dokan/modules/stripe-connect/',
'pp_partner_Ee9F0QbhSGowvH'
);
}
The best way is to disable the stripe module inside the theme. Probably on functions.php has an include to stripe or do a big search for it.
It's better to modify themes (the child theme way) that to modify plugins.
Here is what I need:
In file vendor/magento/module-sales-rule/Model/Rule/Action/Discount/CartFixed.php there is a public function calculate
I want to comment an If condition inside this function without modifying this core file.
if ($availableDiscountAmount > 0) {
I have created a preference inside a custom module but it is not working as expected. I really appreciate if anyone can help me on this.
To customize Magento core file, you can use Preference or Plugin. More detail here
In your case, you can use preference as the following steps:
In the di.xml you can add the reference config:
<preference for="Magento\SalesRule\Model\Rule\Action\Discount\CartFixed" type="Training\Test\Model\Rule\Action\Discount\CartFixed" />
Create Training\Test\Model\Rule\Action\Discount\CartFixed.php:
#author Bach Lee
*/
namespace Training\Test\Model\Rule\Action\Discount;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Pricing\PriceCurrencyInterface;
use Magento\SalesRule\Model\DeltaPriceRound;
use Magento\SalesRule\Model\Validator;
use Magento\SalesRule\Model\Rule\Action\DiscountFactory;
class CartFixed extends \Magento\SalesRule\Model\Rule\Action\Discount\CartFixed
{
/**
* #var string
*/
private static $discountType = 'CartFixed';
/**
* #var DeltaPriceRound
*/
private $deltaPriceRound;
/**
* #param Validator $validator
* #param DataFactory $discountDataFactory
* #param PriceCurrencyInterface $priceCurrency
* #param DeltaPriceRound $deltaPriceRound
*/
public function __construct(
Validator $validator,
DataFactory $discountDataFactory,
PriceCurrencyInterface $priceCurrency,
DeltaPriceRound $deltaPriceRound = null
) {
$this->deltaPriceRound = $deltaPriceRound ?: ObjectManager::getInstance()->get(DeltaPriceRound::class);
parent::__construct($validator, $discountDataFactory, $priceCurrency, $deltaPriceRound);
}
/**
* #param \Magento\SalesRule\Model\Rule $rule
* #param \Magento\Quote\Model\Quote\Item\AbstractItem $item
* #param float $qty
* #return \Magento\SalesRule\Model\Rule\Action\Discount\Data
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function calculate($rule, $item, $qty)
{
/** #var \Magento\SalesRule\Model\Rule\Action\Discount\Data $discountData */
$discountData = $this->discountFactory->create();
$ruleTotals = $this->validator->getRuleItemTotalsInfo($rule->getId());
$quote = $item->getQuote();
$address = $item->getAddress();
$itemPrice = $this->validator->getItemPrice($item);
$baseItemPrice = $this->validator->getItemBasePrice($item);
$itemOriginalPrice = $this->validator->getItemOriginalPrice($item);
$baseItemOriginalPrice = $this->validator->getItemBaseOriginalPrice($item);
/**
* prevent applying whole cart discount for every shipping order, but only for first order
*/
if ($quote->getIsMultiShipping()) {
$usedForAddressId = $this->getCartFixedRuleUsedForAddress($rule->getId());
if ($usedForAddressId && $usedForAddressId != $address->getId()) {
return $discountData;
} else {
$this->setCartFixedRuleUsedForAddress($rule->getId(), $address->getId());
}
}
$cartRules = $address->getCartFixedRules();
if (!isset($cartRules[$rule->getId()])) {
$cartRules[$rule->getId()] = $rule->getDiscountAmount();
}
$availableDiscountAmount = (float)$cartRules[$rule->getId()];
$discountType = self::$discountType . $rule->getId();
$store = $quote->getStore();
if ($ruleTotals['items_count'] <= 1) {
$quoteAmount = $this->priceCurrency->convert($availableDiscountAmount, $store);
$baseDiscountAmount = min($baseItemPrice * $qty, $availableDiscountAmount);
$this->deltaPriceRound->reset($discountType);
} else {
$ratio = $baseItemPrice * $qty / $ruleTotals['base_items_price'];
$maximumItemDiscount = $this->deltaPriceRound->round(
$rule->getDiscountAmount() * $ratio,
$discountType
);
$quoteAmount = $this->priceCurrency->convert($maximumItemDiscount, $store);
$baseDiscountAmount = min($baseItemPrice * $qty, $maximumItemDiscount);
$this->validator->decrementRuleItemTotalsCount($rule->getId());
}
$baseDiscountAmount = $this->priceCurrency->round($baseDiscountAmount);
$availableDiscountAmount -= $baseDiscountAmount;
$cartRules[$rule->getId()] = $availableDiscountAmount;
if ($availableDiscountAmount <= 0) {
$this->deltaPriceRound->reset($discountType);
}
$discountData->setAmount($this->priceCurrency->round(min($itemPrice * $qty, $quoteAmount)));
$discountData->setBaseAmount($baseDiscountAmount);
$discountData->setOriginalAmount(min($itemOriginalPrice * $qty, $quoteAmount));
$discountData->setBaseOriginalAmount($this->priceCurrency->round($baseItemOriginalPrice));
$address->setCartFixedRules($cartRules);
return $discountData;
}
}
Or you can use cweagans/composer-patches following this question to edit core Magento files
Regards
Use like this
code/Magento/* any name or module you need from core file
If you need some more details let me know it is just like Magento 1 but only folder is changed
App/Code/Magento/SalesRule/model/ *
Please use it like this
After adding this please use compile command
It works for me it will work for you also
First add folders
App/Code/Magento/SalesRule/model/folder/file.php
And thank use "setup:upgrade" to update the files.
Than command "setup:di:compile".
Regards
Naseem
I am a newbie to magento 2x. However I tried to create a custom module and a webservice to check a customer email is exist or not. But as a result I am getting just a blank array. Here is what I have done so far:
1. app/code/Test/Customers/registration.php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Test_Customers',
__DIR__
);
2. app/code/Test/Customers/etc/module.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"><module name="Test_Customers" setup_version="1.0.0"></module></config>
3. app/code/Test/Customers/etc/di.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"><preference for="Test\Customers\Api\AccountInterface" type="Test\Customers\Model\Account"/></config>
4. app/code/Test/Customers/etc/webapi.xml
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd"><route url="/V1/customers/isEmailExist/" method="POST"><service class="Test\Customers\Api\AccountInterface" method="isEmailExist"/><resources><resource ref="anonymous"/></resources></route></routes>
5. app/code/Test/Customers/Api/AccountInterface.php
namespace Test\Customers\Api;
/**
* Account interface.
* #api
*/
interface AccountInterface
{
/**
* Check if given email is associated with a customer account in given website.
* #api
* #param string $customerEmail
* #return \Test\Customers\Api\AccountInterface
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function isEmailExist($customerEmail);
}
6. app/code/Test/Customers/Model/Account.php
namespace Test\Customers\Model;
use Test\Customers\Api\AccountInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;
class Account implements AccountInterface
{
/**
* #var CustomerRepositoryInterface
*/
private $customerRepository;
/** #var \Magento\Framework\Controller\Result\JsonFactory */
protected $resultJsonFactory;
/**
* #param CustomerRepositoryInterface $customerRepository
* #param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
*/
public function __construct(
CustomerRepositoryInterface $customerRepository,
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
)
{
$this->customerRepository = $customerRepository;
$this->resultJsonFactory = $resultJsonFactory;
}
/**
* {#inheritdoc}
*/
public
function isEmailExist($customerEmail)
{
/** #var \Magento\Framework\Controller\Result\JsonFactory */
$result = $this->resultJsonFactory->create();
try {
$this->customerRepository->get($customerEmail);
} catch (NoSuchEntityException $e) {
}
return $result->setData(['success' => true]);
}
}
I tried a POST request from REST client
http://127.0.0.1/PATH_TO_MAGENTO_DIRECTORY/index.php/rest/V1/customers/isEmailExist
POST Body:
{
"customerEmail": "sa#example.com"
}
and got a [] as response
I'm working on a custom shipping method and I've been getting exception each times I try to place an order with my method ("Please specify a shipping method").
I tried with the Magento 2 Flat Rate method and it worked.
I found that in Magento/Quote/Model/QuoteValidator.php on line 52, the getShippingMethod() returned nothing because that function :
public function getShippingMethod()
{
return $this->getData('shipping_method');
}
in Magento/Quote/Model/Quote/Address.php returned nothing.
Just in case my code was wrong, I also tried with this custom shipping method (I followed this tutorial to create the method) http://www.blog.magepsycho.com/create-custom-shipping-module-in-magento-2/ (just activate the module and tried to place an order with that method) but I'm facing the same issue.
Does someone know how I can resolve this issue ?
Thanks.
Here is my Model/Carrier/method.php :
class Method extends \Magento\Shipping\Model\Carrier\AbstractCarrier implements \Magento\Shipping\Model\Carrier\CarrierInterface
{
protected $_logger;
/**
* #var string
*/
protected $_code = 'coursierprive_transport';
/**
* #var bool
*/
protected $_isFixed = true;
/**
* #var \Magento\Shipping\Model\Rate\ResultFactory
*/
protected $_rateResultFactory;
/**
* #var \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory
*/
protected $_rateMethodFactory;
/**
* #param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* #param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
* #param \Psr\Log\LoggerInterface $logger
* #param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory
* #param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory
* #param array $data
*/
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
\Psr\Log\LoggerInterface $logger,
\Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
\Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
array $data = []
)
{
$this->_rateResultFactory = $rateResultFactory;
$this->_rateMethodFactory = $rateMethodFactory;
$this->_logger = $logger;
parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
}
/**
* #param RateRequest $request
* #return \Magento\Shipping\Model\Rate\Result
* #SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
public function collectRates(RateRequest $request)
{
if (!$this->getConfigFlag('active'))
return (false);
if ($request->getAllItems())
{
foreach ($request->getAllItems() as $item)
{
// Some stuff used to check dimensions, weight, post code... etc
}
}
if (//some tests)
return (false);
$result = $this->_rateResultFactory->create();
$shippingPrice = 5.5;
$method = $this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod($this->_code);
$method->setMethodTitle($this->getConfigData('name'));
if ($request->getFreeShipping() === true || $request->getPackageQty() == $this->getFreeBoxes())
$shippingPrice = 0;
$method->setPrice($shippingPrice);
$method->setCost($shippingPrice);
$result->append($method);
return ($result);
}
/**
* Get allowed shipping methods
*
* #return array
*/
public function getAllowedMethods()
{
return (['coursierprive_transport' => $this->getConfigData('name')]);
}
}
Here is my Config.xml :
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../Magento/Store/etc/config.xsd">
<default>
<carriers>
<coursierprive_transport>
<active>1</active>
<sallowspecific>0</sallowspecific>
<price>5.5</price>
<model>CoursierPrive\Transport\Model\Carrier\Method</model>
<name>Express</name>
<title>Coursier Privé</title>
<specificerrmsg>This shipping method is not available. To use this shipping method, please contact us.</specificerrmsg>
</coursierprive_transport>
</carriers>
</default>
</config>
I think that I know what messed up. I suppose there is a size limit for the method name and my first name contained too many chars.
Try to change the code to something short like:
protected $_code = 'couriertransport';
I am not sure if this is the issue but doing so fixed the issue:
https://github.com/MagePsycho/magento2-custom-shipping
My case : protected $_code = 'effectconnect_shipment';
=> so full code must be 'effectconnect_shipment_effectconnect_shipment'
I checked the length of this code is only 40 so rename it to $_code = 'effectconnect'; and it work !!!
I have a configurable product which is available in many different colors and sizes. I want the configurable product to appear once for every color. My idea is to assign one simple product of the configurable product in every color to the category of the configurable product. Then I want to change the listing, so that the (colored) simple product links to it's master product (the configurable one).
The other way would be, to just assign the configurable product to a category and then list it multiple times with different colors. But I think this would be to complicated.
Solution
Sincerely I have lost my code. But here is how I've managed it:
Set visibility for all slave products to catalog so that they
appear in the product listing
Override the Product Model and it's getProductUrl function:
public function getProductUrl($useSid = null)
{
$product = $this;
$product->loadParentProductIds();
$parentIds = $product->getParentProductIds();
if(count($parentIds) > 0 && $product->getTypeId() == Mage_Catalog_Model_Product_Type::TYPE_SIMPLE)
{
$parent = Mage::getModel("catalog/product")->setId($parentIds[0])->load();
return $this->getUrlModel()->getProductUrl($parent, $useSid);
}
return $this->getUrlModel()->getProductUrl($product, $useSid);
}
This way each slave product links to it's master product. The tricky part is to attach the attributes to the url. You can add #attributecode1=value1&attributecode2=value2 to the url to preselect the attribute select boxes. I only had this part quick & dirty and am pretty sure someone can do this much better.
Example for preselection:
http://demo.magentocommerce.com/anashria-womens-premier-leather-sandal-7.html
http://demo.magentocommerce.com/anashria-womens-premier-leather-sandal-7.html#502=43
I don't understand why you just don't make a configurable product based on size for every color? That way you don't need to hack the way Magento works.
If you make a simple product that is part of a configurable product visible on the frontend, it will not link to a configurable product, if it is part of one (as you have found out). It wouldn't really make sense for you either because if your configurable products are based on size AND color, the simple products are going to have a set size and set color.
You would be done, fully functional, and hack-free if you just made a configurable product for each shirt color. Then, you can also use related products to show other shirt colors.
The less hacking, the better. That's my opinion.
Necroing this thread in case others need to do this in Magento 2.
Below is my solution. Please keep in mind that it is hacky and will break many things, so only use if you're a Magento developer who knows what he/she is doing and can either fix or live with the negative side-effects of this code.
registration.php
<?php
use \Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Antti_ConfigurableProductSplitter', __DIR__);
etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Antti_ConfigurableProductSplitter" >
<sequence>
<module name="Magento_Catalog" />
</sequence>
</module>
</config>
etc/frontend/events.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="catalog_block_product_list_collection">
<observer name="cps_catalog_block_product_list_collection" instance="Antti\ConfigurableProductSplitter\Observer\CatalogBlockProductCollectionBeforeToHtmlObserver" shared="false" />
</event>
<event name="cps_product_data_merge_after">
<observer name="cps_product_data_merge_after" instance="Antti\ConfigurableProductSplitter\Observer\SetColorPreselectedAfterProductDataMerge" shared="false" />
</event>
</config>
Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php
<?php
namespace Antti\ConfigurableProductSplitter\Observer;
use Magento\Framework\Event\ObserverInterface;
use Antti\ConfigurableProductSplitter\Model\ProductCollectionSplitter;
class CatalogBlockProductCollectionBeforeToHtmlObserver implements ObserverInterface
{
/**
* #var ProductCollectionSplitter
*/
private $productSplitter;
/**
* CatalogBlockProductCollectionBeforeToHtmlObserver constructor.
*
* #param ProductCollectionSplitter $productSplitter
*/
public function __construct(
ProductCollectionSplitter $productSplitter
) {
$this->productSplitter = $productSplitter;
}
/**
* #param \Magento\Framework\Event\Observer $observer
*
* #return $this
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
$productCollection = $observer->getEvent()->getCollection();
if ($productCollection instanceof \Magento\Framework\Data\Collection) {
if (!$productCollection->isLoaded()) {
$productCollection->load();
}
$this->productSplitter->splitConfigurables($productCollection);
}
return $this;
}
}
Observer/SetColorPreselectedAfterProductDataMerge.php
<?php
namespace Antti\ConfigurableProductSplitter\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Eav\Model\Config as EavConfig;
class SetColorPreselectedAfterProductDataMerge implements ObserverInterface
{
/**
* #var EavConfig
*/
private $eavConfig;
/**
* ProductDataMerger constructor.
*
* #param EavConfig $eavConfig
*/
public function __construct(
EavConfig $eavConfig
) {
$this->eavConfig = $eavConfig;
}
/**
* #param \Magento\Framework\Event\Observer $observer
*
* #return $this
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
$product = $observer->getEvent()->getSimple();
$merged = $observer->getEvent()->getMerged();
$this->setColorPreselected($merged, $product->getColor());
return $this;
}
/**
* #param ProductInterface $product
* #param int $color
*
* #throws \Magento\Framework\Exception\LocalizedException
*/
private function setColorPreselected(ProductInterface &$product, int $color)
{
$attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'color');
$preconfiguredValues = new \Magento\Framework\DataObject();
$preconfiguredValues->setData('super_attribute', [$attribute->getId() => $color]);
$product->setPreconfiguredValues($preconfiguredValues);
// TODO: should test whether this works if there is no url rewrite
$product->setRequestPath(sprintf('%s#%d=%d', $product->getRequestPath(), $attribute->getId(), $color));
}
}
Model/ProductDataMerger.php
<?php
namespace Antti\ConfigurableProductSplitter\Model;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\EntityManager\EventManager;
class ProductDataMerger
{
/**
* #var EventManager
*/
private $eventManager;
/**
* #param EventManager $eventManager
*/
public function __construct(
EventManager $eventManager
) {
$this->eventManager = $eventManager;
}
/**
* #param ProductInterface $product
* #param ProductInterface $parentProduct
*
* #return ProductInterface
*/
public function merge(ProductInterface $product, ProductInterface $parentProduct)
{
$merged = clone $parentProduct;
$merged->setParentId($merged->getId());
$merged->setId($product->getId());
$this->setImageFromChildProduct($merged, $product);
$this->eventManager->dispatch(
'cps_product_data_merge_after',
['merged' => $merged, 'simple' => $product, 'configurable' => $parentProduct]
);
return $merged;
}
/**
* #param ProductInterface $product
* #param ProductInterface $childProduct
*/
public function setImageFromChildProduct(ProductInterface &$product, ProductInterface $childProduct)
{
foreach (['image', 'small_image', 'thumbnail'] as $imageType) {
if ($childProduct->getData($imageType) && $childProduct->getData($imageType) !== 'no_selection') {
$product->setData($imageType, $childProduct->getData($imageType));
} else {
$product->setData($imageType, $childProduct->getData('image'));
}
}
}
}
Model/ProductCollectionSplitter.php
<?php
namespace Antti\ConfigurableProductSplitter\Model;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Antti\ConfigurableProductSplitter\Model\ProductDataMerger;
use Magento\Catalog\Model\Layer\Resolver;
class ProductCollectionSplitter
{
/**
* #var \Magento\Catalog\Model\Layer
*/
private $catalogLayer;
/**
* #var ProductDataMerger
*/
private $productDataMerger;
/**
* ProductCollectionSplitter constructor.
*
* #param Resolver $layerResolver
* #param ProductDataMerger $productDataMerger
*/
public function __construct(
Resolver $layerResolver,
ProductDataMerger $productDataMerger
) {
$this->catalogLayer = $layerResolver->get();
$this->productDataMerger = $productDataMerger;
}
/**
* #param \Magento\Framework\Data\Collection $collection
*
* #return $this
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function splitConfigurables(\Magento\Framework\Data\Collection $collection)
{
$items = $collection->getItems();
if (sizeof($items) == 0) {
return $this;
}
$configurables = $otherProducts = [];
$colorFilterValue = $this->getCurrentColorFilterValue();
foreach ($items as $index => $product) {
if ($product->getTypeId() === Configurable::TYPE_CODE) {
/** #var \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection $childProducts */
$childProducts = $product->getTypeInstance()->getUsedProductCollection($product);
if ($colorFilterValue !== null) {
$childProducts->addAttributeToFilter('color', ['eq' => $colorFilterValue]);
}
$childProducts->groupByAttribute('color');
foreach ($childProducts as $childProduct) {
$childProduct->setParentId($product->getId());
$otherProducts[] = $childProduct;
}
$configurables[$product->getId()] = $product;
} else {
$otherProducts[] = $product;
}
$collection->removeItemByKey($index);
}
foreach ($otherProducts as $product) {
if ($product->getParentId() && isset($configurables[$product->getParentId()])) {
$product = $this->productDataMerger->merge($product, $configurables[$product->getParentId()]);
}
$collection->addItem($product);
}
return $this;
}
/**
* #return string|null
* #throws \Magento\Framework\Exception\LocalizedException
*/
private function getCurrentColorFilterValue()
{
/** #var \Magento\Catalog\Model\Layer\Filter\Item $filter */
foreach ($this->catalogLayer->getState()->getFilters() as $filter) {
if($filter->getFilter()->getAttributeModel()->getName() == 'color') {
return $filter->getValueString();
}
}
return null;
}
}
Known issues:
Because of modifying collection items after it has loaded, the collection count will be
invalid, and this may cause issues elsewhere.
Also product ids in the collection will be invalid since configurable items' ids gets replaced by simple products' ids.
If the collection will be loaded again elsewhere, configurable products do not get splitted.
Products per page limiter does not work anymore.
Products count on product list is invalid.
3rd party search modules such as Klevu might not work
Other 3rd party modules may have issues with the implementation.
Product stock data, reviews etc. are broken on product list (although is probably easy to fix in frontend).
Not sure if implementation will work without url rewrites (should be easy to fix though).
Other issues I might not be aware of.
One way would be to make the size and color part of the catalog number (or whatever unique identifying number you are using for the product)
So lets say you have a widget that comes in 2 colors and 3 sizes, and it's catalog number is "qwe123". You would enter the following 2 items into the system, along with appropriate images. I'm assuming you already have a way to deal with the sizes.
qwe123-red
qwe123-blue
There is no extra programing involved to do it this way, but if you want to link to the other colors that are available from the product page then you will have to parse out the first part of the catalog number and search for the ones that match.
In order to redirect simple products to configurable parent product, you can create a Plugin (Interceptor) for Magento\Catalog\Model\Product::getProductUrl(), where to change URL for simple products:
if ($product->getTypeId() === 'simple') {
/*Get the configurable parent product URL and assign it to a simple product.*/
}
To preselect a simple product in a configurable product, the address of a simple product should look like this for example:
/mona-pullover-hoodlie.html#143=167&93=53
where
/mona-pullover-hoodlie.html - configurable product URL,
143, 93 - attributes IDs,
167, 53 - option IDs.
Attributes IDs and option IDs can be obtained using Magento\ConfigurableProduct\Model\Product\Type\Configurable::getConfigurableAttributesAsArray($product) function.
I made a VCT Simple Product URL module on Magento Marketplace that solves this problem.