Magento LAMP vs WIMP: Running Magento with IIS and WinCache

Let’s get started by getting IIS up and running with WinCache. Getting your IIS up and running with PHP itself is easy. Add the IIS role to your Windows Server setup in the Feature Manager and browse to the PHP IIS website. Once there, hit the big blue “Install PHP” button and let the Microsoft Web Platform Installer do it’s magic.

When you’re done, point your browser to this page. Another “install” button is presented. But for some reason this one does not always work (it didn’t in the Windows 2008 R2 Web edition I was using). So here’s how to get WinCache running Manually;
1. Download WinCache for PHP 5.3 from sourceforce.net.
2. Extract php_wincache.dll to “C:\Program Files (x86)\PHP\v5.3\ext”
3. Edit “C:\Program Files (x86)\PHP\v5.3\php.ini” and at the bottom of the file add the line “extension=php_wincache.dll”.
4. Extract wincache.php to your webroot. By default it’s somewhere like “C:\inetpub\wwwroot”.
5. Edit wincache.php and change the username and password in lines 42 and 43.
6. To be sure, restart IIS using yout IIS Manager.

Now browse to yourdefaultwebsite/wincache.php. If you hit a screen like the one below, your glasses are OK. If not, scream out lout and start asking for help…

WinCache statistics

WinCache statistics

Set up Magento to use WinCache
Back to Magento. The first part of WinCache, the opcode cache, kicks in a few levels below Magento. Magento does not know it’s there and does no need to. The opcode cache will keep a copy of the interpreted PHP opcode (created by the PHP binary when intepreting PHP files) in memory. It will use this copy each time a cached script is executed saving CPU cycles and IO operations (at the cost of a little memory). An opcode cache is vital for decent Magento performance. If your webhost does not implement one, sue them for stupidity (just kidding, don’t reference me in court).

The second part of WinCache we will want to use is the user cache. A user cache can be seen as a kind of dictionary. You can put data in it by attaching a label to it. When you want to extract that data at a later time you can access it by using that label. Magento comes with it’s own caching system. It can cache lots of things (System -> Cache management). It does this by utilizing the cache classes in the Zend Framework on which it is build.

To store all this cache it needs a backend. By default Magento will use the filesystem as its backend. It’s wise to keep it this way as the filesystem is the most likely to have space and is allways available. But it’s also wise to add a second backend that is a hell of lot faster then your filesystem. This is where we will want to use the WinCache data cache. Using these two backends is made possible by the TwoLevel cache. More on that in this great post by Fabrizio Branca.

Enable WinCache as the fast cache backend
The Zend Framework handles the backend itself, Magento just uses the interfaces/classes provided by the Zend Framework. At present the code required to use WinCache as a cache backend in not present in the Zend framework that ships with Magento. It is available in Zend framework 1.11. I’ve extracted the proper files here (download). Just download the zip and extract it’s contents to the root of your Magento installation to add WinCache data cache support to Magento 1.6+.

When that’s done we’ll need to configure Magento to use WinCache as it’s fast cache backend. Edit your app/etc/local.xml file and add these lines in the section;

<cache>
     <backend>Zend_Cache_Backend_WinCache</backend>
     <slow_backend>file</slow_backend>
</cache>

After saving your local.xml file be sure to flush Magento’s cache; System -> Cache management -> Select all -> Refresh -> Submit. When you check your wincache.php file again entries should show up under both “User cache” and “Opcode cache”.

URL Rewrites
Something else to think about when using Magento with IIS (or any other webserver then Apache) is to handle URL rewrites. IIS 7.5, which ships with Windows Server 2008R2, comes equipped with it’s own Rewrite module. By default the Magento installation contains .htaccess files which, among other things, instruct Apache’s URL Rewrite module how to handle the URL mappings. IIS does not use these .htaccess files. The IIS equivalent is the web.config file. Use a web.config file (which you have to save in your webroot) like the one below in order to use URL Rewrites (which you really should when doing this in production);

<?xml version="1.0" encoding="UTF-8"?>
<configuration>   
<system.webServer>
<rewrite>
<rules>
<rule name="Magento basic rewrite rule" stopProcessing="true">
      <match url=".*" ignoreCase="false" />
      <conditions>
        <add input="{URL}" pattern="^/(media|skin|js)/" ignoreCase="false" negate="true" />
        <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
      </conditions>
      <action type="Rewrite" url="index.php" />
    </rule>
    </rules>
</rewrite>
</system.webServer>
</configuration>

At this point we’re all done. Magento is up and running with IIS using WinCache.

This series of posts will compare a typical, non-tweaked, WAMP and WIMP stack, in terms of performance, running Magento CE 1.7.
Table of contents (work in progress)
1. Introduction
2. Running Magento with IIS and WinCache
3. Benchmarks (coming soon)
4. Conclusion (coming soon)

Magento LAMP vs WIMP: Introduction

When choosing a webserver stack, used to run PHP applications like Magento, a LAMP (Linux Apache MySQL PHP) stack is favored. Although nginx with php-fpm is on the rise, Apache on Linux is still the most popular choice. When choosing a reactor pattern based webserver like nginx or lightspeed, which some claim perform better under load, Linux is still the most popular choise. In fact here at RapidCommerce, all Magento hosting is done using Linux and a mix of Varnish/Apache/Nginx.

There is a “new” kid in town though, IIS. Obviously IIS does not run under Linux, its THE Microsoft Windows webserver. And it has never been popular to run PHP/MySQL based applications. With good reasons, I might add. IIS6 was a terrible performer when it came to PHP. However for IIS7 things have changed, so they say. Apparently the team behind IIS has been working closely with the PHP guru’s at Zend.

Add to these improvements in IIS7, the availability of a new opcode and user cache; WinCache. I started to wonder… How does this “new” WIMP (Windows IIS MySQL PHP) stack compare to the widely favored LAMP stack? So I decided to put things to the test. This series of posts will compare ZendServer CE (from those same gurus involved in improving IIS/PHP) with IIS7.

Both stacks can be seen as “out-of-the-box” PHP application servers. Both come with their own opcode cacher, Zend Optimizer+ with Zend Datacache SHM versus WinCache. Zend chose to run PHP as mod_php in Apache and in IIS PHP is run with FastCGI. I will acknowledge that adding Nginx/php-fpm and Apache/fast-cgi would be more fair, as they use a more comparable way to integrate PHP with the webserver. But I trust the guys at Zend know what they are doing and had their reasons to use the mod_php. The point being, out-of-the-box vs out-of-the-box. To make thing more comparable we’ll add Zend Server CE under Windows to the mix.

LAMP vs WIMP

This series of posts will compare a typical, non-tweaked, WAMP and WIMP stack, in terms of performance, running Magento CE 1.7.
Table of contents (work in progress)
1. Introduction
2. Running Magento with IIS and WinCache
3. Benchmarks (coming soon)
4. Conclusion (coming soon)

Magento product list AJAX scroll

Category and search result pages revolve around product lists. Magento allows you to control the amount of products that your webshop shows. Magento even lets the customer decided how many products he or she wants to view at any given time.

From a server performance point of view, the more products you show by default, the more load will be put on your server. That is probably the main decision to not always show all possible products in a certain listing. Crawlers like Google bot will add to that load, even when there are no visitors.

As a result your customers, when just browsing products, will have to “click to the next page”. A customer however might not be inclined to keep clicking “Next”. Altough Magento has many nice navigation features, this may have a negative impact on your convergence! Users might just miss that one perfect product they were looking for because he or she never reached the second page.

A few weeks ago I stumbled upon Infinite Ajax Scroll created by Jeroen Fiege. This nice little jQuery plugin controls the “user click for pagination” by detecting when a user hits the bottom of a page. Inspired by a customer that had their “products per page” set to 300, I decided to apply this jQuery plugin to Magento.

Use AJAX to do Magento scrolling!
When applying it to Magento it allows your visitors to keep scrolling trough product listing without clicking, or doing anything else then scrolling. You can download the drop-in extension for Magento here, should work for Magento 1.4, 1.5, 1.6 and 1.7. Or you can check out the demo here It should work for all themes that are not to heavily modified. When no javascript is supported, the traditional Magento toolbar pagination is shown. When javascript is supported, the user just has to scroll to the bottom of the page to see more results.

Magento Ajax Scroll

Magento Ajax Scroll

Instructions for installing this extension in 10 easy do-it-yourself steps.
1. Download the zip (DUH!)
2. Extract it’s contents to the root of your Magento installation. You can use FTP to do that. It’s the directory containing index.php [App] [Skin] [Media] among others
3. Log in to your Magento backend
4. Go to System -> Cache Management, select all options, and in the action dropdown select “Refresh”. Then click Submit.
5. Log out/in to your backend
6. Go to System -> Configuration -> Catalog and drop down the “Front-end” selection
7. This extension adds a new option here; “Use jQuery Infinite Ajax Scroll”
8. Set “Use jQuery Infinite Ajax Scroll” to “Yes”
9. Click save config.
10. All done!

A few thing to note
First of all this extension uses and includes the jQuery library. It needs jQuery to function. Magento ships with Prototype which by default conflicts with jQuery. To solve this a “no conflict” file is included which makes sure jQuery and Prototype don’t start a fight in your browser. jQuery, being the more popular javascript library of the two, is often used in other Magento extensions. In fact you can find Magento extensions just to add jQuery to your Magento setup. If jQuery is allready present somewhere else copy the file app/design/frontend/base/default/layout/rapidcommerce-ias.xml to app/design/frontend/YOUPACKAGE/YOURTHEME/layout/rapidcommerce-ias.xml and remove all the “<action method="addJs"><script>ias/jquery-1.7.2.min.js</script></action>” and “<action method="addJs"><script>ias/jquery-noconflict.js</script></action>” lines. Alternatively use your local.xml layout update file to accomplish the same. It’s not a problem if you don’t, but this will avoid jQuery getting (down)loaded twice.

If you use a custom theme which does not adhere the default Magento “div-layout” or a custom toolbar, then you might want to change the jQuery selector paths used (which default to the way Magento’s base theme is build). You will find these in the app/design/frontend/base/default/templates/ajaxscroll/ias.phtml, so copy that file to app/design/frontend/YOUPACKAGE/YOURTHEME/templates/ajaxscroll/ias.phtml. If you don’t, you’ll find this extension does not work. And if you know what I’m talking about, you’ll know what to change reading the Infinite Ajax Scroll instructions.

Ajax scrolling is added to the catalog pages (anchor and non-anchor), search result pages (both advanced and simple) and the tag list page. If you have other extensions adding controllers that contain a product list you can add these using the layout update XML described above.

Gimme! Gimme!
You can download the Magento Ajaxscroll Extension here. You can check out the demo here.

Always show shipping costs in Magento

Sometimes you just need one shipping method, or perhaps 99% of your customers use a specific method. This is often the case for European shops serving just one country. By default, Magento allows customers to get a quote estimate for shipping costs in the cart page.

The customer has to set it’s country and select the appropriate method in order to get a correct quote, including the shipping costs. In the scenario I mentioned earylier, it could be best to just set the country and shipping method by default. This way the customer will always get a price including shipping costs. Never again will they be surprised by a higher then expected price at the end of the checkout process!

A solution
So how to achieve this? Well… One can write a small extension that catches Magento’s sales_quote_collect_totals_before and set a shipping method if none is set. This shipping method then should be configurable from Magento’s backend. Not into writing code? Check the attachment. Download the extension here. I tested it in Magento CE 1.4, 1.5, 1.6. Should work in 1.7 as well.

Instructions for installing this extension:
1. Download the zip (DUH!)
2. Extract it’s contents to the root of your Magento installation. You can use FTP to do that. It’s the directory containing index.php [App] [Skin] [Media] among others
3. Log in to your Magento backend
4. Go to System -> Cache Management, select all options, and in the action dropdown select “Refresh”. Then click Submit.
5. Log out/in to your backend
6. Go to System -> Configuration -> Shipping Settings and drop down the “Origin” selection
7. This extension adds two new options here; “Apply defaults to empty quote” and “Default shipping method”
8. Set “Apply defaults to empty quote” to “Yes” , set “Default shipping method” to your preferred shipping method. And make sure “Country” is set.
9. Click save config.
10. All done!

When a customer visits your site and starts adding stuff to his shopping cart, all prices will be shown including shipping costs! (If this does not happen the first time you visit your site yourself, clear all your cookies, as you might still have an active quote present, in which case the extension will leave it alone).

How does this work… I mean in code…?
Well… The extension uses a simple observer to check if the shipping address has a country set. If it does not (which is the case for an empty quote) it will set one including the default method. Pretty simple stuff!! That looks like this;

<?php
class RapidCommerce_Defaultdestination_Model_Observer {
	public function handleCollect($observer) {
		if (!Mage::getStoreConfig('shipping/origin/applydefaultstoemptyquote'))
			return $this;
		$quote = $observer->getEvent()->getQuote();
		$shippingAddress = $quote->getShippingAddress();
		if (!$shippingAddress->getCountryId()) {
			$country = Mage::getStoreConfig('shipping/origin/country_id');
			$state = Mage::getStoreConfig('shipping/origin/region_id');
			$postcode = Mage::getStoreConfig('shipping/origin/postcode');
			$method = Mage::getStoreConfig('shipping/origin/shippingmethod');
 
			$shippingAddress
				->setCountryId($country)
				->setRegionId($state)
				->setPostcode($postcode)
				->setShippingMethod($method)
				->setCollectShippingRates(true);
			$shippingAddress->save();
 
			$quote->save();
		}
		return $this;
	}
}

Download this extension here.

Remove ‘Orders and Returns’ from default Magento footer

In Magento 1.6 they’ve added an extra link to the footer called ‘orders and returns’. This link however is added using an ‘addLinkBlock’ call, which doesn’t have a remove equivalent. Normal links added using addLink can be removed using removeLinkByUrl. But as I said, this isn’t possible with addLinkBlock.

First I tried removing the block with a <remove name=”return_link”/> but this generates an error (call to a method on a non object in page/template_links). One solution would be to just copy sales.xml to your own theme and remove the addLinkBlock action. But I find this a nasty solution as it isn’t update compatible.

Another solution, which also will work and doesn’t require you to copy a layout.xml file, is setting the template to an empty template file. This can be done using the following XML in your layout file (you probably want to place it in the <default> handle):

<reference name="return_link">
    <action method="setTemplate"><template></template></action>
</reference>

This makes “toHtml” on the block return nothing (an empty string) and thus doesn’t add anything to the footer_links

Magento infinite HTML escape for attribute option values and labels

Magento 1.6 seems to contain a bug that infinitly escapes dropdown and multiselect attribute values.

For you as the end user this means that when you add an option like “M&M” Magento will escape this to “M&M” on this first save. When you open the attribute you will see “M&M”. However when you save it again, Magento will escape it once more to “M&amp;M” and it will keep adding for each save you do.

This means your users will get weird values in their layered navigation and so on.

A bug report is filed here:

http://www.magentocommerce.com/bug-tracking/issue?issue=12699

A quick fix is to comment out line 164 to 171 of app/code/core/Mage/Adminhtml/controllers/Catalog/Product/AttributeController.php.
If the bug is not fixed when a new version of Magento is released, you will have to make a patch or move the code to local (or just comment these lines out again).

An even better fix by the way would be to unescape the options before displaying them. Will get back to you on how doing that later.

Magento 1.6 clear multiselect bug

Magento 1.6.0.0 and 1.6.1.0 appear to contain a nasty bug that makes it impossible to clear all values assigned to an attribute.
In older versions of Magento there was an “empty” option that you could select to clear the selection.

A quick and dirty fix would be to add this;

$html .= $this->_optionToHtml('', '');

right before

$value = $this->getValue();

around line 62 of the file /lib/Varien/Data/Form/Element/Multiselect.php

This however is a quick fix with two notes;
1. If you don’t expect this thing to be fixed in the next upgrade you install, make a copy of the file in /app/code/local or make a patch. Because if you don’t, the fix will be overwritten on upgrade and the bug will reappear if the Magento team ignores to fix it.
2. The actual problem is not caused in this file, it was however the easiest way to fix it. So when I have the time I will make a better fix. This should however work without side effects.

Magento: Help the tabs aren’t working in IE9!

If you’re running Magento and on the product detail page the tabs, for description, tags et cetera aren’t working in IE9 the problem lies in a prototype or IE9 bug. The easiest way of fixing this is changing one line of javascript.

When you open the template file of the tabs, which is catalog/product/view/tabs.phtml, you’ll find a line which says:

ul.getElementsBySelector('li', 'ol').each(function(el){

The solution is to remove the second parameter of this getElementsBySelector method, so the line looks like:

ul.getElementsBySelector('li').each(function(el){

Another solution I found on the internet is adding a meta tag which makes IE9 render the page in IE8 mode. But this is the same as sticking your head in the sand, or putting some buck-tape on a flashing warning light in your car.

Configuration files with sensitive information are accessible from the outside.

So you’re migrating your Magento installation to another domain and you’re not wanting to wait for your domain name to transfer or for DNS to sync you get this warning in the Magento admin panel: “As a result, configuration files with sensitive information are accessible from the outside.”. That, when you do some research, seems to be associated with the /app/etc/local.xml being accessible from the outside.

But being the good system administrator you are, you made sure that either AllowOverride All was enabled on you Magento base directory or for the better administrators, you moved the config for each directory to Apache and set AllowOverride None, and you’re still getting this message?!

The problem resides in the code Magento uses to check if your config file with sensitive database password is accessible from the outside. It does this in Mage_Adminhtml_Block_Notification_Security.php;

    private function _isFileAccessible()
    {
        $defaultUnsecureBaseURL = (string) Mage::getConfig()->getNode('default/' . Mage_Core_Model_Store::XML_PATH_UNSECURE_BASE_URL);
 
        $http = new Varien_Http_Adapter_Curl();
        $http->setConfig(array('timeout' => $this->_verificationTimeOut));
        $http->write(Zend_Http_Client::POST, $defaultUnsecureBaseURL . $this->_filePath);
        $responseBody = $http->read();
        $responseCode = Zend_Http_Response::extractCode($responseBody);
        $http->close();
 
        return $responseCode == 200;
    }

This needs to be able to resolve itself. If the address still resolves to another address that will return an 200 OK page (perhaps your old domain) then the error will be displayed. This will fix itself when DNS syncs. Or alternatively you can make sure your new domain name is added to /etc/hosts file or c:\Windows\System32\Drivers\etc\hosts.

Link object popup failing in XAF after deployment

Last week I deployed an XAF Web application to one of our production servers running IIS 7. All seemed well until I noticed that all Object Link windows (the popup that allows you to make associations between existing objects) missed the grid control and the image. The button where there but without the grid the popup isn’t very useful is it…

Turned out that the web.config (which was generated at project creation trough a New Project -> XAF template) contained this line;

<add name="WebWindowTemplateHttpHandler" verb="*" path="*.aspx" type="DevExpress.ExpressApp.Web.WebWindowTemplateHttpHandler, DevExpress.ExpressApp.Web.v11.1, Version=11.1.7.0, culture=neutral, PublicKeyToken=b88d1754d700e49a" preCondition="integratedMode,runtimeVersionv2.0" />

The problem being here this handler will not used unless “runtimeVersionv2.0″, which in my production IIS 7 server was not the case. Removing the runtimeVersionv2.0 precondition fixed things…