magento (13)


e-Commerce building blocks

I’ve been working in e-commerce for a good few years now, for very similar companies (early stage, fast fashion focussed) and there’s always the same sorts of problems to solve. Here’s a very basic, very generic guide to the things you need to get up and running. Each bit has a ‘too long / didn’t read’ summary at the end, so you can just look for those if you don’t like reading. Please note that this is entirely my opinion, I don’t get paid by any of the companies I mention (although I am totally open to bribery. Seriously guys, hit me up.)

I’m going to split this up into a few different sections:

  1. Your website
  2. Your logistics (warehouse etc)
  3. Your customer service
  4. Internal IT / infrastructure
  5. Data / Reporting

First up…..




Merchandise magento category by product age / availability

Just a quick snippet of code today, it grabs all the products in a certain category (given in category_id), sorts them by age and stock availability (you can adjust the relative weighting of these using $stock_weight) and then updates the category. The end result is a nice mix of newer products, and products with loads of stock towards the top of the category, older / lower stocked products further down.

This uses the external database script access script from: here

I should probably bundle this up into an extension, with a cron job and so on but…. I’ll leave that as an exercise for the reader!


$category_id = 8;
$stock_weight = 10;


$sql = "select ccp.*, DATEDIFF(Now(),cpe.created_at) as age,
(select sum(qty) from cataloginventory_stock_item as csi join catalog_product_super_link as cpsl on csi.product_id = cpsl.product_id where cpsl.parent_id = cpe.entity_id) as stock_qty,
((select DATEDIFF(Now(),MIN(created_at)) from catalog_product_entity) - (select age)) as age_factor,
(select stock_qty) * $stock_weight as stock_factor,
(select age_factor) + (select stock_factor) as sortby
from catalog_category_product as ccp
	join catalog_product_entity as cpe
		on cpe.entity_id = ccp.product_id
where ccp.category_id = $category_id
and cpe.type_id = 'configurable'
ORDER BY sortby DESC";


function build_query($results){
  $update_sql = "Insert into catalog_category_product (`category_id`,`product_id`,`position`) VALUES ";
  $pos = 0;
  foreach($results as $result){
    $pos++;
    $update_sql .= "('".$result['category_id']."','".$result['product_id']."','".$pos."'),";
  }
  $update_sql = rtrim($update_sql,',');
  return $update_sql;
}


require_once "include/db.php";

$db = new db();

$results = $db->query($sql);
$db->query("delete from catalog_category_product where category_id = $category_id");
$db->query(build_query($results));








Access magento database outside magento

Just a quick note, sometimes you need to access the magento database from a script, but don’t want the overhead of loading the whole magento framework….. Here’s a little snippet of code that  grabs the database details from the magento xml config, so you don’t have to store them in your script:

<?php
 class db {

 private $data = null;


 public function __construct(){
     $xml=simplexml_load_file(realpath(dirname(__FILE__))."/../../app/etc/local.xml");

     $host = (string) $xml->global->resources->default_setup->connection->host;
     $db = (string) $xml->global->resources->default_setup->connection->dbname;
     $user =(string) $xml->global->resources->default_setup->connection->username;
     $pass = (string) $xml->global->resources->default_setup->connection->password;
     $charset = 'utf8mb4';

     $dsn = "mysql:host=$host;dbname=$db;charset=$charset";
     $opt = [
         PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
         PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
         PDO::ATTR_EMULATE_PREPARES => false,
     ];
     $this->data = new PDO($dsn, $user, $pass, $opt);
 }

 public function query($sql){
    return $this->data->query($sql)->fetchAll(PDO::FETCH_ASSOC);
 }

 }

 ?>

Save it somewhere like /magento_root/scripts/include/db.php and use it like this:
 

<?php

require "include/db.php"
$db = new db();

$results = $db->query('select * from cataloginventory_stock_item');

?>

 

 

 




Mage Tools

Quick plug for some magento scripts I wrote for development / deployments of Magento 1.x

There’s some info in the readme, but I might write a couple of quick tutorials if anyone’s interested:

https://github.com/Tall-Paul/mage-tools




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.