Magento Certified Developer

I am now officially a Magento Certified Developer Plus. I took the exam today and passed. I have been studying hard for the last month, and taking the on-demand Fundamentals of Magento Development course. On top of that, having worked with Magento for the past 4 years has certainly helped!

Looking forward to even more years in Magento!

Posted in Magento | 5 Comments

Magento: Module Idea – Scope Level Flags

I have run into the situation numerous times (especially when working on Magento sites that were setup by other people) where configuration has been set on the website or store view levels when it didn’t need to be. You definitely cannot assume that people will always understand configuration scope.

The thing that kind of surprises me is that there is no flag/message/etc. that tells you that a setting has been set at a lower level. If you are looking at the default config, and someone has set some settings on the website level, you’ll never know unless you change the scope to the website level and find it. It would be great if there was a flag or a message next to any system config value that lets you know that settings have been set on the website/store view level. And, with multi-site instances, it should tell you which specific websites have other settings set.

I think I remember trying to create a module to do this a while back, and had a tough time figuring out how to do it well. I may take a look into it again and see if I have some better luck this time, but if anyone wants to join me in trying to figure it out – that would be awesome. It’s something the Magento community could really benefit from.

Posted in Magento | 5 Comments

Starting a New Job

I am excited to announce that on March 5th, I will be joining the extremely talented team at Lyons Consulting Group as an Applications Engineer.

Lyons Consulting Group is the leading Enterprise Magento partner in the U.S. having successfully completed hundreds of Magento projects. In 2010, they won the Magento Innovator of the Year award.

They’ve got great things going on there, and I’m thrilled to be able to be a part of it as they continue to grow in 2012.

Posted in Magento | 1 Comment

Moving to Austin, TX

I have been married for 8 years now, and we have mostly lived up in the St. Paul / Minneapolis area of Minnesota. We’ve been talking about wanting to move down south for years, and we decided we just need to go for it. If we don’t just make the decision and do it, we may never end up doing it. Target time for move is June.

For those of you who might not know, it gets really cold here in the winter. We hate the cold. It just feels miserable. Average high in St. Paul in January is 27 degrees. Average high in Austin in January is 62 degrees. Yes, it gets hot in August in Austin, but I would take really hot over really cold any day. There’s also less daylight during the winter. In Austin, we’ll have nearly an hour and a half extra daylight at the peak of winter. Sunset in December here is at about 4:30pm. So, it’s dark when I get up, dark when I’m in the car going to work, and dark before I get in the car to go home. The only daylight is when I’m in an office building, and it’s too cold to want to go outside and enjoy any of the daylight.

The transition was made a lot easier with my current employer, August Ash, being willing to keep me on working remotely. So I will still have the same job. A huge plus of this setup is that I currently have to commute 45 minutes one-way. So I will gain 7 hours a week not having to drive a long way to work and back.

We have heard nothing but great things about the Austin area. If you have any suggestions about places to go, things to do, groups to get involved in, people to meet, etc., I would love to hear them.

Posted in Misc | 2 Comments

Magento Module: Hide Empty Categories

There are a number of ways that you can hide empty categories: You can disable them manually, you can modify the template output to check the product count and if zero don’t display it, etc. Another way is to write a module and intercept the catalog collection output before it’s sent to the templates.

I could easily package this up and put it up on Magento Connect, but I’m more into trying to help people understand Magento modules, so below is a walkthrough of how to create a module that will provide this functionality.

Note: Update – Fixed for Flat Categories too. This has only been tested on 1.6.x.

Step 1

Create the file ‘/app/etc/modules/Prattski_HideEmptyCategories.xml’. This file tells Magento that your module exists, if it’s enabled or not, and where to find it.

<?xml version="1.0"?>
<!--
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
-->
<config>
    <modules>
        <Prattski_HideEmptyCategories>
            <active>true</active>
            <codePool>local</codePool>
        </Prattski_HideEmptyCategories>
    </modules>
</config>

Step 2

Now we create our module directory. Create ‘/app/code/local/Prattski/HideEmptyCategories/’. This is where our module will reside. Inside that directory, create this file: ‘etc/config.xml’. This file does a number of things: It tells Magento which version your module is, defines your model namespace and directory, and creates and event observer to observe every time a category collection is loaded.

<?xml version="1.0"?>
<!--
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
-->
<config>
    <modules>
        <Prattski_HideEmptyCategories>
            <version>1.0.0</version>
        </Prattski_HideEmptyCategories>
    </modules>
    <global>
        <models>
            <hideemptycategories>
                <class>Prattski_HideEmptyCategories_Model</class>
            </hideemptycategories>
            <catalog_resource>
                <rewrite>
                    <category_flat>Prattski_HideEmptyCategories_Model_Catalog_Resource_Category_Flat</category_flat>
                </rewrite>
            </catalog_resource>
        </models>
    </global>
    <frontend>
        <events>
            <catalog_category_collection_load_after>
                <observers>
                    <hideemptycategories>
                        <type>singleton</type>
                        <class>hideemptycategories/observer</class>
                        <method>catalogCategoryCollectionLoadAfter</method>
                    </hideemptycategories>
                </observers>
            </catalog_category_collection_load_after>
        </events>
    </frontend>
</config>

Step 3

Let’s create our observer model. As you can see in the file above, we are going to have the ‘catalogCategoryCollectionLoadAfter’ method run in this file when it observes the ‘catalog_category_collection_load_after’ event. Create this file: Model/Observer.php

<?php
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
 
/**
 * Event Observer
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 */
class Prattski_HideEmptyCategories_Model_Observer extends Mage_Core_Model_Abstract
{
    /**
     * Remove hidden caegories from the collection
     *
     * @param Varien_Event_Observer $observer
     */
    public function catalogCategoryCollectionLoadAfter($observer)
    {
        if ($this->_isApiRequest()) return;
        $collection = $observer->getEvent()->getCategoryCollection();
        $this->_removeHiddenCollectionItems($collection);
    }
 
    /**
     * Remove hidden items from a product or category collection
     * 
     * @param Mage_Eav_Model_Entity_Collection_Abstract|Mage_Core_Model_Mysql4_Collection_Abstract $collection
     */
    public function _removeHiddenCollectionItems($collection)
    {
        // Loop through each category or product
        foreach ($collection as $key => $item)
        {
            // If it is a category
            if ($item->getEntityTypeId() == 3) {
 
                if ($item->getProductCount() < 1) {
                    $collection->removeItemByKey($key);
                }
            }
        }
    }
 
    /**
     * Return true if the reqest is made via the api
     *
     * @return boolean
     */
    protected function _isApiRequest()
    {
        return Mage::app()->getRequest()->getModuleName() === 'api';
    }
}

Step 4

Magento has the ability to turn on Flat Categories to speed up the performance whenever categories are loaded. The event that we are observing above does not work when Flat Categories are enabled. So, thanks to Vinai’s help, we need to rewrite a method in a core file to make this work. Create this file: Model/Catalog/Resource/Category/Flat.php

<?php
/**
 * Hide Empty Categories
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 * @copyright   Copyright (c) 2011 Prattski (http://prattski.com/)
 * @author      Josh Pratt (Prattski)
 */
 
/**
 * Event Observer
 *
 * @category    Prattski
 * @package     Prattski_HideEmptyCategories
 */
class Prattski_HideEmptyCategories_Model_Catalog_Resource_Category_Flat
    extends Mage_Catalog_Model_Resource_Category_Flat
{
    /**
     * Load nodes by parent id
     *
     * @param unknown_type $parentNode
     * @param integer $recursionLevel
     * @param integer $storeId
     * @return Mage_Catalog_Model_Resource_Category_Flat
     */
    protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId = 0)
    {
        $nodes = parent::_loadNodes($parentNode, $recursionLevel, $storeId);
 
        foreach ($nodes as $node) {
            if ($node->getProductCount() == 0) {
                unset($nodes[$node->getId()]);
            }
        }
        return $nodes;
    }
}

Step 5

To recap all the files we have made:

  • /app/etc/modules/Prattski_HideEmptyCategories.xml
  • /app/code/local/Prattski/HideEmptyCategories/etc/config.xml
  • /app/code/local/Prattski/HideEmptyCategories/Model/Observer.php
  • /app/code/local/Prattski/HideEmptyCategories/Model/Catalog/Resource/Category/Flat.php

Test it out! You’ll be able to see if Magento recognizes your module by going to System > Configuration > Advanced. If you see the module in the list, Magento knows it’s there. You’ll have to have some categories in your system, some with products in them, some without, to see if it’s working properly on the frontend.

I hope you enjoyed learning how to build this module. If you find any issues or bugs, please let me know.

Posted in Magento | 48 Comments

Looking For Small/Medium Magento Module Jobs

I am coming back to the freelance market for a bit, and I am looking for small/medium Magento module jobs. If you or anyone you know needs any work done in this area, please let me know!

Posted in Magento | 3 Comments

Magento: Modify Collection To Include Comma Separated Values From Another Table

It was rather difficult to come up with a title for this post, so I’m not sure that it is completely accurate. But, I have been working on a module, and I needed to modify the catalog/product collection to add a column that contains comma separated skus of the related products associated to each product.

First step is to get the product collection,

$collection = Mage::getModel('catalog/product')->getCollection();

Now we need to modify the collection to add a new select column. The mysql is also important here. You’ll see that I’m using GROUP_CONCAT and DISTINCT, and towards the bottom specifying to group by ‘e.entity_id’. I’m not going to dive into why this is necessary or how it all works. You’ll be better off visiting the mysql documentation on those.

$collection = Mage::getModel('catalog/product')->getCollection();
 
$collection->getSelect()
    ->columns('GROUP_CONCAT(DISTINCT (SELECT sku FROM catalog_product_entity WHERE entity_id = r.linked_product_id) SEPARATOR \', \') AS related_skus')
    ->joinLeft(array('r' => 'catalog_product_link'), 'r.product_id = e.entity_id AND r.link_type_id = 1')
    ->group('e.entity_id');

The result of this is an additional field in the collection called ‘related_skus’ that is a comma-space separated list of skus that are related products for each product in the collection.

If you want to log the actual query that is generated by this, simply add this line below the code above:

Mage::log($collection->getSelect()->__toString());

Hopefully this helps you in some way!

Posted in Magento, MySQL | 1 Comment

Magento: Adding/Updating Products with V2 API

Magento unfortunately has virtually no documentation on the V2 API. I needed to prove to someone today that using the V2 API, you can indeed create a product, assign it to 2 or more websites, and set the product price to different values for each website (as long as you’ve set the price scope to “Website” instead of the default “Global” which is in System > Configuration > Catalog > Price).

In this example, I set the initial product data to create, and run the API method ‘catalogProductCreate’. After the product has been created, I then set the product price for the wholesale website, and then run the ‘catalogProductUpdate’ method. The key here is passing in the correct store view code. Ultimately, the product is created with the price of $45. Then the wholesale website is updated to set a price of $20.

<?php
 
//error_reporting(E_ALL);
//ini_set('display_errors', '1');
 
/**
 * Get API Session
 */
$client = new SoapClient('http://core.local/api/v2_soap?wsdl=1');
$session = $client->login('augustash', 'password');
 
/**
 * Set Product Data
 */
$newProductData                     = new stdClass();
$newProductData->name               = 'Product Name';
$newProductData->description        = 'Description';
$newProductData->short_description  = 'Short Description';
$newProductData->websites	    = array(1,2);
$newProductData->categories         = array(7,15);
$newProductData->status             = 1;
$newProductData->price              = 45;
$newProductData->tax_class_id       = 2;
$newProductData->weight             = 1;
 
/**
 * Create Product Using V2 API
 */
$result = $client->catalogProductCreate(
    $session,           // Soap Session
    'simple',           // Product Type
    4,                  // Attribute Set Id (Default)
    'product-sku',      // Product Sku
    $newProductData     // Product Data
);
 
 
/**
 * Set Price for Wholesale Website
 */
$newProductData         = new stdClass();
$newProductData->price  = 20;
 
/**
 * Update Product with Wholesale Price using V2 API
 */
$result = $client->catalogProductUpdate(
    $session,           // Soap Session
    'product-sku',      // Product Sku
    $newProductData,    // Product Data
    'wholesale'         // Store View Code
);
 
 
/**
 * End Session
 */
$client->endSession($session);

If you are wondering where to look to find out what values you can pass in, and what parameters are needed when making V2 API calls, you’ll need to look at the wsdl. In app/code/core/Mage/Catalog/etc/wsdl.xml, you’ll find the product API info. I’ve extracted the product data fields that you can pass in when creating a product:

<complexType name="catalogProductCreateEntity">
    <all>
        <element name="categories" type="typens:ArrayOfString" minOccurs="0" />
        <element name="websites" type="typens:ArrayOfString" minOccurs="0" />
        <element name="name" type="xsd:string" minOccurs="0" />
        <element name="description" type="xsd:string" minOccurs="0" />
        <element name="short_description" type="xsd:string" minOccurs="0" />
        <element name="weight" type="xsd:string" minOccurs="0" />
        <element name="status" type="xsd:string" minOccurs="0" />
        <element name="url_key" type="xsd:string" minOccurs="0" />
        <element name="url_path" type="xsd:string" minOccurs="0" />
        <element name="visibility" type="xsd:string" minOccurs="0" />
        <element name="category_ids" type="typens:ArrayOfString" minOccurs="0" />
        <element name="website_ids" type="typens:ArrayOfString" minOccurs="0" />
        <element name="has_options" type="xsd:string" minOccurs="0" />
        <element name="gift_message_available" type="xsd:string" minOccurs="0" />
        <element name="price" type="xsd:string" minOccurs="0" />
        <element name="special_price" type="xsd:string" minOccurs="0" />
        <element name="special_from_date" type="xsd:string" minOccurs="0" />
        <element name="special_to_date" type="xsd:string" minOccurs="0" />
        <element name="tax_class_id" type="xsd:string" minOccurs="0" />
        <element name="tier_price" type="typens:catalogProductTierPriceEntityArray" minOccurs="0" />
        <element name="meta_title" type="xsd:string" minOccurs="0" />
        <element name="meta_keyword" type="xsd:string" minOccurs="0" />
        <element name="meta_description" type="xsd:string" minOccurs="0" />
        <element name="custom_design" type="xsd:string" minOccurs="0" />
        <element name="custom_layout_update" type="xsd:string" minOccurs="0" />
        <element name="options_container" type="xsd:string" minOccurs="0" />
        <element name="additional_attributes" type="typens:associativeArray" minOccurs="0" />
    </all>
</complexType>

Here are the parameters that can be passed in when using the create and update methods:

<message name="catalogProductCreateRequest">
    <part name="sessionId" type="xsd:string" />
    <part name="type" type="xsd:string" />
    <part name="set" type="xsd:string" />
    <part name="sku" type="xsd:string" />
    <part name="productData" type="typens:catalogProductCreateEntity" />
</message>
 
<message name="catalogProductUpdateRequest">
    <part name="sessionId" type="xsd:string" />
    <part name="product" type="xsd:string" />
    <part name="productData" type="typens:catalogProductCreateEntity" />
    <part name="storeView" type="xsd:string" />
    <part name="productIdentifierType" type="xsd:string" />
</message>
Posted in Magento | 3 Comments

Magento: Bug With Editing Date & Time, Time Custom Options

Using Magento 1.5.0.1, I noticed an interesting bug this afternoon. If you have setup any Date & Time or Time custom attributes, and have made them NOT required, here’s something you may notice:

When initially adding the product to the cart, and you leave the Date & Time or Time custom options NOT filled out, your product will be added to the cart just as you would expect. The untouched custom options are not applied to the product in your cart. However, in the rare case where a user is clicking the “Edit” link in the cart to edit it, and you still leave those custom options untouched, the validation will bark at you saying “Field is not complete” on those custom options. The only way to edit that product then is to put values in for those fields. Magento updates the product as it should, but you now have those other custom options in play, which could screw things up.

I have submitted the bug to Magento: http://www.magentocommerce.com/bug-tracking/issue?issue=12183

Posted in Magento | Comments Off on Magento: Bug With Editing Date & Time, Time Custom Options

Disappointed with Magento Module Sales/Distribution

Magento, from the beginning, has offered a system of packaging extensions, and installing those packages. It was a smart move, and one that I really appreciated. There are a lot of developers out there who properly package their extensions (everything that you can get directly off Magento Connect is packaged). However, there are still loads of developers who do not package their extensions. This is especially true with developers who sell their extensions.

One particular developer that I’d like to point out my frustration with is AITOC. Before I get started though, they ARE talented developers, and they make good functioning modules. However, I can’t stand their pricing and distributing philosophies.

Most of their modules are based off of how many enabled products you have in your catalog, how many stores/websites you have setup in Magento, or how many admin accounts you have setup. Let’s say you want to use one of their modules for 50 of your products, but you have 10,000 products in your catalog. Or, you want to use one of their modules for one of your websites, but you have 10 websites setup in Magento. With both of these scenarios, prepare to spend big bucks. You can easily go from a $99 module, to a $599 module. I hate that.

Also, let’s say you have 950 products in your catalog, and you buy a module from them and you get the license that allows up to 1000 products. When you hit 1001, the functionality of that module will disable itself.

Now to their distribution. When you buy one of their modules, prepare for a headache! They are loaded and bloated full of files. It’s a mess. You cannot install their module with a simple command line. Instead, you need to upload a ton of files, set special permissions, and then walk through a somewhat complicated installation process. There’s even a module in there just to install/manage the module you want to install, loading up your Magento install with over 100 additional files. They override a few Core Magento files and send out a web service request, checking your license to make sure it’s valid before it will fully install your module. Almost every single file in their module is wrapped in a PHP call that checks to make sure you have properly installed it, and that you are not exceeding the license rule (# of products, stores, admin accounts, etc.).

They make it a headache to install, a headache to maintain, and a nightmare to fully uninstall and remove all the files. If they properly packaged their extension, it would be cakewalk to install, update, and uninstall.

Why do they do it? It’s simple. As for their pricing, they want to make more money off you. They tease you with a price, but many people will not pay the base price for their modules. Why do they not package their extension? They want more control over you and your store, and more control ensuring people aren’t abusing their license (because of the dumb limitations they enforce) or re-using it over and over, or passing it along to other people to use.

They are smart in that regard, but when it become a total pain for the end-user, you should re-think the process. I think they need to ditch the limitations. And, when it comes to the modules, properly package them up so that they can be installed with one command or using Magneto Connect. There are other ways of protecting a module from being re-used or re-distributed like IonCube that are far less of a headache to deal with.

So, if you are a Magento developer distributing or selling modules, please, make them into a proper Magento extension. It doesn’t take long, it’s not hard, and it works way, way better. And, don’t put annoying limitations on them like AITOC does.

Posted in Magento | 62 Comments