Customizing Plugins in Joomla! 1.5x (Part 1)

Exclusive offer: get 50% off this eBook here
Joomla! 1.5x Customization: Make Your Site Adapt to Your Needs

Joomla! 1.5x Customization: Make Your Site Adapt to Your Needs — Save 50%

Create and customize a professional Joomla! site that suits your business requirements

$26.99    $13.50
by Daniel Chapman | August 2009 | Joomla! Content Management Open Source PHP

Plugins are an essential, but rarely noticed part of a successful Joomla!-based business. Plugins are very flexible and can execute for various purposes at many different times. Part of this article by Daniel Chapman will be to look at these purposes and times, and understand how they can affect our web site by going through the following topics:

  • plugin composition and operation
  • plugin types
  • plugin events
  • plugin ordering
  • plugin customization

We will also make some changes to an existing plugin so that it better meets our needs.

The code used in this article can be downloaded from here.

Plugin composition and operation

   

Like a module, in its simplest form, a plugin can consist of only two files, a PHP file with the actual code for the plugin, and an XML manifest that tells Joomla! what to do with the plugin.

Despite this apparent simplicity, plugins are very powerful, and more difficult to understand than modules and components.

Plugins are designed to run at certain times during the execution of our site, and they perform actions that can only be done at these times.

For example, in our sample site we want to hide some of our content from guests, and only show it to paid subscribers. This action can only be performed when we are actually preparing the content to be displayed, because we need to wait until we identify if the viewer is a guest or subscriber, and then make the changes to the content dynamically.

In a different example, checking if a subscriber's subscription is valid is something that only needs to be done when they try to login, and not on every page load.

Plugin types

Plugins are divided into eight types, as follows:

  • Authentication
  • Content
  • Editors
  • Editors-XTD
  • Search
  • System
  • User
  • XML-RPC

Authentication

Authentication plugins are designed to allow the site to check the user's authentication against a variety of sources.

The default is to check the user's authentication against a username and password stored in the Joomla! database, which, as of Joomla! 1.5, will be the username and password fields in the #__user table (#__ is the table prefix we chose when setting up Joomla!). However, any source with a public API can be used to verify someone's authentication details.

Common uses are LDAP, OpenID, a Google account, a subscription, community component, and more.

On our site, for example, we are already using an authentication plugin to verify the subscriptions of users when they attempt to login.

Content

Possibly the most commonly used of all plugins, content plugins allow content items to be modified or have additional features added to them.

We could, for example, use content plugins to cloak email addresses, embed audio or video into our pages, or do text replacements. We can even embed components and modules into our pages via plugins.

We will later look at a content plugin that we will use to hide and show content depending on a user's subscription.

Editors

Editors plugins add WYSIWYG editors that we can use when editing our content.

We installed JCE on our site earlier, which is the most popular Joomla! editor plugin as of this publication according to Joomla.org.

Editors-XTD

Editors-XTD (extended) plugins allow us to add additional buttons to the editors. The Image, Read more, and Pagebreak buttons on the default Joomla! WYSIWYG editor, for example, are actually plugins.

Search

Search plugins allow us to search through the data from different components.

By default, Joomla! comes with plugins that search through content articles and the Contacts and Weblinks components. These can be expanded upon by creating or installing search plugins for other extensions.

System

System plugins are arguably the most powerful and most flexible types of plugins for Joomla!, as they can execute at several different pre-defined points during the execution of a Joomla! page plugin.

They can be used to perform a vast array of functions, such as loading extra scripts or CSS into the header of a web page, redirecting people away from pages, logging statistics, and more.

User

User plugins allow us to perform actions at different times with respect to users. Such times include logging in and out, and also when saving changes to a user's profile. User plugins are often used to create a "bridge" between Joomla! and other web applications (such as the phpBB forum or the osCommerce e-commerce platform.).

XML-RPC

XML-RPC plugins are for communicating between our Joomla! site and other external applications, such as a desktop application or a different web site.

Plugin events

As a Joomla! site loads a page, it steps through a series of events as part of that process. The events it steps through are determined by the type of page it is loading. Plugins are always tied to one or more of these events, and are executed during those events as required.

When loading a page of content, for example, we would step through a mix of the system and some of the content events. When loading the same page for editing, we will step through the system events, different content events, and also possibly editor events.

The events triggered in Joomla! are:

Authentication

  • onAuthenticate

Content

  • onPrepareContent
  • onAfterDisplayTitle
  • onBeforeDisplayContent
  • onBeforeContentSave
  • onAfterContentSave

Editors

  • onInit
  • onGetContent
  • onSetContent
  • onSave
  • onDisplay
  • onGetInsertMethod

Editors XTD (Extended)

  • onDisplay

Search

  • onSearch
  • onSearchAreas

System

  • onAfterInitialise
  • onAfterRoute
  • onAfterDispatch
  • onAfterRender

User

  • onLoginUsexr
  • onLoginFailure
  • onLogoutUser
  • onLogoutFailure
  • onBeforeStoreUser
  • onAfterStoreUser
  • onBeforeDeleteUser
  • onAfterDeleteUser

XML-RPC

  • onGetWebServices

Most of these events are easy to understand from their name, but just in case, more information can be found on the Joomla! documentation wiki at http://docs.joomla.org/CategoryPlugins.

Some events are only activated at specific times, such as onAuthenticate, which is only activated when someone logs into their account. Others are activated on every page load. Content events are activated on all content pages and only on content pages, not on pages with components other than com_content.

Content plugins are also only executed on the main body content itself and don't have access to the template or other module data. So a text replacement content plugin, for example, wouldn't change any text in modules or the template, only in the main content itself.

It is actually possible for modules and components to manually activate plugin events with clever programming, but this is not the default Joomla! behavior. It is usually done when a developer wants to apply content plugin restrictions/changes to a module.

Plugin order

Aside from the events and types, there is a third important factor to consider when setting up our plugins. That is the order in which the plugins of a particular type are executed. This order is best observed on the Plugin Manager screen that can be found under the Extensions menu.

The order in which the plugins execute is something not many people think about, but is really quite powerful and useful. This is because the plugins which execute later, can then use the output or effects of the earlier executing plugins as input.

For example, imagine we have a plugin that displays different text for different user types, and we have another plugin that reads certain text values and replaces them with embedded video or audio. If we wanted to be able to show different videos to different groups, then we could use the first plugin to generate the different command strings for the second plugin, and have it generate them based on the user type.

The second plugin, our media embedding plugin, doesn't even know that the first plugin exists. All it knows is which videos it needs to display based on what is in the content item.

If the media plugin executes first, then it will generate both videos regardless of the user type.

As another example, imagine we have some sort of external application and we log users into it after they authenticate via an authentication plugin. We need to make sure that this plugin is executed after all of our other authentication plugins that may check a user's credentials or account status. Otherwise, someone may get logged into our external application even though they were prevented from login into our Joomla! site. So a hacker, for example, could get access to our external application without needing to even successfully get into our main site. This was all because we had the order of our plugins wrong.

Joomla! 1.5x Customization

So when we install and activate plugins, it is well worth taking the time to double check that everything happens in the order it is meant to be in. If one of our plugins is not behaving how it should, it might be worth checking the order to see if another plugin is conflicting with it.

Customizing a Plugin

Now that we have a better understanding of how our plugins work and fit together, we are going to try our hand at customizing one for our site. This will hopefully give us the understanding and confidence to make any other customizations we need in the future.

As with modules, it is often easier to find a plugin that does most of what we want it to do and then make changes to it so that it meets our needs more completely.

Looking back over our goals, one that requires a plugin is that we want to limit access to certain parts of our content to only our paying subscribers. This effect is going to be best achieved via content plugin, so we chose the Ninja Access plugin to fill this need.

To use Ninja Access we first need to mark the content we want to restrict with special tags and indicate the user groups we want to see what is contained within the tags. When the page is produced, the plugin reads the visitor's user group and then compares it to the ones in the list provided by the tag.

If the user groups match, then the content is displayed, if not, then it is hidden.

For example:

{njaccess 0}shows only to guest users{/njaccess}
{njaccess 18,19,20,21,23,24,25}shows to all users who are not a guest{/njaccess}

The numbers in the examples above indicate the default Joomla! user group ids.

The most important ones are:

  • 0 = Guests
  • 18 = Registered
  • 24 = Administrators
  • 25 = Super Administrators

We could use this as it is, but as we don't have a component installed to create new access groups, it won't be very flexible. We could get into trouble in the future if we decide to let people register without getting a subscription, or create a free subscription. In this instance, we will have paying and free subscribers all in the number 18 user group.

Also, as we are always going to be restricting the same groups, we don't really need to type the parameters in every single time. Making our plugins always restrict the same groups automatically will save us some time and reduce mistakes.

Lastly, do we really need to type njaccess every time? Let's shorten it to something like soc—subscriber only content.

For our first dilemma, a better idea than groups might be to associate the access to certain AEC subscriptions that are currently active. That way if people's subscriptions expire, or they get a free account, the content is still properly controlled regardless of their user groups.

Joomla! 1.5x Customization: Make Your Site Adapt to Your Needs Create and customize a professional Joomla! site that suits your business requirements
Published: August 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

Step 1: Understand the existing code

The customizing process for plugins is basically identical to modules. We will start by unzipping the file that the plugin came in, plg_ninjaaccess_v1.1.zip. Now, let's take a look at the ZIP file's contents.

en-GB.plg_content_njaccess.ini

This can be easily recognizable as a language file. We can see that the name is slightly different but the convention is the same, <language>-<locale>.<extension_name>.ini.

Opening up this file, we find a single line:

NINJACONTENT=<IFRAME SRC="../plugins/content/njaccess/njaccess_desc.html" WIDTH=600 
HEIGHT=600 FRAMEBORDER=0 SCROLLING=yes></IFRAME>

This doesn't look much like a typical language entry. After the Key, the Value is actually some HTML calling an IFrame.

This is actually a work around for a "bug" in the current (current at time of writing) Joomla! code that handles modules and plugins.

Description handling in Joomla! Plugins and modules

In Joomla! 1.0, rich extension descriptions, descriptions containing HTML, links, images, detailed information and instructions, and so on were usually done by wrapping everything in CDATA tags in the XML manifest. However, with Joomla! 1.5, a move was made to allow for greater language support, and developers were encouraged to use the new .ini style language files instead of CDATA to display their descriptions.

This did make sense, and has allowed for greater language support. However, left developers (like myself) who liked having rich extension descriptions with a bit of a problem. The problem was that .ini language files need to contain the entire Value for a certain Key on a single line. So what would usually be 200-500 lines of HTML and data was now compressed down to one line. This made finding and fixing problems, or editing the description pages, very difficult.

To top it off, when we install a module or plugin for the first time the language files aren't actually loaded, so all we see is the Key value. It isn't until we go to the parameters screen that we see the language file contents.

Conversely, CDATA works in the install screen, but not in the parameters screen for a module or plugin.

As an answer to this, some developers decided to use IFrames that called an external file, which was formatted with the description information, until a more suitable solution can be found.

Our custom Plugin description

Lucky that we do not need to worry about a fancy description page because this will be our private custom plugin and we don't need to teach others how to use it. Normally, we could just remove this here, but with our custom plugin we may want it to output a message for our visitors when content is explaining to them that they can see it if they get a subscription. So for now we will leave this file as it is and edit it later.

njaccess.php

This is the main part of our plugin, and where we will do most of our work.

Let's take a look at the code piece by piece.

defined('_JEXEC') or die( "Direct Access Is Not Allowed" );
jimport('joomla.eventPlugin');

The first line is our ever-important check to make sure that our code is running inside a Joomla! site. If this line is ever missing from some code it is a potential security risk, so we need to always include it if it is missing. It cannot be stressed enough how important this check is.

Next, we load the plugin event class, which gives us access to the JPlugin class, which we will use as shown:

class plgContentNJaccess extends JPlugin {
function plgContentNJaccess ( &$subject ) {
parent::__construct( $subject );
$this->_plugin = JPluginHelper::getPlugin('Content',
'ninjaacess');
$this->_params = new JParameter($this->_plugin->params);
}

Now we start the main part of our plugin, which is our plugin class, plgContentNJaccess. The name is comprised of three parts:

  • plg, identifying this as a plugin
  • Content, indicating the type of plugin this is—a content plugin
  • Njaccess, the name of our plugin file, without the .php

This is a very important naming convention used for plugins and needs to be followed if we want to make use of the Joomla! framework functions. The convention is: plg + proper case name of the plugin type + proper case name of the plugin file without the extension.

Proper case means that we capitalize the first letter of each word when we assemble them. Once assembled, it's then referred to as "Camel Case", which is a reference to how the capital letters in the one word look like humps on a camel. Strictly speaking, the case is not actually important for execution as PHP classes are not case-sensitive but it's the convention Joomla! uses and does make the code easier to read.

Our plugin class is created as an extension of the JPlugin class. What this does is allow our class to inherit all the functions and variables of the JPlugin class, which we need for the basic running of a plugin, and so we can add our own to it for the extra functionality we want.

Some of the terminology used here is specific to a style of programming known as Object-oriented Design. For more information, seehttp://en.wikipedia.org/wiki/Object-oriented_design.

After our class statement we have a function called plgContentNJaccess. We may notice that this is the same name as our class. This is because this first function is what is called a 'constructor' function, and gets executed when the class is loaded. This is a great place to do any preparation we need for running our plugin. In this case, Ninja Access is loading the parameters for itself via the lines:

$this->_plugin = JPluginHelper::getPlugin( 'Content', 'njaccess' );
$this->_params = new JParameter( $this->_plugin->params );

The first line loads all the information Joomla! has stored for our content type plugin named njaccess. Then, we convert the flat data that is stored in the Joomla! params column into a parameter object which is then stored in _params (note the underscore).

After our constructor function is another function.

function onPrepareContent(&$article, &$params, $limitstart)
{
$regex = "#{njaccess(.*?)}(.*?){/njaccess}#s";
$article->text = preg_replace_callback($regex,array($this,"njaccess"),
$article->text);
return true;
}

Now this is looking a little more complex.

First, let's look at the name of this function—onPrepareContent. That looks familiar doesn't it? Where did we see that before? Well, it looks familiar because it's the same name as one of our content plugin events.

One Joomla! plugin can actually contain multiple events, as long as they are for the same plugin type. The way we tell Joomla! which function to execute when is via the function name. In this case, we want to check our code and replace the values while we are preparing the content. This is always the best event if we want to alter the contents of a content item.

The onPrepareContent function receives three parameters:

  • $article, which is the contents of our content item
  • $params, which is the parameter settings for the content item
  • $limitstart, which is the number of the page of the content item (if multipage)

From these parameters, we can then start to modify our content item.

The next line is a bit confusing. What exactly does regex mean, and what is the crazy piece of text being assigned to it?

$regex = "#{njaccess(.*?)}(.*?){/njaccess}#s";

Well, regex stands for regular expression, which is a system for searching for patterns in blocks of text. The crazy piece of text is the pattern we are going to be searching for.

There are many different symbols and patterns we can use for a regex, but the one we are using here is relatively simple, the # and #s on either end indicate that we will ignore anything on either side of our search pattern (except for other copies of the pattern of course). The {njaccess is actually the real text that we are searching for.

The next piece of the regex is special,(.*?). This indicates that we want to collect everything between the real texts on either side, and do something with it. It will be put into an array with all the values we collect. For the Ninja Access plugin, the data in here will be the list of user group ids that are allowed to see the content between the tags.

Just after these collector symbols is another single character of real text, a closing brace, }, which closes our {njaccess tag we opened earlier.

After this, we have another patch of collector symbols. These are collecting the actual text that we want to show/hide. This text will be put into the same array as the first set of collected data.

Then finally, we have a closing tag, {njaccess}, which finishes our pattern off.

There are a lot of sites around the web where we can learn more about different regex patterns and how to write them ourselves. We can just put the word "regex" into our favorite search engine to find them.

The next line down is where the magic begins.

$article->text = preg_replace_callback($regex,array($this,"njaccess"), $article->text);

We take the $article parameter which was passed in, and we are going to assign a new value to its text attribute. The text attribute is where the actual contents of our content item are.

Then we call a function, preg_replace_callback. This function is used to perform regex searches and then, it will execute a function for every instance of the search pattern it finds.

The three parameters we are using for this function are first, a regular expression search pattern, in this case $regex that we made earlier.

The next one is a function to execute, or an array containing an object, and the name of a member function within that object to execute. We are choosing the second route, choosing the object member function, and passing it our plgContentNJaccess class via the $this variable, and telling it to call the njaccess function, which we will look at in a moment.

An array is a type of variable that holds multiple separate values within itself. Arrays are usually used to hold lists of related data, such as a list of staff id or names. Arrays are referenced by using the notation $arrayName[arrayId]. The array id is usually a number and is the method used to pick out individual pieces of data. More information can be found here: www.php.net/array

The third parameter is the text we want to search through. In this case, we are going to pass the text attribute of our $article object.

The final line in our function returns the output from our preg_replace_callback function, which should now contain our edited text.

Now, we have the final function for our plgContentNJaccess class, and the function we called in the parameters for preg_replace_callback.

function njaccess(&$matches)
{
$user = &JFactory::getUser();
$acl = &JFactory::getACL();
$myRealGid = intval( $acl->get_group_id( $user->usertype ) );
$accessLevels = '';
$output= $matches[2];
if (@$matches[1])
{
$accessLevels = explode(",", trim($matches[1]));
}
if (in_array($myRealGid,$accessLevels))
return $output;
return "";
}

Here we have named the function njaccess for consistency. Since this is an internal function for our class, we could have called it anything we wanted. In fact, it might have been better to call it textReplace or something similar.

The first thing we need to notice is the parameter that our function receives. The only one coming in is the $matches variable, which is an array containing in position 0 the complete matched string, and in positions 1 and above, all the values that we collected with our collection patterns. We only have two collection patterns, so our $matches array will only have positions 0, 1, and 2 populated.

Position 1 will have our list of user groups, and position 2 our text that we want to show/hide.

Our function begins with us calling in the ACL (user groups) and user information and putting them into two variables.

$user = &JFactory::getUser();
$acl = &JFactory::getACL();
$myRealGid = intval( $acl->get_group_id( $user->usertype ) );

The $user variable contains the output from JFactory::getUser(), which will be a user object filled with information about the visitor for whom this actual page is being generated.

We then use this user's information and find out their user group so we can compare it later on.

$accessLevels = '';

This next line just creates an empty variable. PHP can throw error and warning messages if we try to print out a variable that hasn't been created. The $accessLevels variable may not actually get populated or created later on, so we will create it here and make it empty to avoid notices or errors.

$output= $matches[2];

Next, we take the position 2 data from our $matches array, which is our text we want to show/hide, and put it into $output. This isn't really needed, but is just to make it easier to handle and understand instead of writing $matches[2] all the time.

if (@$matches[1])
{
$accessLevels = explode(",", trim($matches[1]));
}

This next section says if we have a value in $matches[1] our list of user groups then we will explode the data and put it into our $accessLevels variable.

What does explode mean? It is a PHP function that takes a list of values that are delimited (separated) by a certain string of values, breaks them up according to that string, and puts them into an array.

For example, in this case we are taking a list of comma separated values and splitting them up by the comma, removing the comma in the process (so be careful with this), and then placing each of the broken up strings into positions in an array.

So the string "18,24,25" would become an array like this:

  • Position 0 => 18
  • Position 1 => 24
  • Position 2 => 25

Why are we putting them into an array?

if (in_array($myRealGid,$accessLevels))
return $output;

This is why. We can now use the in_array function to compare our page visitor's group id to the array of group ids that are allowed to see this information. If there is a match, then we return $output, which contains our text.

Whenever the command return is executed from within a function, that function immediately stops operating and doesn't execute any more code in the function unless it is called again. It also passes back whatever value (if any) is specified after the return keyword.

If there is no match between our visitor's group id, and the group ids allowed to see this text, then we skip the next line, return $output, and continue executing, which gets us to the final line:

return "";

This line tells our function to end and returns an empty string, effectively hiding our text.

njaccess.xml

Again, as with our module, this file is our XML manifest and contains the information that Joomla! needs to put our plugin in the right place and to get it operating properly.

<?xml version="1.0" encoding="utf-8"?>
<install version="1.5" type="plugin" group="content">
<name>Ninja Access</name>
<author>Daniel Chapman</author>
<creationDate>February 2008</creationDate>
<copyright>(C) 2008 Ninja Forge</copyright>
<license>http://www.gnu.org/copyleft/gpl.html GNU/GPL</license>
<authorEmail>daniel@ninjaforge.com</authorEmail>
<authorUrl>www.ninjaforge.com</authorUrl>
<version>1.1</version>
<description>NINJACONTENT</description>
<files>
<filename plugin="njaccess">njaccess.php</filename>
<filename>njaccess/njaccess_desc.html</filename>
<filename>njaccess/js/ninja.js</filename>
<filename>njaccess/images/logo.jpg</filename>
<filename>njaccess/images/ninjoomla.png</filename>
<filename>njaccess/images/firefox2.gif</filename>
<filename>njaccess/images/jcda.png</filename>
<filename>njaccess/images/validation_xhtml.png</filename>
<filename>njaccess/images/validation_css.png</filename>
<filename>njaccess/images/info.png</filename>
<filename>njaccess/images/change.png</filename>
<filename>njaccess/images/inst.png</filename>
<filename>njaccess/images/tabbg.gif</filename>
<filename>njaccess/images/tab2.png</filename>
<filename>njaccess/images/gnugpl.png</filename>
</files>
<params>
</params>
<languages>
<language tag="en-GB">en-GB.plg_content_njaccess.ini</language>
</languages>
</install>

There is quite a lot in here, but we should be familiar with most of it by now.

The most important parts for us now are the lines that are unique to plugins. Lines such as:

<install version="1.5" type="plugin" group="content">

Our module's XML manifest reads:

<install type="module" version="1.5.0">

Essentially they are the same, except that the plugin has one extra attribute in the install tag, a group attribute. This attribute, as we can probably guess, identifies the type of plugin this is, content, system, authentication, and so on.

We will also notice that our description tags have a language file Key in them:

<description>NINJACONTENT</description>

The Value for this Key loads an external HTML file in an IFrame instead of being actual text as we saw earlier.

Looking at the list of files we can also see a line that is similar, but slightly different than our module.

<filename plugin="njaccess">njaccess.php</filename>

In our module we had:

<filename module="mod_fbmodule">mod_fbmodule.php</filename>

Based on what we know about the module, we should be able to work out what this does. That's right, it identifies to Joomla! that this particular file is the main file for the plugin and should be executed first.

The rest?

Looking at the rest of our file list, there are a lot of files that we haven't looked at yet, particularly the many image files. These files are all for the rich description, and used in the IFrame our language file loads. We will remove these later to streamline our plugin since as we don't need it for our own plugin.

>> Continue Reading: Customizing Plugins in Joomla! 1.5x (Part 2)

 

If you have read this article you may be interested to view :

 

Joomla! 1.5x Customization: Make Your Site Adapt to Your Needs Create and customize a professional Joomla! site that suits your business requirements
Published: August 2009
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Daniel Chapman

Daniel Chapman has been working in IT since 1995, firstly as an Oracle Database consultant and trainer, then in freelance web development, and now as a Joomla extension developer. He is the founder and CEO of Ninja Forge, a leading Joomla! extension club. Currently based in Japan, he is an entrepreneur with extensive experience in designing and customizing sites, as well as in building successful web-based businesses, having worked on the design and development of several.

Books From Packt

Joomla! 1.5 Template Design
Joomla! 1.5 Template Design

Joomla! E-Commerce with VirtueMart
Joomla! E-Commerce with VirtueMart

Learning Joomla! 1.5 Extension Development
Learning Joomla! 1.5 Extension Development

WordPress 2.7 Cookbook
WordPress 2.7 Cookbook

Drupal 6 Site Blueprints
Drupal 6 Site Blueprints

Flex 3 with Java
Flex 3 with Java

ASP.NET 3.5 CMS Development
ASP.NET 3.5 CMS Development

Drools JBoss Rules 5.0 Developer's Guide
Drools JBoss Rules 5.0 Developer's Guide

 

Your rating: None Average: 5 (2 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
e
W
L
K
D
U
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software