Introduction

ATutor 1.5.2 introduced the concept of modules, providing developers with a framework to implement additional functionality in a coherent and loosely coupled way.

The framework defines methods for assignable privileges, backing-up and restoring content, deleting course specific content, and side menu, student tool, instructor management, and administrator components. Additional methods of interaction have already been planned and will be added in future versions.

The intent is to allow for the development and distribution of modules independently to the ongoing development and release of ATutor. The module structure should also allow for the creation of third party modules that cannot be distributed under the GNU General Public License, but that should be distributed separately under their own license.

A Hello World example module is included with ATutor for developers who want to investigate this new feature. The module is found in the mods/hello_world directory.

Structure

Modules are stored under ATutor's mods directory. Core modules are stored in the mods/_core subdirectory and are made available with every release of ATutor. These modules cannot be disabled by the administrator as they are vital to ATutor's functionality. Standard modules are stored in the mods/_standard subdirectory and are also made available with every release of ATutor. Standard modules can be disabled by the administrator. Extra modules are stored in the mods directory and are installed and distributed independently of ATutor. Although the process of developing modules is the same for each type of module, only extra modules can be distributed separately, while core and standard modules are added to the ATutor code repository (i.e. SVN trunk).

Whenever a module identifier is needed within code, it should appear in lowercase with spaces converted to underscores.

The module name, and hence the directory and function names (see below for additional details), must be unique across all possible modules. A module should not be made available if an existing module is already being distributed under that same name. It is up to the module developer to ensure that their module name is unique.

Directory Name

The name given to the directory must be chosen carefully. The name is used to namespace the module's function by prefixing required functions with that directory name. For example, a module named Example Maker should be placed in a directory named example_maker and the delete function would be named example_maker_delete().

Files

The following files should exist under the module's top level directory: mods/module_name.

module.php
This is the main module file which gets included whenever a page is loaded. Required.
module.xml
This file is used only for identifying the module for distribution and is only used when viewing a module's details. Required.
module_backup.php
This file is used when backing-up and restoring course content. Optional.
module_delete.php
This file is used when deleting course specific content. Optional.
module_install.php
This file is used when installing the module. Required.

The module.xml File

The module.xml file is used for displaying information about the module before it is installed and is useful when distributing the module.

<?xml version="1.0" encoding="ISO-8859-1"?> 
<module version="0.1"> 
    <name lang="en">Example Maker</name> 
    <description lang="en">This is an example module that makes examples.</description> 
    <maintainers>
        <maintainer> 
            <name>ATutor Team</name> 
            <email>info@atutor.ca</email> 
        </maintainer>
        <maintainer> 
            <name>John Doe</name> 
            <email>jd@example.com</email> 
        </maintainer>
    </maintainers> 
    <url>http://www.example.com</url> 
    <license>BSD</license> 
    <release> 
        <version>0.2</version> 
        <date>2005-08-22</date> 
        <state>stable</state> 
        <notes>Fixes several bugs in previous version.</notes> 
    </release> 
</module>

Installation

The module_install.php script gets executed during the installation process. If its execution results in $msg->containsErrors() evaluating to TRUE, then the errors are displayed and the user is prompted to correct them. The process is then repeated until errors are no longer being generated and the module is installed successfully. Eventually, it is up to the module to determine the logical steps involved in its installation. For example, it might be better to create the data directories before trying to create any database tables since creating the directory may require several attempts. Typically the flow we describe here should be suitable in most cases.

Theoretically, the install script's execution is wide-open and does not have to adhere to the process outlined below or make use of any special privileges, provided it generates errors as appropriate.

# pseudo-code for installing a module:
while (there are errors)
    print the error message

    # inside module_install.php:
    define the privileges used

    if (create database tables is unsuccessful) then
        generate an error message

    if (there are no errors AND there is an SQL file) then
        execute the SQL file
end while

add the module to the system using the defined privileges

Specifying Privileges

Privileges control who has access to the course management and administrative sections.

See the Authentication & Privileges section for additional details using the privileges.

$_course_privilege

This variable controls access to a course's management section and can take one of the following values:

$_admin_privilege

This variable can take one of the following values:

Note that creating a privilege is not in itself enough to make the module appear in the Manage section! The hierarchy and navigation path to the management page must be set correctly. See the Navigation & Hierarchy section for additional details.

Creating a Data Directory

It is best to keep the directory within the AT_CONTENT_DIR directory as it should already allow the creation of files and directories by the web server. It is then up to the module to create individual course directories as needed.

$directory = AT_CONTENT_DIR .'example_maker';

// check if the directory is writable
if (!is_dir($directory) && !@mkdir($directory)) {
    $msg->addError(array('MODULE_INSTALL', '<li>'
                         .$directory
                         .' does not exist. Please create it.</li>'));
} else if (!is_writable($directory) && @chmod($directory, 0666)) {
    $msg->addError(array('MODULE_INSTALL', '<li>'
                         . $directory
                         .' is not writable.</li>'));
} // else: it was created successfully.

Executing an SQL File

If the module requires its own database tables or custom language, then it will have to create them itself. The SQL can either be executed inline using PHP database execution directly, or using the SQLUtility class to execute an external SQL file.

if (!$msg->containsErrors() && file_exists(dirname(__FILE__) . '/module.sql')) {
    // deal with the SQL file:
    require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
    $sqlUtility = new SqlUtility();
    $sqlUtility->queryFromFile(dirname(__FILE__) . '/module.sql', TABLE_PREFIX);
}

Generating Errors

It is up to the module to generate and check for any errors that occur during the installation. An error message can be generated using $msg->addError(array('MODULE_INSTALL', '<li>your error msg goes here</li>'));. Note that the text supplied to the error message is not translated. If the language should be localised, then a custom solution should be implemented to correctly supply the appropriate language.

To check if any errors have been generated, use $msg->containsErrors() which evaluates to TRUE if a previous error has been generated.

Authentication & Privileges

See the Installation: Specifying Privileges section on creating privileges during the installation process.

Authentication uses constants for the privilege levels. The privileges should be declared in the module.php file using the $this->getPrivilege() and $this->getAdminPrivilege() methods, respectively.

define('AT_PRIV_FORUMS',       $this->getPrivilege()      );
define('AT_ADMIN_PRIV_FORUMS', $this->getAdminPrivilege() );

Once declared, a page can then authenticate against those privileges using either the authentication() or the admin_authenticate() functions.

define('AT_INCLUDE_PATH', '../include/');
require(AT_INCLUDE_PATH.'vitals.inc.php');
// authenticate the administrator forums section:
admin_authenticate(AT_ADMIN_PRIV_FORUMS);

Localisation

Although a module can be created with all hard-coded language, it is recommended to use ATutor's localisation functions. All of ATutor's language is stored in the database, which is then retrieved using the _AT() function for simple terms and the $msg object for feedback and error messages.

Additional details on localising ATutor can be found on the Thing You Should Know Before Translating and ATutor Developer Documentation pages.

Module-specific language should be inserted into the language_text table during the installation process. The fields are as follows:

language_code
The ISO-639-1 language code plus locale.
variable
Set to _module for modules.
term
The variable used for retrieving the language.
text
The language text.
revised_date
Set to NOW() for modules.
context
Short description of the language text.
# Insert module specific language:
INSERT INTO `language_text` VALUES ('en',
                                    '_module',
                                    'example_maker',
                                    'Example Maker',
                                     NOW(),
                                    'the module title');

Custom Style Sheets

A custom style sheet can be linked into pages by setting $_custom_css to be the absolute path to the style sheet. This variable must be set on every page that requires that style sheet.

define('AT_INCLUDE_PATH', '../../include/');
require(AT_INCLUDE_PATH.'vitals.inc.php');
// using a custom style sheet:
$_custom_css = $_base_path . 'mods/example_maker/module.css';

Side Menu Boxes

Side menu boxes generally appear in a column at the side of a course (though this layout can be altered by a theme). A module may implement one or more side menu boxes.

Side menus are specified using the $_module_stacks array in module.php. $_module_stacks have the attributes title_var (or title) and file. The title_var value is the language key used for that box; the title will be generated by executing _AT($title_var). If title is set instead, a hard-coded title will be used. The file attribute specifies the absolute path to the side menu's include file.

The key to the $_module_stacks should be the name of the module.

$_module_stacks['example_maker'] = array('title_var' => 'example_maker', 
                                         'file' => dirname(__FILE__).'\side_menu.inc.php');

Creating a side menu box involves using the $savant template object and assigning the output of the box to the dropdown_contents variable.

<?php global $savant;

$box_content = 'This is my side menu box';

$savant->assign('dropdown_contents', $box_content);

$savant->assign('title', _AT('example_maker'));
$savant->display('include/box.tmpl.php');
?>

Student Tools

Student tools are pages linked from the home page or the main navigation of courses. A module can only implement one student tool. An instructor controls which student tools are available to a course using the Student Tools section found under Manage.

The tool main page must be specified using the $_student_tool variable in the module.php file. The value of that variable must be the relative path to the file from the ATutor base directory (not the module directory). Example: $_student_tool = 'mods/example_maker/index.php';.

For the tool to correctly appear its Navigation & Hierarchy must be defined correctly. If the tool is to have an instructor management section then the parent must be specified as being tools/index.php and the module must have a non-zero privilege level.

Navigation & Hierarchy

Every page in ATutor must have an entry in the global $_pages array where the key to the array is the relative path to the file from ATutor's base directory. Module pages are specified using the $_module_pages array, which are then merged into the $_pages array when the module.php file is loaded. The array supports the following attributes:

title_var
The language variable to be used with _AT().
title
The hard-coded version of the language title. If set, overrides the usage of _AT(title_var). This version is not language independent.
parent
The relative ATutor path to the parent page. Omit for Student Tools.
img
The relative ATutor path to the icon to use. Only for Student Tools.
children
An array whose values are relative ATutor paths to sub pages.
guide
The the section of the handbook that the module page should link to. Not used for modules at this time.

For pages to appear in the instructor Manage section, their parent field must be set to tools/index.php.

$path = 'mods/example_maker/';

// the student tool:
$_module_pages[$path.'index.php']['title_var'] = 'example_maker';
$_module_pages[$path.'index.php']['img']       = $path.'icon.gif';
$_module_pages[$path.'index.php']['children']  = array($path.'sub.php', $path.'more.php');

    $_module_pages[$path.'sub.php']['title_var'] = 'sub_page';
    $_module_pages[$path.'sub.php']['parent']    = $path.'index.php';

    $_module_pages[$path.'more.php']['title_var'] = 'more_page';
    $_module_pages[$path.'more.php']['parent']    = $path.'index.php';

// the instructor page:
$_module_pages[$path . 'inst_index.php']['title_var'] = 'example_maker';
$_module_pages[$path . 'inst_index.php']['parent']    = 'tools/index.php';

Course Deletion

When a course is being deleted, or when a back-up is being restored by overriding (i.e. deleting) existing content, a module has to ensure that the content for that course is also deleted. If the module maintains course data directories, then those directories have to either be emptied or deleted. If the module uses database tables for course content, then it has to delete the appropriate entries for that course.

The function used to delete the course content for that module must be stored in the module_delete.php file and named module_name_delete(). The delete function takes a single argument which is the ID of the course to delete.

<?php
function example_maker_delete($course) {
    global $db;

    // delete directory
    $path = AT_CONTENT_DIR . 'example_maker/' . $course . '/';
    clr_dir($path);

    // delete from database
    $sql = "DELETE 
            FROM ".TABLE_PREFIX."example_content 
            WHERE course_id=$course";
    mysql_query($sql, $db);
}
?>

Backing-Up and Restoring

It is possible for a module to include its content when a course backup is being created or restored. Backups support database tables with foreign-key constraints as well as course specific directories.

Directories

A module can backup as many directories as it requires, all specified using the $dirs array variable.

The example below uses the special ? token as the place holder for the course ID. When the course is backed-up, the question mark will be replaced with the correct course ID. The key to the array is the unique name of the directory to be used inside the backup archive file. The same information to create the backup is also used to restore it, so no additional details are required.

$dirs = array();
$dirs['example_maker/'] = AT_CONTENT_DIR . 'example_maker/?/';

Database Tables

There are two parts to backing-up and restoring a module's database tables. First, the SQL queries must be specified using the $sql array variable and then the restore functions must convert the rows so that they can be inserted into the database tables.

The example below uses the special ? token as the place holder for the course ID. When the course is backed-up the question-mark will be replaced with the correct course ID. The key to the array is the unique name of the CSV file to save in the backup archive, without the extension. The SQL query itself must only select the fields that will be backed up. If there are foreign key constraints to preserve then the key will have to be retrieved as well so that it can be used when restoring the tables.

$sql = array();
$sql['example']  = 'SELECT title FROM '.TABLE_PREFIX.'example WHERE course_id=?';

For each key in the $sql array there must be a function with the same name, but suffixed with _convert. The tbl_name_convert() function must return the newly transformed row with respect to the version of ATutor that was used to generate the CSV file. The function accepts the following arguments:

$row
An array which represents a single row in the CSV file.
$course_id
The course ID which this content should be associated with.
$table_id_map
An associative array representing previously restored tables and their new keys. Used to preserve foreign key constraints.
$version
The version of ATutor that was used to generate this file.
function example_convert($row, $course_id, $table_id_map, $version) {
    $new_row = array();
    $new_row[0]  = 0; // auto-increment field
    $new_row[1]  = $course_id;
    $new_row[2]  = $row[1]; // the title
    if (version_compare($version, '1.5.2', '<')) {
        // this field did not exist prior to 1.5.2
        $new_row[3] = '';
    } else {
        $new_row[3] = $row[2];
    }

    return $new_row;
}