Solving Magento

Solutions for Magento E-Commerce Platform

by Oleg Ishenko

Magento Downloadable Product Type Tutorial

In this tutorial I am going to demonstrate some of the functions I’ve talked about in my overview of the Downloadable product type. I will develop a simple module that will add a new feature to the Downloadable type and while doing so I will be using some of the unique functions available to downloadable products.

The new feature is this: I want file size displayed next to product and sample links on the downloadable product detail page. For that I will have to do the following:

  • extend Link and Sample models with attributes to store file sizes,
  • obtain file sizes from files uploaded locally or from external URLs,
  • save file sizes to the respective attributes of Link and Sample models,
  • modify downloadable product templates to display the new attributes.

Before I begin coding, I have to set up the required directory structure and add a module registration file to /app/etc/modules. My package is called “Solvingmagento” and I name my module “DownloadableSize”. Thus, the name of the registration file is Solvingmagento_DownloadableSize.xml and its contents is this:

<?xml version="1.0"?>
<config>
    <modules>
        <Solvingmagento_DownloadableSize>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Downloadable/>
            </depends>
        </Solvingmagento_DownloadableSize>
    </modules>
</config>

Listing 1. Solvingmagento_DownloadableSize module’s registration file.

My extension will overwrite some of the Mage_Downloadable module’s settings and I must ensure that those settings are loaded before my modifications. That is why I declare my module dependent on Mage_Downloadable. The directory structure required by my module is quite simple. I’ll need a helper, an observer, a configuration file, and an installation script. Altogether my folders look like this:

/app/
    code/
        local/
            Solvingmagento/
                DownloadableSize/
                    etc/
                    Helper/
                    Model/
                    sql/
                        solvingmagento_downloadablesize_setup/
Figure 1. Directory structure of the Solvingmagento_DownloadableSize module.

Once the preliminary work has been done, I can move to the first step in my tutorial: adding new attributes to Link and Sample models. This is achieved by inserting new columns to their tables downloadable_link and downloadable_sample. I am going to create an install script that is executed the first time the system runs after the module’s installation. To declare an installation script I have to add a resources node tree to my module’s configuration. At this stage it it has the following contents:

<config>
    <modules>
        <Solvingmagento_DownloadableSize>
            <version>0.1.0</version>
        </Solvingmagento_DownloadableSize>
    </modules>
    <global>
        <resources>
            <solvingmagento_downloadablesize_setup>
                <setup>
                    <module>Solvingmagento_DownloadableSize</module>
                    <class>Mage_Core_Model_Resource_Setup</class>
                </setup>
            </solvingmagento_downloadablesize_setup>
        </resources>
</config>

Listing 2. Declaring a setup class.

The name of my setup resource, solvingmagento_downloadablesize_setup, points to a directory that contains the setup script – sql/solvingmagento_downloadablesize_setup/. I am going to use a standard core setup class Mage_Core_Model_Resource_Setup that has everything I need to execute the module’s installation. The module’s current version is “0.1.0” and it is used in the name of the script: install-0.1.0.php.

To add a column I use the $connection object to access tables and alter them. I use PDO functions instead of DDL SQL code:

$connection = $installer->getConnection();

$connection->addColumn(
    $installer->getTable('downloadable/link'),
    'filesize',
    array(
        'type'      => Varien_Db_Ddl_Table::TYPE_INTEGER,
        'unsigned'  => true,
        'nullable'  => false,
        'comment'   => 'File size in KB'
    )
);

Listing 3. Adding a new column to table downloadable_link

In the same manner I add two more columns: sample_filesize to table downloadable_link to store file sizes of link-specific samples, and sample_filesize to table downloadable_sample for file sizes of product-specific samples.

My next task is to find a way to get sizes of download files and save them to respective models. To do that I am going to observe an event that is fired whenever a model is saved. The name of the event is model_save_before and the required configuration in the config.xml file is as follows:

<config>
    <!-- code omitted for brevity -->
    <global>
        <!-- code omitted for brevity -->
        <events>
            <model_save_before>
                <observers>
                    <solvingmagento_downloadablesize>
                        <class>Solvingmagento_DownloadableSize_Model_Observer</class>
                        <method>saveFileSize</method>
                    </solvingmagento_downloadablesize>
                </observers>
            </model_save_before>
        </events>
    </global>
    <!-- code omitted for brevity -->
</config>

Listing 4. Registering an event observer.

The method Solvingmagento_DownloadableSize_Model_Observer::saveFileSize() receives a model from the event and checks if it is a Link or a Sample. If the model is of a right type, the system invokes methods that access download files and get their sizes. The sizes are then added to their respective attributes in the model and will later get written to database. The saveFileSize method contents is this:

public function saveFileSize(Varien_Event_Observer $observer)
{
    $object = $observer->getEvent()->getObject();
    if (($object instanceof Mage_Downloadable_Model_Link)
        || ($object instanceof Mage_Downloadable_Model_Sample)
    ) {

        if ($object->getLinkType()) {
            Mage::helper('solvingmagento_downloadablesize')
                ->setFileSize($object, 'link_type', 'link_file', 'link_url', 'filesize');
        }

        if ($object->getSampleType()) {
            Mage::helper('solvingmagento_downloadablesize')
                ->setFileSize($object, 'sample_type', 'sample_file', 'sample_url', 'sample_filesize');
        }
    }
    return;
}

Listing 5. Setting file size to Link and Sample models.

The code checks if the $object variable has attributes link_type and sample_type and then calls a helper function setFileSize(). The parameters passed to this function include a Link (or Sample) object and attribute names whose data will be used to evaluate file sizes. The system uses the first condition to set sizes of main Link files and the second one to do the same for sample files. Note that a Link model may have both a main and a sample file – in this case the helper function will be called twice and set Link attributes filesize and sample_filesize respectively. Objects of type Mage_Downloadable_Model_Sample only have sample files and will skip the first condition.

The helper function setFileSize contains this code:

public function setFileSize($object, $typeParam, $fileParam, $urlParam, $sizeParam)
{
    $resourceType = $object->getData($typeParam);

    if ($resourceType == Mage_Downloadable_Helper_Download::LINK_TYPE_FILE) {
        $resource = Mage::helper('downloadable/file')->getFilePath(
            $this->getBasePath($object, $typeParam), $object->getData($fileParam)
        );
    } elseif ($resourceType == Mage_Downloadable_Helper_Download::LINK_TYPE_URL) {
        $resource = $object->getData($urlParam);
    }

    if (!isset($resource)) {
        $filesize = 0;
    } else {
        $filesize = $this->getFileSize($resource, $resourceType);
    }

    $object->setData($sizeParam, $filesize);
}

Listing 6. Function setFileSize().

As I’ve mention above, its parameters (except the first one) are attribute names, and because of this I can use this function for two different model types – Links and Samples. The $resourceType variable in the first line can be file or url, and depending on that I set my $resource variable to access either a local path or a remote URL. If a resource is set, I use it to get the file size using the same helper’s function getFileSize():

protected function getFileSize($resource, $resourceType)
{
    //removing an existing instance of the download helper
    Mage::unregister('_helper/downloadable/download');

    $helper = Mage::helper('downloadable/download');

    $helper->setResource($resource, $resourceType);

    $filesize = 0;

    try {
        $filesize = $helper->getFilesize();
    } catch (Exception $e) {
        Mage::logException($e);
    }

    $filesize = round($filesize / 1024);

    return $filesize;
}

Listing 7. Using the Mage_Downloadable module’s helper to access file resource and get file size.

In this method I am going to use a download helper from the Mage_Downloadable module. But before I do so I must remove an instance of it from the Mage registry. The reason for that is that helpers are instantiated as singletons and retain their initial properties. If there is already a download helper with, say, a URL handle, I won’t be able to use it to assess size of a local file. So I unregister a registry key that may hold a helper instance and immediately after that get a new helper object to which I set a resource and a resource type. The rest of the job is done by the download helper’s function Mage_Downloadable_Helper_Download::getFilesize():

public function getFilesize()
{
    $handle = $this->_getHandle();
    if ($this->_linkType == self::LINK_TYPE_FILE) {
        return $handle->streamStat('size');
    }
    elseif ($this->_linkType == self::LINK_TYPE_URL) {
        if (isset($this->_urlHeaders['content-length'])) {
            return $this->_urlHeaders['content-length'];
        }
    }
    return null;
}

Listing 8. Mage_Downloadable’s helper returning file sizes, /app/code/core/Mage/Downloadable/Helper/Download.php, line 187.

At the end the file size value is returned to the observer method where it gets set to Link’s attributes filesize or sample_filesize, or to Sample’s attribute sample_filesize. The objects modified by the observer are then saved to database. Done.

The last task in this tutorial is to customize the output so that the new values are displayed in the front-end. To do that I copy two template files links.phtml and samples.phtml to a template folder of my extension. The original templates belong to the Mage_Downloadable module and can be found in the /app/design/frontend/base/default/template/downloadable/catalog/product/ folder. My new folder is /app/design/frontend/base/default/template/solvingmagento/downloadablesize/. I modify the templates so that file sizes are shown next to links and samples. So I put the following code at the point where a link’s title is being displayed:

<?php echo $this->escapeHtml($_link->getTitle());
if ($_link->getFilesize() > 0) {
    echo ' ' . Mage::helper('solvingmagento_downloadablesize')
        ->formatFileSize($_link->getFilesize());
}
?>

Listing 9. Displaying a link’s file size.

I use a helper function to format the output to display a correct decimal separator depending on a shop locale, e.g. period in the North American and comma in European locales. Also, if a file size is bigger that a megabyte, I convert it to MBs:

public function formatFileSize($filesize)
{
    $unit = 'KB';

    if ($filesize / 1024 > 1) {
        $filesize = round($filesize / 1024, 2);
        $unit     = 'MB';
    }

    $filesize = Zend_Locale_Format::toNumber(
        $filesize,
        array('locale' => Mage::app()->getLocale()->getLocale())
    );

    $filesize .= ' ' . $unit;

    return $filesize;
}

Listing 10. Formatting the file size.

I’ve made similar modifications to the samples file and now I must tell the system to use my templates instead of the original ones. To do that I create a layout updates file which I register in my configuration:

<config>
    <!-- code omitted for brevity -->
    <frontend>
        <layout>
            <updates>
                <solvingmagento_downloadablesize>
                    <file>solvingmagento_downloadablesize.xml</file>
                </solvingmagento_downloadablesize>
            </updates>
        </layout>
    </frontend>
</config>

Listing 11. Declaring a layout update.

The layout updates file solvingmagento_downloadablesize.xml is placed into the /app/design/frontend/base/default/layout/ folder. In the file I define the following updates:

<layout version="0.1.0">
    <PRODUCT_TYPE_downloadable>
        <reference name="product.info.downloadable.samples">
            <action method="setTemplate">
                <template>solvingmagento/downloadablesize/samples.phtml</template>
            </action>
        </reference>
        <reference name="product.info.downloadable.options">
            <action method="setTemplate">
                <template>solvingmagento/downloadablesize/links.phtml</template>
            </action>
        </reference>
    </PRODUCT_TYPE_downloadable>
</layout>

Listing 12. Downloadable layout updates.

The handle I am updating is PRODUCT_TYPE_downloadable, which is an area in the product details page specific to downloadable products. I tell blocks responsible for samples and links to use my new templates. After I save my sample downlodable product to get file sizes, I can finally demonstrate how a downloadable product’s page look like with the new attributes in it:

downloadable_front_end_new

Figure 2. Displaying file sizes in a downloadable product page.

This tutorial covers only a tiny part of the functionality available to Downloadable products and there is much more to explore. Still, I hope that this exercise has been helpful in showing a way this product type can be extended with a new feature. A complete module can be obtained here: Magento Downloadable Product Type Tutorial.

3 thoughts on “Magento Downloadable Product Type Tutorial

  1. Hi Oleg Ishenko
    thanks you for topic
    I have create extension, everything is nice until I edit a download product in backend,an error in the helper
    you can please check $this->getBasePath help me
    Regards!

  2. Thanks for the tutorial! I will give it try right now. Been looking to add description and other details to to link downloads and this should help. Thank you!

  3. Hi Oleg,

    thanks you for topic. I need help with the following:
    How to modify a downloadable product type that does not download the file, but occasion clicking the link in your account make the redirection to URL where are the pageflip?
    Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *

Theme: Esquire by Matthew Buchanan.