My task is related to the smart categories. Smart categories are those which assign the products based on the rules.
Rules of categories can be
Category A: Those products which quantity is more than 1000 should be assign to this category.
Category B: Assign the products based on the brand of the product.
Magento 2 Enterprise provide us rules functionality after adding which we can achieve the above functionality. But we want to update category whenever any product full fill the rule then it should assign to rule related category.
Magento 2 Enterprise does not assign newly products which follow the rules automatically until we re-save the category. So i try to re-save the smart categories using cron. It's code is
class Category
{
/**
* #var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory
*/
protected $_categoryCollectionFactory;
/**
* #var \Magento\Catalog\Api\CategoryRepositoryInterface
*/
protected $_repository;
/**
* #var \Psr\Log\LoggerInterface
*/
protected $_logger;
/**
* #var \Magento\VisualMerchandiser\Model\Rules
*/
protected $_modelRulesFactory;
/**
* Index constructor. Here we are gettingthe logger for log. categorycollection to get all the categories and category repository to save the smart categories
* #param \Magento\Framework\App\Action\Context $context
* #param LoggerInterface $logger
* #param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory
* #param \Magento\Catalog\Api\CategoryRepositoryInterface $repository
* #param RulesFactory $modelRulesFactory
*/
public function __construct(\Psr\Log\LoggerInterface $logger,
\Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $categoryCollectionFactory,
\Magento\Catalog\Api\CategoryRepositoryInterface $repository,
RulesFactory $modelRulesFactory) {
$this->_logger = $logger;
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_repository = $repository;
$this->_modelRulesFactory = $modelRulesFactory;
}
/**
* In this function we load all the categories and only save the smart categories so rules for products can apply and assign the new best seller products according to rules
*
* #throws \Magento\Framework\Exception\LocalizedException
*/
public function execute() {
try{
$this->_logger->addDebug('Updating categories');
$categories = $this->_categoryCollectionFactory->create();
$categories->addAttributeToSelect('*');
$rulesModel = $this->_modelRulesFactory->create();
foreach ($categories as $category) {
$rule = $rulesModel->loadByCategory($category);
if ($rule->getId() && $rule->getIsActive()) {
$category->setStoreId(0);
$rule->setIsActive(1);
$this->_repository->save($category);
}
}
}catch (Exception $e){
$this->_logger->addDebug($e->getMessage());
}
}
}
The above code works very good with those rules which are using the rules based on the default attributes. For example if rule is quantity > 1000 here quantity is default attributes so those categories update very well. But if it is like Brand = 'Nike', then after execute the above code it's assigned products become 0.
Can you please help me why it is happening? Thank you very much.
i want to override AbstractBlock class, i tried with my custom module but it not working
di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Framework\View\Element\AbstractBlock" type="YourCompany\YourModule\Framework\View\Element\AbstractBlock" />
</config>
AbstractBlock.php
<?php
namespace YourCompany\YourModule\Framework\View\Element;
abstract class AbstractBlock extends \Magento\Framework\View\Element\AbstractBlock {
/**
* Retrieve child block HTML
*
* #param string $alias
* #param boolean $useCache
* #return string
*/
public function getChildHtml($alias = '', $useCache = true)
{
die("here");
}
}
You cannot replace a class in the middle of the hierarchy.
Meaning, exchanging an existing parent (abstract) class by another abstract class.
May be you can show how are you using the original class (I guess in a constructor) and that would help a little bit to give a better answer.
I'm trying to add a custom tab in catalog, I'm following FishPig's tutorial.
So I want to achieve something like this
I have followed every instructions in the tutorial but still can't get it right.
I have disabled the catching and enabled debugging. I have checked the logs but I don't get any errors related this module.
My code
app/etc/modules/Fishpig_Customtabs.xml
<config>
<modules>
<Fishpig_Customtabs>
<active>true</active>
<codePool>local</codePool>
</Fishpig_Customtabs>
</modules>
</config>
app/code/local/Fishpig/Customtabs/etc/config.xml
<?xml version="1.0"?>
<config>
<modules>
<Fishpig_CustomTabs>
<version>0.1.0</version>
</Fishpig_CustomTabs>
</modules>
<global>
<blocks>
<customtabs>
<class>Fishpig_Customtabs_Block</class>
</customtabs>
</blocks>
<models>
<customtabs>
<class>Fishpig_Customtabs_Model</class>
</customtabs>
</models>
</global>
<adminhtml>
<layout>
<updates>
<customtabs>
<file>customtabs.xml</file>
</customtabs>
</updates>
</layout>
<events>
<catalog_product_save_after>
<observers>
<fishpig_save_product_data>
<type>singleton</type>
<class>customtabs/observer</class>
<method>saveProductTabData</method>
</fishpig_save_product_data>
</observers>
</catalog_product_save_after>
</events>
</adminhtml>
</config>
app/code/local/Fishpig/Customtabs/Block/Adminhtml/Catalog/Product/Tab.php
<?php
class Fishpig_Customtabs_Block_Adminhtml_Catalog_Product_Tab
extends Mage_Adminhtml_Block_Template
implements Mage_Adminhtml_Block_Widget_Tab_Interface {
/**
* Set the template for the block
*
*/
public function _construct()
{
parent::_construct();
$this->setTemplate('customtabs/catalog/product/tab.phtml');
}
/**
* Retrieve the label used for the tab relating to this block
*
* #return string
*/
public function getTabLabel()
{
return $this->__('My Custom Tab');
}
/**
* Retrieve the title used by this tab
*
* #return string
*/
public function getTabTitle()
{
return $this->__('Click here to view your custom tab content');
}
/**
* Determines whether to display the tab
* Add logic here to decide whether you want the tab to display
*
* #return bool
*/
public function canShowTab()
{
return true;
}
/**
* Stops the tab being hidden
*
* #return bool
*/
public function isHidden()
{
return false;
}
/**
* AJAX TAB's
* If you want to use an AJAX tab, uncomment the following functions
* Please note that you will need to setup a controller to recieve
* the tab content request
*
*/
/**
* Retrieve the class name of the tab
* Return 'ajax' here if you want the tab to be loaded via Ajax
*
* return string
*/
# public function getTabClass()
# {
# return 'my-custom-tab';
# }
/**
* Determine whether to generate content on load or via AJAX
* If true, the tab's content won't be loaded until the tab is clicked
* You will need to setup a controller to handle the tab request
*
* #return bool
*/
# public function getSkipGenerateContent()
# {
# return false;
# }
/**
* Retrieve the URL used to load the tab content
* Return the URL here used to load the content by Ajax
* see self::getSkipGenerateContent & self::getTabClass
*
* #return string
*/
# public function getTabUrl()
# {
# return null;
# }
}
app/design/adminhtml/default/default/layout/customtabs.xml
<?xml version="1.0"?>
<layout>
<adminhtml_catalog_product_edit>
<reference name="product_tabs">
<action method="addTab">
<name>my_custom_tab</name>
<block>customtabs/adminhtml_catalog_product_tab</block>
</action>
</reference>
</adminhtml_catalog_product_edit>
</layout>
app/design/adminhtml/default/default/template/customtabs/catalog/product/tab.phtml
<div class="input-field">
<label for="custom_field">Custom Field</label>
<input type="text" class="input-text" name="custom_field" id="custom_field" />
</div>
I have checked the code twice still the tab does not appear, am I missing something? How do I check if I have made a mistake in my module?
To be sure I have also added the model code
app/code/local/Fishpig/Customtabs/Model/Observer.php
class Fishpig_Customtabs_Model_Observer
{
/**
* Flag to stop observer executing more than once
*
* #var static bool
*/
static protected $_singletonFlag = false;
/**
* This method will run when the product is saved from the Magento Admin
* Use this function to update the product model, process the
* data or anything you like
*
* #param Varien_Event_Observer $observer
*/
public function saveProductTabData(Varien_Event_Observer $observer)
{
if (!self::$_singletonFlag) {
self::$_singletonFlag = true;
$product = $observer->getEvent()->getProduct();
try {
/**
* Perform any actions you want here
*
*/
$customFieldValue = $this->_getRequest()->getPost('custom_field');
/**
* Uncomment the line below to save the product
*
*/
//$product->save();
}
catch (Exception $e) {
Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
}
}
}
/**
* Retrieve the product model
*
* #return Mage_Catalog_Model_Product $product
*/
public function getProduct()
{
return Mage::registry('product');
}
/**
* Shortcut to getRequest
*
*/
protected function _getRequest()
{
return Mage::app()->getRequest();
}
}
Still it does not work. I'm new to magento and I have no idea what has gone wrong. Can someone please point out my mistake or a sample code?
My full code can be found here and the archive can be found here.
Your extension will not working because of the following points
1. Disable the Magento Compiler functionality
The Magento Compiler is found through System > Tools > Compilation. The compiler collects Magento's files and stores a compressed copy under /includes/src. Every time your files change, you must refresh the Compiler by clicking 'Run Compilation Process'. This both refreshes the compressed files as well as enable the Compiler.
Make sure the Compiler is disabled nefore continuing.
2. Check read/write permissions :
Although usually not necessary, in some cases you may need to verify the copied files permissions. On Unix/Linux systems you should set the files to readable by the server
3. Flush caches
Once you have disabled the compiler and copied the files it is time to flush the Magento caches. Go to System > Cache Management and flush all caches, including CSS, image and external caches.
3. Log out of Magento and log back in
Now it is necessary to log out of Magento's admin, and log back in. This is needed to enable any new permissions for the permission system.
**4 Confirm Extension Conflation **
Must be confirm your new extension whether conflict to another controller or no?
Look How to install an Appmerce Magento extension manually
In order to register the custom tab in the new product and the edit product
I changed my code to
app/design/adminhtml/default/default/layout/customtabs.xml
<?xml version="1.0"?>
<layout>
<adminhtml_catalog_product_new>
<reference name="product_tabs">
<action method="addTab">
<name>my_custom_tab</name>
<block>customtabs/adminhtml_catalog_product_tab</block>
</action>
</reference>
</adminhtml_catalog_product_new>
<adminhtml_catalog_product_edit>
<reference name="product_tabs">
<action method="addTab">
<name>my_custom_tab</name>
<block>customtabs/adminhtml_catalog_product_edit</block>
</action>
</reference>
</adminhtml_catalog_product_edit>
</layout>
and added another block called Edit.php besides Tab.php
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];
}
}
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.