Creating a JAR file

I’m no Java programmer, but we used Magnolia CMS, a Java-based content management system, for creating interactive online learning materials, so have to mess about in the world of Java from time to time. In order to make modifications to the modules that we use for Magnolia (generally minor bug fixes or alterations to make them better suited to our needs), we have to unpack, edit and then repack .jar (Java ARchive) files. I always forget how to repack the jar files, and have recently had to set up a new PC to do it, so here’s the process I used, in case it is useful to anyone (especially the future me):

  • Get and install the latest Java Development Kit: http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
  • Add the location of the JDK bin (where jar.exe lives) to the Windows Path environment variable (Windows 8, but similar for other Windows versions):
    • Go to Control Panel > System > Advanced system settings > Environment Variables…
    • Under System variables, find the Path variable, select it and click Edit
    • Add “;C:\Program Files\Java\jdk1.7.0_21\bin” (or whatever the path to you JDK bin directory is – it will be something like this if you install with the default location settings) at the end of the Path variable
    • This will enable you to run jar.exe by just typing “jar …” in the command line
  • Open a command prompt and navigate to the directory containing the class files etc that you want to “jar”
  • Use this command to create the jar: “jar cf jar_name.jar *”
    • c = create new archive
    • f = specify file name
    • * = add all of the files in the directory to the jar
  • That’s it, you should now have a new jar file called “jar_name.jar”

 

XAMPP (1.8.1) on Windows 8 – Apache not starting as Port 80 “in use by system”

I have recently upgraded to Windows 8 and installed XAMPP 1.8.1. On trying to start Apache using XAMPP Controller, I kept getting an error message saying that Apache could not be started because port 80 (the default port for Apache) was “in use by system”.

After a bit of searching around, one common solution, and the one that worked for me, was to turn off the “World Wide Web Publishing Service”, as follows:

  • Go to Control Panel > Administrative Tools > Services
  • Find “World Wide Web Publishing Service”
  • Right click and select “Stop”
  • Right click and select “Properties”, then change “Startup type” to “Manual”

If this doesn’t fix it, you can find out what is using port 80 as follows:

  • In a Command Prompt, run ‘netstat -ao > netstat.txt’ to write active connections and associated process IDs (PID) to a file (for me, there were too many to just view them in the command prompt)
  • Find the PID that is listening on port 80 (0.0.0.0:80) – for me, the PID was 4, which seems quite common
  • Open Task Manager (Ctrl + Shift + Esc), right click on the column headers, and then click PID to show the PID column. Sort by PID to find the process for the PID that you identified above.

Once you know the process that is causing the problem, a bit of Googling (other search providers are available) should help you fix it.

CakePHP 2.x: Migrate from 1.3

The CakePHP 1.3 to 2.0 migration guide does probably cover most of what we have found below…but it appears to be written by and for the developers of CakePHP rather than for people who use CakePHP to develop their own applications. So here are some of the things we are doing during our 1.3 to 2.4 migration (although I can’t guarantee that they were all actually necessary), in case they help others. Please note that this is a work in progress 🙂

Upgrade Shell

Upgrade Shell – this promised considerably more than it delivered but did at least rename the controllers and changed the case of some of the folders under app. The rest were corrected using the Folder Names section of the migration guide, including the Views sub-folders and renaming ‘helpers’ to ‘Helper’

Files to copy from 2.x code

Copy the following from your download to the same place in \app:

  • \app\webroot\index.php
  • \app\View\Helper\AppHelper.php
  • \app\Config\core.php (contains a new error handling config. info)
  • \app\Config\routes.php (contains a couple of new setting to load plgin routes and ‘default’ CakePHP routes.

Update Paginator code

In view:

echo $this->Paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% courses out of %count% total', true)
    ));

changes to:

echo $this->Paginator->counter(
    'Page {:page} of {:pages}, showing {:current} records out of
     {:count} total'
);

In controller, this syntax:

$this->paginate['Event'] = array(
            'order'=>array(
                ..
                ),
             'conditions'=>array(
                ..
                 )
             );

$this->set('events', $this->paginate());

changes to:

$this->paginate = array(
                'order'=>array(
                     ..
                    ),
                 'conditions'=>array(
                    ..
                     )
                 );
$this->set('events', $this->paginate('Event'));

Change all ‘action’ functions in ctp files from:

function index(){}

to

public function index(){}

Auth Component

Now required to explicitly log users in – not done automagically any more. Where we use WebAuth to authenticate and manually log users in:

if($this->Auth->login($user_id))
     {
     $this->Session->setFlash('Logged in');
     }

becomes:

$this->request->data['User'] = array_merge($this->request->data['User'], array('id' => $user_id));
if($this->Auth->login($this->request->data['User']))
     {
     $this->Session->setFlash('Logged in');
     }

Form2Helper

Although, some of this is specific to the Form2Helper (to make MySQL enum fields show enumerated values rather than a number), some of these points will apply to all helpers:

  1. Change name of helper to Form2Helper.php, instead of Form2.php;
  2. Change App::import(‘Form’) to App::uses(‘FormHelper’, ‘View/Helper’) above class definition (replacement of App::import in 2.x with App::uses)
  3. $this->_introspectModel($modelKey, ‘fields’, $fieldKey) instead of $this->_introspectModel($modelKey) (changes to _introspectModel)
  4. After an hour or two of trying to work out why the first enum input in a form was being output as a standard text box while subsequent inputs for the same field in the db, were coming out correctly as selects, I realised that:
    isset($this->fieldset[$modelKey])

    was not returning false when:

    $this->fieldset[$modelKey]['fields']

    was returning ‘null’ so I have replaced it with:

    if (!isset($this->fieldset[$modelKey]['fields'][$fieldKey]))

    and it’s all working nicely now.

Request Object

Use $this->request->data instead of $this->data

FormHelper

FormHelper::select(string $fieldName, array $options, mixed $selected, array $attributes)

becomes

FormHelper::select(string $fieldName, array $optionsarray $attributes)

and

$html->link

becomes

$this->Html-link()

Maths with QTI – Things I wish I’d known before I started

Writing maths questions in QTI has been a bit of a learning curve, and I took my (/everyone’s?) usual approach of diving straight in and trying things out, without spending too much time reading the specification. As a result, there have been some things that I didn’t really understand when I started, but have realised along the way, and really wish that I had  taken the time to find out before I started. As with pretty much all of my QTI-related posts, this is a work in progress and will probably be updated as and when more things that I should have known crop up!

The meaning of adaptive (for an assessmentItem)

I didn’t really look too much at the assessmentItem properties when creating new questions, I just copied an old question, changed the identifier and title, and added extra namespace definitions if I found I hadn’t defined one that I was using (e.g. MathML or MathAssess) when I tested the question. I didn’t really pay attention to the ‘adaptive’ property, and didn’t notice that on some questions it was true and some it was false.

It was not until I put tests together and tried them out that I found out that items for which adaptive was set to false only allowed a single attempt. Since these are formative questions, I wanted students to be able to have multiple attempts, see a hint, see the solution, etc, which means setting adaptive to true. Therefore, had to go back through lots of questions, change adaptive to true and then re-add them to my tests.

What setCorrectResponse does

The name of the setCorrectReponse class gives a pretty good hint as to what it does, but I hadn’t fully understood it. As well as setting the correct response for a response variable (explained below), it is also used for displaying the model answer when a student is reviewing a test. If the test is set up to allow solutions to be shown when reviewing (using <itemSessionControl showSolution="true" /> in the testPart in the assessment.xml file for the test), the user can then choose the show the model solution by clicking the “Show Solution” button. This replaces their answer in the answer box with the model solution. The can switch back to the answer they gave by clicking the “Hide Solution” button.

To summarise the setting/getting of a correct response in an asssessment item, there are two ways to assign a correct response to a response variable:

  1. When declaring the response variable:
    <responseDeclaration identifier="RESPONSE" cardinality="single" baseType="float" >
       <correctResponse>
          <value>5.2</value>
       </correctResponse>
    </responseDeclaration>
  2. Using setCorrectResponse in templateProcessing. This is often more useful as the correct response can be assigned after template variables have been set and/or calculated.
    <setCorrectResponse identifier="RESPONSE">
       <variable identifier="fAns"/>
    </setCorrectResponse>

It is then possible to get the correct response for a response variable, in the responseProcessing, as follows:

<correct identifier="RESPONSE"/>

This can be used for comparing a given response to the correct response:

<equalRounded roundingMode="decimalPlaces" figures="0">
   <correct identifier="RESPONSE"/>
   <variable identifier="RESPONSE"/>
</equalRounded>

 

Writing Tests in Uniqurate – Tips and Tricks

We are using the QTI specification to create maths questions that can be delivered online. In order to do this we have used Uniqurate, partly as a tool for writing questions (although the vast majority of questions have been ‘hand-coded’ in QTI XML, due to their complexity), but mainly as a tool for bringing questions together into  tests.

I thought I would share a few things that have made my life easier when using Uniqurate, in case they are useful to anyone else:

  • Use ‘Enter’ (carefully!) to confirm actions – Uniqurate often asks for confirmation, e.g. when deleting questions, using a pop up box. This can make deleting lots of questions an annoying process if you use the mouse to click the OK button in the confirm box. However, if you just click the delete button (rubbish bin) and then hit the enter key, this will confirm, so you can delete lots of questions quickly. Needless to say be very careful when doing this!
  • Go back to the main menu if it starts misbehaving – Uniqurate sometimes starts misbehaving, e.g. not adding questions properly. If this happens, just go back to the main menu, then click the “Edit current test” button to return to the edit screen.
  • Click on the Section name to edit it – it’s not immediately obvious, but you can edit section names by clicking on/selecting the current name of a section and then typing in the new name.
  • To move a question, click on white space, not the question name – You can drag and drop questions within a section (but unfortunately not from one section to another). To do this, you need to click on the white space to the right of the question name, not on the question name itself (doing so just selects the text).
  • “Add question to section” link only works when the mouse cursor is an arrow – Depending on your browser (I’m talking about IE10 on Windows), the pointer will be one of an arrow, a pointer (hand with pointed figure) or a text cursor when you hover over the “Add question to section” link, depending on which part of the link you are over. It only seems to actually work as a link when it is the arrow, so move around until you get an arrow before clicking!
  • Check the total score when you have repeated questions – see the post about modifying assessment.xml for more information

I will add any further things as and when they crop up.

Maths with QTI – Item Body

The itemBody section is where the question text, interactions, buttons and feedback are defined. Questions, solutions, etc will vary hugely depending on the question being asked, but I have included some standard sections that tend to be used regularly.

Question Text

Question text displaying an equation using variables defined using Maxima in templateProcessing:

<div class="QTEXT">
   <p>Solve the equation
      <m:math display="block">
         <m:mrow>
            <m:mi>mLHS1</m:mi>
            <m:mo>=</m:mo>
            <m:mi>mRHS1</m:mi>
         </m:mrow>
      </m:math>
      giving your answer as an integer or as a fraction in its lowest terms.
   </p>
</div>

Displaying Variables

Maths variable: <printedVariable identifier="mVar"/>
Integer: <printedVariable identifier="oQansVars" format="%.0f"/>
Number (to 2 decimal places – change the number before the f in the format attribute to change the number of decimal places): <printedVariable identifier="oQansVars" format="%.2f"/>

Interactions

Text/numerical entry interaction

<textEntryInteraction responseIdentifier="RESPONSE" expectedLength="2" />

Mathematical expression entry interaction

<customInteraction id="customInteraction1" ma:syntax="text/x-maxima" ma:printIdentifier="printMath" responseIdentifier="RESPONSE"/>

Show Hint button

<feedbackBlock showHide="show" identifier="askhint" outcomeIdentifier="ASKHINT">
   <p>
      <endAttemptInteraction responseIdentifier="HINTREQUEST" title="Show Hint"/>
   </p>
</feedbackBlock>

Show Solution button

<feedbackBlock showHide="show" identifier="asksolution" outcomeIdentifier="ASKSOLUTION">
   <p>
      <endAttemptInteraction responseIdentifier="SOLREQUEST" title="Show Solution"/>
   </p>
</feedbackBlock>

Feedback

Correct

<feedbackBlock outcomeIdentifier="FEEDBACK" identifier="RESPONSE-CORRECT" showHide="show">
   <div>Correct</div>
</feedbackBlock>

Incorrect

<feedbackBlock outcomeIdentifier="FEEDBACK" identifier="RESPONSE-INCORRECT" showHide="show">
   <div>Incorrect, try again. <br /><br /> </div>
</feedbackBlock>

No answer entered

<feedbackBlock outcomeIdentifier="FEEDBACK" identifier="RESPONSE-NULL" showHide="show">
   <div>Please enter an answer<br /><br /></div>
</feedbackBlock>

Hint

<feedbackBlock identifier="HINT" outcomeIdentifier="FEEDBACK" showHide="show">
   <div>Solution goes here<br /><br /></div>
</feedbackBlock>

Solution

<feedbackBlock identifier="SOLUTION" outcomeIdentifier="FEEDBACK" showHide="show">
   <div>Solution goes here<br /><br /></div>
</feedbackBlock>

MathML

I won’t cover MathML in any detail here, but the University of Edinburgh’s LaTeX to MathML converter is a very useful tool to help with this. This section contains a few tips for achieving some things that aren’t immediately obvious in MathML.

Greek Letters in MathML

These must be represented by the HTML decimal (e.g. &#913;) or hex codes (e.g. &#x391;), not the HTML entity codes (e.g. &Alpha;). Here’s a full list of codes.

Operators in MathML

Times symbol: <m:mo>&#xd7;</m:mo>
Space: <m:mi>&#160;</m:mi>

Selecting and Replacing Columns/Blocks of Text in Eclipse

In Eclipse (I’m using Eclipse June 4.2.2 at the moment), it is possible to select columns/vertical blocks of text by using Alt+Shift+A to switch to block selection mode. You can then drag over a column/block of text. With a block selected, typing text will replace the selected text in all of the selected rows.

Maths with QTI – Template Processing

Please note that this is a work in progress! I have added blocks that I just happen to have been using since I started writing this post, so they are not necessarily being added ‘simplest’ first. I will continue to add more as and when I use them.

After declaring all of the variables in an assessment item in a QTI XML file, the next step is to do the template processing. This is where variables are set or randomised, answers are calculated etc. There are too many possibilities for this section to get anywhere near specifying everything that is possible, and I won’t cover what can be done using Maxima in this post, but hopefully some of the blocks of code here can be used as they are, or will act as good starting points for doing other things.

templateProcessing Start and End

<templateProcessing>
   <!-- Template processing goes here -->
</templateProcessing>

Set Fixed values

Setting a Fixed Integer value

<setTemplateValue identifier="iA">
   <baseValue baseType="integer">2</baseValue>
</setTemplateValue>

Setting a Fixed Float value

<setTemplateValue identifier="fFloat">
   <baseValue baseType="float">3.14159</baseValue>
</setTemplateValue>

Set Randomised values

Set a positive Randomised Integer value from a range

<setTemplateValue identifier="iA">
   <randomInteger min="2" max="9"/>
</setTemplateValue>

Set a positive or negative Randomised Integer value from a range

<setTemplateValue identifier="iC">
   <product>
      <randomInteger min="1" max="10"/>
      <randomInteger min="-1" max="1" step="2"/>
   </product>
</setTemplateValue>

Note that the step attribute means that the second randomInteger will be selected only every other number in the specified range. In this case, that means it can only be -1 or 1, and not 0, so this defines whether the value is positive or negative.

Set an integer to a random value from within a range

<setTemplateValue identifier="iB">
   <random>
      <multiple>
         <baseValue baseType="integer">2</baseValue>
         <baseValue baseType="integer">3</baseValue>
         <baseValue baseType="integer">5</baseValue>
         <baseValue baseType="integer">8</baseValue>
         <baseValue baseType="integer">13</baseValue>
         <baseValue baseType="integer">21</baseValue>
      </multiple>
   </random>
</setTemplateValue>

Rounding

Round to a number of significant figures

<setTemplateValue identifier="fFloatRounded">
   <roundTo roundingMode="significantFigures" figures="3">
      <variable identifier="fFloat"/>
   </roundTo>
</setTemplateValue>

Round to a number of decimal places

<setTemplateValue identifier="fFloatRounded">
   <roundTo roundingMode="decimalPlaces" figures="2">
      <variable identifier="fFloat"/>
   </roundTo>
</setTemplateValue>

 

Constraints

Set a value to not equal a fixed value

<templateConstraint>
   <not>
      <equal toleranceMode="exact">
         <variable identifier="iA"/>
         <baseValue baseType="integer">7</baseValue>
      </equal>
   </not>
</templateConstraint>

Ensure Greatest Common Denomiator of 2 values is 1 (i.e. the values share no common factors other than 1)

<templateConstraint>
   <equal toleranceMode="exact">
      <gcd>
         <variable identifier="iA"/>
         <variable identifier="iB"/>
      </gcd>
      <baseValue baseType="integer">1</baseValue>
   </equal>
</templateConstraint>

Ensure 2 values are not equal

<templateConstraint>
   <not>
      <equal toleranceMode="exact">
         <variable identifier="iA"/>
         <variable identifier="iB"/>
      </equal>
   </not>
</templateConstraint>

Ensure one value is larger than another

<templateConstraint>
   <gt>
      <variable identifier="iB"/>
      <variable identifier="iC"/>
   </gt>
</templateConstraint>

Constraint to ensure a – (b + c) != 0, i.e. a != (b + c)

<templateConstraint>
   <not>
      <equal toleranceMode="exact">
         <subtract>
            <variable identifier="iA"/>
            <sum>
               <variable identifier="iB"/>
               <variable identifier="iC"/>
            </sum>
         </subtract>
         <baseValue baseType="integer">0</baseValue>
      </equal>
   </not>
</templateConstraint>

Conditions

Set iB to equal 6, 8 or 10 if iA < 5, otherwise iB equal to 2, 4 or 6

<templateCondition>
   <templateIf>
      <lt>
         <variable identifier="iA"/>
         <baseValue baseType="integer">5</baseValue>
      </lt>
      <setTemplateValue identifier="iB">
         <randomInteger min="6" max="10" step="2"/>
      </setTemplateValue>
   </templateIf>
   <templateElse>
      <setTemplateValue identifier="iB">
         <randomInteger min="2" max="6" step="2"/>
      </setTemplateValue>
   </templateElse>
</templateCondition>

Constants

<mathConstant name="pi"/>

Maxima Script Dummy Variable

I won’t cover what is possible with Maxima and hence what goes in this variable, but this is the code used for setting the dummy variable:

<setTemplateValue identifier="tDummy">
   <customOperator class="org.qtitools.mathassess.ScriptRule" ma:syntax="text/x-maxima">
      <baseValue baseType="string"><![CDATA[
         mAns : ev(iA*x^2iB,simp);
      ]]></baseValue>
   </customOperator>
</setTemplateValue>

Maths with QTI – Issues

Please note that this is a work in progress, and we will add issues as we find them, or remove them as they are fixed (or we find out that they aren’t really issues).

As discussed in other posts, we’re using QTI (Question and Test Interoperability) to create and deliver maths questions. The majority of the questions are being written by modifying (directly in the XML) questions from Dr Sue Milne’s collection. Uniqurate is being used to put questions together into tests, and QTIWorks is being used to deliver the questions.

We have found all the parts of the system – Uniqurate, QTIWorks and the QTI specification in general – very useful, usable and flexible. However, as is inevitable with such a complex and extensive specification and combination of creation and delivery systems, we have come across some issues. None of these are major problems that we cannot work with or around, but I thought I would continue my recent theme of writing “work in progress” blogs, and write one containing all of the issues that we have with each part of the system/process.

QTI (inc. Maxima, MathAssess engine)

  • Rounding floats doesn’t seem to work properly. After rounding, there seems to often be an arbitrary (small) value added to or subtracted from the rounded value, giving a value to >10 decimal places. For example, rounding 41.28947 to 2 decimal places, which should give 41.29, might give 41.29000014723.
  • There can be issues with recognising 2 equally valid correct answers as such. For example, if 1/x^3 is the correct answer to a question, x^-3 might be considered not fully simplified, even though it is at least as simple and arguably simpler. Another example would be requiring -(3x^2)/y, when (-3x^2)/y or -3x^2/y, or even -3x^2y^-1 would be equally correct. It might be possible to fix this within the XML, but would ideally be built into the Maths comparison engine.

Uniqurate

  • Questions are sometimes added to the wrong section of a test, or are not added at all (the latter seems to happen after a question has just been deleted from the test section). Generally returning to the Main Menu and then editing the test again gets rid of the issue temporarily.
  • It would be great to be able to add multiple questions to a test section in one go, rather than having to add them one at a time.
  • It is possible (and not that difficult) to create invalid combinations of of the “Number of questions to choose from section” and “Show questions only once/Repeat Questions” settings. If you tell it to show more questions that you have added, but leave it set to use each question only once, the test is invalid. It would be good if Uniqurate stopped you from doing this. To me, it seems as though there is significant overlap between these settings, at least when you tell it to show more question instances that there are questions in the section.
  • Can only specify the total number of questions in a section, not the number of each question.
  • The “Add question to section” link only works when the mouse cursor is an arrow. Depending on the browser (I’m talking about IE10 on Windows), the pointer will be one of an arrow, a pointer (hand with pointed figure) or a text cursor when you hover over the “Add question to section” link, depending on which part of the link you are over. It only seems to actually work as a link when it is the arrow.
  • Related to the above point, if you choose to show more questions than there are in the section, and hence set it to repeat questions, you are not guaranteed one of each question. E.g. if you have 2 question, and choose to show 3 questions, you could get 3 of one question and none of the other.
  • IDs given to sections are random strings, which makes them hard to identify when looking at results in QTIWorks. It would be good to be able to specify the ID for a section.
  • If questions are repeated in a section, the total score for the test is calculated as if each question was to be shown only once.

QTIWorks

  • The maths input help box could do with some improvements – see my post about a Help Guide for Maths Entry Questions.
  • Would like to be able to display multiple questions on a single page, rather than one question per page. This would be possible to set up by creating multiple questions in a single XML file, but it would be much nicer to be able to do it through the delivery settings.
  • Right clicking on a MathML block, then going to Settings > Math Renderer > MathML sometimes shows a simplified version of what is displayed, which for some questions (e.g. division of fractions) might actually be the answer. While it is unlikely someone would realise and do this, and it is only for certain questions where it would give the answer, it is something that could be looked at.
  • When a single part question is answered, it shows as “Answered” (in blue). However, when a multi-part question is answered, it is shown as “Finished” (in grey).
  • Similarly, if you answer a question (so it is marked as “Answered” and in blue), but then look at the solution, it is then marked as finished. This makes sense if the answer given was incorrect, but if the answer was correct and then the student looked at the solution, it would be good if the status remained as “Answered”.

Fixed/Non Issues

  • Would like scores, feedback and/or solutions to be shown when reviewing a test – Feedback and model solutions can be shown when reviewing by adding the following to the testPart section in the assessment.xml file for a test:
    <itemSessionControl showFeedback="true" showSolution="true" allowReview="true" />

    showFeedback means that the last feedback that the user was shown when attempting the question is shown when they review it. showSolution provides a “Show Solution” button, which puts the correct response (as defined in the response declaration, or by using setCorrectResponse in templateProcessing) in the answer box. However, it seems that if a maths variable is set as the correct response to a question, this will not  be displayed properly as the solution when reviewing.

Three MSDLT talks as part of IT Services ‘Make:’ series

Make: Grow your own technology – 21st Century vegetable and fruit growing

08 May from 12:30 to 13:30

Allotmentor (www.allotmentor.com) is a free online tool that Damion Young has put together in his spare time. Use it to plan your bed, allotment, smallholding or windowbox and replace that tattered vegetable notebook with something altogether more hi-tech. He will show you how it is possible to take freely available online technologies and resources (such as Google Maps, Simile Timelines, Facebook and Creative Commons images) and create something novel, without having access to a huge development team. He will also show you how, with remarkably little effort, and something called PhoneGap, you can easily transform a portion of a website into a free cross-platform mobile app so you can take your website with you, even if you do not have a signal.

Make: Turning Film Noir into Fun Noir

14 May from 12:30 to 13:30

Pauline Woolley, (Medical Sciences Learning Technologies Group) played a key part in the creation of The Blackened Mirror, a film noir drama in the form of an online video series. She will be talking about the techniques used to create the series, the use of various forms of social media in publicising the show and driving the narrative forward (with characters having their own Twitter or Live Journal streams). She will also be talking about the evolving nature of the games and how they moved from practical rewards to rewarding with information with the ultimate prizes being the opportunity to enter the environment.

Make: Medlearn – it’s not just for medics

22 May from 12:30 to 13:30

Medlearn is currently used by the Medical Sciences Division to provide interactive learning resources on a wide variety of topics. It is based on a free, open source content management system (Magnolia CMS), but has been adapted to enable easy creation of self-test questions alongside multi-media teaching content. Learning content is created and managed online, with no need to retain local templates, or upload completed packages, e.g. to WebLearn. Having recently made some significant improvements to the system, we are keen to make it available to other divisions/departments. Come along and see what you can do!