Extra Renderer for Dynamic Thumbnails in Cart

(Magento 1.9.2.2 on Debian Jessie, running PHP 5.6.30 with Imagick 3.1.2)

In some cases it becomes necessary to show Dynamic Thumbnails, like SVG or canvas-gernerated images, in your cart rather than static product photos. In this article we address a possible solution for this.

Situation

We’ve developed a module that lets end customers put together their desired color-combination of a product by assigning patterns to a SVG via JavaScript. They can choose between over 50 colors for 4 separate product parts, therefore the number of possible color-combinations is quite huge.

Each color-combination is represented by a simple unqiue code (e.g. A: 22 B:09, C: 01, D: 11) that is stored into a hidden custom option input when added to cart.

Problem

Our item’s cart thumbnail needs to represent the actual color-combination (preferrably SVG into JPG). However, Magento doesn’t make Dynamic Thumbnails possible.

Our Solution

We add a custom cart-renderer that will overwrite Mage_Checkout_Block_Cart_Item_Renderer_Configurable::getProductThumbnail().

Alternatively, you could add the new renderer and assign it to a new template file that will for instance call a new method like getDynamicThumbnail(), without overwriting anything. However, in our case we had to work with the already existing renderer templates.

Step 1

We took a very similar approach like they did in this article. However, we made our module listen to a different Event, sales_quote_item_set_product:

<?xml version="1.0" encoding="utf-8"?>
<config>
	<!-- ... -->
	<frontend>
		<!-- ... -->
		<events>
		<!-- ... -->
			<controller_action_layout_load_before>
				<observers>
					<sales_quote_item_set_product>
				<observers>
					<my_cart_observer>
						<class>my_module/observer</class>
						<method>salesQuoteItemSetProduct</method>
					</my_cart_observer>
				</observers>
			</sales_quote_item_set_product>
		</events>
	</frontend>
	<!-- ... -->
</config>

Step 2

In our Observer method we first check if set quote item is the one attached to our SVG module. In our case we’re storing this product ID in our module’s settings so you might want to adjust this code to your business logic:

class My_Module_Model_Observer
{	 
	public function salesQuoteItemSetProduct(Varien_Event_Observer $observer){
		
		$product = $observer->getEvent()->getProduct();
		
		if($product->getId() != Mage::getStoreConfig('model_config/general/product_id'))
		{
			return $this;
		}
		
		$quoteItem = $observer->getEvent()->getQuoteItem();
		if(!($option = $quoteItem->getOptionByCode('product_type')) 
				|| $option->getValue() != 'customized_product')
		{
			$option = new Varien_Object();
			$option->setData(array(
				'product' => $product,
				'code' => 'product_type',
				'value' => 'customized_product'
			));		
			$quoteItem->addOption($option);
		}
		return $this;
	}	
}

So, what are we doing there? Well, what we don’t do is adding an actual new product type here. No, we simply assign a new option with the code “product_type” and the value “customized_product” (the name of the pseudo product-type; you can choose any name that isn’t already taken by actual product types). Why does this even work? Read up in the above linked article.

Step 3

Now we have to assign our custom Item Renderer, that will generate and add our Dynamic Thumbnails, to our new “pseudo product type”. We do this in a layout file – in our case our module’s own layout file. This is an example:

<?xml version="1.0" encoding="UTF-8" ?>
<layout version="0.1.0">
	<default>
		<reference name="cart_topcart">
        	<action method="addItemRender"><type>customized_product</type><block>my_module/cart_renderer</block><template>your/sidecart/renderer/layout.phtml</template></action>
		</reference>
	</default>	
	<checkout_cart_index>
		<reference name="checkout.cart">
        	<action method="addItemRender"><type>customized_product</type><block>my_module/cart_renderer</block><template>your/cart/renderer/layout.phtml</template></action>
		</reference>
	</checkout_cart_index>
	<checkout_onepage_index>
		<reference name="cart_sidebar">
        	<action method="addItemRender"><type>customized_product</type><block>my_module/cart_renderer</block><template>your/checkout/renderer/layout.phtml</template></action>
		</reference>
	</checkout_onepage_index>
	<checkout_onepage_review>
		<reference name="root">
        	<action method="addItemRender"><type>customized_product</type><block>my_module/cart_renderer</block><template>your/review/renderer/layout.phtml</template></action>
		</reference>
	</checkout_onepage_review>
</layout>

You’d have to adjust this to the renderer-layout files and references you are using. Also, keep in mind that depending on whether you already have other Modules addressing the Checkout/Cart process in place, you want to add them to the <depends> tags in your new module’s my_module.xml.

Step 4

Because our customizable product is a simple product belonging to a configurable product, we let our Renderer extend Magento’s Configurable Renderer:

class My_Module_Block_Cart_Item_Renderer extends Mage_Checkout_Block_Cart_Item_Renderer_Configurable
{	
    /**
     * @return Mage_Catalog_Model_Product_Image
     */
    public function getProductThumbnail()
    {
        $isCustomized	= false;
		$code 			= [];
		$product 		= $this->getChildProduct();
		
		if(($options = $this->getProductOptions())){
			/** 	
			 *	Get and form your necessary code.
			 *	Set $isCustomized to true during this process. 
			**/
		} 
		if( $isCustomized ){
			
			$helper = Mage::helper('my_module');
			/**
			 * We form following variables here:
			 * 
			 * $baseDir - the base path to the directory we'll store the connverted file
			 * $path - the absolute path to the directory we'll store the converted SVG files
			 * $filename - the name of the converted file. In our case it's the code combination.
			 * $svg -	the content of our svg with set color patterns. if you have trouble saving
			 * 			your SVG with patterns, write them directly (base64_encode) into your SVG
			 * 			instead of linking the image file.
			 * 
			**/
			$filepath = $path . DS . $filename;
			if(!is_file($filepath)){
				clearstatcache();
				
				$image = new Imagick();
				$image->readImageBlob($svg);
				
				$image->setImageFormat('jpeg');
      			$image->setImageCompressionQuality(70);
				$image->adaptiveResizeImage(100, 100);
				$image->writeImage($path . DS . $filename);
				
			}	
			return Mage::helper('my_module/image')->init($product, $baseDir . DS . $filename);
			
		} else {
			return parent::getProductThumbnail();
		}
	}
}

Step 5

Normally, getProductThumbnail() is returning Mage_Catalog_Model_Product_Image, called by $this->helper(‘catalog/image’)->init($product, ‘thumbnail’). This module does only access images within {ROOT}/media/catalog/product, however, we store our converted image files in {ROOT}/media/my/module. Therefore we make a custom helper “jump in”:

class My_Module_Helper_Image extends Mage_Core_Helper_Abstract
{
	protected $_product;
    public function init($product, $imageFile)
    {
        $this->setImageFile($imageFile);
        return $this;
    }
    public function resize($width, $height = null)
    {
        return $this;
    }	
    protected function setWatermark($watermark)
    {
        return $this;
    }
    protected function setImageFile($file)
    {
        $this->_imageFile = $file;
        return $this;
    }
    protected function getImageFile()
    {
        return $this->_imageFile;
    }
    public function __toString()
    {
       return Mage::getBaseUrl('media') . $this->getImageFile();
    }
}

In our case this helper doesn’t need to do more at the moment. But we could extend it anytime, if necessary.

This is pretty much it.

Let us know if you have ideas on improvements or some related questions!