magento (13)


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