Sending shipment email when tracking info is added

January 22, 2014  |  No Comments  |  by Raj (MagePsycho)  |  Latest, Magento

From the topic of this blog post it’s clear that we are going to discuss on ‘How to send shipment email when tracking info is added to the system?’
This may by useful if carrier tracking info is pushed to Magento by some third party system and want to send shipment email.

As usual, instead of rewriting core classes we will be using event-observer pattern.
And events used here will be: sales_order_shipment_save_before & sales_order_shipment_save_after

1. Register the events: sales_order_shipment_save_before & sales_order_shipment_save_after
File: app/code/local/MagePsycho/Shipmentemail/etc/config.xml

...
<global>
    <events>
        <sales_order_shipment_save_before>
            <observers>
                <magepsycho_shipmentemail_sales_order_shipment_save_before>
                    <type>singleton</type>
                    <class>magepsycho_shipmentemail/observer</class>
                    <method>salesOrderShipmentSaveBefore</method>
                </magepsycho_shipmentemail_sales_order_shipment_save_before>
            </observers>
        </sales_order_shipment_save_before>
        <sales_order_shipment_save_after>
            <observers>
                <magepsycho_shipmentemail_sales_order_shipment_save_after>
                    <type>singleton</type>
                    <class>magepsycho_shipmentemail/observer</class>
                    <method>salesOrderShipmentSaveAfter</method>
                </magepsycho_shipmentemail_sales_order_shipment_save_after>
            </observers>
        </sales_order_shipment_save_after>
    </events>
</global>
...

2. Send email using observer model
File: app/code/local/MagePsycho/Shipmentemail/Model/Observer.php

<?php
/**
 * @category   MagePsycho
 * @package    MagePsycho_Shipmentemail
 * @author     magepsycho@gmail.com
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class MagePsycho_Shipmentemail_Model_Observer
{
    protected function _isValidForShipmentEmail($shipment)
    {
        $trackingNumbers = array();
        foreach ($shipment->getAllTracks() as $track) {
            $trackingNumbers[] = $track->getNumber();
        };
        // send shipment email only when carrier tracking info is added
        if (count($trackingNumbers) > 0) {
            return true;
        } else {
            return false;
        }
    }
    
    public function salesOrderShipmentSaveBefore(Varien_Event_Observer $observer)
    {
        if (Mage::registry('salesOrderShipmentSaveBeforeTriggered')) {
            return $this;
        }

        /* @var $shipment Mage_Sales_Model_Order_Shipment */
        $shipment = $observer->getEvent()->getShipment();
        if ($shipment) {
            if ($this->_isValidForShipmentEmail($shipment)) {
                $shipment->setEmailSent(true);
                Mage::register('salesOrderShipmentSaveBeforeTriggered', true);
            }
        }
        return $this;
    }
    
    public function salesOrderShipmentSaveAfter(Varien_Event_Observer $observer)
    {
        if (Mage::registry('salesOrderShipmentSaveAfterTriggered')) {
            return $this;
        }
       
        /* @var $shipment Mage_Sales_Model_Order_Shipment */
        $shipment = $observer->getEvent()->getShipment();
        if ($shipment) {
            if ($this->_isValidForShipmentEmail($shipment)) {
                $shipment->sendEmail();
                Mage::register('salesOrderShipmentSaveAfterTriggered', true);
            }
        }
        return $this;
    }
}

Notes:

  • We have used both events: sales_order_shipment_save_before & sales_order_shipment_save_after. _before is used to set the email_sent flag to true and _after event to send email.
  • If you combine methods: $shipment->setEmailSent(true) and $shipment->sendEmail() in _before event, you will get shipment email but without shipment number.
  • If you combine methods: $shipment->setEmailSent(true) and $shipment->sendEmail() in _after event, it will go in recursive infinite loop sending you the millions of email.

Thanks for reading & sharing.

Product List: Show out of stock products in last

December 18, 2013  |  No Comments  |  by Raj (MagePsycho)  |  Latest, Magento

In this blog post we will be sharing how to show products in last of product listing page.

Assumption: MagePsycho_Productsort is the skeleton module

Steps:
1. Register event: catalog_product_collection_load_before
File: app/code/local/MagePsycho/Productsort/etc/config.xml

...
<frontend>
    <events>
        <catalog_product_collection_load_before>
            <observers>
                <magepsycho_productsort_catalog_product_collection_load_before>
                    <type>singleton</type>
                    <class>magepsycho_productsort/observer</class>
                    <method>catalogProductCollectionLoadBefore</method>
                </magepsycho_productsort_catalog_product_collection_load_before>
            </observers>
        </catalog_product_collection_load_before>
    </events>
</frontend>
...

2. Implement observer model:
File: app/code/local/MagePsycho/Productsort/Model/Observer.php

<?php
/**
 * @category   MagePsycho
 * @package    MagePsycho_Productsort
 * @author     magepsycho@gmail.com
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class MagePsycho_Productsort_Model_Observer
{

    public function catalogProductCollectionLoadBefore(Varien_Event_Observer $observer)
    {
        $collection = $observer->getCollection();
        $collection->getSelect()->joinLeft(
            array('_inventory_table'=>$collection->getTable('cataloginventory/stock_item')),
            "_inventory_table.product_id = e.entity_id",
            array('is_in_stock', 'manage_stock')
        );
        $collection->addExpressionAttributeToSelect(
            'on_top',
            '(CASE WHEN (((_inventory_table.use_config_manage_stock = 1) AND (_inventory_table.is_in_stock = 1)) OR  ((_inventory_table.use_config_manage_stock = 0) AND (1 - _inventory_table.manage_stock + _inventory_table.is_in_stock >= 1))) THEN 1 ELSE 0 END)',
            array()
        );
        $collection->getSelect()->order('on_top DESC');
        // Make sure on_top is the first order directive
        $order = $collection->getSelect()->getPart('order');
        array_unshift($order, array_pop($order));
        $collection->getSelect()->setPart('order', $order);
    }
}

3. Bingo!
Out of Stock products

Extend Magento sales order grid using event observer

November 17, 2013  |  No Comments  |  by Raj (MagePsycho)  |  Magento

You can add new columns to the Sales Order grid easily by extending the block class: Mage_Adminhtml_Block_Sales_Order_Grid
But this is not the preferred way due to conflict issues.

Here I will show you how to extend order grid by using events(core_block_abstract_to_html_before & sales_order_grid_collection_load_before) only.

For example purpose we will be adding Payment Method to sales order grid and MagePsycho_Gridextend will be the skeleton extension.
1. Register the events:

...
<adminhtml>
    <events>
        <core_block_abstract_to_html_before>
            <observers>
                <magepsycho_gridextend_core_block_abstract_to_html_before>
                    <class>magepsycho_gridextend/observer</class>
                    <method>coreBlockAbstractToHtmlBefore</method>
                </magepsycho_gridextend_core_block_abstract_to_html_before>
            </observers>
        </core_block_abstract_to_html_before>
        <sales_order_grid_collection_load_before>
            <observers>
                <magepsycho_gridextend_sales_order_grid_collection_load_before>
                    <class>magepsycho_gridextend/observer</class>
                    <method>salesOrderGridCollectionLoadBefore</method>
                </magepsycho_gridextend_sales_order_grid_collection_load_before>
            </observers>
        </sales_order_grid_collection_load_before>
    </events>
</adminhtml>
...

2. Create the observer model:

<?php
/**
 * @category   MagePsycho
 * @package    MagePsycho_Gridextend
 * @author     magepsycho@gmail.com
 * @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
class MagePsycho_Gridextend_Model_Observer {

    /**
     * Moved to Block class
     * @param Varien_Event_Observer $observer
     */
    public function coreBlockAbstractToHtmlBefore(Varien_Event_Observer $observer)
    {
        /** @var $block Mage_Core_Block_Abstract */
        $block = $observer->getEvent()->getBlock();
        if ($block->getId() == 'sales_order_grid') {
            
            //add new column: payment method
            $paymentArray = Mage::getSingleton('payment/config')->getActiveMethods();
            $paymentMethods = array();
            foreach ($paymentArray as $code => $payment) {
                // not sure why ops_dl was not in the loop so tweaked it
                $paymentTitle = Mage::getStoreConfig('payment/'.$code.'/title');
                $paymentMethods[$code] = $paymentTitle;
            }
            $block->addColumnAfter(
                'payment_method',
                array(
                    'header'   => Mage::helper('sales')->__('Payment Method'),
                    'align'    => 'left',
                    'type'     => 'options',
                    'options'  => $paymentMethods,
                    'index'    => 'payment_method',
                    'filter_index'    => 'payment.method',
                ),
                'shipping_name'
            );
            
            //similary you can addd new columns
            //...

            // Set the new columns order.. otherwise our column would be the last one
            $block->sortColumnsByOrder();
        }
    }

    /**
     * Moved to block class
     * @param Varien_Event_Observer $observer
     */
    public function salesOrderGridCollectionLoadBefore(Varien_Event_Observer $observer)
    {
        $collection = $observer->getOrderGridCollection();
        $select = $collection->getSelect();
        $select->joinLeft(array('payment' => $collection->getTable('sales/order_payment')), 'payment.parent_id=main_table.entity_id',array('payment_method' => 'method'));
    }
}

3. Refresh the Sales > Order page, you will see page similar to:
Sales Order Grid

Similarly, you can extend any admin grid like sales invoice, customer etc.

Change shipping price & handling fee on the fly in Magento

October 10, 2013  |  1 Comments  |  by Raj (MagePsycho)  |  Magento

Problem: How do I change shipping prices and handling fees on the fly?

You may want to have different shipping prices for different scenarios. Your scenario could be any of the following:

  1. 1. Different shipping price based on shipping address
  2. 2. Different shipping price based on weight
  3. 3. Different shipping price based on categories
  4. 4. Different shipping price based on no of items
  5. 5. Different shipping price based on your custom module
  6. 6. Different shipping price based on some conditions

There may be many solutions. A flexible solution which works for most cases is to develop a custom module which implements the event called ‘sales_quote_collect_totals_before’

Solution:

Assuming the skeleton module: MagePsycho_Shipmentfilter has already been created.
1. Register the event ‘sales_quote_collect_totals_before’:
Add the following xml code in app/code/local/MagePsycho/Shipmentfilter/etc/config.xml

...
<events>
    <sales_quote_collect_totals_before>
        <observers>
            <your_module>
                <type>singleton</type>
                <class>shipmentfilter/observer</class>
                <method>salesQuoteCollectTotalsBefore</method>
            </your_module>
        </observers>
    </sales_quote_collect_totals_before>
</events>
...

2. Implement the observer model:
Create the observer file: app/code/local/MagePsycho/Shipmentfilter/Model/Observer.php and add the following code:

class MagePsycho_Shipmentfilter_Model_Observer
{
    public function salesQuoteCollectTotalsBefore(Varien_Event_Observer $observer)
    {
        /** @var Mage_Sales_Model_Quote $quote */
        $quote = $observer->getQuote();
        $someConditions = true; //this can be any condition based on your requirements
        $newHandlingFee = 15;
        $store    = Mage::app()->getStore($quote->getStoreId());
        $carriers = Mage::getStoreConfig('carriers', $store);
        foreach ($carriers as $carrierCode => $carrierConfig) {
            if($carrierCode == 'fedex'){
                if($someConditions){
                    Mage::log('Handling Fee(Before):' . $store->getConfig("carriers/{$carrierCode}/handling_fee"), null, 'shipping-price.log');
                    $store->setConfig("carriers/{$carrierCode}/handling_type", 'F'); #F - Fixed, P - Percentage                  
                    $store->setConfig("carriers/{$carrierCode}/handling_fee", $newHandlingFee);
    
                    ###If you want to set the price instead of handling fee you can simply use as:
                    #$store->setConfig("carriers/{$carrierCode}/price", $newPrice);
    
                    Mage::log('Handling Fee(After):' . $store->getConfig("carriers/{$carrierCode}/handling_fee"), null, 'shipping-price.log');
                }
            }
        }
    }
}

Notes: Here we are adding handling fee of 15 for Fedex shipping carrier on some conditions.
If you want to set a new price instead of a handling fee then you can simply use:

$store->setConfig("carriers/{$carrierCode}/price", $newPrice);

in above code.
Similarly you can add any custom shipping price for any other shipping method.

3. Bingo!

Password protecting your development site using .htaccess / .htpasswd

September 21, 2013  |  1 Comments  |  by Raj (MagePsycho)  |  Apache

You may want to restrict the development site from search engine or from direct access. One of the popular way of doing so is to use Apache2 Basic Authentication.
Here we will be discussing on how to password protect your development site using Apache2 Basic Authentication which makes a use of .htaccess & .htpasswd files.

Steps:

1. Create & upload .htaccess file to the root of your dev site with the following content:

AuthName "Authorisation Required"
AuthUserFile "/path/to/.htpasswd"
AuthType Basic
require valid-user
ErrorDocument 401 "Authorisation Required"

Notes: If .htaccess file already exists then you can add the above code on the top.
Also note that omitting ‘ErrorDocument 401 “Authorisation Required”‘ line can lead to 404 error.
2. Create & upload the .htpasswd file to ‘AuthUserFile’ path with the following content:

foo:$apr1$yB1.9vIT$IVVBmq5vMauwsNR8CZdHQ.

Notes: Where ‘foo’ is the username and ‘$apr1$yB1.9vIT$IVVBmq5vMauwsNR8CZdHQ.’ is the MD5 encrypted password of ‘bar’.
That was just an example, in fact you can use any username/password combination.
In order to create encrypted password you can use some free online tool like:
http://www.htaccesstools.com/htpasswd-generator/

3. Now try to visit the dev site, you will get the screen similar as:

Apache2 Basic Authentication

Apache2 Basic Authentication


And you will only be able to browse the dev site once the correct username/password are entered.

Hope this helps you in password protecting your site.