How you could serve static content files from your Magento extension folder

As a part of my personal ongoing “unobtrusive Magento extensions” campaign. I will show you another “hack”/approach you can apply in order to squeeze your static files under the main extension folder.

When I say static, I am mainly referring to images, CSS and JavaScript in this case.

For example, imagine you are coding an extension called “Sociable“, which will display several links to various web services like Twitter, GoogleBuzz, etc. Links that you can click and the publish a short message about a product/category on that web service. Something like on the image shown below. Logical question is, where do you store your images?

Usually images are stored within the root /media/ folder. So it might seem perfectly fit to store them under the /media/mycompany/mymodule/ folder.

Similar to that, CSS files are usually stored under the /skin/frontend/default/default/css/, possibly in a sub-folder like /skin/frontend/default/default/css/mycompany/mymodule/.

Now, this is not a bad approach, possibly just little “distributed” in terms of spreading your extension across several root folders, making it a bit harder for manual “cleanup” or maintenance, etc. This is the way how most of extensions are build these days.

Now I will show you another possible approach (not necessarily the better one) but more “centralized” in terms of keeping everything within one folder.

Basically it comes down to storing all your public static files under the extension folders like:

  • /app/code/community/Inchoo/Sociable/public/js
  • /app/code/community/Inchoo/Sociable/public/css
  • /app/code/community/Inchoo/Sociable/public/image

And adding few lines of code into a custom controller like /app/code/community/Inchoo/Sociable/controllers/StaticController.php that will basically be acting as static file server. Don’t worry, its actually pretty simple.

Let’s start with our /app/code/community/Inchoo/Sociable/etc/config.xml file. Within it we will first add the router definition like shown below.

<frontend>
<routers>
<inchoo_sociable>
<use>standard</use>
<args>
<module>Inchoo_Sociable</module>
<frontName>inchoo_sociable</frontName>
</args>
</inchoo_sociable>
</routers>
</frontend>

Now we will add some basic security check/handling to our files we will be serving torugh controller action. We can put this logic into the /app/code/community/Inchoo/Sociable/Helper/Data.php file, like shown below.

class Inchoo_Sociable_Helper_Data extends Mage_Core_Helper_Abstract
{
const DIR_IMAGE = 'image';
const DIR_CSS = 'css';
const DIR_JS = 'js';
 
/**
* @param string 'icons' or 'css' or 'js'
* @return string
*/
public function getExtPubDir($type)
{
return __DIR__.DS.'..'.DS.DS.'public'.DS.$type;
}
 
public function getAllowedFiles($dir)
{
$results = array();
$handler = opendir($this->getExtPubDir($dir));
 
/* Might be improved later via cache, not to list entire folder on each request. */
while ($file = readdir($handler)) {
if ($file != "." && $file != "..") {
$results[] = $file;
}
}
 
closedir($handler);
 
return $results;
}
}

Finally we will create controller /app/code/community/Inchoo/Sociable/controllers/StaticController.php that will be used to serve our static files directly from our extension folder (code shown below).

class Inchoo_Sociable_StaticController extends Mage_Core_Controller_Front_Action
{
public function getAction()
{
$type = $this->getRequest()->getParam('t');
 
switch ($type) {
case 'css':
return $this->_css();
break;
case 'image':
return $this->_image();
break;
case 'js':
return $this->_js();
break;
default:
$this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
$this->getResponse()->setHeader('Status','404 File not found');
break;
}
}
 
private function _css()
{
$file = $this->getRequest()->getParam('f');
$type = $this->getRequest()->getParam('t');
 
$helper = Mage::helper('inchoo_sociable');
 
if (in_array($file, $helper->getAllowedFiles($type))) {
$this->getResponse()->setHeader('Content-Type', 'text/css');
$this->getResponse()->setBody(file_get_contents($helper->getExtPubDir($helper::DIR_CSS).DS.$file));
} else {
$this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
$this->getResponse()->setHeader('Status','404 File not found');
}
}
 
private function _image()
{
$file = $this->getRequest()->getParam('f');
$type = $this->getRequest()->getParam('t');
 
$helper = Mage::helper('inchoo_sociable');
 
if (in_array($file, $helper->getAllowedFiles($type))) {
$icon = new Varien_Image($helper->getExtPubDir($helper::DIR_IMAGE).DS.$file);
$this->getResponse()->setHeader('Content-Type', $icon->getMimeType());
$this->getResponse()->setBody($icon->display());
} else {
$this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
$this->getResponse()->setHeader('Status','404 File not found');
}
}
 
private function _js()
{
$file = $this->getRequest()->getParam('f');
$type = $this->getRequest()->getParam('t');
 
$helper = Mage::helper('inchoo_sociable');
 
if (in_array($file, $helper->getAllowedFiles($type))) {
$this->getResponse()->setHeader('Content-Type', 'application/javascript');
$this->getResponse()->setBody(file_get_contents($helper->getExtPubDir($helper::DIR_JS).DS.$file));
} else {
$this->getResponse()->setHeader('HTTP/1.1','404 Not Found');
$this->getResponse()->setHeader('Status','404 File not found');
}
}
}

Basically that’s all we need. Now if we run the url like http://{{unsecure_base_url}}/index.php/inchoo_sociable/static/get/t/js/f/jquery.js we would get jquery script served. Same goes for url like http://{{unsecure_base_url}}/index.php/inchoo_sociable/static/get/t/image/f/delicious.png which would give us the image.

So now, you can use write HTML like img src=”http://{{unsecure_base_url}}/index.php/inchoo_sociable/static/get/t/image/f/delicious.png” in your extension block that would then pull the image from your extension folder.

Once again, this approach is not “official Magento way” if there is a such thing. Point of this article is to merely show how you can do it if you wish to keep your static content in one extension folder, and not other, folder like /media, /skin, /js, etc. Like shown below on the image.

P.S. I am not actually using jQuery in Magento (would never mix Prototype and jQuery). Image above is merely for demo purpose.

Surely there are better ways to handle this “serve static content files from your Magento extension folder” concept. You be the judge. The important thing is to know what the code does and you might do it differently in a given situation.

Hope someone finds it useful.

Cheers.