How do I write a sh404SEF® plugin for my component?

Introduction

As you read through the information below, it will be helpful to examine our Example sh404SEF® plugin. It is publicly available in out Downloads area.

If after reading this docuemtn you run into trouble writing your plugin, please feel free to post a question on the sh404SEF® Developer board on the support forum.

 

A - Background

sh40SEF builds and manages SEF URL for the Joomla! CMS. It uses a plugin system to adapt to various components. sh404SEF® is used in two ways : to "create" URL and to "revert" URL.

1 - Creating URL

Creating a SEF URL is the process of finding the SEF counterpart to one of Joomla! classical non-sef URL. As an example :

  • non-sef URL = index.php?option=com_content&task=view&id=24&Itemid=3
  • SEF URL    = /News-section/Fun-category/what-is-the-difference-between-a-pigeon.html

It is up to each component to trigger this process, as it is not automatic. Before using any URL, a component should call the folowing function, passing the non-sef URL as a parameter.

JRoute:_()

The returned value is the SEF URL calculated for the non-sef value passed. However, JRoute:_() is effective but limited. It produces URLs with the following format:

http://example.org/magazine/article/552-add-styling-parameters-for-joomla-15-articles-titles.html

The role of sh404SEF® is to build up shorter, more significant URL, removing unwanted parts such as /article/ or the article id, starting from the same initial data : the non-sef URL.

sh404SEF® uses a plugin system. It provides a framework for handling the URL themselves, storing them in DB, retrieving them upon request, but depending on the option=com_component part of the non-sef URL, it calls upon a dedicated php file to work out the details of building the SEF URL. These "plugins" can be either "native" or "foreign": Native plugins have been designed and written specifically for sh404SEF®, and take full advantage of its features. Native plugins either come with sh404SEF® (Joomla! core such as com_content, com_newsfeeds, com_search, com_poll,..., popular components such as Kunena, Virtuemart, Community Builder,...), or can be packaged by components authors within the components themselves.

Foreign plugins have been designed either directly for Joomla own SEF system or for popular SEF components such as SEF Advanced. Joomla plugins are actually router.php files, located in the extension front-end directory. Plugins for other SEF extensions usually come with the components. They are stored in the component own directory, and usually take the form of a single file : sef_ext.php. Usually, sh404SEF® can use these plugins automatically and transparently, even though some compatibility issues may arise on some occasions. If both a native and a foreign plugin exist for a given component, sh404SEF® backend parameters allow you to decide which one should be used.

After being calculated, each SEF URL is stored in Joomla! database, together with its non-sef counterpart, in order to avoid this time consuming process next time the same URL is requested.

Note : in addition to writing the URL to the database, sh404SEF® has a cache system to speed up the retrieval process. Both writing to the database and to the cache is part of sh404SEF® framework, and therefore component developers willing to write a sh404SEF® native plugin can focus only on building up the URL as they wish.

 Writing native plugins is the main topic of the remaining of this document.

2 - Reverting URL

When Joomla! receives a request to serve a page, it passes on the requested URL to sh404SEF®, which looks up the database to try find a record that matches the request. If a SEF match is found, then sh404SEF® returns the non-sef (index.php?option=com_xxx&task=zzz...) part of the record URL for further processing. This process is called "reverting" the URL. It is needed because Joomla! does not know anything about SEF URL, and can only process non-sef URL.

Reverting URL is totally automatic with sh404SEF®. In other words, and contrary to foreign plugins, there is no code to write in order to "decode" the SEF URL and turn it back into a non-sef URL.

B - Directory layout and names

Historically, sh404sef plugins have always been installed either with sh404sef itself or integrated with the extension they are for. Adding a sh404sef after installation was (and can still be done) by uploading the plugin files directly into specific directories. From version 2.2.0, plugins can (but don't have to) be installed as Joomla! plugins.

Here are information about both ways to layout and provide plugins:

1 - plugin delivered with sh404sef or 3rd party extensions

Plugins located in either one of the following locations will be picked up automatically by sh404SEF®:

  • foreign plugins are located in /components/com_component_name
  • native plugins delivered with sh404SEF® are in /components/com_sh404sef/sef_ext
  • individual native plugins delivered by an extension author should be in /components/com_component_name/sef_ext

Native plugins have the same name as the component they work for. For instance, assuming that we have written a nice component called HeyJoe, and that we have also produced a sh404SEF® native plugin for com_HeyJoe, then main plugin file will be :

/components/com_HeyJoe/sef_ext/com_HeyJoe.php  

You can make use of any other file you like, like languages files for instance, but it is up to the component author to manage these additional files.

Note: If sh404SEF® and the component both have a plugin for sh404SEF®, then the component plugin will be used instead of sh404SEF® own plugin.

if you are a Joomla! extension author, and you want to provide support for sh404SEF® SEF urls or meta data, you should normally provide the plugin in your extension component directory, in a /sef_ext sub-directory
2 - Installable plugins

Plugins in locations mentioned in previous section will keep working as usual. However, starting with version 2.2.0, plugins can pretty much be located anywhere, and can (but don't have to) be wrapped inside a Joomla! plugin for easy installation by users.

If you have written a sh404SEF® plugin for an extension which you are not the author you should use the installable plugin format. Your users can then simply download your plugin, and install it separately from the main extension it works with. This makes it easy for the site owner to update the extension without having to worry about uploading the sh404SEF® manually after each upgrade.

In summary:

  • Plugin content is not changed at all. The Joomla! plugin wrapper is just that, a wrapper, used for easy installation and update by user
  • You don't have to use a wrapper and make your plugins installable. As a matter of fact, sh404SEF® built in plugins will still be provided as simple files in the /sef_ext directories. Extensions such as K2 or Flexicontent come with built in support for sh404SEF®, with plugins located in the /sef_ext sub directory

You can download a sample plugin on the developer resources area of the forum. This plugin will appear in the sh404sefextplugins plugin group in the Joomla plugin manager. Let's go through the manifest file first:

<?xml version="1.0" encoding="utf-8"?>
<install version="1.5" type="plugin" group="sh404sefextplugins" method="upgrade">
  <name>sh404sef - Sample component support plugin</name>
  <author>Your name here</author>
  <creationDate>current_date_here</creationDate>
  <copyright>2011 Your name here</copyright>
  <license>http://www.gnu.org/copyleft/gpl.html GNU/GPL</license>
  <authorEmail>Your email here</authorEmail>
  <authorUrl>your URL here</authorUrl>
  <version>version_number_here</version>
  <description>Provide support for sef urls and meta data for com_XXXXX</description>
  <files>
    <filename plugin="com_xxxxx">com_xxxxx.php</filename>
    <folder plugin="com_xxxxx">xxxxx</folder>
  </files>
</install>

This is a standard Joomla! plugin. You can add files as needed. We suggest you use a subdirectory named xxxxx, where xxxxx is the name of the extension to put additional files, so as to avoid name conflicts with other plugins.

You can see that there must be 2 com_xxxxx.php files. One is the main Joomla! plugin files, and will be installed by Joomla in the following location:

/plugins/sh404sefextplugins/

The wrapper will contain the following code:

class  Sh404sefExtpluginCom_weblinks extends Sh404sefClassBaseextplugin {

  protected $_extName = 'com_xxxxx';

  /**
   * Standard constructor don't change
   */    
  public function __construct( $option, $config) {

    parent::__construct( $option, $config);
    $this->_pluginType = Sh404sefClassBaseextplugin::TYPE_SH404SEF_ROUTER;

  }

  /**
   * Adjust returned path to your own plugin. This method will be used to find the exact
   * and full path to your plugin main file. The location used below is just a sample.
   * Your plugin can be stored anywhere, and use as many files as you need. sh404SEF® only
   * needs to know about the main entry point.
   *
   * @params array $nonSefVars an array of key=>values representing the non-sef vars of the url
   *                we are trying to SEFy. You can adjust the plugin used depending on the
   *                request being made (or other elements). For instance, you could use
   *                a different plugin based on the currently installed version of the extension              
   */    
  protected function _findSefPluginPath( $nonSefVars = array()) {

    $this->_sefPluginPath =  JPATH_ROOT . DS. 'plugins'.DS.'sh404sefextplugins'.DS.'xxxxx'.DS.'com_xxxxx.php';

  }

}

Basically, the only thing you have to do is override the _findSefPluginPath() and set the $this->_sefPluginPath property so that it contains the full path to your actual plugin file. When calling this method, sh404SEF® will pass as parameters the non-sef vars of the URL that we are trying to make SEF. You can possibly use these variables to decide on using one plugin or another.

You could also, inside _findSefPluginPath(), check for the currently installed version of your extension and make sh404SEF® use one plugin or another based on that.

  • another one should be in /plugins/sh404sefextplugins/, this is the actual php code that does the SEF url creation

There is a sample of such plugin delivered with sh404SEF® in the following location for your use:

/components/com_sh404sef/sef_ext/sample_com_plugin.php
 

C - Writing a sh404SEF® plugin

1 - Plugin anatomy

A native plugin has a main file called com_component_name.php. A sample plugin file is supplied with sh404SEF®. It is named "sample_com_plugin.php", and resides in the /components/com_sh404sef/sef_ext directory. It is made up of 3 main parts :

a - Initialization section

This part simply calls a sh404SEF® initialization function, and also set or reset some variables. It should not be modified except by experienced programmers. The initiliazation code is :

// ------------------ standard plugin initialize function - don't change ---------------------------
global $sh_LANG, $sefConfig;
$shLangName = '';;
$shLangIso = '';
$title = array();
$shItemidString = '';
$dosef = shInitializePlugin( $lang, $shLangName, $shLangIso, $option);
// ------------------ standard plugin initialize function - don't change ---------------------------

You may not be using different languages in your component. However, sh404SEF® has been designed with multi-lingual sites in mind. Specifically, this part "discovers" the non-sef URL language, taking into account the Translate URL and InsertLanguageCode parameters that can be set in sh404SEF® backend.

b - main URL build up

This part is what a component author has to write, defining how URL should be built according to the inner working of the component. This is done by filling up an array called $title. Each element of the URL is simply added to the array, in the same order it should appear in the final URL. sh404SEF® will process this array to produce the final URL.

Additionnaly, sh404SEF® provides a mechanism by which you can decide to leave some URL variables (GET params) as part of the "query string" instead. For instance, let's look at this non-sef URL :

index.php?option=myComponent&task=viewUserDetails &userId=2456&Itemid=23

You can either decide to turn it into fully SEF urls such as:

This-is-my-User-name/View-user-details.html

Or instead have a partially SEF URL such as :

View-user-details.html?userId=2456

This is a good solution in cases where having all URL fully SEF would generate large quantities of records in the database, without adding value in terms of search engine optimisation. A few examples are :

  • a component handling users. There might be a link to view details, to update user record, to delete user, to print, export, etc for each individual users. That's fine with a few tens of users, maybe with a few hundreds, but if you plan on having several thousands users, then it is probably better to leave user identification as a GET variable instead of turning the id into a user name
  • limit and limitstart variables are used by Joomla! to handling multipage. Again, with a large number of articles or items to show (hundreds, thousands), this may generate excessive space consumption in the database
  • if you have a search feature using GET variables, then it is better to leave the search word out of the SEF url, or else any word searched by a user will generate a unique URL record in the database.

Generally speaking, it is suggested that parameters not conveying any meaning be left out of the text part of the URL, at least if a large number of them is expected. Make sure you don't take any risk there. It is important to decide early on what your SEF URL will look. A good thing is also to provide users with the ability to change the URL format, according to their specific need, site size, etc. At the moment, sh404SEF® does not provide a facility to integrate parameters for plugins other than those supplied with sh404SEF®. So if you add user defined parameters, you have to do so in your own control panel.

c - Finalization section

This part calls a sh404SEF® function. Again you should not change it, unless you want to do something specific. You may for instance do something special in case $dosef is false, that is you have decided for some reason not to build a real SEF URL.

The finalization code is :

// ------------------ standard plugin finalize function - don't change ---------------------------
if ($dosef){
$string = shFinalizePlugin( $string, $title, $shAppendString, $shItemidString,
(isset($limit) ? @$limit : null), (isset($limitstart) ? @$limitstart : null),
(isset($shLangName) ? @$shLangName : null));
}
// ------------------ standard plugin finalize function - don't change ---------------------------
2 - Building up the SEF URL

A few important things to know:

a - You only have to build up the SEF URL part after the domain name.

For instance, if you whish to produce a URL like :

http://www.mysite.com/product-infos/giant-cliffhanger/

Then you only have to include in your plugin :

$title[] = 'product-infos';
$title[] = 'giant-cliffhanger';

All parameters contained in the non-sef URL have already been extracted, and are available in the plugin file context. For instance, you can directly use something like :

if (isset($task))
$title[] = $task;

If the non-sef URL contains : ...&task=view..., then the resulting URL will contain : /view/

Please test variable existence using isset() or empty() before using a variable. We have found that many people run their server with NOTICE errors showing. As sh404SEF® may be called before headers are output by Joomla!, having a NOTICE error displayed will break everything and will totally prevent to page to being displayed.

b - sh404SEF® defaults to leaving each parameter as a GET param.

You have to specifically tell sh404SEF® that a variable has been turned into its SEF equivalent, or is not required. You do this by calling shRemoveFromGETVarsList() function. For instance, if you have a GET param like :

...&task=viewUserDetails....

and you have decided instead to insert in the SEF URL a meaningfull string such as /View-user-Details/, you'll do the following :

if (isset($task) &&($task == 'viewUserDetails')) {
$title[] = 'View-user-Details';
shRemoveFromGETVarsList('task');
}

which means :

If $task is View user detail:

  1. Insert appropriate text in URL
  2. Tell sh404SEF® to not add $task to URL anymore

If  the call to shRemoveFromGETVarsList() is missing, then the SEF URL will have an added part : ?task=viewUserDetails. This will actually not cause any issues, and will not prevent Joomla! operation.

c - Most plugins should be fine having this somewhere in their code:
shRemoveFromGETVarsList('option');
shRemoveFromGETVarsList('lang');
if (!empty($Itemid))

shRemoveFromGETVarsList('Itemid');
// optional removal of limit and limitstart
if (!empty($limit))                       // use empty to test $limit as $limit is not allowed to be zero

shRemoveFromGETVarsList('limit');
if (isset($limitstart))                   // use isset to test $limitstart, as it can be zero

shRemoveFromGETVarsList('limitstart'); 
d - all cleaning up, url encoding, characters replacement, etc is done automatically by sh404SEF®. You don't need to do it yourself. Just add the text you want to appear, straight from the database if you wish.
e - you should not insert yourself a language code in the SEF URL. This is handled automatically by sh404SEF®, again taking into account users defined backend parameters.
f - if you are using title as a parameter in your URL, then this would interfere with the $title array. To prevent this issue, the content, if any, of ...&title=.... has been stored in : $sh404SEF®_title variable.
You should use $sh404SEF®_title instead of $title to build the URL
g - do not add intermediate slashes, they'll be added automatically later on. However, the last element in the URL (ie. the last element in $title) can be a /. If so, the final URL will end with a slash. If the last element is anything else than a /, then a suffix will possibly be added. Users can set up a suffix in sh404SEF® backend (such as .html for instance), and they can turn this feature on/off as they wish. Suffix being appended is also automatic.
h - anywhere in the plugin you can set $dosef to false; this will force the URL back to non-sef. It can be used for instance if some specific URL are too complicated or too numerous to justify a SEF version.
i - Utility function : getMenuTitle()

This function will return the menu item title corresponding to either an option (option=com_content for instance) or Itemid, combined with language information.
It can be called by :

$title[] = getMenuTitle($option, (isset($task) ? @$task : null), $shCurrentItemid, null, $shLangName );
$option is always available, it is the component name (com_HeyJoe for instance)
$task may or may not exists, according to your component operation
j - $shCurrentItemid is always available.

It is set by sh404SEF® and contains the current page Itemid. Just remember that your plugin is being called upon normally, as part of JRoute::_(), when Joomla! is preparing the content of a page and needs to convert all links on this page to SEF format. $shCurrentItemid is this page Itemid. You don't have to use $shCurrentItemid as, due to the way your component operates, you may already one what is the Itemid of the menu element you want to use. Just replace $shCurrentItemid by your own Itemid.

k - $shLangName is needed to handle tranlsation or not, it is automatically set within the Initialization section of the plugin, as seen before
l - Utility function : getContentTitles()

This function uses pretty much the same parameters as the previous one. It will return a regular content element title. 
It can be called by :

$title[] = sef_404::getContentTitles($task,$id,$Itemid, $shLangName)
(please make sure $task and $id exist before using them)

$task can be 'section', 'category', 'blogsection', 'blogcategory' or 'view'.
$id is the section, category or content element id. If $id is set, then the corresponding section, category or element title will be fetched from database and returned (alias can be returned for content elements, according to sh404SEF® backend params). If no $id is passed to the function, then it will return the menu element title instead (internally using getMenuTitle()) 

3 - Working with multiple languages

URLs in sh404SEF® sites have two parts: some URL elements are constant, and some are variables.

  • Constant elements are things like : .../view-products-details or /Delete-product
  • Variable elements are things like : .../pet-mammoth-clothing

"Delete-product" is a fixed text string that describes an action for instance, whereas "pet-mammoth-clothing" is fetched from the database such as a content title.

Constant elements are translated through a set of language strings that should be supplied with the plugin, either inside the plugin, or as a separate text file. Beware that all translation strings must be available at anytime, as a single web page may have URLs in several languages at the same time. Plugins delivered with sh404SEF® use a global variable called $sh_LANG. It is a two-dimensionnal array, indexed on language iso code and string ID. For instance :

$sh_LANG['fr']['_SH404SEF_FB_SHOW_USER_PROFILE'] = 'Voir vos informations utilisateur';
$sh_LANG['en']['_SH404SEF_FB_SHOW_USER_PROFILE'] = 'View user information';

When using such an array, you can make use of two variables that are initialized with the appropriate values in the initialization section of the plugin :

$shLangName : contains the URL language name ("french", "english", "spanish")
$shLangIso : contains the URL language short iso code ("fr", "en", "es")

So you would use :

$title[] = $sh_LANG[$shLangIso]['_SH404SEF_FB_SHOW_USER_PROFILE']

Variable elements are translated using Joomfish. Because URL translation can be switched on or off, on a component by component basis by the user, it is recommanded to use the following code whenever fetching any data that can be translated from the database :

if (shTranslateUrl($option, $shLangName))
$database->loadObject( $sectionTitle);
else $database->loadObject( $sectionTitle, false);

shTranslateUrl() will return true if URL for the component "$option" should be translated, and false otherwise. shTranslateUrl() reads the user set backend parameters to decide about this. It will also always return false if Joomfish is not installed, therefore this syntax will work in any situation. When Joomfish is installed, each classical Joomla! database object function can be called with a second parameter to force translation on or off.

Warning : for Joomfish to operate and perform translation, you MUST include the table id in the select clause of your query! For instance :

"SELECT name FROM #__contents WHERE id=3" will NEVER be translated.

To allow translation, query has to be :

"SELECT id, name FROM #__contents WHERE id=3"

With this last query :

$database->loadObject( $sectionTitle); will return the translated value of 'name'
$database->loadObject( $sectionTitle, false); will return the original value of 'name' (ie its value in the site default language)

Note : remember to explicitly remove the $lang variable from the URL (using shRemoveFromGETVarsList('lang')), otherwise it will show up in the final URL as a query string bit (...?=xx...)

4 - Creating pageId

PageId is the automated URL shortener in sh404SEF®. Because we may not want to create pageId for each and every URL on your web site, pageId are only created when the plugin for your extension says so.

This is done by inserting a single line of code in locations where you want to have a pageId create. For instance, here is a sample taken from sh404SEF® plugin for Joomla! weblinks extension:

  case 'category':
      if (!empty($id)) { // V 1.2.4.q
          $arg2[] = sef_404::getcategories($id, $shLangName);
          $title = array_merge($title, $arg2);
          $title[] = '/';
          shMustCreatePageId( 'set', true);
      }
  break;

 As you can see, a single instruction is needed:

shMustCreatePageId( 'set', true);

This instruction wil instruct sh404SEF® to create a pageId for URLs that falls within this 'category' case.

 
 

Get the latest updates on our extensions