Having got the "hello World" 'wstemplate' Web-service plugin working as described in last night's post, I tried modifying it to create a function of my own, that doubles its argument. This is how. I'd unzipped the plugin into a directory called wstemplate. First, I edited wstemplate/externallib.php:

require_once($CFG->libdir . "/externallib.php");

class local_jnip_external extends external_api 
{
  /**
   * Returns description of method parameters.
   * @return external_function_parameters
   */
  public static function double_parameters() 
  {
    return new external_function_parameters(
      array( 'n' => new external_value( PARAM_TEXT
                                      , 'The number to be doubled.'
                                      )
           )
    );
  }


  /**
   * Returns welcome message.
   * @return string welcome message
   */
  public static function double( $n ) 
  {
    global $USER;

    // Parameter validation.
    // REQUIRED.
    $params = self::validate_parameters( self::double_parameters()
                                       , array( 'n' => $n ) 
                                       );

    // Context validation.
    // OPTIONAL. Not needed for this function.

    // Capability checking.
    // OPTIONAL. Not needed for this function.

    return $params['n'] * 2;
  }


  /**
   * Returns description of method result value.
   * @return external_description
   */
  public static function double_returns() 
  {
    return new external_value( PARAM_TEXT, 'The argument doubled.' );
  }
}

In the above, I've changed the classname to local_jnip_external. (My initials are JNIP). I've replaced 'hello_world' by 'double' in the names of the three functions above. I've also altered the parameter and result descriptions so that they described double's argument and result. Unlike hello_world, double doesn't have a default value for its parameter, so I have removed that part of the parameter description. The parameter name, I've changed to 'n'.

And I've removed the capability check at the end of 'double'. I don't know how likely one would be to write some kind of arithmetic Web service in real life -- perhaps one would for a statistics add-on -- but I can't see that any security checks are needed at all. Therefore, I have also removed the context validation.

Next, I edited wstemplate/db/services.php:

// We define the Web-service functions to install.
//
$functions = array(
        'local_jnip_double' => array(
                'classname'   => 'local_jnip_external',
                'methodname'  => 'double',
                'classpath'   => 'local/jnip/externallib.php',
                'description' => 'Doubles its argument.',
                'type'        => 'read',
        )
);

// We define the services to install as pre-built services. 
// A pre-built service is not editable by the administrator.
//
$services = array(
        "Jocelyn's service" => array(
                'functions' => array ('local_jnip_double'),
                'restrictedusers' => 0,
                'enabled'=>1,
        )
);

Here, I've changed the outer key in the $functions array to local_jnip_double, and the class name, method name, class path, and description in the inner array to their new values. In the $services array, I've changed the service name and function name.

I left wstemplate/version.php and wstemplate/lang/en/local_wstemplate.php alone. Then I changed the directory name from wstemplate to jnip, so that on my Moodle's Linux host, it sat at /var/www/moodle/moodle/local/jnip/ .

To make Moodle install the plugin, I went to Site administration > Notifications. Moodle had noticed the new directory, and showed it to me in a plugins table. I pressed the Upgrade button on that page, and then Continue on the next page.

I then went to Site administration > Plugins > Web services > External services, and saw a table listing my services, including a row for "Jocelyn's service". I clicked on the Functions link -- and got an error saying that the description of the return value was corrupt. I'd mistyped it. So I corrected it in externallib.php . (The code above shows the corrected one.) Then I had to make Moodle notice this update. I remembered reading that it notices when the version number in a version.php file has been increased. So I edited wstemplate/version.php. The original version, the one I'd copied from wstemplate, looked like this:

$plugin->version  = 2011101202;   // The (date) version of this module + 2 extra digital for daily versions
                                  // This version number is displayed into /admin/forms.php
                                  // TODO: if ever this plugin get branched, the old branch number
                                  // will not be updated to the current date but just incremented. We will
                                  // need then a $plugin->release human friendly date. For the moment, we use
                                  // display this version number with userdate (dev friendly)
$plugin->requires = 2010112400;  // Requires this Moodle version - at least 2.0
$plugin->cron     = 0;
$plugin->release = '1.0 (Build: 2011101202)';
$plugin->maturity = MATURITY_STABLE;

I wasn't really sure what to do -- I've never seen a Moodle document about the correct care and feeding of version files -- but it seemed reasonable to replace both occurrences of 2011101202 by 2011101203. I then went back to Site administration > Notifications. Again, Moodle showed me the table of plugins, saying that this one needed upgrading. So it had noticed the change. I pressed Upgrade, and then Continue.

For my client, I wrote this program:

  echo "Demo of Web service doubling function using REST\n";
  echo "================================================\n";

  $token = '401eae223b346c10c3298d74itv7ddf5';
  $domain = 'http://moodle.ireson-paine.com';

  $n = 5;

  $function_name = 'local_jnip_double';

  $params = array( 'n' => $n );

  $serverurl = $domain . '/webservice/rest/server.php'. '?wstoken=' . $token . '&wsfunction='.$function_name;

  require_once( './curl.php' );
  $curl = new curl;

  echo "\nAbout to call function.\n";

  $response = $curl->post( $serverurl, $params );

  echo "\nCalled function.\n";

  echo "\nResponse = $response.\n";

The token in it, I made by going to Site administration > Plugins > Web services > Manage tokens, and adding a new token for my Web-services user and the service called "Jocelyn's service".

And running the client produced this output:

Demo of Web service doubling function using REST
================================================

About to call function.

Called function.

Response = <?xml version="1.0" encoding="UTF-8" ?>
<RESPONSE>
<VALUE>10</VALUE>
</RESPONSE>
.

I also tried an XML-RPC version of the client, like this:

  echo "Demo of Web service doubling function using XML-RPC\n";
  echo "===================================================\n";

  $token = '401eae223b346c10c3298d74itv7ddf5';
  $domain = 'http://moodle.ireson-paine.com';

  $n = 5;

  $function_name = 'local_jnip_double';

  $params = array( $n );

  $serverurl = $domain . '/webservice/xmlrpc/server.php'. '?wstoken=' . $token . '&wsfunction='.$function_name;

  require_once( './curl.php' );
  $curl = new curl;

  echo "\nAbout to call function.\n";

  $post = xmlrpc_encode_request( $function_name, $params );

  $response = xmlrpc_decode($curl->post($serverurl, $post));

  echo "\nCalled function.\n";

  echo "\nResponse = "; print_r($response); echo ".\n";

But I got an error message saying that the parameter didn't match its description. This happened also when I set $params to array( n=>$n ). I haven't worked out what I should write, but because I'm likely to keep on using REST rather than XML-RPC, I'm not worrying about it.

At General plugins (Local): Web service template, there's a local plugin that installs a new Web-service function. It's a very simple function that returns the message "Hello, Web". I've been trying this, and got it to work, but only after missing out one important step. So I thought it might be useful if I were to describe how I installed it: a checklist that others can follow if they have problems. The client that calls the function uses the XML-RPC protocol, but I'll also show a version that uses REST.

These are the steps I took:

  • I downloaded the plugin from http://moodle.org/plugins/pluginversion.php?id=401 by clicking on the orange "Download for Moodle 2.0/Moodle 2.1/Moodle 2.2" box.

  • The download arrived as a zip file. I copied it into my Moodle's local directory. Incidentally, this is /var/www/moodle/moodle/local .

  • I unzipped the zip file there. This created a subdirectory named 'wstemplate' containing client, db, externallib.php, lang, README, and version.php .

  • From there, I downloaded local/wstemplate/client/client.php and local/wstemplate/client/curl.php onto my PC.

  • On my Moodle, I went to Site administration > Notifications. This gave me a table of three plugins, the final one of which had this row:

    Web service template   /local/wstemplate   Extension   2011101202    Moodle 2010112400   To be installed .
    

    I pressed the 'Upgrade' button below the table. This brought up a page saying

     
    local_wstemplate
    Success
    

    I pressed the 'Continue' button on it. This brought up a blank Notifications page.

  • I went to Site administration > Advanced features, and checked that the "Enable web services" checkbox was ticked.

  • I went to Site administration > Plugins > Web services > Manage protocols, and checked that the checkbox for XML-RPC protocol was ticked.

  • I went to Site administration > Plugins > Web services > External services. This showed a table with the line

    My service   local_wstemplate   Functions   All users   Edit .
    

    So the service had evidently been installed. Clicking on "Functions" showed another table saying

    local_wstemplate_hello_world   Return Hello World FIRSTNAME. 
                                        Can change the text (Hello World) 
                                        sending a new text as parameter
    

    That concluded the installation instructions at the top of client.php .

  • I went to Site administration > Plugins > Web services > Manage tokens, and clicked the "Add" link. (If you've already created any tokens, this appears below a table of them.) For the user, I selected the Web-service user that I'd created according to the instructions in Site administration > Plugins > Web services > Overview when I began experimenting with Web services last week. For the service, I selected 'My Service'.

    By the way, I'm not sure whether I needed to use my Web-services user, which I'd given special capabilities, or whether any would do. The plugin contains a source file services.php in the db subdirectory, in which there's an assignment to the $services array. (The conventions are explained at the bottom of Creating a web service and a web service function.) This sets up the service so that it is not restricted to a particular user. So I may have been able to select any user when creating my token.

  • On my PC, I edited client.php to make $token the token just created. And also changed $domainname to be my Moodle's URL.

  • And I ran client.php by giving the DOS command php client.php . This displayed the message "Hello, Web".

Having got that working, I modified the client so that it used the REST protocol. (That will work with the instructions above, if in the 'Manage protocols' step, you also enable REST.) This is the source code:

  echo "Demo of Web service Hello World using REST\n";
  echo "==========================================\n";

  $token = 'bb4314dsp0mdd4f986fd4fb3ac2itv59';
  $domain = 'http://moodle.ireson-paine.com';

  $welcomemsg = 'Hello, ';

  $function_name = 'local_wstemplate_hello_world';

  $params = array( 'welcomemessage' => $welcomemsg );

  $serverurl = $domain . '/webservice/rest/server.php'. '?wstoken=' . $token . '&wsfunction='.$function_name;

  require_once( './curl.php' );
  $curl = new curl;

  echo "\nAbout to call function.\n";

  $response = $curl->post( $serverurl, $params );

  echo "\nCalled function.\n";

  echo "\nResponse = $response.\n";

The coding style is slightly different than for client.php, because I use different variable names and messages. For comparison, this is client.php rewritten in the same style:

  echo "Demo of Web service Hello World using XML-RPC\n";
  echo "=============================================\n";

  $token = 'bb4314dsp0mdd4f986fd4fb3ac2itv59';
  $domain = 'http://moodle.ireson-paine.com';

  $welcomemsg = 'Hello, ';

  $function_name = 'local_wstemplate_hello_world';

  $params = array( $welcomemsg );

  $serverurl = $domain . '/webservice/xmlrpc/server.php'. '?wstoken=' . $token . '&wsfunction='.$function_name;

  require_once( './curl.php' );
  $curl = new curl;

  echo "\nAbout to call function.\n";

  $post = xmlrpc_encode_request( $function_name, $params );

  $response = xmlrpc_decode($curl->post($serverurl, $post));

  echo "\nCalled function.\n";

  echo "\nResponse = "; print_r($response); echo ".\n";

In our Moodle, we will have a collection of courses written by experts and saved as course backups. We want new Moodle users (teachers or managers) to be able to copy these, then add their own teachers and students. We'd be doing this via Web services. But as far as I can see, there is no built-in Web service that copies a course from a backup, and nothing in OKTech's Web services either.

So I need to write my own Web service. Luckily, Rosario Carcò has written a script that restores courses from backups, available at uploaduser.php enhanced to upload also courses. From it, I see that my Web service will have to call import_backup_file_silently from /backup/lib.php. Its specification being:

* import_backup_file_silently($pathtofile,
* $destinationcourse,
* $emptyfirst=false,
* $userdata=false,
* $preferences=array())
*
* From the corresponding X-REF:
* this function will restore an entire backup.zip into the specified course
* using standard moodle backup/restore functions, but silently.
* @param string $pathtofile the absolute path to the backup file.
* @param int $destinationcourse the course id to restore to.
* @param boolean $emptyfirst whether to delete all coursedata first.
* @param boolean $userdata whether to include any userdata that may be in the backup file.
* @param array $preferences optional, 0 will be used. Can contain:
* metacourse
* logs
* course_files
* messages
So that's what I'll be trying.

Here's my latest version of my Moodle Web-services demo, which I've also posted to Example of using Web services. I've added code to parse the XML responses, so that I can extract user data and course data returned by core_user_get_users_by_id and core_course_get_courses. I'm not yet doing this as completely as I should: my code doesn't handle multiple users, and it doesn't convert strings to integers where it should. However, there's enough there for me to check that Moodle has in fact created the users and courses it thinks it has, and for me to get their details one at a time. Note that role assignment and user deletion give spurious error messages (under Moodle 2.2) as I describe at Am I calling core_role_assign_roles correctly? What do these errors mean?, and that role assignment is giving me other errors that I didn't expect, also as discussed there. I don't yet know whether these are correct.

/*
The 'curl' that this uses comes from
https://github.com/moodlehq/sample-ws-clients/blob/master/PHP-REST/curl.php .
*/


/* If true, the program displays each
   XML response returned by calling Moodle.
*/
define( "TRACING", true );


/* Returns a structure defining
   a test user whose name, password, etc. end
   in $n.
*/
function make_test_user( $n ) 
{
  $user = new stdClass();
  $user->username = 'testusername' . $n;
  $user->password = 'testpassword' . $n;
  $user->firstname = 'testfirstname' . $n;
  $user->lastname = 'testlastname' . $n;
  $user->email = 'testemail' . $n . '@moodle.com';
  $user->auth = 'manual';
  $user->idnumber = 'testidnumber' . $n;
  $user->lang = 'en';
  $user->theme = 'standard';
  $user->timezone = '0';
  $user->mailformat = 0;
  $user->description = 'Hello World!';
  $user->city = 'testcity' . $n;
  $user->country = 'uk';
  return $user;
}


/* Returns a structure defining
   a test course whose name etc. end
   in $n.

   I have set the category ID to 1.
   This works, but is almost certainly wrong.
   I need to find out what it should be.
*/
function make_test_course( $n ) 
{
  $course = new stdClass();
  $course->fullname = 'testcourse' . $n;
  $course->shortname = 'testcourse' . $n;
  $course->categoryid = 1;
  return $course;
}


/* Creates a user from a
   structure defining a user. If the
   creation succeeds, returns the 
   ID for this user. If not, throws
   an exception whose text is the XML
   returned by Moodle.
*/
function create_user( $user, $token )
{
  $users = array( $user );
  $params = array( 'users' => $users );

  $response = call_moodle( 'core_user_create_users', $params, $token );

  if ( xmlresponse_is_exception( $response ) )
    throw new Exception( $response );
  else {
    $user_id = xmlresponse_to_id( $response );
    return $user_id;
  }
}


/* Returns a user data structure containing Moodle's
   data for $user_id. It generates this by
   parsing the XML that Moodle returns. If Moodle
   thinks there is no such user, returns NULL.
*/
function get_user( $user_id, $token )
{
  $userids = array( $user_id );
  $params = array( 'userids' => $userids );

  $response = call_moodle( 'core_user_get_users_by_id', $params, $token );

  $user = xmlresponse_to_user( $response );

  if ( array_key_exists( 'id', $user ) )
    return $user;
  else
    return NULL;
  // If there is no user with this ID, Moodle
  // returns the same enclosing XML as if there were, but 
  // with no values for ID and the other fields. My
  // XML-parsing code therefore creates an object
  // with no fields, which the conditional above
  // detects.
}


/* Deletes the user with ID $user_id. 
   If the delete fails, throws
   an exception whose text is the XML
   returned by Moodle.
*/
function delete_user( $user_id, $token )
{
  $userids = array( $user_id );
  $params = array( 'userids' => $userids );

  $response = call_moodle( 'core_user_delete_users', $params, $token );

  if ( xmlresponse_is_exception( $response ) )
    throw new Exception( $response );
}


/* Assigns the role with $role_id to the user with $user_id
   in the specified context.

   At the moment, always returns an error. I don't know 
   whether this is a bug in Moodle, or a problem with my
   configuration or user or whatever.
*/
function assign_role( $user_id, $role_id, $context_id, $token )
{
  $assignment = array( 'roleid' => $role_id, 'userid' => $user_id, 'contextid' => $context_id );
  $assignments = array( $assignment );
  $params = array( 'assignments' => $assignments );

  $response = call_moodle( 'core_role_assign_roles', $params, $token );
}


/* Creates a course from a
   structure defining a course. If the
   creation succeeds, returns the 
   ID for this course. If not, throws
   an exception whose text is the XML
   returned by Moodle.
*/
function create_course( $course, $token ) 
{
  $courses = array( $course );
  $params = array( 'courses' => $courses );

  $response = call_moodle( 'core_course_create_courses', $params, $token );

  if ( xmlresponse_is_exception( $response ) )
    throw new Exception( $response );
  else {
    $course_id = xmlresponse_to_id( $response );
    return $course_id;
  }
}


/* Returns a course data structure containing Moodle's
   data for $course_id. It generates this by
   parsing the XML that Moodle returns. If Moodle
   thinks there is no such course, returns NULL.
*/
function get_course( $course_id, $token )
{
  $courseids = array( $course_id );
  $params = array( 'options' => array('ids' => $courseids ) );

  $response = call_moodle( 'core_course_get_courses', $params, $token );

  $course = xmlresponse_to_course( $response );

  if ( array_key_exists( 'id', $course ) )
    return $course;
  else
    return NULL;
  // If there is no user with this ID, Moodle
  // returns the same enclosing XML as if there were, but 
  // with no values for ID and the other fields. My
  // XML-parsing code therefore creates an object
  // with no fields, which the conditional above
  // detects.
}


/* Enrols the user into the course with the specified role.
   Does not yet check for errors.

   I haven't tested this.
*/
function enrol( $user_id, $course_id, $role_id, $token ) 
{
  $enrolment = array( 'roleid' => $role_id, 'userid' => $user_id, 'courseid' => $course_id );
  $enrolments = array( $enrolment );
  $params = array( 'enrolments' => $enrolments );

  $response = call_moodle( 'enrol_manual_enrol_users', $params, $token );
}


/* Returns data about users enrolled in the specified course.

   Not sure what Moodle returns yet, so exactly how
   I should parse it. Does not handle multiple users.
*/
function get_enrolled_users( $course_id, $token ) 
{
  $params = array( 'courseid' => $course_id );

  $response = call_moodle( 'core_enrol_get_enrolled_users', $params, $token );

  $user = xmlresponse_to_user( $response );
  return $user;
}


/* Calls the Moodle at http://moodle.mazegreenyachts.com, invoking the specified
   function on $params. Also takes a token. 
   Returns Moodle's response as a string containing XML.
*/ 
function call_moodle( $function_name, $params, $token )
{
  $domain = 'http://moodle.mazegreenyachts.com';

  $serverurl = $domain . '/webservice/rest/server.php'. '?wstoken=' . $token . '&wsfunction='.$function_name;

  require_once( './curl.php' );
  $curl = new curl;

  $response = $curl->post( $serverurl . $restformat, $params );

  if ( TRACING ) 
    echo "Response from $function_name: \n", $response, "\n";

  return $response;
}


/* Given a string containing XML returned
   by a successful user creation or course
   creation, parses it and returns the user or course ID
   as an integer.
   Undefined if the XML does not contain such an ID,
   for example if it's an error response.
*/
function xmlresponse_to_id( $xml_string )
{
  $xml_tree = new SimpleXMLElement( $xml_string );          

  $value = $xml_tree->MULTIPLE->SINGLE->KEY->VALUE;
  $id = intval( sprintf( "%s", $value ) );
  // See discussion on http://php.net/manual/es/book.simplexml.php ,
  // especially the posting for "info at kevinbelanger dot com 20-Jan-2011 05:07".
  // There is a bug in the XML parser whereby it doesn't return the
  // text associated with property [0] of a node. The above
  // posting uses sprintf to force a conversion to string.

  return $id;
}  


/* Given a string containing XML returned
   by a successful call to core_user_get_users_by_id,
   parses it and returns the data as a user
   data structure.
   Undefined if the XML does not contain such an ID,
   for example if it's an error response.

   Does not yet handle fields with multiple values.
   I think these are customfields, preferences,
   and enrolledcourses.
*/
function xmlresponse_to_user( $xml_string )
{
  return xmlresponse_parse_names_and_values( $xml_string );
}


/* Given a string containing XML returned
   by a successful call to core_course_get_courses,
   parses it and returns the data as a course
   data structure.
   Undefined if the XML does not contain such an ID,
   for example if it's an error response.

   Does not yet handle fields with multiple values.
*/
function xmlresponse_to_course( $xml_string )
{
  return xmlresponse_parse_names_and_values( $xml_string );
}


/* This parses a string containing the XML returned by
   functions such as core_course_get_courses,
   core_user_get_users_by_id, or core_enrol_get_enrolled_users.
   These strings contain name-value pairs encoded thus:
     <RESPONSE>
     <MULTIPLE>
     <SINGLE>
     <KEY name="id"><VALUE>169</VALUE>
     </KEY>
     <KEY name="username"><VALUE>testusername32</VALUE>
     </KEY>
     </SINGLE>
     </MULTIPLE>
     </RESPONSE>
   The function returns an object with the corresponding
   keys and values.

   Does not yet convert strings to integers where they
   ought to be converted.
*/
function xmlresponse_parse_names_and_values( $xml_string )
{
  $xml_tree = new SimpleXMLElement( $xml_string ); 

  $struct = new StdClass();

  foreach ( $xml_tree->MULTIPLE->SINGLE->KEY as $key ) {
    $name = $key['name'];
    $value = (string)$key->VALUE;
    $struct->$name = $value;
  }

  return $struct;
}


/* True if $xml_string's top-level is
   <EXCEPTION>. I use this to check for error
   responses from Moodle.
*/
function xmlresponse_is_exception( $xml_string )
{
  $xml_tree = new SimpleXMLElement( $xml_string );          

  $is_exception = $xml_tree->getName() == 'EXCEPTION';
  return $is_exception;
}  


/* These are some role IDs from our Moodle,
   obtained by querying the database with
   the command
     select * from mdl_role;
   Hopefully, Moodle won't change them.
   There are a few other roles, but not ones
   I think we need.
*/
define( "MANAGER_ROLE_ID", 1 );
define( "COURSE_CREATOR_ROLE_ID", 2 );
define( "TEACHER_ROLE_ID", 3 );
define( "NON_EDITING_TEACHER_ROLE_ID", 4 );
define( "STUDENT_ROLE_ID", 5 );
define( "GUEST_ROLE_ID", 6 );
define( "AUTHENTICATED_USER_ROLE_ID", 7 );
define( "AUTHENTICATED_USER_ON_FRONTPAGE_ROLE_ID", 8 );


/* These are some context IDs from our Moodle,
   obtained by querying the database with
   the command
     select * from mdl_context;
   and by reading http://moodle.org/mod/forum/discuss.php?d=60125 ,
   "Roles and contexts in Moodle 1.7".
   Hopefully, Moodle won't change them.
   There are other contexts, but not ones
   I think we need.
*/
define( "SYSTEM_CONTEXT_ID", 1 );


function demo()
{
  try {
    echo "Demo of Moodle Web services\n";
    echo "===========================\n";

    $token = '9bbdcddccb00480ce6c87fa8358c4a54';
    echo "\nUses this token which I created manually: " . $token . "\n"; 

    echo "\nWill now create two users from the following data:\n";
    $user_suffix = 38;
    $user_data_1 = make_test_user( $user_suffix );
    print_r( $user_data_1 );
    echo "\n";

    $user_data_2 = make_test_user( $user_suffix+1 );
    print_r( $user_data_2 ); 
    echo "\n";

    $user_id_1 = create_user( $user_data_1, $token );
    if ( is_null( get_user( $user_id_1, $token ) ) )
      echo "\nUser 1 doesn't seem to have been created\n";
    else
      echo "\nUser 1's ID = " . $user_id_1 . "\n";

    $user_id_2 = create_user( $user_data_2, $token );
    if ( is_null( get_user( $user_id_2, $token ) ) )
      echo "\nUser 2 doesn't seem to have been created\n";
    else
      echo "\nUser 2's ID = " . $user_id_2 . "\n";

    echo "\nWill now assign a role to make user 1 a student:\n";
    
    assign_role( $user_id_1, STUDENT_ROLE_ID, SYSTEM_CONTEXT_ID, $token );

    echo "\nWill now get the user details using user 1's ID:\n";
    $user_data_from_moodle_1 = get_user( $user_id_1, $token );
    echo "\nUser details:\n";
    print_r( $user_data_from_moodle_1 );

    echo "\nFor comparison, will now get details for a non-existent user:\n";
    $user_data_from_moodle_3 = get_user( 9999, $token );
    echo "\nUser details:\n";
    print_r( $user_data_from_moodle_3 );

    echo "\nWill now create a course from the following data:\n";
    $course_suffix = 127;
    $course_data = make_test_course( $course_suffix );
    print_r( $course_data ); 
    echo "\n";

    $course_id = create_course( $course_data, $token );
    if ( is_null( get_course( $course_id, $token ) ) )
      echo "\nCourse doesn't seem to have been created\n";
    else
      echo "\nCourse ID = " . $course_id . "\n";

    echo "\nWill now get the course details using that ID:\n";
    $course_data_from_moodle = get_course( $course_id, $token );
    echo "\nCourse details:\n";
    print_r( $course_data_from_moodle );

    echo "\nFor comparison, will now get details for a non-existent course:\n";
    $course_data_from_moodle_3 = get_course( 9999, $token );
    echo "\nCourse details:\n";
    print_r( $course_data_from_moodle_3 );

    echo "\nWill now enrol users 1 and 2:\n";
    $role_id = STUDENT_ROLE_ID;
    enrol( $user_id_1, $course_id, $role_id, $token );
    enrol( $user_id_2, $course_id, $role_id, $token );

    echo "\nWill now get the IDs of who Moodle thinks is enrolled:\n";
    $users_in_course = get_enrolled_users( $course_id, $token );
    echo "\nUser details:\n";
    print_r( $users_in_course );

    echo "\nWill now delete the users:\n";
    delete_user( $user_id_1, $token );
    if ( ! is_null( get_user( $user_id_1, $token ) ) )
      echo "\nUser 1 doesn't seem to have been deleted\n";
    else
      echo "\nUser 1 deleted\n";

    delete_user( $user_id_2, $token );
    if ( ! is_null( get_user( $user_id_2, $token ) ) )
      echo "\nUser 2 doesn't seem to have been deleted\n";
    else
      echo "\nUser 2 deleted\n";
  } 
  catch ( Exception $e ) {
    echo "\nCaught exception:\n" .  $e->getMessage() . "\n";
  }
}


demo();

I've just posted my first experiment with Moodle Web services to Example of using Web services. It's a PHP program that I've written while teaching myself about Web services. It contains functions that encapsulate adding a user, getting user details from an ID, deleting a user, assigning a rôle, creating a course, and enroling a user in a course. The end of the program calls these one by one, displaying its progress as it goes. I use XML, and call the PHP SimpleXml parser to distinguish error responses, and to extract the ID from newly created users and courses.

Here's a portfolio of my cartoons in Word, with explanations of the technical ones. I gave out copies at the East Oxford Drawing Collective's exhibition in the Said Business School.

V.I.P Briggs

| No Comments | No TrackBacks

Cartoon of lady wearing 1980s-style power suit and a badge saying 'VIP'. She carries a huge key-ring, and is wagging a finger and saying 'You've been very naughty. I'm going to have to SMACK you.' The cartoon is captioned 'V.I.P. Briggs, Senior Manager.'

A caricature I drew of an acquaintance's wife. She's a social worker.

Cartoon of knights finding Holy Grail in Summertown Scope shop. The grail gleams at the end of a shelf, next to a coffee cup, a travel clock, a photo of a flower, a straw donkey souvenir of España, and a china cat. The knights converse as follows. A: 'The Holy Grail! We've found it!' B: '£9.99 is a lot of money.' C: 'Cheaper than that one in Oxfam.' D: 'Wonder who gave it?' E:'They do say Summertown is rich.' F (sniffing disdainfully): 'Rich beyond the dreams of avarice.'

I drew this for a charity card sale. Selling my cards for Scope in Summertown, and giving them half the profit.

BB Sheep One

| No Comments | No TrackBacks

Cartoon with three frames. First frame shows a sheep standing on a hillside, with wind turbines whirring away in the background. The sheep looks a bit angry and has a thought bubble saying 'Bored'. Second frame shows the sheep with a thought bubble enclosing a light-bulb with the word 'IDEA!' nearby. Third frame shows the sheep contentedly chewing grass and watching a television with a power cable running to one of the turbines. The television screen says 'BB Sheep One'.

A birthday card I drew for a friend who likes wind turbines.

Tommy Guthrie

| No Comments | No TrackBacks

Roughish caricature of guitarist in front of microphone, with drum displaying 'The Tommy Guthrie One-Man Band'. The guitarist is singing 'I heard a siren / Coming from the docks / Saw a train / Set the night on fire.'

Quick caricature of busker in Cornmarket. I originally used these words, from Hobo's Lullaby:

I know the police cause you trouble
They cause trouble everywhere
But when you die and go to Heaven
You'll find no policemen there.
but he thought that might be provocative.