J2.5

Supporting SEF URLs in your component

From Joomla! Documentation

The "J2.5" namespace is a namespace scheduled to be archived. This page contains information for a Joomla! version which is no longer supported. It exists only as a historical reference, it will not be improved and its content may be incomplete and/or contain broken links.

Quill icon.png
Content is Incomplete

This article or section is incomplete, which means it may be lacking information. You are welcome to assist in its completion by editing it as well. If this article or section has not been edited in several days, please consider helping complete the content.
This article was last edited by Panther (talk| contribs) 15 years ago. (Purge)

Routing Overview[edit]

Joomla! 1.5 is capable of creating and parsing URLs in any format, including human readable URL's. Another improvement is that this still works even if Joomla! runs a server other than Apache with the mod_rewrite module.

A good example of this is the "Welcome to Joomla" article. The first link shown was generated without mod_rewrite, and the second link was generated with mod_rewrite:

  • http://www.example.com/index.php/the-­news/1-­latest­-news/1­-welcome­-to­-joomla
  • http://www.example.com/the-­news/1­-latest-­news/1-­welcome-­to­-joomla

Preparing Your Data for Routing[edit]

The Alias[edit]

The first step is the generation of the so called alias. The alias is used in the URL instead of the title (the title is the text you want to have in the url). The alias has to be URI safe, which means accented UTF­8 characters are replaced by their ASCII­7 equivalents, white spaces by hyphens, etc.

The alias can be defined by the user, but you should ensure that the above requirements for a URL safe alias are met. A good way to do so is to use the JTable::check() method during the save process. Have a look at this example code:

function check()
{
       jimport( 'joomla.filter.output' );
       $alias = JFilterOutput::stringURLSafe( $this-­>title );
       if (empty( $this-­>alias ) || $this-­>alias === $alias ) {
               $this->alias = $alias;
       }
       /* All your other checks */
       return true;
}

If the alias field is empty or has a different type than the URL safe title then this one will be used as the alias.

The Slug[edit]

Continuing with the same example, the "slug" - "1­-welcome­-to­-joomla" has two parts. The first part is the article identifier (id) and the second is the alias. They are separated by a hyphen. These two elements were combined during the database query in the model:

$query = 'SELECT a.*'.
        ' CASE WHEN CHAR_LENGTH (a.alias) THEN CONCAT_WS(\':\', a.id, a.alias) ELSE a.id END as slug,'.
        [...];

After this step the slug is used instead of the id.

Routing URL's[edit]

The JRoute::_ method translates the internal Joomla! URL to a custom URL. JRoute has three parameters and its prototype is:

JRoute::_( $url, $xhtml = true, $ssl=0 );

Where:

  • $url is a string containing the absolute or relative internal Joomla! URL.
  • $xhtml is a boolean value that specifies whether or not the output should be in XHTML. This parameter is optional and if omitted defaults to true.
  • $ssl is an integer value that specifies whether the URI should be secure. It should be set to 1 to force the URI to be secure using the global secure site URI, 0 to leave it in the same state as when it was passed, and -1 to force the URI to be unsecure using the global unsecure site URI.

The most important parameter is $url. A call to this method might look like:

JRoute::_( 'index.php?view=article&id='.$row->slug );

$row-­>slug is the value that was generated in step 2 from a combination of id and title alias.

Another advantage of using JRoute is that the router now handles $option (the component name) and the $Itemid (the menu item ID). The component itself doesn’t have to know its name ($option) or the active menu item ($Itemid) like it did in previous version of Joomla!.

It is important that you think about the sequence of the URL parameter in this stage. This will be more clear when we have a deeper look at the router.php in the next section.

The building process of JRouter is divided into two steps:

  • Create the application route. The application route is fully handled by JRouter and the component developer doesn’t have to do anything to make it work.
  • Create the component route. To create the component route, JRouter looks for the router.php in the component directory which is responsible for building the route for the component.

The Component Router[edit]

We will have two functions In the router.php. One is responsible for building the URL and the other is responsible for parsing it. In the next examples, a very basic and a more advanced one, we assume that we have three views that links can point to. The first is a categories overview (view=categories), the second is a single category (view=category) and the third is a single article (view=article).

A Simple Example[edit]

This simple example will illustrate the basics of implementing a router for your component.

function [''Componentname'']BuildRoute( &$query )
{
       $segments = array();
       if(isset($query['view']))
       {
                $segments[] = $query['view'];
                unset( $query['view'] );
       }
       if(isset($query['id']))
       {
                $segments[] = $query['id'];
                unset( $query['id'] );
       };
       return $segments;
}

JRouter passes a $query array to the [Componentname]BuildRoute function. This function will add the relevant parts of the array to the $segments array in the right order and will return the properly ordered array. The content of the $query array needs to be unset, otherwise JRouter will add it to the URL in the form of a query string (i.e. any variables that are not handled by the router will be passed in the query string).

The next function in the router.php parses the URL:

function [''Componentname'']ParseRoute( $segments )
{
       $vars = array();
       switch($segments[0])
       {
               case 'categories':
                       $vars['view'] = 'categories';
                       break;
               case 'category':
                       $vars['view'] = 'category';
                       $id = explode( ':', $segments[1] );
                       $vars['id'] = (int) $id[0];
                       break;
               case 'article':
                       $vars['view'] = 'article';
                       $id = explode( ':', $segments[1] );
                       $vars['id'] = (int) $id[0];
                       break;
       }
       return $vars;
}

What happens here? In the function [Componentname]BuildRoute we arranged the items in the $query array in a specific sequence. This means that in this example the view is first, the catid is second and the id is third in the array.

By reading $segments[0], we access the name of the view. We set the right view and/or identifier depending on its value and we return the $vars array to JRouter. $vars should be an associative array similar to the array that was passed to the BuildRoute method.

The above example of the router.php is a very simple way to generate sef URL's but should show how this works quite clearly.

The generated URL in this example contains the name of the view and doesn't reflect the content hierarchy:

http://www.example.com/[menualias]/[view]/[slug]

A More Advanced Example[edit]

In the next example we will try to get rid of the need for the view and we will try to reflect the current hierarchy level in the URL.

The goal is URL's that look like:

  • When viewing an article: http://www.example.com/[menualias]/[category]/[article]
  • When viewing a category: http://www.example.com/[menualias]/[category]
  • When viewing the categories overview: http://www.example.com/[menualias]

Let's assume we have done step 1 and 2 also for the category.

The link to the article would look like this:

JRoute::_( 'index.php?view=article&catid='.$row-­>catslug .'&id='.$row-­>slug );

And the Link to the category would look like this:

JRoute::_( 'index.php?view=category&id='.$row->catslug );

The corresponding router.php:

function [''Componentname'']BuildRoute(&$query)
{
       $segments = array();
       if(isset( $query['catid'] ))
       {
                $segments[] = $query['catid'];
                unset( $query['catid'] );
       };
       if( isset($query['id']) )
       {
                $segments[] = $query['id'];
                unset( $query['id'] );
       };
       unset( $query['view'] );
       return $segments;
}

The difference now is that we don’t add the name of the view to the $segments array. We still unset the view key since otherwise, JRouter would add it to the URL as part of the query string. Another new thing here is the additional parameter catid that we push into the $segments array.

function [''Componentname'']ParseRoute($segments)
{
       $vars = array();
       $menu =& JMenu::getInstance();
       $item =& $menu->getActive();
       // Count segments
       $count = count( $segments );
       //Handle View and Identifier
       switch( $item-­>query['view'] )
       {
               case 'categories':
                       if($count == 1) {
                               $vars['view'] = 'category';
                       }
                       if($count == 2) {
                               $vars['view'] = 'article';
                       }
                       $id = explode( ':', $segments[$count­1] );
                       $vars['id'] = (int) $id[0];
                       break;
               case 'category':
                       $id   = explode( ':', $segments[$count­1] );
                       $vars['id']   = (int) $id[0];
                       $vars['view'] = 'article';
                       break;
       }
       return $vars;
}

You can see that this ParseRoute function has a lot of different code parts in comparison to the previous. The reason for this is simple. We don’t have the name of the view in the $segments array and we need to find another way to determine it.

We need to find out which level of hierarchy we are in by receiving the root element. We do this by looking to the view name of the active menu item:

$item-­>query['view']

Also we need to know the number of items in the $segments array:

$count = count( $segments );

With this information we can correctly set the view for all possible three cases:

  • The menu item is a link to the categories view and the $segments array has two items ($catid and $id). In this case we know that we need to parse a link to an article .
  • The menu item is a link to the categories view and the $segments array has one item ($id). In this case we know that we need to parse a link to a category.
  • The menu item is a link to a category. In this case, we know that any item in the $segments array is the identifier for an article .

The result of all this code is nice and human readable component URL's.

Application Route Parsing[edit]

The API Execution Order outlines that the route (URL) is parsed immediately after initialisation is complete. Since fancy URL's are not treated (yet) in the Administrator, we will follow the route parsing process in detail when JSite::route in the index.php file.

  • Call to JApplication::route
    • Clone the URI
    • Call to JApplication::getRouter
      • Call to JRouter::getInstance passing the type ("site")
    • Call to JRouterSite::parse passing the URI
      • Strip the suffix if applicable (added to $vars['format'])
      • Re-set the route (URI)
      • Call to JRouter::parse passing the URI
        • Call to JRouterSite::_processParseRules passing the URI (this will call custom route rules)
          • Call to JRouter::_processParseRules passing the URI
            • Call any custom routing rules (probably added via a system plugin using the onAfterInitialise event trigger) passing the URI
            • Returns an array of vars
          • If SEF mode, replace start variable with limitstart
        • If raw mode, call to JRouterSite::_parseRawRoute passing the URI
        • If SEF mode, call to JRouterSite::_parseSefRoute passing the URI
          • If the route (the URI path) is empty, load it from the default menu item; set the active menu item as the default
          • If first part is /component/com_content, set the option as the second segement. Null the Itemid.
          • Else, loop through menu alias values and take off segments that match as the menu tree is traversed. Set option and Itemid based on the last menu item found.
          • If the Itemid is set in the URL, set the active menu item based on this value.
          • Push the vars collected so far (eg, option, Itemid, etc) into the router object ($this).
          • If the route and option is set, load the component router;
          • Else, get the active menu item and get the route vars from it


Application Route Building[edit]

TODO

Custom Router Rules[edit]

TODO

Additional References[edit]

There is a useful thread on this subject here: jtopic:148632 (note, may be out of date)