Web services

Example of using Web services

 
Picture of Jocelyn Ireson-Paine
Example of using Web services
 

I thought people might like to see the code below. 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.

/*
A demonstration of Moodle 2 Web services.
Written by Jocelyn Ireson-Paine,
http://www.j-paine.org .

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


/* 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( 'moodle_user_create_users', $params, $token );

  print "Response from moodle_user_create_users: \n";
  print_r( $response );

  if ( xml_is_exception( $response ) )
    throw new Exception( $response );
  else {
    $user_id = success_xml_to_id( $response );
    return $user_id;
  }
}


/* Returns the XML string containing Moodle's
   data for user ID $id. Does not
   check for an error response, because if there
   is no such user, Moodle seems just to
   return a piece of XML containing no user
   data.
*/
function get_user( $user_id, $token )
{
  $userids = array( $user_id );
  $params = array( 'userids' => $userids );

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

  print "Response from moodle_user_get_users_by_id: \n";
  print_r( $response );

  return $response;
}


/* 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( 'moodle_user_delete_users', $params, $token );

  print "Response from moodle_user_delete_users: \n";
  print_r( $response );

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


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

   I haven't tested this, because I can't find a function
   for converting the roles we have to role IDs.
*/
function assign_role( $role_id, $user_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( 'moodle_role_assign', $params, $token );

  print "Response from moodle_role_assign: \n";
  print_r( $response );
}


/* 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( 'moodle_course_create_courses', $params, $token );

  print "Response from moodle_course_create_courses: \n";
  print_r( $response );

  if ( xml_is_exception( $response ) )
    throw new Exception( $response );
  else {
    $course_id = success_xml_to_id( $response );
    return $course_id;
  }
}


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

   I haven't tested this, because our Moodle is
   missing moodle_enrol_manual_enrol_users.
*/
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( 'moodle_enrol_manual_enrol_users', $params, $token );

  print "Response from moodle_enrol_manual_enrol_users: \n";
  print_r( $response );
}


/* Calls the Moodle at http://moodle.jocelyn_ireson-paine.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.jocelyn_ireson-paine.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 );
  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.
   Undefined if the XML does not contain such an ID,
   for example if it's an error response.
*/
function success_xml_to_id( $xml_string )
{
  $xml_tree = new SimpleXMLElement( $xml_string );

  $value = $xml_tree->MULTIPLE->SINGLE->KEY->VALUE;
  $id = 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;
}


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

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


function demo()
{
  try {

    print "Demo of Moodle Web services\n";
    print "===========================\n";

    $token = '0ea793fubf08r68db36fe8f632304x20';
    print "\nUses this token which I created manually: " . $token . "\n";

    print "\nWill now create a user from this data:\n";
    $user_suffix = 100;
    $user_data = make_test_user( $user_suffix );
    print_r( $user_data );
    print "\n";

    $user_id = create_user( $user_data, $token );
    print "\nUser's ID = " . $user_id . "\n";

    print "\nWill now get the user details using that ID:\n";
    $xml_user_data = get_user( $user_id, $token );
    print "\nUser details:\n";
    print $xml_user_data;

    print "\nWill now create a course from this data:\n";
    $course_suffix = 100;
    $course_data = make_test_course( $course_suffix );
    print_r( $course_data );
    print "\n";

    $course_id = create_course( $course_data, $token );
    print "\nCourse ID = " . $course_id . "\n";

    print "\nWill now enrol the user:\n";
    $role_id = 1;
               // I don't know how to get the IDs for the roles we actually have.
    enrol( $user_id, $course_id, $role_id, $token );

    print "\nWill now delete the user:\n";
    delete_user( $user_id, $token );

  }
  catch ( Exception $e ) {
    print "\nCaught exception:\n" . $e->getMessage() . "\n";
  }
}


demo();

 
Average of ratings: Useful (1)
Picture of Jocelyn Ireson-Paine
Re: Example of using Web services
 

Here's my latest version of this program. 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 http://moodle.org/mod/forum/discuss.php?d=193402#p842339, 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://ireson-paine.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://ireson-paine.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 = '9_anchovy_and_walnut_flan_4a54';
    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();
 
Average of ratings: Useful (2)
Picture of amit janghu
Re: Example of using Web services
 

Great work Jocelyn !! I really appreciate this but I think adding all the remaining core web-services functions and bit modification can develop this as a rest client plugin. 

 
Average of ratings: -
Picture of pratik shrstha
Re: Example of using Web services
 

thanx for all these things....its helping a lot but i m nt figuring out one thing is.....

 

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

what should be the value of $restformat here?? and while calling post function it takes 3 parameters.

Could you please help me

 
Average of ratings: -
Picture of Jocelyn Ireson-Paine
Re: Example of using Web services
 

I've just realised, when replying to someone's question about my postings, that I didn't explain where the value of the $token variable came from. The values in my demo are made up, and will not work. Anyone using my code will need to make their own values. I don't have access to Moodle at the moment, but I seem to remember that the way to do this is as described in section "Create a token" of "Using web services". The steps it describes will create and display a new token, whose value you can then use in the code.

Jocelyn Ireson-Paine

 
Average of ratings: -