MetaMod

MetaMod — the Joomla! module for selectively including other modules on a Joomla! page

download Joomla 1.0.x version of MetaMod download for Joomla 1.0.x (current version: 1.0e, 15 Aug 2008)
download Joomla 1.5 version of MetaMod download for Joomla 1.5 (current version: 1.5e, 15 Aug 2008)

changelog
1.0e/1.5e:
fixed undefined variable in debug mode; added some more help
1.0d/1.5d: support for GeoIP Cities database (in addition to Country); new debug mode

Cost: Free (GPL). Donations welcome.
Author: ; Brandon IT Consulting
Compatibility: Joomla 1.0.x (tested on v1.0.12, v1.0.13, v1.0.15), and Joomla 1.53, on PHP v4 and v5, MySQL v4 and v5.

Description

Have you ever wished you could set start and end dates for when modules display? Or wished you could set up your modules so that guests connecting from the USA see a different module to people connecting from Germany? Or wanted to restrict modules by IP address?

Now you can. MetaMod is a “Meta Module” - a module that includes other modules, according to rules that you set up.

If you download and install the free GeoLite Country database from MaxMind, you can base your rules on the country names of the people connecting to your site (GeoIP targeting).

Suggested uses (see recipes, below)

  • Make modules appear at certain times of the day
  • Give modules start and end publishing dates
  • Show a particular module for a certain number of page-views by a guest, then switch to the next module (uses cookies)
  • Control modules such as FacileForms (show a different form based on where in the world someone is connecting from, etc.)
  • Show different modules to people connecting from different countries (“include” list and “exclude” list)
  • Turn blocks of modules on and off together (one MetaMod can control a series of other modules)
  • Make modules appear and disappear, or alternative modules appear, for different users, based on user type, group or even login name
  • Show different modules depending on what Section and/or Category the main article on the page is in (this is powerful!)
  • Make a module disappear once someone has logged in (there’s no standard way to do this in Joomla!)
  • Interface with any other information you have in your Joomla database, and make a tree of rules, e.g.
    1. for people outside the US show module 22,
    2. otherwise if they are not logged in show module 23,
    3. otherwise if they have not donated recently show the donation form,
    4. otherwise if they have already donated show a “thank you” message

Installation

mod_metamod basic package

  • Install the mod_metamod.zip package into Joomla! in the usual way:
    1. in the administrator menu, click on “Installers–>Modules”
    2. click on “Choose File”, select the mod_metamod.zip file,
    3. then click on “Upload File and Install”.

GeoIP Country/City database

  • Download the MaxMind GeoLite Country database, uncompress it (it's in gz format), then place it at the location geoip/GeoIP.dat in the Joomla installation on your web server (you will usually require an FTP or SFTP client to upload this to your site).
  • For even more power, download the Maxmind GeoLite City database, and install it at geoip/GeoLiteCity.dat. This gives you the ability to make module rules based on the city/state/county/area code/postal code/latitude/longitude of the person viewing the site.

Joomla modifications (for Joomla 1.0.x version only — J1.5 version doesn’t require this)

  • In Joomla 1.0, MetaMod works best in if some changes are made to a core Joomla file. These changes allow you to see the list of modules and their ID numbers on the mod_metamod configuration page, which is very useful.
  • Here are the changes:
Before (from administrator/components/com_modules/admin.modules.php:441)
  $xmlDoc->resolveErrors( true );
  if ($xmlDoc->loadXML( $xmlfile, false, true )) {
    $root = &$xmlDoc->documentElement;

    if ($root->getTagName() == 'mosinstall' && $root->getAttribute( 'type' ) == 'module' ) {
      $element = &$root->getElementsByPath( 'description', 1 );
      $row->description = $element ? trim( $element->getText() ) : '';
    }
  }

  // get params definitions
  $params = new mosParameters( $row->params, $xmlfile, 'module' );
After (from line 441) [Note: updated Friday 2 November 2007]
  $xmlDoc->resolveErrors( true );
  $new_param_class="";
  $new_param_class_loaded=false;
  if ($xmlDoc->loadXML( $xmlfile, false, true )) {
    $root = &$xmlDoc->documentElement;
    if ($root->getTagName() == 'mosinstall' && $root->getAttribute( 'type' ) == 'module' ) {
      $element = &$root->getElementsByPath( 'description', 1 );
      $row->description = $element ? trim( $element->getText() ) : '';
      $parameter_class_node = &$root->getElementsByPath( 'files/filename', 1 );
      if ($parameter_class_node) {
        $new_param_class = substr($parameter_class_node->getAttribute( 'module' ),4);
        if (file_exists($mosConfig_absolute_path . "/modules/mod_$new_param_class/$new_param_class.class.php")) {
          require_once( $mosConfig_absolute_path . "/modules/mod_$new_param_class/$new_param_class.class.php" );
          $new_param_class_loaded = true;
        }
      }
    }
  }
  if ($new_param_class_loaded && $new_param_class && class_exists($new_param_class."Parameters"))
    $new_param_class .= "Parameters";
  $param_class = ($new_param_class_loaded && $new_param_class) ? $new_param_class : "mosParameters";  // get params definitions
  $params = new $param_class( $row->params, $xmlfile, 'module' );

How to Use

  1. Set up a the module you want to display in a module position as normal – EXCEPT instead of entering the Menu Item Links to link it to your page, select the “None” or “Unassigned” menu items. The “position” you assign the module to at this point is irrelevant.
  2. Make a note of the Module ID of the module(s) you want to display.
  3. Set up a MetaMod module:
    • Give it the “position” you want it to appear in
    • Set it “Published” (Yes)
    • Set up any rules, and add the ID number(s) of the module(s) you want to display in this module position.
  4. That’s it – the original module you wanted to display will be displayed by MetaMod, on the page and module position specified by MetaMod, not the page and position specified by the original module.

NOTE:

  • If MetaMod is set to PUBLIC access, it can include modules that are set to REGISTERED or SPECIAL, but these will only display if the logged-in user has permissions to see them.

Configuration

To set up MetaMod, find the MetaMod module in the Modules->Site Modules menu.
To set up a second (or further) MetaMod module, check the checkbox next to an existing MetaMod module, and click on the “Copy” icon.

Title: This title will appear at the top of the module position, if the “Show title” option is chosen (next).

Show title: No/Yes: (standard module control) In many cases you won't want this turned on, because the included modules will have their own titles.

Position: (standard module control) the module position the modules will be loaded into

Module Order: (standard module control)

Access Level: (standard module control)

Published: No/Yes: (standard module control) Don't forget to turn this on!

Available Modules: If the admin.modules.php patch is applied (see above) then this will display a list of available modules and their ID numbers. This is for convenience.

Module Class Suffix: a suffix which will be added to the CSS class name of the <div> or <table> surrounding the entire module and any other modules it contains.

Start and End date/time (freeform):

These fields are extremely versatile. They control when MetaMod will start/stop including other modules. Important: the main title of the MetaMod module is not controlled by this date – if you have “Show title” turned on then it will always show regardless of the Start and End date/time settings.

Here are some examples:

  • 10 Oct 2007
  • 1 January 2008
  • 10 Oct 2007 9:30
  • 10 Oct 2007 9:30PM
  • 01:00   [starts/ends at 1AM every day]
  • 22:03:59   [starts/ends at 22:03:59 every day]

Start date/time (freeform): Enter a date, time or both, for when the module will start to include other modules.

End date/time (freeform): Note: if you enter a date without a time, then the time period will end at the BEGINNING (midnight) of the day that you specify.

Debug: Off/On: If you are having trouble working out why your modules are not being included, or what the values of $id, $Itemid etc are, then turn this on. You'll see a summary of information in the module position, about all these parameters, and which modules it decided to include in the end.

Check: If the admin.modules.php patch is applied (see above) this checks to see if the GeoIP.dat file is installed on your system. It looks for a folder in your Joomla installation called “geoip” and a file in it called “GeoIP.dat”. Because MaxMind updates their database monthly, you need to get a fresh file from their website, and it's not included in this module. See GeoLite Country page, or download latest database.

Enable GeoIP/GeoCity: Disabled/GeoIP Country/GeoLite City (free)/GeoCity (commercial): Because looking up the country code of the web visitor takes a little time, MetaMod won't do it unless you say you want it. Select your database here if you want to use the “Only these countries” or “Exclude these countries” fields below, or if you want to use the variables $fromCountryId or $fromCountryName in the PHP block. You will also need to download the relevant database from Maxmind and install them on your server.

Only these countries: Enter a comma-separated list of 2-letter country codes. Only visitors from these countries will see the modules that you specify below. Don't forget to turn on the lookup, above.

Exclude these countries: Enter a comma-separated list of 2-letter country codes. If a visitor comes from one of these countries, the modules will not be shown. Don't forget to turn on the lookup, above.

Style for included modules: Specify how the included modules will appear. These are standard Joomla options for what will surround the contents of a module. All included modules for this MetaMod module will share this parameter. If you want to use a different parameter for one of the included modules, then make another MetaMod module to wrap the target module, and include the second MetaMod inside the first one.

  • In a table (default) – uses <th> for title
  • Multiple divs (for rounded corners) – uses <h3> for title
  • XHTML – in div, uses <h3> for title
  • Naked – no div, no title

Quick module id include: Here's where you can put in a module id or comma-separated list of ids. The modules that you specify will be displayed inside this MetaMod module in the order that you give, subject to the date and country constraints that you specify above. If you have some PHP rules below that return more module numbers, they will be displayed AFTER any modules specified here.

PHP: Here's where you can get creative with your modules, and create rules. At the end of each rule, return the id number of the module that you want to display. If you want to display more than one, return an array or a comma-separated string of id numbers.

You get access to several PHP variables which you can use in addition to $_SERVER, $_GET, $_POST and $_COOKIE variables:

  • $fromCountryId – the upper-case 2-letter ISO country code (e.g. GB, US, NL, DE). View the official ISO country code list.
  • $fromCountryName – the official ISO country name, mixed-case. (e.g. United Kingdom, United States, Netherlands, Germany)
  • $geoip - if you have enabled GeoLiteCity or GeoIPCity, a record containing the following items:
    • $geoip->country_name - full country name, as above
    • $geoip->country_code - 2-letter ISO country code, as above
    • $geoip->country_code3 - 3-letter ISO country code (e.g. GBR, USA, FRA, DEU)
    • $geoip->region - 2-letter code. For US/Canada, ISO-3166-2 code for the state/province name, e.g. "GA" (Georgia, USA). Outside of the US and Canada, FIPS 10-4 code., e.g. "M9" (Staffordshire, UK)
    • $geoip->city - full city name $geoip->postal_code - For US, Zipcodes, for Canada, postal codes. Available for about 56% of US GeoIP Records. More info.
    • $geoip->latitude
    • $geoip->longitude
    • $geoip->dma_code - 3-digit DMA/Metro code (US only)
    • $geoip->area_code - 3-digit telephone prefix (US Only)
  • $id – the id of the item in the main component on the page (e.g. "24:content-layouts" in Jooma 1.5, or "24" in Joomla 1.0)
  • $option – the option (i.e. the component name) of the main component on the page (e.g. com_content)
  • $view – Joomla 1.5 only – the "view" of the main component on the page (e.g. "article", "category", "categories" etc.)
  • $task – Joomla 1.0 only – the "task" of the main component on the page (e.g. "view", "category", "blogcategory" etc. )
  • $Itemid – the Itemid of the main component on the page (identifies which menu item was clicked)
  • $database – Joomla 1.0 only – in case you want to query the database for anything (for experts!)
  • $db – Joomla 1.5 only – in case you want to query the database for anything (for experts!)
  • $my – Joomla 1.0 only – contains information about the logged-in user:
    • $my->id
    • $my->name
    • $my->username
    • $my->email
    • $my->usertype ("" or "Public Frontend"=not logged in, or one of these: "Super Administrator", "Administrator", "Editor", "User", "Author", "Publisher", "Manager")
    • $my->registerDate e.g. "2007-05-17 01:25:52"
    • $my->lastvisitDate e.g. "2007-11-02 18:51:29"
  • $user – Joomla 1.5 only – contains information about the logged-in user:
    • $user->id
    • $user->name
    • $user->username
    • $user->email
    • $user->usertype ("" or "Public Frontend"=not logged in, or one of these: "Super Administrator", "Administrator", "Editor", "User", "Author", "Publisher", "Manager")
    • $user->registerDate e.g. "2007-05-17 01:25:52"
    • $user->lastvisitDate e.g. "2007-11-02 18:51:29"

Help: If the admin.modules.php patch is applied in Joomla 1.0.x (see above) this displays some helpful hints about how to use MetaMod.

PHP Example Recipes

Important: the numbers after the “return” statements have to correspond to the ID number of the module you want to include. Don’t forget to customise these for your own setup!

// date and time examples
  if (time() >= strtotime("1 Oct 2007") && time() < strtotime("1 November 2007")) return 74;
  if (time() < strtotime("1 Nov 2008")) return 75;

//GeoIP examples
  if ($fromCountryId == "US") return 55;
  if ($fromCountryId == "GB") return "55,56,57";
  if ($fromCountryId == "NL") return array(58,59,73);
  if ($fromCountryName == "New Zealand") return 21;
//advert in mornings for US viewers
  if ($fromCountryId == "US" && time() >= strtotime("07:00") && time() <= strtotime("12:00")) return 23;
//advert in lunch break for US viewers
  if ($fromCountryId == "US" && time() >= strtotime("12:00") && time() <= strtotime("14:00")) return 24; 

// explicit client IP address example 
  if ($_SERVER['REMOTE_ADDR'] == "123.22.33.44") return 56;
	
// IP address range example 123.22.33.50-55
  $ip_numbers = explode(".",$_SERVER['REMOTE_ADDR']); // split ip address into numbers, so we can test them individually
  if ($ip_numbers[0] == 123 &&
      $ip_numbers[1] == 22 &&
      $ip_numbers[2] == 33 &&
      $ip_numbers[3] >= 50 &&
      $ip_numbers[3] <= 55) return 12;

// randomised modules example
  $r = rand(1,3); // try some random stuff
  if ($r == 1) return 44;
  if ($r == 2) return 88;
  if ($r == 3) return 42; 

// time of last visit example
  if (strtotime("-1 month") > strtotime($my->lastvisitDate)) return 12; // if user has not logged in for more than a month

// browser detection example (display a different module based on which browser is being used)
  $UA = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
  $SF = strstr($UA, 'Safari') ? true : false;
  $OP = strstr($UA, 'Opera') ? true : false;
  $OPV = $OP ? preg_split('/opera\//i', $UA) : false;
  $OPV = $OPV ? floatval($OPV[1]) : false;

  $FF = !$OP && strstr($UA, 'Firefox') ? true : false;
  $FFV = $FF ? preg_split('/firefox\//i', $UA) : false;
  $FFV = $FFV ? floatval($FFV[1]) : false;

  $IE = strstr($UA, 'MSIE') ? true : false;
  $IEV = $IE ? preg_split('/msie/i', $UA) : false;
  $IEV = $IEV ? floatval($IEV[1]) : false;

  if ($IEV >= 7) return 55; // any version of IE greater or equal to 7
  if ($IEV >= 6) return 55; // any version of IE greater or equal to 6
  if ($IE) return 55; // any version (at all) of IE
  if ($OP) return 66; // any version of Opera
  if ($FF) return 77; // any version of Firefox
  if ($SF) return 88; // any version of Safari

// user group things JOOMLA 1.0.x
  if ($my->usertype == "") return 44; // user is not logged in - show something generic
  // user IS logged in - show nothing (or if they weren’t, show module 45):
  if ($my->usertype != "" && $my->usertype != "Public Frontend") return ""; else return 45; 
  if ($my->usertype == "User") return 46; // a plain registered user, no additional privileges
  if ($my->usertype == "Publisher") return 47; // show a notice for users with "Publisher" privileges

// user group things JOOMLA 1.5
  if ($user->usertype == "") return 44; // user is not logged in - show something generic
  // user IS logged in - show nothing (or if they weren’t, show module 45):
  if ($user->usertype != "" && $user->usertype != "Public Frontend") return ""; else return 45; 
  if ($user->usertype == "User") return 46; // a plain registered user, no additional privileges
  if ($user->usertype == "Publisher") return 47; // show a notice for users with "Publisher" privileges

// Detect the section and category of the article currently displaying on the page, and make decisions
// based on which section or category is being displayed.
//
// e.g. show different modules when the "hockey" or "football" (category ids 14 and 15) categories in
// the "sports" section (section id 5) are being displayed.
//
// This assumes that the module is only displaying on pages that are displaying 
// a content article of some sort. Therefore the "id" is assumed to be an article id. 
// If it's not, then you might get some strange results.
// THIS VERSION FOR JOOMLA 1.0.x ONLY! (revised 6 Aug 2008)
global $id;
$nullDate = $database->Quote( $database->getNullDate() );
$row = null;
$my_id = $database->getEscaped($id);
$query = "SELECT title, id, catid, sectionid"
. " FROM #__content WHERE id = '$my_id' AND state = 1"
. " AND ( publish_up = " . $nullDate
. " OR publish_up <= CURRENT_TIMESTAMP )"
. " AND ( publish_down = " . $nullDate 
. " OR publish_down >= CURRENT_TIMESTAMP " 
. " )" ;
$database->setQuery( $query, 0, 1 );
$database->loadObject($row);
$catid = $row->catid;
$sectionid = $row->sectionid;
// now you can use $catid and $sectionid in your test:
if ($catid == 14) return 1234; // for hockey articles show module 1234
if ($catid == 15) return 5678; // for football articles show module 5678
///////////////////////////////////


// JOOMLA 1.5 VERSION OF THE ABOVE (revised 6 Aug 2008)
$nullDate = $db->Quote($db->getNullDate());
$my_id = $db->getEscaped((int)$id);
$query = "SELECT  title,  id,  catid,  sectionid"
. " FROM #__content WHERE id = '$my_id' AND state = 1"
. " AND ( publish_up = " .  $nullDate 
. " OR publish_up <= CURRENT_TIMESTAMP )"
. " AND ( publish_down = " .  $nullDate 
. " OR publish_down >= CURRENT_TIMESTAMP  )" ;

$db->setQuery( $query, 0, 1 );
$row = $db->loadObject();
$catid = $row->catid;
$sectionid = $row->sectionid;
// now you can use $catid and $sectionid in your test:
if ($catid == 14) return 1234; // for hockey articles show module 1234
if ($catid == 15) return 5678; // for football articles show module 5678
///////////////////////////////////


// JREVIEWS (on Joomla 1.5).
// In this example, we return different modules depending on whether jReviews is on its
// "base" page or looking at a "category". We can choose the modules according to category
// as seen below. Note that if you have SEO urls turned on, jReviews acts slightly differently
// to when they are not turned on, so the code below tests for both cases. It's not the most
// elegant way to handle it, but shows you how to handle the situation.
if ($option == "com_jreviews") {
    $url = preg_replace("#/$#",'',$_GET['url']); 
    if ($url == "") return 18; // the top level page, not in a section or category
    $parts = explode("/",$url);
    $last_part = $parts[count($parts)-1];
    $last_part = preg_replace("#_.*$#","",$last_part); 
    switch ($last_part) {
        case "jreviews-category-list": // seo off
        case "jreviews:category:list": // seo on
          return 38;
        case "the-community": // seo off
        case "the:community": // seo on
          return 47;
        case "the-project": // seo off
        case "the:project": // seo on
          return 19;
    }
}