magento (9)


Create products in magento from excel file

As per this thread on reddit, here’s my script for importing products into magento via an excel file. Some of it, specifically the attributes and attribute sets are hardcoded in places, but other than that it should be generally applicable to most stores. It makes use of the PHPExcel library for loading the data from an xls or xlsx file.

 




Magento: Image upload script

Hey all, been a long time since I posted anything since my job is keeping me pretty busy…. here’s something I implemented for prettylittlething.com that might come in useful for you.

It’s a little script that basically scans a directory for images and, if it finds any, adds them to the relevant products.

Images have to be named as follows:

SKU-1.jpgSKU-2.jpg
SKU-3.jpg

ANOTHERSKU-1.jpgANOTHERSKU-2.jpg

etc…




Category based shipping in magento

One of the things that’s always annoyed me about Magento is the dearth of decent, free shipping modules available for the system. Out of the box you can do a reasonable amount with regards shipping, setting a flat rate or using one of the built in shipping methods…but if you want to do anything fancy (and in my experience, people always do) then you need to pay out for one of the many customisable shipping modules available.

It’s been nearly a year since I last looked at Magento, but a recent freelance project has meant me getting back into it and as the client has some very specific shipping requirements, I thought I’d write my own shipping module. I spent a few hours fiddling with config files, before rememebering why Magento always annoyed me so much (hint, it’s the config files) and used the rather brilliant magento module generator to get me started.

Here’s what I wanted from a shipping module:

  • Different shipping rates for individual products
  • Different shipping rates to different countries
  • Easy assigning of shipping rates to products

So, to tackle these problems I came up with a module that uses Shipping rules defined through categories. This means you can setup your rules, and then use the standard product / category concept in Magento to assign your shipping rates.

Once you install the module (see below for a download) each category gets a new attribute under its ‘general’ tab, named ‘Shipping’. That’s where you enter your rules. Rules are extremely simple, and consist of a series of lines, each of which is made up of ‘country code’ = ‘price’

here’s an example of one ruleset:

Capture

This translates to ‘If the delivery address is in the UK, free shipping. If it’s in the European union, shipping is 3.00, for the rest of the world it’s 5.00’.

You can have as many lines as you want (they’re processed top to bottom) and if you have multiple items in your basket that produce different shipping rates, the most expensive rate is picked (there’s no option to sum the rates together yet, I might add that in at some point).

You can add these rules to your actual, published product categories or do what I’ve done for this client and set up a seperate tree of ‘Shipping categories’ like so:

Capture

So there you go. Bit basic, but does what I needed it to do.

Here’s the download link (upload the ‘app’ folder from the archive into the root of your magento installation):




Magento: Email address when using paypal express in 1.6

Just a quick fix for today.  Magento appears to have a bug whereby if a user places an order using paypal express when they’re not logged into your site (ie: using the ‘guest checkout’ functionality of magento their email address will not be passed through correctly.  One annoying symptom of this is that the user won’t receive any order confirmation, or order update emails.

 

After a lot of debugging I tracked the problem down to the ‘place’ function in /Mage/Paypal/Model/Express/Checkout.php which looks like this:

    /**
     * Place the order and recurring payment profiles when customer returned from paypal
     * Until this moment all quote data must be valid
     *
     * @param string $token
     * @param string $shippingMethodCode
     */
    public function place($token, $shippingMethodCode = null)
    {
        if ($shippingMethodCode) {
            $this->updateShippingMethod($shippingMethodCode);
        }
        $isNewCustomer = false;
        switch ($this->_quote->getCheckoutMethod()) {
            case Mage_Checkout_Model_Type_Onepage::METHOD_GUEST:
                $this->_prepareGuestQuote();
                break;
            case Mage_Checkout_Model_Type_Onepage::METHOD_REGISTER:
                $this->_prepareNewCustomerQuote();
                $isNewCustomer = true;
                break;
            default:
                $this->_prepareCustomerQuote();
                break;
        }

Here magento uses the ‘checkoutmethod’ to determine which method to call when preparing the ‘quote’ (order to you and me).  The problem is that for whatever reason, orders placed by guests come through with the checkout method set to a blank string.  They then fall through to the default handling method, which doesn’t set the email address.

Some more digging turned up this thread which mentioned the Magento developers were aware of this problem.  A little more googling determined this problem is fixed in 1.7…. so I decided to backport the changes made there into our 1.6 site.

Basically I override the ‘place’ function and invoke a new ‘getcheckoutmethod’ (ripped from the 1.7 class) which sets the correct checkout method.  Then I call the original class method, which trundles off and does whatever it has to.

Here’s the code, I’ll get it packaged up and upload it later on today:

class Tallpaul_Paypalexpressfix_Model_Express_Checkout extends Mage_Paypal_Model_Express_Checkout{

/**
     * Get checkout method blatantly ripped off from magento 1.7
     *
     * @return string
     */
    public function getCheckoutMethod()
    {
        if ($this->getCustomerSession()->isLoggedIn()) {            
            return Mage_Checkout_Model_Type_Onepage::METHOD_CUSTOMER;
        } else {
             $this->_quote->setCheckoutMethod(Mage_Checkout_Model_Type_Onepage::METHOD_GUEST);
             return $this->_quote->getCheckoutMethod();
        }       
    }

    public function place($token, $shippingMethodCode = null){
        $this->getCheckoutMethod();
        parent::place($token,$shippingMethodCode);
    }

}

Update: here’s the module

 

 

 




Magento: config file mapping

Just had a moment of epiphany that I thought I’d share…I’m sure this is well known to most Magento developers, but I’ve not seen it clearly spelled out anywhere and it’s taken me a while to get my head round this.

Have a look at this snippet of a config file for one of my modules:

 

 

 

 

 

 

Now, notice the lines that say ‘<CustomAttributes>’.  I’ve seen various explanations of what should go in here ranging from ‘it should match the name of your module’ to ‘it doesn’t matter’.  However, I’ve finally figured out what that element is used for.  It’s a mapping string, that maps your classes to an easily used string.

Once I’ve defined my mapping string as ‘CustomAttributes’ magento has a registry that allows me to call my helpers as follows:

Mage::helper(‘CustomAttributes/helper1’);

This would return the helper located at ‘TallPaul/Customattributes/Helper/helper1.php’.

The same goes for blocks (using something like createBlock) and so on.  It’s probably recommended that you use ‘yournamespace_yourmodule’ as the mapping string, to avoid conflicts, but technically speaking you can use anything you like.

This is confused slightly by the fact that Magento uses a lot of default values in its own code so you’ll rarely see helpers called using mapping strings that make sense.  For example the default helper is called ‘Data.php’.  So when magento makes a call like ‘Mage::helper(‘catalog’); it’s actually asking for ‘Mage_Catalog_Helper_Data.php’.

 

 

 




Magento: solving ‘invalid API path’ errors

I’ve noticed a bug with Magento that affects the code I wrote for the ‘Extending the API’ tutorial so I thought I’d post a fix for it here.  Well, I assume it’s a bug since I don’t see how it could be anything else…

Here’s the article in question

Here’s the api.xml file from that tutorial:

<config>
    <api>
        <resources>
            <modulename_category translate="title" module="modulename">
                <model>namespace_modulename_model_category_api</model>
                <title>New Category API</title>
                <methods>
                	<getID translate="title" module="modulename">
                		<title>Retrieve Category ID from its name</title>
                		<acl>catalog/category</acl>
                	</getID>
                </methods>
            </modulename_category>
            </resources>
        	<v2>
            	<resources_function_prefix>
                	<modulename_category>catalogCategory</modulename_category>
            	</resources_function_prefix>
        	</v2>
    </api>
</config>

You’ll notice that I declare a ‘resources_function_prefix’ of ‘catalogCategory’ to fit in with the existing Magento API.  this means that my new method called ‘getID’ can be accessed via a soap call to ‘catalogCategoryGetID’.  This all seems straightforward… or so I thought.

It appears that during execution of soap calls, that resource prefix is mapped directly to a magento module.  Hence, if you have a prefix of ‘catalogCategory’ defined in your module any methods with that prefix will be mapped to your module..including the methods defined in the Magento class we’re overriding with our module.  So for all the standard Magento API calls you’ll receive a nice ‘invalid APi path’ error.

The workaround for this is pretty obvious, we need to declare our new methods with a different function prefix and then update the wsi.xml (the wsdl file) accordingly.

This of course means you’ll be accessing your methods with a different prefix than the standard magento one (so something like myModuleCatalogCategoryGetID rather than catalogCategoryGetID) but that’s a small price to pay for it actually, you know, working.

 




Magento: Block users using a group

One of the most glaring omissions of magento is a way of blocking users. Currently your only option when faced with a user you wish to ban from the site is to do some kind of IP blocking in your web server configuration, or delete the user account. Neither of those options is really useful, IP blocks are difficult to maintain and if you delete a user account there’s nothing to stop the miscreant just creating a new account.

Here’s how you can create a simple module that allows you to set up a ‘blocked’ group. When customers are placed in this group they can still log in, however they’re not able to add anything to their cart and thus can’t actually place an order.

First up we need to create our module folder structure. Here I’m using a namespace of ‘Tallpaul’ and a module name of ‘bangroup’, but you can change these according to your requirements (just make sure you change them throughout, or you’ll get missing classpaths and all sorts of fun).

 

 

 

 

 

 

You can see we only have a couple of files in here.  ‘Config.xml’ is a configuration file that defines how our module is going to behave, and’CartController.php’ is our code that’s going to stop blocked users from adding items to their cart.

Lets take a look at ‘config.xml’ first.

<config>
 <modules>
 <Tallpaul_Bangroup>
 <version>0.1.0</version>
 </Tallpaul_Bangroup>
 </modules>
 <frontend>
 <routers>
 <checkout>
 <args>
 <modules>
 <tallpaul_bangroup before="Mage_Checkout_CartController">Tallpaul_Bangroup_Frontend_Checkout</tallpaul_bangroup>
 </modules>
 </args>
 </checkout>
 </routers>
 </frontend>
</config>

After the usual configuration stuff of defining a version for our module, there’s only one line here that does anything.  We’re defining that our module (tallpaul_bangroup) is overriding a specific controller (Mage_Checkout_CartController) with a new controller (Tallpaul_Bangroup_Frontend_Checkout).  And that’s pretty much it.  You can see here that controller overrides are dfined in a different way to bloc / helper / etc overrides.  I’m not entirely sure why, but this is just the way magento does it.  It usually takes me a bit of trial and error to work out how to override a specific controller, but once you’ve done it a few times it becomes second nature to map these files to a classpath.

Anyway, now we’ve got our configuration out of the way, lets have a look at CartController.php.  In here we’re going to extend the magento checkout controller class, and override a couple of functions.

/**
 * Shopping cart controller
 */
require_once Mage::getModuleDir('controllers', 'Mage_Checkout').DS.'CartController.php';

class Tallpaul_Bangroup_Frontend_Checkout_CartController extends Mage_Checkout_CartController
{

  /**
   * Check if user is blocked (ie: if they're in the 'Blocked' group)
   */
  public function isBlocked(){
  		if(Mage::getSingleton('customer/session')->isLoggedIn())
		{
  			$groupId = Mage::getSingleton('customer/session')->getCustomerGroupId();
			$group = Mage::getModel('customer/group')->load($groupId);
			$groupName = $group->getCode();
			if ($groupName == "Blocked"){
				return true;
			}
		}
		return false; //user can't be blocked if they're not logged in
  }

  /**
   * Empty current user cart
   */
  public function emptyCart(){
  		$cartHelper = Mage::helper('checkout/cart');
     	$items = $cartHelper->getCart()->getItems();
        foreach ($items as $item) {
            	$itemId = $item->getItemId();
            	$cartHelper->getCart()->removeItem($itemId)->save();
        }
  }

   /**
     * Shopping cart display action
     */
   public function indexAction()
   {
   		if ($this->isBlocked()){
   			$this->emptyCart();
		}
		parent::indexAction();
   }

    /**
     * Add product to shopping cart action
     */
    public function addAction()
    {
			if ($this->isBlocked()){
				$this->emptyCart();
				$this->_goBack();
                return;
			}
		parent::addAction();

    }

}

 

The actions we’re overriding are ‘indexAction’ (which is used to display a user’s cart) and ‘addAction’ (called whenever an item is added to the cart.  The other 2 functions here ‘isBlocked’ and ’emptyCart’ are simple helper functions I’ve defined to keep things tidy.

The basic logic here is that when a user adds an item to their cart we first check if they’re in a specific group (here I’ve used ‘Blocked’ as the name of the group, but you can call it something else as long as you update the code in ‘isBlocked’).  If they are we empty their cart, and then return.  The effect of this is to take the user to an empty cart with nothing added to it and no error message to indicate what the problem might be.

As a doublecheck against a user who already has items in their cart when they’re placed in the ‘Blocked’ group, we also add a check to the ‘indexAction’ to empty their cart when they view it.

At the end of our indexAction and addAction we use the nifty trick of calling the relevant method in the ‘parent’ class.  This means our code remains clean and simple, while allowing requests that we don’t want to block using our code to ‘fall through’ to the parent implementation and be dealt with by the standard Magento logic.

Now all that’s required is an ‘activation’ xml file in app/code/etc and we’re good to go.  Create a group called ‘Blocked’, add your rogue users to it and laugh as you imagine their confusion at being unable to add items to their basket.

You can download the code for this article here:

 




Magento: Extending the API (v2)

Update:  I’ve fixed a couple of issues with the code in this post, based on the comments in this post on the magento forums.

One of the nice features of Magento is the extensive SOAP api it provides for integrating magento with your own or third party applications.  However, when it comes to extending that API the documentation is pretty sparse.  I’ve spent the last few days struggling to get to grips with this and, after spending some time debugging in the Varien autoloader class I’ve finally got it working.




Magento Show Remote IP When using cloudFlare

There’s an update to this article here. The below article is here as tutorial for writing a helper in Magento, but if you just want to get the visitor IP behind cloudflare you’re better using the ‘proper’ method described here (click the ‘here’, it’s a link despite my stupid CSS not making it obvious!)

This week we setup one of our eCommerce sites to use CloudFlare, the CDN and optimisation solution for websites.  This enabled us to deal with some sporadic burts of traffic we were seeing by taking the majority of the load off the site.  One problem we encountered though was how to get a visitor’s IP address (which is shown in Magento agaianst their order).  The standard PHP method of getting the IP from the server header(which magento uses) doesn’t work, because the IP shown here is the Cloudflare server address.

Helpfully, Cloudflare do provide us with the visitor IP, in a custom header (HTTP_CF_CONNECTING_IP) they pass in as seen here

So, how to get Magento to use this value instead of the SERVER header?  Well, thanks to Magento’s override system it’s actually quite simple.  First off, we need to figure out where Magento actually gets its IP from.  After some searching I found out it’s done in one of the core helpers, specifically the ‘Http’ one:

/app/core/Mage/Core/Helper/Http.php

This has some other stuff in it, but the only bit we’re interested in is this method:

 /**
     * Retrieve Client Remote Address
     *
     * @param bool $ipToLong converting IP to long format
     * @return string IPv4|long
     */
    public function getRemoteAddr($ipToLong = false)
    {
        if (is_null($this->_remoteAddr)) {
            $headers = $this->getRemoteAddrHeaders();
            foreach ($headers as $var) {
                if ($this->_getRequest()->getServer($var, false)) {
                    $this->_remoteAddr = $_SERVER[$var];
                    break;
                }
            }

            if (!$this->_remoteAddr) {
                $this->_remoteAddr = $this->_getRequest()->getServer('REMOTE_ADDR');
            }
        }

        if (!$this->_remoteAddr) {
            return false;
        }

        return $ipToLong ? ip2long($this->_remoteAddr) : $this->_remoteAddr;
    }

Luckily, Magento (and the underlying PHP) allow us some pretty nifty functionality that let’s us override just this method, while leaving the rest of the helper untouched.  So, lets get down to it.

First off, we need to create our file structure.  For this helper the file structure we need looks like this:

 

 

 

 

 

 

 

As you can see we’re ceating our helper in the ‘local’ codepool, under a namespace of ‘TallPaul’ and I’m creating a new ‘http’ module.  The 2 files we’re concerned with here are the ‘Config.xml’ which initialises our module and defines what it does, and ‘cloudflarehttp.php’ which contains the code for our helper.

here’s /app/code/local/Tallpaul/http/etc/config.xml:

<?xml version="1.0"?>
    <config>
        <modules>
            <Tallpaul_http>
                <version>0.1.0</version>
            </Tallpaul_http>
        </modules>
        <global>
           <helpers>
              <core>
                  <rewrite>
                      <http>Tallpaul_Http_Helper_Cloudflarehttp</http>
                 </rewrite>
              </core>
           </helpers>
        </global>
  </config>

here we define our module, and tell it we want to ‘rewrite’ the ‘http’ module which is a global helper which lives in ‘core’.  That’s pretty straightforward, and really shows some of the power of Magento, that’s all it takes to override one of the core modules.  Now, because we don’t want to override the whole module, we can just extend from it as a base class like this:

/app/code/local/TallPaul/http/helper/cloudflarehttp.php

<?php
class Tallpaul_Http_Helper_Cloudflarehttp extends Mage_Core_Helper_Http{

    public function getRemoteAddr($ipToLong = false){        
        if (is_null($this->_remoteAddr)) {
            if ( $_SERVER["HTTP_CF_CONNECTING_IP"] ) {
                $this->_remoteAddr = $_SERVER["HTTP_CF_CONNECTING_IP"];
            }
            else    {
                $this->_remoteAddr = parent::getRemoteAddr(false);
            }
        }    
        return $ipToLong ? ip2long($this->_remoteAddr) : $this->_remoteAddr;
}
}
?>

because we’re extending the base class (ie: the core http helper) our class has all the functionality of the base without us hing to code it all.  We can also even access the base class’ implementation of the function we’re overriding (using ‘parent::’) to access that functionality.  In our class we try and get the Visitor IP from the cloudflare header, and if we don’t get anything back just pass the request through to the base implementation.

All that’s left to do now is activate our module in Magento.  We do this by adding an xml file into /app/etc/modules like this:

/app/etc/modules/TallPaul.xml

<?xml version="1.0"?>
       <config>
             <modules>
                  <Tallpaul_Http>
                       <active>true</active>
                       <codePool>local</codePool>
                  </Tallpaul_Http>
            </modules>
     </config>

And that’s all you need.  Clear your magento cache and you should be good to go.

 

You can download the code for this article here