Latest posts

Magento 1.8 Development Cookbook

Recently, Packt published a new Magento book that I had been reviewing named Magento 1.8 Development Cookbook, by Bart Delvaux and Nurul Ferdous.
The book covers topics from very easy like installing Magento and setting up a sample configuration to more advanced, like creating a module (a useful one, not just a most basic Hello World!) or performance optimization.

The quality of code and hints inside the book is very good – you don’t have to fear that after reading it you’ll learn bad practices and nasty code hacks.
I would definitely recommend the book for novice and experienced Magento developers and I think that even after a year or two of making Magento websites you still can learn something from this book!

Here’s the link to a website where you can buy it: http://www.packtpub.com/magento-1-8-development-cookbook-2e/book

Magento 2: add CSS to your custom module

Note: this post applies to Magento ver. 2.0.0.0-dev57. It may not be valid in later versions anymore.

Thanks to improved modularity in Magento 2, you can place CSS files straight in the module directory (instead of placing some things in the app directory and other things in skin directory).

The CSS file itself goes into the view directory, where all the view-related things go. If you want to add it on the frontend, it goes into view/frontend and to the view/backend if it’s supposed to be included in the admin panel. You can place it in subdirectories as well. I, for example, like to keep CSS files in a separate directory, so when developing a test module named CookieInformation, I placed my CSS file in the app/code/Bgorski/CookieInformation/view/frontend/css directory.

The next thing you need to do is to actually include the file where it needs to be included. In Magento 1.* layout handles were actually XML nodes inside the <layout> node, like this:

<?xml version="1.0" encoding="UTF-8"?>

    
        
        
            cookieinformation/styles.css
        
    

It’s no longer the case in Magento 2. Each layout handle has actually its separate file named after the handle (like default.xml or adminhtml_system_config_edit.xml). Let’s say we want to add out CSS file on every page in the frontend. This means that we should create a view/frontend/layout directory and create a default.xml inside of it (because the default layout handle is used on every page along with any module_controller_action xml files specific to a given page). In my case (the CookieInformation module I mentioned above), the content of the file looks like this:


    
        
            
                Bgorski_CookieInformation::css/cookie-information.css
            
        
    

Notice that there’s no layout handle defined in the file (no <default> node), because as I mentioned above, the handle is defined in the file name. Also, notice that there is css/cookie-information.css in the argument string. It’s because my file is in the css subdirectory. If it was directly in view/frontend/ directory, there would be no css/ as part of the string, so the <argument> node would have just Bgorski_CookieInformation::cookie-information.css inside.

Magento 1.7 and 1.8: Redirect to referer in an observer

Normally, redirection is handled by controllers. However, if you want to do it in an observer, you can use the following piece of code inside its function that gets invoked when listening to an event:

$url = Mage::helper('core/http')->getHttpReferer() ? Mage::helper('core/http')->getHttpReferer()  : Mage::getUrl();
Mage::app()->getFrontController()->getResponse()->setRedirect($url);
Mage::app()->getResponse()->sendResponse();
exit;

In the code above, the system checks if there’s any referer. If so, Magento redirects to it and if not – there’s a fallback provided.

IMPORTANT: if you decide to make a redirect in an observer, you have to be aware that no further observers listening to the event (or next events from the same request) will be processed. There’s a good reason this is by default done in controllers and you should consider avoiding it in observers. However, if you decide to do it in an observer anyway, make double sure you know what you’re doing (and what doesn’t get processed as a result) and consider making a note about it in your project’s documentation.

Magento 1.7 and 1.8: JavaScript translations (translate.js)

Most Magento programmers know about the way things can be translated in Magento in the PHP code. You just have to use a syntax like that:

echo $this->__('Text to translate'); //in blocks, defined by Mage_Core_Block_Abstract
echo Mage::helper('somemodule/somehelper')->__('Text to translate'); // defined by Mage_Core_Block_Abstract
echo $this->__('There are %s things here',$this->countThingsHere()); //if you need something more dynamic

But what if you need to translate something that’s in JavaScript? Of course, sometimes you can make an inline script like this one:

<script type="text/javascript">
confirm("<?php echo $this->__('Are you sure?') ?>");
</script>

Or, you can use the Translator object that’s available in Magento. It has two methods – one for adding translations and the other one returning a translated string – much like the .csv-based translations do. You can use it inline in your php scripts like this:

Translator.add('Some text to translate','<?php echo Mage::helper('yourmodule')->__('Translated text') ?>'); //adds a translation
alert(Translator.translate('Some text to translate')); //returns an alert with 'Translated text' inside

But most of the time, you want your JavaScript code separated from HTML code and placed in .js files. Obviously, you can’t use any PHP translating functions there. Instead of that, you can put your translations in a jstranslator.xml in the etc directory of your module. The file looks like that:


    
        Some text to translate
    
    
        Some other text
    

When having this, you can add translations to a .csv file, like for normal, PHP-based translations, and you use them just like this:

alert(Translator.translate('Some text to translate')); 

It returns a translated text, if it exists in some csv file – and it doesn’t need adding the transiation via Translator.add() function anymore.

IMPORTANT: this method won’t work on Magento versions lesser than 1.7.0.0, as this feature wasn’t implemented there yet. You have to use other methods I described above.

Magento 1.7 and 1.8: What’s in the quote model when you add a configurable product to cart

When working with a quote (an object containing all the items in the cart, among other data), it’s important to know what’s actually there.

A configurable product contains simple products – one for every attributes values set available. For example, if you sell a t-shirt available in blue and red colors from which every one is available in in S, M and L sizes, you have six simple products tied to a configurable one. If you decide not to sell blue one in S size, you have five.

When you add a configurable product to your cart, you actually add two items to the quote item:

  • the configurable product you wanted to add
  • a simple product containing options values you added (for example: a blue t-shirt with a size of M)
  • The configurable one has the quantity you wanted to buy. The simple one has a quantity of 1.

    But here comes a question many Magento developers ask themselves: what if I add to cart the same configurable product with a different set of options? The answer is actually not so obvious. Magento adds another two products to the quote – one being the same configurable product with a new quantity (containing the quantity of the product with new set of options) and the other one being a simple product with a quantity of one.

    If you want to take a look what’s really inside the quote object when you add products to it, you can paste the following debug code to some place available everywhere – for example header.phtml (remember that it’s only for testing purposes and should never go live with any project):

    foreach (Mage::getSingleton('checkout/session')->getQuote()->getAllItems() as $item) {
        echo "There's an item named " . $item->getName() . " in qty of " . $item->getQty() . "
    "; }

    This way you’ll list all the items in the quote – no matter if they represent configurable, simple or other available types of products.

Magento 1.7 and 1.8: setting up a .gitignore file for development environment

If you chose Git as your favorite version control system and use it in your Magento project, you probably want to exclude some files from your Magento installation when pushing it to a remote repository – for example files that contain passwords or ones that could compromise safety or of your project in any other way. Such files should be only kept locally – imagine what would happen if you made an automatic deployment from Git repository to your client’s server, overwriting his local.xml file with your development one. One client less to worry about.

Here is a list of files that should be put in the .gitignore file in the main directory of your project:

app/etc/local.xml
media/catalog/product/cache/*
media/tmp/*
errors/local.xml
var/*
downloader/*
.htaccess
!*/.htaccess

app/etc/local.xml contains all the settings for your Magento installation, including database username and password. Media-related entries are for temporary files and for auto-generated thumbnails and small images that Magento generates from bigger product images provided. The errors/local.xml, if it’s there, manages error reporting – and by default print errors on the screen, so if there are any errors on the website, users see them in their browsers. Bad for business, believe me. Next directory, var stores auto-generated information like sessions states, cached files or error reports. You don’t want it in your repository, because every fresh website instance should have its sessions and cache cleaned out to ensure that the system really takes your files you modify into consideration when displaying things. The last directory there, the downloader directory, stores information about extensions downloaded from Magento Connect. It’s irrelevant for development purposes, because once they’re installed, they are in the rest of directories (e.g. app/code/community or skin/frontend/base/default – depending on extensions themselves) and you don’t need the info that they were installed via connect or how their ready-to-install packages look like.

The last declaration in the .gitignore also require some explanation. It’s an exlusion made for .htaccess files inside directories other than root. These files exist in various of places, including your ignored var and downloader directories. They shouldn’t be ignored, because they contain some important information, like denying access to session-related files. That’s why, though we ignore entire var and downloader directories, there is this one file in each of them that we want to keep.

It’s also worth mentioning that because we don’t ignore .htaccess files in var and downloader, those directories will still show up when you use git status or any other command that shows files and directories. But although they’re there, Git sees only .htaccess files inside – so don’t worry about using git add var/. command, or anything like that. You won’t add any unnecessary files this way.

There may be situations when you don’t want to ignore only Magento-related files. There may be other files, like project files/directories created by IDEs that you, or other developers working on the same project work or worked on. You may also see projects containing .httpasswd files – they also should be ignored for security reasons. Here is a .gitignore file ignoring .htpasswd file and files created by some major PHP IDEs like NetBeans (nbproject dir), PhpStorm (.idea dir), Eclipse (.project file) and Aptana Studio (based on Eclipse, so the file is the same):

app/etc/local.xml
media/catalog/product/cache/*
media/tmp/*
errors/local.xml
var/*
downloader/*
.htaccess
!*/.htaccess
.htpasswd
nbproject/*
.idea
.project

Magento: filtering collections

There are two types of collections in Magento – those that contain EAV objects and those that do not. Filtering methods are slightly different between them. It may be confusing at first glance, so I’ll try to explain it all in this post.

addAttributeToFilter vs addFieldToFilter function

Basic functions used for filtering collections are addAttributeToFilter and addFieldToFilter. Both of them are used in various places in the code, so it can be confusing to a beginning programmer which function to use. The main difference is that the addAttributeToFilter function is for EAV-based collection and addFieldToFilter is for the rest. Fortunatelly, Mage_Eav_Model_Entity_Collection_Abstract class (the one that is extended by EAV-based collections) also has the addFieldToFilter function that, in this case just executes addAttributeToFilter. The compatibility isn’t two-way, though (with the exception of Sales module collections and flat catalog – to ensure compatibility with EAV-based one), so when unsure which function should be used, it’s always safer to use addFieldToFilter.

So why there are two functions instead of just one? It’s because the addAttributeToFilter function allows us to set a JOIN type, whether addFieldToFilter does not. Let’s look at the method declaration in Mage_Eav_Model_Entity_Collection_Abstract:

    public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner')
    {
        // method body
    }

I cut off the method body, because it’s not the point here to analyze the function line by line – only to see what parameters can be passed to it. As we see, we can provide not only the attribute that has to be filtered and conditions that has to be met by a row to be included in the result (more about those conditions later). We can also provide JOIN type, which defaults to inner. We can set it to left instead – the only other possible value (at least I don’t see more possible values from looking at the code from Magento 1.7.0.2 – correct me in a comment if I’m wrong).

Filtering conditions

First of all, I’ll present an example of how addAttributeToFilter function can be used:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter('type_id', array('like' => 'simple'))
        ->load();

In the example above, we filter the collection, so it returns only products that has a type_id attribute set to simple. In other words, we get only simple products. We did this by using the like operator.

Here is a table of operators that can be used, along with their equivalents in plain English and in the resulting database query:

In plain English In the query In the addAttributeToFilter function Comments
Equal to = eq
Not equal to != neq
Greater than > gt
Greater than or equal to >= gteq
Less than < lt
Less than or equal to <= lteq
Like LIKE like For string comparison. SQL wildcard % can be part of the string.
Not like NOT LIKE nlike For string comparison. SQL wildcard % can be part of the string.
In IN() in Needs an array as a value. Returns only those object that has attributes with values matching an array element
Not in NOT IN() nin Needs an array as a value. Returns only those object that has attributes with values not matching any array element.
Is null (doesn’t have a value) IS NULL null It doesn’t matter what value you’ll provide, it’s just a check if the attribute has no value at all.
Is not null (has any value) IS NOT NULL notnull It doesn’t matter what value you’ll provide, it’s just a check if the attribute has a value, not what the value is.
Find in set FIND_IN_SET() finset Needs an array as a value. If unsure how does it differ from IN, see #2 Here

One more thing – those operators were not defined in the Abstract class. They exist in the classes that really need them and extend the Abstract one. That’s a good thing, because it prevents people from using operators on a data that wouldn’t make sense with them. If you do that, you’ll just be presented a nice exception instead of being given some unexpected and weird result from the database.

Filtering by date

When filtering collection using dates, you can use two additional conditions: from and to. Let me show it on an example:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter('updated_at', array(
            'from' => '2012-11-29',
            'to' => '2012-11-30',
            ))
        ->load();

This filters the collection so it contains only products modified between 29 and 30 November 2012. Of course, you don’t have to use both from and to simultaneously – you can use only one of them if you don’t need the other one.

There are also two additional attributes you can provide to the addAttributeToFilter function:

  • date – specifies that the from/to values should be converted to date before making the comparison
  • datetime – specifies that the from/to values should be converted to datetime before making the comparison

Because of the, you can provide your dates in some other format, for example in Unix timestamp, as in the example below:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter('updated_at', array(
            'from' => '1354147200',
            'to' => '1354233600',
            'date' => true
            ))
        ->load();

This example will do the exactly same thing as the previous one.

Simple filtering by comparison

There is also one filtering method I haven’t mentioned before. If you don’t need anything fancy and just need a simple comparison, you can filter a collection this way:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter('some_attribute', 3) 
        ->load();

Or this way:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter('some_attribute', array(2,3)) 
        ->load();

The first example selects only products with some_attribute set to 3 and the next one – only those with some_attribute set to 2 or 3. One thing, though – as I stated, this is a COMPARISON and uses “=” symbol. It’s not supposed to act as a NULL/NOT NULL checker – checking if something = 'NULL' is not the same as checking if it IS NULL, so you may get some unexpected results. And in German language, ‘null’ actually means ‘zero’, so in German shops the results might be even more unexpected ;)

Filtering with two or more filters – ‘AND’ and ‘OR’ operators

Consider the following example:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter('name', array('like' => '%Mouse%'))
        ->addAttributeToFilter('updated_at', array('to' => '2012-11-29'))
        ->load();

By default, filters added this way return an intersection of their collections. In this example, we will get only products updated before 2012-11-29 AND having ‘Mouse’ in the name. If you want to use OR instead of AND, you have to pass an array of arrays to the addAttributeToFilter method. Like that:

$productCollection = Mage::getModel('catalog/product')->getCollection()
        ->addAttributeToFilter(array(
            array(
                'attribute' => 'name',
                'like' => '%Mouse%'
            ),
            array(
                'attribute' => 'updated_at',
                'to' => '2012-11-29'
            )))
        ->load();

The code above will return all the products updated before 2012-11-29 OR having ‘Mouse’ in the name.

That’s probably everything I had to say about filtering collections right now. If you have any questions, found a bug in the code, some wrong explanation or just think that there should be any other filtering aspect covered in this article, feel free to leave a comment. Thanks!

Git: How to push the current branch typing “git push” only

If you want to push only the current branch commit(s) to the remote repository, even if the branch is not already there, using only git push (without specifying remote and local branches) you can change the default push behavior using the command git config push.default current. It will work for local git repository in which you currently are. If you want this change to work for all your local git repositories, type git config --global push.default current instead.

You can read more about push default actions (and other git config stuff) here: http://www.kernel.org/pub/software/scm/git/docs/git-config.html.

Magento 1.7: Internal Server Error instead of the installation page on Apache 2.0.x

Just a quick note – if you’re getting a 500 internal server error instead of the installation page, check what is your server running on. If it’s based on Apache in the 2.0.x version, this error might be due to a bug in the Apache – the RewriteRule won’t accept R values outside of 300 – 400 range.

Find this in your .htaccess code:

############################################
## TRACE and TRACK HTTP methods disabled to prevent XSS attacks

    RewriteCond %{REQUEST_METHOD} ^TRAC[EK]
    RewriteRule .* - [L,R=405]

Change the HTTP Error 405 Method not allowed redirect (R=405) to something in the 300 – 400 range or just remove it if you don’t care

This problem occurs on Magento in the 1.7.x versions – the RewriteRule in question wasn’t present in previous Magento versions.

Magento: how to add an admin user programmatically

There are situations, when it’s necessary to add an admin user via the code. For example, when Magento encounters the local.xml file (normally created during the installation) with a database defined, but the database itself is empty. In this case, an installation will be performed, but no admin account will be added, so it has to be added manually.

Here is a sample code:

$user = Mage::getModel('admin/user')->setData(array(
            'username' => 'admin',
            'firstname' => 'John',
            'lastname' => 'Doe',
            'email' => 'john@doe.com',
            'password' => 'mypassword',
            'is_active' => 1
        ))
        ->save();
$user->setRoleIds(array(1))
        ->setRoleUserId($user->getUserId())
        ->saveRelations();

First, some basic data is set and save. Then, we set a role, so the account becomes usable.

You can put the code in an install/upgrade script of the module if you want to do it in a clean way, or put it somewhere in the Magento code (for example at the end of index.php) if it’s just a quick fix on the dev or staging server (don’t do it on the production website!) – but remember to delete it from there after it’s executed once, because further executions will result in an exception being thrown (some of the data is unique).