Introducing RackSeo

I remember reading How to level up as a developer and thinking that I should try and write a few different types of programs to challenge myself. I’d been curious about Rack Middleware so I made it a resolution for 2013 to code up an idea I’d had for a while.

It’s a common scenario that website owners to commission a site, then run an SEO campaign 6 months after the site has launched when they realise they have no traffic. This often leads to a request from the SEO people is to sort out unique and relevant meta tags for each page, matching the text of the h1 element and so on. If this wasn’t considered from the start it can be hard to drop in on some sites, particulary where there static/hard coded pages involved.

…Enter RackSeo

It’s a plain rack middleware which should work with any Rack based framework or app (Rails, Sinatra, Padrino, Merb etc.).

It processes the final HTML response, sets up the meta tags and populates them with sensible content based on the text in the page (using the summarize gem). You can specify a format for the title tag based on css selectors (eg pull the h1 text and append the name of the company) and it will work dynamically on every page without changing any code.

It’s configurable using a YAML file and can be set for different paths including wildcards.

You can read all about it over on the Github readme. I’m all ears on how to take it forward to being a useful tool in the future.

Enjoy!

Test Driving Prestashop With Behat, Mink and Selenium - Step by Step

Whilst I don’t do so much PHP development nowdays, I’ve been watching the current rennaissance with interest. PHP is taking note of trends being pushed by the Rails world with “clean and classy” frameworks such as Laravel and best practice manifestos like PHP the right way coming to light. This is all good news for the web because better quality code (whatever language it’s written in) raises the bar all round.

That said, there still seems to be a lack of good documentation concerning proper Behaviour Driven Development (BDD) with PHP and it’s a problem that we came across recently at Kyan. I’m going to try and do a walkthrough of setting up some simple fetaure testing using a default install of the Prestashop ecommerce framework. It’s a fairly sizeable project that doesn’t ship with test coverage - let’s fix that!

What you’ll need

 

1.) Install Prestashop

Prestashop has a good set of instructions so I won’t duplicate them all here. Suffice to say you need to set up a MySQL db and change a few permissions.

2.) Install Composer

Make sure PHP is in your path. You can test this by firing up terminal and typing

1
php -v

If you’re using MAMP, put something like this at the bottom of your .bashrc

1
export PATH="/Applications/MAMP/bin/:$PATH"

Double check the path to the MAMP binaries. It’s different in different versions of MAMP.

Now cd into your newly created prestashop folder and run the following commands to install composer;

1
2
mkdir bin
curl -s https://getcomposer.org/installer | php -- --install-dir=bin

If you’re installing using the default OSX apache and you get an error about “detect_unicode = Off”, you’ll also need to run the following;

1
2
3
4
sudo su
echo "detect_unicode = Off" >> /private/etc/php.ini
apachectl restart
exit

3.) Install Behat and Mink

This part is the reason I’m writing this blog post. There seems to be a lack of clear information about how to get these up and running from scratch. Hopefully this will be sorted out as time goes on but I would suggest people submit their points about what works and doesn’t work for them.

To get started, make a file called composer.json in the root of the Prestashop install and put in the following;

1
2
3
4
5
6
7
8
9
10
11
12
{
    "require": {
        "behat/behat":           "2.4@stable",
        "behat/mink-extension":  "*",
        "behat/mink-goutte-driver":     "*",
        "behat/mink-selenium2-driver":  "*"
    },
    "minimum-stability": "dev",
    "config": {
        "bin-dir": "bin"
    }
}

This is the equivalent of a Gemfile if you’ve used bundler and Ruby. So to get everything up and running, run composer like so.

1
php bin/composer.phar install

All being, well you should see the packages installing into a vendor folder in the project root.

4.) Setting up Behat

1
2
3
4
> bin/behat --init
+d features - place your *.feature files here
+d features/bootstrap - place bootstrap scripts and static files here
+f features/bootstrap/FeatureContext.php - place your feature related code here

This bootstraps the files needed to run the tests. Let’s go ahead and write our first test. Usually in test driven development, we write the tests before the code, but I’m advocating here that we can add tests to and exisiting site to give us the confidence to refactor and add features later on. Start editing /features/basket.feature and put in the following;

1
2
3
4
5
6
7
8
9
10
11
12
Feature: Basket
  In order to add a product to the basket
  As a website user
  I need to add the product to my basket
  And I should see the product in the basket 

  @javascript
  Scenario: Add a product to basket
    Given I am on a product page
    When I click "Add to cart"
    And I hover over "#shopping_cart a"
    Then I should see the product title 

This is an example of a Cucumber test (the natural language syntax is referred to as Gherkin) which is a popular way of doing acceptance testing in Rails and other frameworks. We proceed by running the test like so;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> bin/behat
Feature: Basket
  In order to add a product to the basket
  As a website user
  I need to add the product to my basket
  And I should see the product in the basket

  @javascript
  Scenario: Add a product to basket     # features/basket.feature:8
    Given I am on a product page
    When I click on "Add to cart"
    And I hover over "#shopping_cart a"
    Then I should see the product title

1 scenario (1 undefined)
4 steps (4 undefined)
0m0.039s

You can implement step definitions for undefined steps with these snippets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * @Given /^I am on a product page$/
 */
public function iAmOnAProductPage()
{
    throw new PendingException();
}

/**
 * @When /^I click "([^"]*)"$/
 */
public function iClick($arg1)
{
    throw new PendingException();
}

/**
 * @Given /^I hover over "([^"]*)"$/
 */
public function iHoverOver($arg1)
{
    throw new PendingException();
}

/**
 * @Then /^I should see the product title$/
 */
public function iShouldSeeTheProductTitle()
{
    throw new PendingException();
}

This runs the test and tells you what to do next. Copy the snippets into the features/bootstrap/FeatureContext.php file above the final curly brace. After saving and running bin/behat again, the output should change to include

1
TODO: write pending definition

5.) Enable Mink

This is where the documentation starts to part ways with the latest versions of Mink and Behat. After struggling with the official run through at http://mink.behat.org/ trying to get Zombie working as the Javascript client, I stumbled across the Mink example repository in the Behat official Github page here https://github.com/Behat/MinkExtension-example

The key is swapping out BehatContext in the FeaturesContext file, to make sure that it reads;

1
class FeatureContext extends Behat\MinkExtension\Context\MinkContext

and then you need to configure Behat and Mink to be looking in the right places and choosing the correct browser drivers. Make a file called behat.yml in the root of the project with the following;

1
2
3
4
5
6
7
8
default:
  context:
    class:  'FeatureContext'
  extensions:
    Behat\MinkExtension\Extension:
      base_url:  'http://tdd.prestashopexample.com/'
      goutte:    ~
      selenium2: ~

goutte seems to be a requirement as far as I can tell, but who knows…

Now when you run bin/behat again, you should see an error like this;

1
Curl error thrown for http POST to http://localhost:4444/wd/hub/session with params: {"desiredCapabilities":{"browserName":"firefox","version":"8","platform":"ANY","browserVersion":"8","browser":"firefox"},"requiredCapabilities":[]}

Enter Selenium…

6.) Setting up selenium

This should be quite straightforward. First off, make sure you have the standard version of Firefox installed. Download Selenium RC from the link at the top of the page and move the .jar file to the vendor/ folder. Then run the following command to start the Selenium server;

1
java -jar vendor/selenium-server-standalone-2.25.0.jar > /dev/null &

You should see INFO: Launching a standalone server and you’re good to try the tests again by running bin/behat. This time you’ll see Firefox start up, flash up with the page content and close again. The first test passed! Now let’s implement the others.

7.) Finishing the steps

Change the methods in FeatureContext.php to look like this;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * @Given /^I hover over "([^"]*)"$/
 */
public function iHoverOver($arg1)
{
    $this->getSession()->getPage()->find('css', $arg1)->mouseOver();
    $this->getSession()->wait(5000, "$('#cart_block_list').hasClass('expanded')");
}

/**
 * @Then /^I should see the product title$/
 */
public function iShouldSeeTheProductTitle()
{
    $product_title = $this->getSession()->getPage()->find('css', '.cart_block_product_name');
    $product_title == "iPod Nano";
}

A few things to note here before we proceed - this isn’t a very robust test and could be improved. There’s duplication all over the place which could be refactored (but it’s late and I’ve just got it working…). The real problem is that it’s assuming the title of the product as the assertion in the last point. A better approch would be to call/mock the product from Prestashop in the constructor and use that instead. Maybe in part 2.

Also, it bugged me that the wait->(...) call which executes JS is executed in the context of the session. It makes perfect sense in hindsight as getPage is returning a sort of DOM object but it tripped me up nonetheless.

Down to business - run bin/behat one last time and the output should look like this;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> bin/behat                                                                                       1 ↵
Feature: Basket
  In order to add a product to the basket
  As a website user
  I need to add the product to my basket
  And I should see the product in the basket

  @javascript
  Scenario: Add a product to basket     # features/basket.feature:8
    Given I am on a product page        # FeatureContext::iAmOnAProductPage()
    When I click on "Add to cart"       # FeatureContext::iClickOn()
    And I hover over "#shopping_cart a" # FeatureContext::iHoverOver()
    Then I should see the product title # FeatureContext::iShouldSeeTheProductTitle()

1 scenario (1 passed)
4 steps (4 passed)
0m4.056s

Happy days!

Conclusion

There’s a ton of PHP apps out there that would benefit from regression tests like this. They’re not that hard to write once you get going and they give you an invaluable safety net and peace of mind when it comes to refactoring and adding new features.

My motivation for writing this was that the current state of documentation seems a bit patchy, despite the Behat and Mink projects being well established. I suppose this is a call to arms to PHP devs to try this stuff out and get their feedback into the loop.

I’d love to see more advanced posts dealing with the various drivers that are available and also to hear what people think of this one. I’ll finish by saying I’m starting out on this trail so my advice is only what I’ve managed to cobble together up to now. If there’s improvements or suggestions in the comments I’ll happily fold them back in to the main post. Happy testing…

Transferring Customers From Prestashop to Magento

There’s always debate about which ecommerce platform is harder/better/faster/stronger, and from experience of Prestashop and Magento I can say they’re both really good for different types of shops. For Forsyths, I developed a Prestashop solution for their Sheet Music department whilst I was working there and it’s been great so far but…

When picking a solution it really pays to do some forward planning and in this case, Prestashop can’t scale up to the kind of multi-store functionality that this music department store needed. That said, it was a lot easier getting a Prestashop store off the ground (I tried both at the start) and it’s served us well. My only gripe is that Prestashop’s module and templating system isn’t flexible enough to make real changes without editing the core – and that screws up future updates. Not what you need for ecommerce purposes.

UPDATE Since writing this, it’s all change with Prestashop v1.5 They’ve had a basic overrides system in place for a while now. Also the new version supports multistore but I haven’t used it, yet…

** Making the switch

Swapping out the products from PS to Mage is pretty straightforward – it’s just a case of a mysql query which maps them into the right fields. As with any Magento import the process is more or less similar;

  • Add some dummy data into Magento, using all the fields you’re likely to use
  • Export the data as a csv using System > Import/Export > Profiles
  • Look at the file in your var/export folder and take a look at the headers

Tip for *nix users working with csv files – in terminal use the following command to save the first two lines to a file;

1
head -n 2 your_csv_file.csv > your_csv_file_head.csv

That’s if your csv file is really big.

** The problem with users

Now the above works fine for most things, but the problem with users lies in the different authentication that both shops use. Magento uses MD5 with a salt on the end, Prestashop uses a ‘Cookie Key’ prefix to the customer password, which is then MD5 encrypted. Anyone who’s looked into MD5 knows that this is a pig – you can’t reverse an MD5 into plain text, therefore you can’t get the original password strings in order to re-encode them (which is actually a good thing…).

** How to fix it

Clearly its impossible to convert from one MD5 hash to another so we have to try something different. Make the following file;

1
(magentoroot)/app/code/local/Mage/Customer/Model/Customer.php

and in it put the following;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Mage_Customer_Model_Customer extends Mage_Core_Model_Abstract
{
/**
* Authenticate customer
*
* @param string $login
* @param string $password
* @return true
* @throws Exception
*/
public function authenticate($login, $password)
{

$this->loadByEmail($login);
if ($this->getConfirmation() && $this->isConfirmationRequired()) {
throw Mage::exception('Mage_Core', Mage::helper('customer')->__('This account is not confirmed.'),
self::EXCEPTION_EMAIL_NOT_CONFIRMED
);
}
if (!$this->validatePassword($password) && !$this->validatePassword('hKvthisisyourgibberishcookiestringfromprestashopCM'.$password)) {
throw Mage::exception('Mage_Core', Mage::helper('customer')->__('Invalid login or password.'),
self::EXCEPTION_INVALID_EMAIL_OR_PASSWORD
);
}
Mage::dispatchEvent('customer_customer_authenticated', array(
'model' => $this,
'password' => $password,
));
return true;
}

//end class
}
}

Notice the random string prepended to the password variable? That’s your cookie string which you’ll find in your Prestashop install here;

1
(prestshop root)/config/settings.inc.php

It’s the line beginning

1
define('_COOKIE_KEY_', 'ThisIsTheBitYouWant...' 

Believe it or not that’s all you need to do. Import your MD5 hashes from Prestashop straight into Magento and it’ll authenticate them as Prestashop would do. I’ll go through the mysql import in another post.