Category: Magento

Magento Trashcan extension

It might have happened with each of the Magento store owners out there that you deleted any product by mistakes. This extension might come in handy at that time to recovers those deleted products from the trashcan. A must have extension for all Magento stores.

Feature:

      Simple and quick installation
      Restore of products, including metadata and images
      100% open source
      No limitations, no extra costs
      Restore easily from the trashcan (Trashcan > Manage Trashcan)

Quick steps to get it working
1. Download TrashCan from here
2. Copy all file to your site
3. Clear cache.

Done!

View and Filter Product sku and name in Magento sales order grid

This feature is really very important, but by default this feature is missing. So lets do it.
NameSpace : MyPackage and Module: MyModule

Module configuration

<!-- app/etc/modules/MyPackage_MyModule.xml -->
<config>
    <modules>
        <MyPackage_MyModule>
            <active>true</active>
            <codePool>local</codePool>
        </MyPackage_MyModule>
    </modules>
</config>

Create config file for this module

<!-- app/code/local/MyPackage/MyModule/etc/config.xml -->
<?xml version="1.0"?>
<config>
    <modules>
        <MyPackage_MyModule>
            <version>0.0.0.1</version>
        </MyPackage_MyModule>
    </modules>
    <global>
        <blocks>
            <mymodule>
                <class>MyPackage_MyModule_Block</class>
            </mymodule>
            <adminhtml>
                <rewrite>
                    <sales_order_grid>MyPackage_MyModule_Block_Adminhtml_Sales_Order_Grid</sales_order_grid>
                </rewrite>
            </adminhtml>
        </blocks>
    </global>
</config>

Create a Grid.php file in the following location.

<?php
// app/code/local/MyPackage/MyModule/Block/Adminhtml/Sales/Order/Grid.php
class MyPackage_MyModule_Block_Adminhtml_Sales_Order_Grid extends Mage_Adminhtml_Block_Widget_Grid
{
    
}

Copy all content from /app/code/core/Mage/Adminhtml/Block/Sales/Order/grid.php into app/code/local/MyPackage/MyModule/Block/Adminhtml/Sales/Order/Grid.php.

<?php
// app/code/local/MyPackage/MyModule/Block/Adminhtml/Sales/Order/Grid.php
class MyPackage_MyModule_Block_Adminhtml_Sales_Order_Grid extends Mage_Adminhtml_Block_Widget_Grid
{
    public function __construct()
    {
        parent::__construct();
        $this->setId('sales_order_grid');
        $this->setUseAjax(true);
        $this->setDefaultSort('created_at');
        $this->setDefaultDir('DESC');
        $this->setSaveParametersInSession(true);
    }

    /**
     * Retrieve collection class
     *
     * @return string
     */
    protected function _getCollectionClass()
    {
        return 'sales/order_grid_collection';
    }
---------------
--------
----------
    public function getGridUrl()
    {
        return $this->getUrl('*/*/grid', array('_current'=>true));
    }
}

Now replace _prepareCollection() function to the following code,

protected function _prepareCollection()
    {
        $collection = Mage::getResourceModel($this->_getCollectionClass())
            ->join(
                'sales/order_item',
                '`sales/order_item`.order_id=`main_table`.entity_id',
                array(
                    'sku'  => new Zend_Db_Expr('group_concat(`sales/order_item`.sku SEPARATOR ",")'),
                    'name' => new Zend_Db_Expr('group_concat(`sales/order_item`.name SEPARATOR ",")'),
                )
            );
        $collection->getSelect()->group('entity_id');
        $this->setCollection($collection);
        return parent::_prepareCollection();
    }

After adding this function lets add the columns in grid for sku and product name inside _prepareColumns() function.

$this->addColumn('sku', array(
            'header'    => Mage::helper('Sales')->__('Skus'),
            'width'     => '100px',
            'index'     => 'sku',
            'type'        => 'text',
        ));

        $this->addColumn('name', array(
            'header'    => Mage::helper('Sales')->__('Product Names'),
            'width'     => '100px',
            'index'     => 'name',
            'type'        => 'text',
        ));

Enjoy!

Programmatically change magento theme in runtime

Magento is that platform you can provide a solution in hundred way. But you need to know which is the best. Today I discuss about Magento theme change in runtime, that means your system work on many themes. So lets start. Today I discuss two possible way to do this.

Possible way 1:

Write below code in action to set Package and theme for the action

  Mage::getDesign()->setArea('frontend') //Area (frontend/adminhtml)
            ->setPackageName('default') //Name of Package
            ->setTheme('modern'); // Name of theme

Possible way 2 :

Overwrite Mage_Core_Model_Design in following way.

<models>
    <core>
        <rewrite>
            <design>Packagename_Modulename_Model_Design</design>
        </rewrite>
    </core>
</models>

Modify loadChange method as required, I just place an example of that, see following.

public function loadChange($storeId, $date = null)
    {
        $result = $this->getResource()
            ->loadChange($storeId, $date);

        if (!empty($result)) {
            if (!empty($result['design'])) {
                $tmp = explode('/', $result['design']);
                $result['package'] = $tmp[0];
                $result['theme'] = $tmp[1];
            }

            $this->setData($result);
        }
        // your custom code goes here
        // custom condition
        if(true) {
            $result['package'] = 'default';
            $result['theme'] = 'modern';
            $this->setData($result);
        }
        
        return $this;
    }

That’s it.

Creating custom shipping method in magento

This tutorial is dedicated to Magento Certification. Let’s start.

NameSpace : MyPackage and Module: CustomShipping

Register the module

<!-- app/etc/modules/MyPackage_CustomShipping.xml -->
<?xml version="1.0"?>
<config>
    <modules>
        <MyPackage_CustomShipping>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Shipping />
            </depends>
        </MyPackage_CustomShipping>
    </modules>
</config>


Create the config

<!-- app/code/local/MyPackage/CustomShipping/etc/config.xml -->
<?xml version="1.0"?>
<config>
    <modules>
        <MyPackage_CustomShipping>
            <version>0.0.0.1</version>
        </MyPackage_CustomShipping>
    </modules>
    <global>
        <models>
            <customshipping>
                <class>MyPackage_CustomShipping_Model</class>
            </customshipping>
        </models>
    </global>
    <!-- Default configuration -->
    <default>
        <carriers>
            <customshipping>
                <active>0</active>
                <title>Custom Shipping</title>
                <name>Standard</name>
                <price>9</price>
                <!-- this model hold all logic of custom module -->
                <model>customshipping/carrier_customshipping</model>
                <sallowspecific>0</sallowspecific>
                <sort_order>0</sort_order>
            </customshipping>
        </carriers>
    </default>
</config>

Adapter model
To create our shipping carrier, we need to extend Mage_Shipping_Model_Carrier_Abstract, implement Mage_Shipping_Model_Carrier_Interface and add the required abstract methods.

The most important method is collectRates and getAllowedMethods. collectRates is the method that receives a shipping request, appends applicable shipping methods and returns a shipping result.

<?php
//app/code/local/MyPackage/CustomShipping/Model/Carrier/Customshipping.php
class MyPackage_CustomShipping_Model_Carrier_Customshipping
    extends Mage_Shipping_Model_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface
{
    protected $_code = 'customshipping';

    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    {

        return Mage::getModel('shipping/rate_result');
    }

    public function getAllowedMethods()
    {
        return array('custom_shipping'=>$this->getConfigData('name'));
    }
}

This is the skeleton for a shipping method class, it has no meaning if it has no shipping method.

Now creating meaningful of this method, suppose we use configure price from admin. Default method “Standard” and price is “9”.

<?php
//app/code/local/MyPackage/CustomShipping/Model/Carrier/Customshipping.php
class MyPackage_CustomShipping_Model_Carrier_Customshipping
    extends Mage_Shipping_Model_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface
{
    protected $_code = 'customshipping';

    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    {
        if (!$this->getConfigFlag('active')) {
            return false;
        }

        $result = Mage::getModel('shipping/rate_result');
        $method = Mage::getModel('shipping/rate_result_method');

        $method->setCarrier('customshipping');
        $method->setCarrierTitle($this->getConfigData('title'));

        $method->setMethod('custom_shipping');
        $method->setMethodTitle($this->getConfigData('name'));

        $method->setPrice($this->getConfigData('price'));
        $method->setCost($this->getConfigData('price'));

        $result->append($method);

        return $result;
    }

    public function getAllowedMethods()
    {
        return array('custom_shipping'=>$this->getConfigData('name'));
    }
}

System Configuration

<!-- app/code/local/MyPackage/CustomShipping/etc/system.xml -->
<?xml version="1.0"?>
<config>
    <sections>
        <carriers>
            <groups>
                <customshipping translate="label">
                    <label>Custom Configurable Shipping</label>
                    <frontend_type>text</frontend_type>
                    <sort_order>2</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>1</show_in_website>
                    <show_in_store>1</show_in_store>
                    <fields>
                        <active translate="label">
                            <label>Enabled</label>
                            <frontend_type>select</frontend_type>
                            <source_model>adminhtml/system_config_source_yesno</source_model>
                            <sort_order>1</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>0</show_in_store>
                        </active>
                        <name translate="label">
                            <label>Method Name</label>
                            <frontend_type>text</frontend_type>
                            <sort_order>3</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </name>
                        <price translate="label">
                            <label>Fixed Price</label>
                            <frontend_type>text</frontend_type>
                            <validate>validate-number validate-zero-or-greater</validate>
                            <sort_order>5</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>0</show_in_store>
                        </price>
                        <sort_order translate="label">
                            <label>Sort Order</label>
                            <frontend_type>text</frontend_type>
                            <sort_order>100</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>0</show_in_store>
                        </sort_order>
                        <title translate="label">
                            <label>Title</label>
                            <frontend_type>text</frontend_type>
                            <sort_order>2</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </title>
                    </fields>
                </customshipping>
            </groups>
        </carriers>
    </sections>
</config>

This is simple shipping method module creation.
N.B: Inside shipping method model class, you should take care of following condition

1. $item->getHasChildren() && $item->isShipSeparately()
2. $item->getProduct()->isVirtual() == true
3. $item->getProduct()->isVirtual() || $item->getParentItem()
4. $item->getFreeShipping() == true

Happy Coding!

Add captcha to contact form in magento

If you need to add captcha in contacts page, you need to add this. So lets start how to add captcha in contacts page.
NameSpace : MyPackage and Module: MyModule

Module configuration

<!-- app/etc/modules/MyPackage_MyModule.xml -->
<config>
    <modules>
        <MyPackage_MyModule>
            <active>true</active>
            <codePool>local</codePool>
        </MyPackage_MyModule>
    </modules>
</config>

Create config file for this module

<!-- app/code/local/MyPackage/MyModule/etc/config.xml -->
<?xml version="1.0"?>
<config>
    <modules>
        <MyPackage_MyModule>
            <version>0.0.0.1</version>
        </MyPackage_MyModule>
    </modules>
    <global>
        <models>
            <mymodule>
                <class>MyPackage_MyModule_Model</class>
            </mymodule>
        </models>
        <events>
            <controller_action_predispatch_contacts_index_post>
                <observers>
                    <mymodule>
                        <class>mymodule/observer</class>
                        <method>checkContacts</method>
                    </mymodule>
                </observers>
            </controller_action_predispatch_contacts_index_post>
        </events>
    </global>
    <default>
        <captcha>
            <frontend>
                <areas>
                    <contacts>
                        <label>Contacts Page</label>
                    </contacts>
                </areas>
            </frontend>
        </captcha>
        <customer>
            <captcha>
                <always_for>
                    <contacts>1</contacts>
                </always_for>
            </captcha>
        </customer>
    </default>
</config>

Create a observer

<?php
// app/code/local/MyPackage/MyModule/Model/Observer.php
class MyPackage_MyModule_Model_Observer
{
    public function checkContacts($observer){
        $formId = 'contacts';
        $captchaModel = Mage::helper('captcha')->getCaptcha($formId);
        if ($captchaModel->isRequired()) {
            $controller = $observer->getControllerAction();
            $word = $this->_getCaptchaString($controller->getRequest(), $formId);
            if (!$captchaModel->isCorrect($word)) {
                Mage::getSingleton('customer/session')->addError(Mage::helper('captcha')->__('Incorrect CAPTCHA.'));
                $controller->setFlag('', Mage_Core_Controller_Varien_Action::FLAG_NO_DISPATCH, true);
                $url =  Mage::getUrl('contacts');
                $controller->getResponse()->setRedirect($url);
            }
        }
        return $this;
    }
    /**
     * Get Captcha String
     *
     * @param Varien_Object $request
     * @param string $formId
     * @return string
     */
    protected function _getCaptchaString($request, $formId)
    {
        $captchaParams = $request->getPost(Mage_Captcha_Helper_Data::INPUT_NAME_FIELD_VALUE);
        return $captchaParams[$formId];
    }
}

Create a local.xml to your active theme inside layout folder

<?xml version="1.0"?>
<layout version="0.1.0">
    <contacts_index_index>
        <reference name="contactForm">
            <action method="setTemplate"><template>mymodule/contacts/form.phtml</template></action>
            <block type="core/text_list" name="form.additional.info">
                <block type="captcha/captcha" name="captcha">
                    <reference name="head">
                        <action method="addJs"><file>mage/captcha.js</file></action>
                    </reference>
                    <action method="setFormId"><formId>contacts</formId></action>
                    <action method="setImgWidth"><width>230</width></action>
                    <action method="setImgHeight"><width>50</width></action>
                </block>
            </block>
        </reference>
    </contacts_index_index>
</layout>

Now copy “contacts/form.phtml” to “mymodule/contacts/form.phtml”, add

 <?php echo $this->getChildHtml('form.additional.info'); ?>

to your requirement. example…

.....
<li class="wide">
                <label for="comment" class="required"><em>*</em><?php echo Mage::helper('contacts')->__('Comment') ?></label>
                <div class="input-box">
                    <textarea name="comment" id="comment" title="<?php echo Mage::helper('contacts')->__('Comment') ?>" class="required-entry input-text" cols="5" rows="3"></textarea>
                </div>
            </li>
            <?php echo $this->getChildHtml('form.additional.info'); ?>
        </ul>
....

Clear magento cache.

Now Go to System -> Configuration -> Customer Configuration -> Captcha. Select Contact Page and Save.

Happy Coding!

Keep product in the wishlist after adding to cart in magento

In Magento, after adding product from wishlist, they are completely remove from wishlist. If you need to keep this to wishlist after adding product and if it is fully control from admin, Then you need to code something. Today I will discuss about this topic that is depend on configuration so any time you can change it from admin.

Suppose NameSpace : MyPackage and Module : MyModule

Now create a module configuration file,name is MyPackage_MyModule.xml and location is

app/etc/modules/MyPackage_MyModule.xml
<?xml version="1.0"?>
<config>
    <modules>
        <MyPackage_MyModule>
            <active>true</active>
            <codePool>local</codePool>
        </MyPackage_MyModule>
    </modules>
</config>

Create configuration file

app/code/local/MyPackage/MyModule/etc/config.xml
<?xml version="1.0"?>
<config>
    <modules>
        <MyPackage_MyModule>
            <version>0.0.0.1</version>
        </MyPackage_MyModule>
    </modules>
    <global>
        <helpers>
            <mymodule>
                <class>MyPackage_MyModule_Helper</class>
            </mymodule>
        </helpers>
    </global>
    <frontend>
        <routers>
            <wishlist>
                <args>
                    <modules>
                        <mypackage_mymodule before="Mage_Wishlist">MyPackage_MyModule</mypackage_mymodule>
                    </modules>
                </args>
            </wishlist>
        </routers>
    </frontend>
</config>

Create System Configuration file

app/code/local/MyPackage/MyModule/etc/system.xml
<config>
    <tabs>
        <mymodule translate="label" module="mymodule">
            <label>My Module</label>
            <sort_order>100</sort_order>
        </mymodule>
    </tabs>
    <sections>
        <mypackage_mymodule>
            <label>My Module Configuration</label>
            <sort_order>1</sort_order>
            <tab>mymodule</tab>
            <show_in_default>1</show_in_default>
            <show_in_website>1</show_in_website>
            <show_in_store>1</show_in_store>
            <groups>
                <configuration translate="label">
                    <label>Keep products in the wishlist after adding them to cart configuration</label>
                    <frontend_type>text</frontend_type>
                    <sort_order>2</sort_order>
                    <show_in_default>1</show_in_default>
                    <show_in_website>1</show_in_website>
                    <show_in_store>1</show_in_store>
                    <fields>
                        <keep_item translate="label">
                            <label>Keep item after adding them to cart</label>
                            <frontend_type>select</frontend_type>
                            <source_model>adminhtml/system_config_source_yesno</source_model>
                            <sort_order>2</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>0</show_in_store>
                        </keep_item>
                    </fields>
                </configuration>
            </groups>
        </mypackage_mymodule>
    </sections>
</config>

Create ACL file

app/code/local/MyPackage/MyModule/etc/adminhtml.xml
<config>
    <acl>
        <resources>
            <admin>
                <children>
                    <system>
                        <children>
                            <config>
                                <children>
                                    <mypackage_mymodule translate="title" module="mymodule">
                                        <title>MyModule Section</title>
                                    </mypackage_mymodule>
                                </children>
                            </config>
                        </children>
                    </system>
                </children>
            </admin>
        </resources>
    </acl>
</config>

Create helper file

app/code/local/MyPackage/MyModule/Helper/Data.php
<?php
class MyPackage_MyModule_Helper_Data extends Mage_Core_Helper_Abstract
{

}

Now Create Controller file

app/code/local/MyPackage/MyModule/controllers/IndexController.php
<?php
require_once Mage::getModuleDir('controllers', 'Mage_Wishlist').DS.'IndexController.php';
class MyPackage_MyModule_IndexController extends Mage_Wishlist_IndexController
{
    /**
     * Add wishlist item to shopping cart and remove from wishlist
     *
     * If Product has required options - item removed from wishlist and redirect
     * to product view page with message about needed defined required options
     */
    public function cartAction()
    {
        if (!$this->_validateFormKey()) {
            return $this->_redirect('*/*');
        }
        $itemId = (int) $this->getRequest()->getParam('item');

        /* @var $item Mage_Wishlist_Model_Item */
        $item = Mage::getModel('wishlist/item')->load($itemId);
        if (!$item->getId()) {
            return $this->_redirect('*/*');
        }
        $wishlist = $this->_getWishlist($item->getWishlistId());
        if (!$wishlist) {
            return $this->_redirect('*/*');
        }

        // Set qty
        $qty = $this->getRequest()->getParam('qty');
        if (is_array($qty)) {
            if (isset($qty[$itemId])) {
                $qty = $qty[$itemId];
            } else {
                $qty = 1;
            }
        }
        $qty = $this->_processLocalizedQty($qty);
        if ($qty) {
            $item->setQty($qty);
        }

        /* @var $session Mage_Wishlist_Model_Session */
        $session    = Mage::getSingleton('wishlist/session');
        $cart       = Mage::getSingleton('checkout/cart');

        $redirectUrl = Mage::getUrl('*/*');

        try {
            $options = Mage::getModel('wishlist/item_option')->getCollection()
                    ->addItemFilter(array($itemId));
            $item->setOptions($options->getOptionsByItem($itemId));

            $buyRequest = Mage::helper('catalog/product')->addParamsToBuyRequest(
                $this->getRequest()->getParams(),
                array('current_config' => $item->getBuyRequest())
            );

            $item->mergeBuyRequest($buyRequest);
            $keepItem = Mage::getStoreConfig('mypackage_mymodule/configuration/keep_item');
            $keepItem = $keepItem?false:true;
            if ($item->addToCart($cart, $keepItem)) {
                $cart->save()->getQuote()->collectTotals();
            }

            $wishlist->save();
            Mage::helper('wishlist')->calculate();

            if (Mage::helper('checkout/cart')->getShouldRedirectToCart()) {
                $redirectUrl = Mage::helper('checkout/cart')->getCartUrl();
            } else if ($this->_getRefererUrl()) {
                $redirectUrl = $this->_getRefererUrl();
            }
            Mage::helper('wishlist')->calculate();
        } catch (Mage_Core_Exception $e) {
            if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_NOT_SALABLE) {
                $session->addError($this->__('This product(s) is currently out of stock'));
            } else if ($e->getCode() == Mage_Wishlist_Model_Item::EXCEPTION_CODE_HAS_REQUIRED_OPTIONS) {
                Mage::getSingleton('catalog/session')->addNotice($e->getMessage());
                $redirectUrl = Mage::getUrl('*/*/configure/', array('id' => $item->getId()));
            } else {
                Mage::getSingleton('catalog/session')->addNotice($e->getMessage());
                $redirectUrl = Mage::getUrl('*/*/configure/', array('id' => $item->getId()));
            }
        } catch (Exception $e) {
            Mage::logException($e);
            $session->addException($e, $this->__('Cannot add item to shopping cart'));
        }

        Mage::helper('wishlist')->calculate();

        return $this->_redirectUrl($redirectUrl);
    }
}

Clear cache and configure from System -> Configuration -> My Module Configuration

Happy Coding!

Magento bundle products items – selection_id cannot be null?

In magento bundle product, when you save bundle item in website scope. It’s throw an error that is “SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘selection_id’ cannot be null”. To generate this error go to System -> Configuration -> Catalog -> Price . Select “Catalog Price Scope” to “Website”. Then Create a bundle product, Yes you can see this error. Any Idea? What’s wrong here? I think this is known issue from magento, this type of error comes from all version of magento.

How comes?
In magento, bundle items are save “catalog_product_bundle_selection” table. When you save this item as a scope of website label, then this will be related to another table called “catalog_product_bundle_selection_price”. Where selection_id column is a foreign key of “catalog_product_bundle_selection” table selection_id column. When you save this item as website label, it’s try to save “catalog_product_bundle_selection_price” first, That’s why this error comes.

What’s Solution
Solution is simple šŸ™‚ . Overwrite Mage_Bundle_Model_Selection model class and codes are look like

class Package_MyModule_Model_Selection extends Mage_Bundle_Model_Selection
{
    /**
     * Processing object before save data
     *
     * @return Mage_Bundle_Model_Selection
     */
    protected function _beforeSave()
    {
        // No code please
    }

    /**
     * Processing object after save data
     *
     * @return Mage_Bundle_Model_Selection
     */
    protected function _afterSave()
    {
        $storeId = Mage::registry('product')->getStoreId();
        if (!Mage::helper('catalog')->isPriceGlobal() && $storeId) {
            $this->setWebsiteId(Mage::app()->getStore($storeId)->getWebsiteId());

            $this->getResource()->saveSelectionPrice($this);

            if (!$this->getDefaultPriceScope()) {
                $this->unsSelectionPriceValue();
                $this->unsSelectionPriceType();
            }
        }
        parent::_afterSave();
    }
}

Happy Coding šŸ™‚