8

I have special order form and now I need to create orders without payment. So the payment needs to be skipped. I am not sure what would be the best way to accomplish this part. Should I send my form data as JSON to REST API like how it's doing in the default checkout module and current theme? Or it would be better and easy to have my own controller and model where I can use some factory methods to create orders like what we did in Magento 1.x?

2

6 Answers 6

18

IMHO best Magento2 way is to use a REST call.

The Magento2 frontend is going to have a complete frontend/backend isolation, so a REST call should be the best option.

Otherwise, if you wish to programmatically create an order use the following code (Source: http://webkul.com/blog/create-quote-and-order-programmatically-in-magento2/)

<?php
namespace YourNameSpace\ModuleName\Helper;

class Data extends \Magento\Framework\App\Helper\AbstractHelper
{
     /**
    * @param Magento\Framework\App\Helper\Context $context
    * @param Magento\Store\Model\StoreManagerInterface $storeManager
    * @param Magento\Catalog\Model\Product $product
    * @param Magento\Framework\Data\Form\FormKey $formKey $formkey,
    * @param Magento\Quote\Model\Quote $quote,
    * @param Magento\Customer\Model\CustomerFactory $customerFactory,
    * @param Magento\Sales\Model\Service\OrderService $orderService,
    */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\Product $product,
        \Magento\Framework\Data\Form\FormKey $formkey,
        \Magento\Quote\Model\QuoteFactory $quote,
        \Magento\Quote\Model\QuoteManagement $quoteManagement,
        \Magento\Customer\Model\CustomerFactory $customerFactory,
        \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
        \Magento\Sales\Model\Service\OrderService $orderService  
    ) {
        $this->_storeManager = $storeManager;
        $this->_product = $product;
        $this->_formkey = $formkey;
        $this->quote = $quote;
        $this->quoteManagement = $quoteManagement;
        $this->customerFactory = $customerFactory;
        $this->customerRepository = $customerRepository;
        $this->orderService = $orderService;
        parent::__construct($context);
    }

    /**
     * Create Order On Your Store
     * 
     * @param array $orderData
     * @return array
     * 
    */
    public function createMageOrder($orderData) {
        $store=$this->_storeManager->getStore();
        $websiteId = $this->_storeManager->getStore()->getWebsiteId();
        $customer=$this->customerFactory->create();
        $customer->setWebsiteId($websiteId);
        $customer->loadByEmail($orderData['email']);// load customet by email address
        if(!$customer->getEntityId()){
            //If not avilable then create this customer 
            $customer->setWebsiteId($websiteId)
                    ->setStore($store)
                    ->setFirstname($orderData['shipping_address']['firstname'])
                    ->setLastname($orderData['shipping_address']['lastname'])
                    ->setEmail($orderData['email']) 
                    ->setPassword($orderData['email']);
            $customer->save();
        }
        $quote=$this->quote->create(); //Create object of quote
        $quote->setStore($store); //set store for which you create quote
        // if you have allready buyer id then you can load customer directly 
        $customer= $this->customerRepository->getById($customer->getEntityId());
        $quote->setCurrency();
        $quote->assignCustomer($customer); //Assign quote to customer

        //add items in quote
        foreach($orderData['items'] as $item){
            $product=$this->_product->load($item['product_id']);
            $product->setPrice($item['price']);
            $quote->addProduct(
                $product,
                intval($item['qty'])
            );
        }

        //Set Address to quote
        $quote->getBillingAddress()->addData($orderData['shipping_address']);
        $quote->getShippingAddress()->addData($orderData['shipping_address']);

        // Collect Rates and Set Shipping & Payment Method

        $shippingAddress=$quote->getShippingAddress();
        $shippingAddress->setCollectShippingRates(true)
                        ->collectShippingRates()
                        ->setShippingMethod('freeshipping_freeshipping'); //shipping method
        $quote->setPaymentMethod('checkmo'); //payment method
        $quote->setInventoryProcessed(false); //not effetc inventory
        $quote->save(); //Now Save quote and your quote is ready

        // Set Sales Order Payment
        $quote->getPayment()->importData(['method' => 'checkmo']);

        // Collect Totals & Save Quote
        $quote->collectTotals()->save();

        // Create Order From Quote
        $order = $this->quoteManagement->submit($quote);

        $order->setEmailSent(0);
        $increment_id = $order->getRealOrderId();
        if($order->getEntityId()){
            $result['order_id']= $order->getRealOrderId();
        }else{
            $result=['error'=>1,'msg'=>'Your custom message'];
        }
        return $result;
    }
}

Parameters example:

$tempOrder=[
     'currency_id'  => 'USD',
     'email'        => '[email protected]', //buyer email id
     'shipping_address' =>[
            'firstname'    => 'jhon', //address Details
            'lastname'     => 'Deo',
                    'street' => 'xxxxx',
                    'city' => 'xxxxx',
            'country_id' => 'IN',
            'region' => 'xxx',
            'postcode' => '43244',
            'telephone' => '52332',
            'fax' => '32423',
            'save_in_address_book' => 1
                 ],
   'items'=> [ //array of product which order you want to create
              ['product_id'=>'1','qty'=>1],
              ['product_id'=>'2','qty'=>2]
            ]
];
9
  • If I want to create an order without payment, how can I do it?
    – TFS
    Aug 11, 2016 at 12:59
  • Although the above code works fine with simple product but if try to add bundle option, an exception error will occurred We found an invalid request for adding product to quote.
    – mjdevloper
    Nov 21, 2016 at 9:18
  • Could you please explain why the shipping method code is freeshipping_freeshipping while we see the actual code is freeshipping
    – Nahid
    Mar 31, 2017 at 6:06
  • It's because each shipping method can be divided into "carrier" and "method". Eg.: "UPS" / "Fast Delivery" (could be "ups_fast") or "UPS" / "Standard Delivery" (could be "ups_standard"). So for shipping methods without internal divisions there is the convention to double the carrier name. Mar 31, 2017 at 7:43
  • Why use // Set Sales Order Payment $quote->getPayment()->importData(['method' => 'checkmo']); then // Collect Totals & Save Quote $quote->collectTotals()->save(); Jul 9, 2018 at 11:37
6

For Magento 2.1, my solution managed to create order successfully without the 0 price, multi products issues. (It's based a post Create Quote And Order Programmatically In Magento2 on webkul and Frank's update)

Sample data:

$orderData=[
'currency_id'  => 'USD',
'email'        => '[email protected]', //buyer email id
'shipping_address' =>[
    'firstname'    => 'jhon', //address Details
    'lastname'     => 'Deo',
    'street' => 'xxxxx',
    'city' => 'xxxxx',
    'country_id' => 'IN',
    'region' => 'xxx',
    'postcode' => '43244',
    'telephone' => '52332',
    'fax' => '32423',
    'save_in_address_book' => 1
];

Code for creating order:

<?php
/**
 * @author    Godric Cao
 */
namespace Vendor\Namespace\Model\Subscription\Order;
class Create
{
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\ProductFactory $productFactory,
        \Magento\Quote\Model\QuoteManagement $quoteManagement,
        \Magento\Customer\Model\CustomerFactory $customerFactory,
        \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
        \Magento\Sales\Model\Service\OrderService $orderService,
        \Magento\Quote\Api\CartRepositoryInterface $cartRepositoryInterface,
        \Magento\Quote\Api\CartManagementInterface $cartManagementInterface,
        \Magento\Quote\Model\Quote\Address\Rate $shippingRate
    ) {
        $this->_storeManager = $storeManager;
        $this->_productFactory = $productFactory;
        $this->quoteManagement = $quoteManagement;
        $this->customerFactory = $customerFactory;
        $this->customerRepository = $customerRepository;
        $this->orderService = $orderService;
        $this->cartRepositoryInterface = $cartRepositoryInterface;
        $this->cartManagementInterface = $cartManagementInterface;
        $this->shippingRate = $shippingRate
    }
    /**
     * Create Order On Your Store
     *
     * @param array $orderData
     * @return int $orderId
     *
     */
    public function createOrder($orderData) {
        //init the store id and website id @todo pass from array
        $store = $this->_storeManager->getStore();
        $websiteId = $this->_storeManager->getStore()->getWebsiteId();
        //init the customer
        $customer=$this->customerFactory->create();
        $customer->setWebsiteId($websiteId);
        $customer->loadByEmail($orderData['email']);// load customet by email address
        //check the customer
        if(!$customer->getEntityId()){
            //If not avilable then create this customer
            $customer->setWebsiteId($websiteId)
                ->setStore($store)
                ->setFirstname($orderData['shipping_address']['firstname'])
                ->setLastname($orderData['shipping_address']['lastname'])
                ->setEmail($orderData['email'])
                ->setPassword($orderData['email']);
            $customer->save();
        }
        //init the quote
        $cart_id = $this->cartManagementInterface->createEmptyCart();
        $cart = $this->cartRepositoryInterface->get($cart_id);
        $cart->setStore($store);
        // if you have already buyer id then you can load customer directly
        $customer= $this->customerRepository->getById($customer->getEntityId());
        $cart->setCurrency();
        $cart->assignCustomer($customer); //Assign quote to customer
        //add items in quote
        foreach($orderData['items'] as $item){
            $product = $this->_productFactory->create()->load($item['product_id']);
            $cart->addProduct(
                $product,
                intval($item['qty'])
            );
        }
        //Set Address to quote @todo add section in order data for seperate billing and handle it
        $cart->getBillingAddress()->addData($orderData['shipping_address']);
        $cart->getShippingAddress()->addData($orderData['shipping_address']);
        // Collect Rates and Set Shipping & Payment Method
        $this->shippingRate
            ->setCode('freeshipping_freeshipping')
            ->getPrice(1);
        $shippingAddress = $cart->getShippingAddress();
        //@todo set in order data
        $shippingAddress->setCollectShippingRates(true)
            ->collectShippingRates()
            ->setShippingMethod('flatrate_flatrate'); //shipping method
        $cart->getShippingAddress()->addShippingRate($this->rate);
        $cart->setPaymentMethod('checkmo'); //payment method
        //@todo insert a variable to affect the invetory
        $cart->setInventoryProcessed(false);
        // Set sales order payment
        $cart->getPayment()->importData(['method' => 'checkmo']);
        // Collect total and saeve
        $cart->collectTotals();
        // Submit the quote and create the order
        $cart->save();
        $cart = $this->cartRepositoryInterface->get($cart->getId());
        $order_id = $this->cartManagementInterface->placeOrder($cart->getId());
        return $order_id;
}

To avoid multiple products counted as one product, instead of using \Magento\Catalog\Model\Product in __construct parameter, I used \Magento\Catalog\Model\ProductFactory. Then I create product object by $product=$this->_product->create()->load($item['product_id']);. Here is the snippet that add products to cart(quote):

    //add items in quote
    foreach($orderData['items'] as $item){
        $product=$this->_productFactory->create()->load($item['product_id']);
            $cart->addProduct(
                $product,
                intval($item['qty'])
            );
    }

To avoid 0 price issue, I'm not saving the order by $this->cartRepositoryInterface->save($quote);. Instead, I still used $quote->save(); style. (In fact my code is a little different. I used the following code to create a $cart, which is the interface of $quote:

$cart_id = $this->cartManagementInterface->createEmptyCart();
$cart = $this->cartRepositoryInterface->get($cart_id);

I find my flatrate_flatrate shipping method is saved properly with Frank's code. But when I want to use freeshipping_freeshipping, I have to give the __construct a \Magento\Quote\Model\Quote\Address\Rate $shippingRate parameter and added some code like this:

    // Collect Rates and Set Shipping & Payment Method
    $this->shippingRate
        ->setCode('freeshipping_freeshipping')
        ->getPrice(1);
    $shippingAddress = $cart->getShippingAddress();
    //@todo set in order data
    $shippingAddress->setCollectShippingRates(true)
        ->collectShippingRates()
        ->setShippingMethod('flatrate_flatrate'); //shipping method
    $cart->getShippingAddress()->addShippingRate($this->rate);
    $cart->setPaymentMethod('checkmo'); //payment method
2
  • the shipping method and rate is not added to the order
    – Zorox
    Aug 25, 2017 at 13:02
  • where to put sample data in module Mar 12, 2019 at 7:22
1

Thanks, the code works fine after fixing some minor bugs within.

Below is updated code for reference:

<?php
/**
 * @author    Godric Cao
 */
namespace Hiddenbrains\Easyship\Model\Subscription\Order;

class Create
{
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\ProductFactory $productFactory,
        \Magento\Quote\Model\QuoteManagement $quoteManagement,
        \Magento\Customer\Model\CustomerFactory $customerFactory,
        \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
        \Magento\Sales\Model\Service\OrderService $orderService,
        \Magento\Quote\Api\CartRepositoryInterface $cartRepositoryInterface,
        \Magento\Quote\Api\CartManagementInterface $cartManagementInterface,
        \Magento\Quote\Model\Quote\Address\Rate $shippingRate
    ) {
        $this->_storeManager = $storeManager;
        $this->_productFactory = $productFactory;
        $this->quoteManagement = $quoteManagement;
        $this->customerFactory = $customerFactory;
        $this->customerRepository = $customerRepository;
        $this->orderService = $orderService;
        $this->cartRepositoryInterface = $cartRepositoryInterface;
        $this->cartManagementInterface = $cartManagementInterface;
        $this->shippingRate = $shippingRate;
    }
    /**
     * Create Order On Your Store
     *
     * @param array $orderData
     * @return int $orderId
     *
     */
    public function createOrder($orderData) {

        //init the store id and website id @todo pass from array
        $store = $this->_storeManager->getStore();
        $websiteId = $this->_storeManager->getStore()->getWebsiteId();
        //init the customer
        $customer=$this->customerFactory->create();
        $customer->setWebsiteId($websiteId);
        $customer->loadByEmail($orderData['email']);// load customet by email address
        //check the customer
        if(!$customer->getEntityId()){
            //If not avilable then create this customer
            $customer->setWebsiteId($websiteId)
                ->setStore($store)
                ->setFirstname($orderData['shipping_address']['firstname'])
                ->setLastname($orderData['shipping_address']['lastname'])
                ->setEmail($orderData['email'])
                ->setPassword($orderData['email']);
            $customer->save();
        }
        //init the quote
        $cart_id = $this->cartManagementInterface->createEmptyCart();
        $cart = $this->cartRepositoryInterface->get($cart_id);
        $cart->setStore($store);
        // if you have already buyer id then you can load customer directly
        $customer= $this->customerRepository->getById($customer->getEntityId());
        $cart->setCurrency();
        $cart->assignCustomer($customer); //Assign quote to customer
        //add items in quote
        foreach($orderData['items'] as $item){
            $product = $this->_productFactory->create()->load($item['product_id']);
            $cart->addProduct(
                $product,
                intval($item['qty'])
            );
        }
        //Set Address to quote @todo add section in order data for seperate billing and handle it
        $cart->getBillingAddress()->addData($orderData['shipping_address']);
        $cart->getShippingAddress()->addData($orderData['shipping_address']);
        // Collect Rates and Set Shipping & Payment Method
        $this->shippingRate
            ->setCode('freeshipping_freeshipping')
            ->getPrice(1);
        $shippingAddress = $cart->getShippingAddress();
        //@todo set in order data
        $shippingAddress->setCollectShippingRates(true)
            ->collectShippingRates()
            ->setShippingMethod('flatrate_flatrate'); //shipping method
        $cart->getShippingAddress()->addShippingRate($this->shippingRate);
        $cart->setPaymentMethod('checkmo'); //payment method
        //@todo insert a variable to affect the invetory
        $cart->setInventoryProcessed(false);
        // Set sales order payment
        $cart->getPayment()->importData(['method' => 'checkmo']);
        // Collect total and saeve
        $cart->collectTotals();
        // Submit the quote and create the order
        $cart->save();
        $cart = $this->cartRepositoryInterface->get($cart->getId());
        $order_id = $this->cartManagementInterface->placeOrder($cart->getId());
        return $order_id;
    }
}
1
  • Why need to use $cart->getPayment()->importData(['method' => 'checkmo']); Jul 9, 2018 at 11:35
0

I have updated the code in order to use in my own module. Some fixes.

<?php

namespace MyCompany\ImportOrders\Model;

/*
 *
 *
    SET FOREIGN_KEY_CHECKS = 0;

    TRUNCATE TABLE `gift_message`;
    TRUNCATE TABLE `quote`;
    TRUNCATE TABLE `quote_address`;
    TRUNCATE TABLE `quote_address_item`;
    TRUNCATE TABLE `quote_id_mask`;
    TRUNCATE TABLE `quote_item`;
    TRUNCATE TABLE `quote_item_option`;
    TRUNCATE TABLE `quote_payment`;
    TRUNCATE TABLE `quote_shipping_rate`;
    TRUNCATE TABLE `reporting_orders`;
    TRUNCATE TABLE `sales_bestsellers_aggregated_daily`;
    TRUNCATE TABLE `sales_bestsellers_aggregated_monthly`;
    TRUNCATE TABLE `sales_bestsellers_aggregated_yearly`;
    TRUNCATE TABLE `sales_creditmemo`;
    TRUNCATE TABLE `sales_creditmemo_comment`;
    TRUNCATE TABLE `sales_creditmemo_grid`;
    TRUNCATE TABLE `sales_creditmemo_item`;
    TRUNCATE TABLE `sales_invoice`;
    TRUNCATE TABLE `sales_invoiced_aggregated`;
    TRUNCATE TABLE `sales_invoiced_aggregated_order`;
    TRUNCATE TABLE `sales_invoice_comment`;
    TRUNCATE TABLE `sales_invoice_grid`;
    TRUNCATE TABLE `sales_invoice_item`;
    TRUNCATE TABLE `sales_order`;
    TRUNCATE TABLE `sales_order_address`;
    TRUNCATE TABLE `sales_order_aggregated_created`;
    TRUNCATE TABLE `sales_order_aggregated_updated`;
    TRUNCATE TABLE `sales_order_grid`;
    TRUNCATE TABLE `sales_order_item`;
    TRUNCATE TABLE `sales_order_payment`;
    TRUNCATE TABLE `sales_order_status_history`;
    TRUNCATE TABLE `sales_order_tax`;
    TRUNCATE TABLE `sales_order_tax_item`;
    TRUNCATE TABLE `sales_payment_transaction`;
    TRUNCATE TABLE `sales_refunded_aggregated`;
    TRUNCATE TABLE `sales_refunded_aggregated_order`;
    TRUNCATE TABLE `sales_shipment`;
    TRUNCATE TABLE `sales_shipment_comment`;
    TRUNCATE TABLE `sales_shipment_grid`;
    TRUNCATE TABLE `sales_shipment_item`;
    TRUNCATE TABLE `sales_shipment_track`;
    TRUNCATE TABLE `sales_shipping_aggregated`;
    TRUNCATE TABLE `sales_shipping_aggregated_order`;
    TRUNCATE TABLE `tax_order_aggregated_created`;
    TRUNCATE TABLE `tax_order_aggregated_updated`;

 */
class Orders {

    /**
     * Orders constructor.
     * @param \Magento\Framework\App\Helper\Context $context
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Catalog\Model\ProductFactory $productFactory
     * @param \Magento\Quote\Model\QuoteManagement $quoteManagement
     * @param \Magento\Customer\Model\CustomerFactory $customerFactory
     * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
     * @param \Magento\Sales\Model\Service\OrderService $orderService
     * @param \Magento\Quote\Api\CartRepositoryInterface $cartRepositoryInterface
     * @param \Magento\Quote\Api\CartManagementInterface $cartManagementInterface
     * @param \Magento\Quote\Model\Quote\Address\Rate $shippingRate
     */
    public function __construct(
        \Magento\Framework\App\Helper\Context $context,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\ProductFactory $productFactory,
        \Magento\Quote\Model\QuoteManagement $quoteManagement,
        \Magento\Customer\Model\CustomerFactory $customerFactory,
        \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
        \Magento\Sales\Model\Service\OrderService $orderService,
        \Magento\Quote\Api\CartRepositoryInterface $cartRepositoryInterface,
        \Magento\Quote\Api\CartManagementInterface $cartManagementInterface,
        \Magento\Quote\Model\Quote\Address\Rate $shippingRate
    )
    {
        $this->_storeManager = $storeManager;
        $this->_productFactory = $productFactory;
        $this->quoteManagement = $quoteManagement;
        $this->customerFactory = $customerFactory;
        $this->customerRepository = $customerRepository;
        $this->orderService = $orderService;
        $this->cartRepositoryInterface = $cartRepositoryInterface;
        $this->cartManagementInterface = $cartManagementInterface;
        $this->shippingRate = $shippingRate;
    }

    /**
     * Create Order On Your Store
     *
     * @param array $orderData
     * @return int $orderId
     *
     */
    public function createOrder($orderData) {
        //init the store id and website id @todo pass from array
        $store = $this->_storeManager->getStore();
        $websiteId = $this->_storeManager->getStore()->getWebsiteId();
        //init the customer
        $customer=$this->customerFactory->create();
        $customer->setWebsiteId($websiteId);
        $customer->loadByEmail($orderData['email']);// load customet by email address

        //check the customer
        if(!$customer->getEntityId()){
            //If not avilable then create this customer
            $customer->setWebsiteId($websiteId)
                ->setStore($store)
                ->setFirstname($orderData['shipping_address']['firstname'])
                ->setLastname($orderData['shipping_address']['lastname'])
                ->setEmail($orderData['email'])
                ->setPassword($orderData['email']);
            $customer->save();
        }

        //init the quote
        $cart_id = $this->cartManagementInterface->createEmptyCart();
        $cart = $this->cartRepositoryInterface->get($cart_id);
        $cart->setStore($store);
        // if you have already buyer id then you can load customer directly
        $customer= $this->customerRepository->getById($customer->getEntityId());
        $cart->setCurrency();
        $cart->assignCustomer($customer); //Assign quote to customer
        //add items in quote
        foreach($orderData['items'] as $item){
            $product = $this->_productFactory->create()->load($item['product_id']);
            $cart->addProduct(
                $product,
                intval($item['qty'])
            );
        }

        //Set Address to quote @todo add section in order data for seperate billing and handle it
        $cart->getBillingAddress()->addData($orderData['shipping_address']);
        $cart->getShippingAddress()->addData($orderData['shipping_address']);

        // Collect Rates and Set Shipping & Payment Method
        $this->shippingRate
            ->setCode('freeshipping_freeshipping')
            ->getPrice(1);

        $shippingAddress = $cart->getShippingAddress();

        //@todo set in order data
        $shippingAddress->setCollectShippingRates(true)
            ->collectShippingRates()
            ->setShippingMethod('flatrate_flatrate'); //shipping method

        $cart->getShippingAddress()->addShippingRate($this->shippingRate);
        $cart->setPaymentMethod('banktransfer'); //payment method

        //@todo insert a variable to affect the invetory
        $cart->setInventoryProcessed(false);

        // Set sales order payment
        $cart->getPayment()->importData(['method' => 'banktransfer']);

        // Collect total and saeve
        $cart->collectTotals();

        // Submit the quote and create the order
        $cart->save();
        $cart = $this->cartRepositoryInterface->get($cart->getId());
        $order_id = $this->cartManagementInterface->placeOrder($cart->getId());

        return $order_id;
    }
}
0

Above code from webkul places an order after declaring following class properties:-

protected $_storeManager;

protected $_product;

protected $_formkey;

protected $quote;

protected $quoteManagement;

protected $customerFactory;

protected $customerRepository;

protected $orderService;



and adding price in items :-

    'items'=> [ //array of product which order you want to create

                  ['product_id'=>'15','qty'=>2,'price'=>20],

                          ['product_id'=>'16','qty'=>1,'price'=>200]

                        ]

But, still it is buggy. It adds quantity of all items and sets it to the last item. So for me it is showing an order in backend which costs $600 rather than $240. I am using magento version 2.0.7

-2

I have followed the Create order programmatically in Magento 2

It seems like this solution is viral everywhere. :)

Not the answer you're looking for? Browse other questions tagged or ask your own question.