Using Moodle Web ServicesWhat is Moodle?No, it's not Mudilo, not even related to $mudilo - the variable my brother programmer used for objects in his code... well, until his boss once asked what the name meant... pronouncing it in a rather fancy way...Moodle is web-based software written in PHP that provides online courses. See moodle.org. For example, one could create a course about something, and then people from all over may learn about it. I was given an assignment to research how to create Moodle users programmatically from a PHP program. Additionally, my client wanted an ability to enroll users in courses. This manual describes how to do it. If You Need Help with MoodleI do occasional work via doitcontractors. If you need something done consider posting a project there. If you are a coder, you can also register and maybe get an occasional project to work on.VersionsHere are the versions of things I experimented with.
Preliminary NotesI had to go through many steps before things started to work. Also, based on my personal experience, Moodle web services API tends to change from version to version as they re-write stuff. IMHO, this API needs re-work anyway, so it is normal to expect things to be different as time flies. I hope someone will find the troubleshooting techniques described below useful.Briefly, all you need to do to make use of Moodle web services API is just two things:
Configuring MoodleThe following were the steps I needed to do to make my code examples working.Install Moodle ServerIn this step we install Moodle 2.4.3. I did it on a Windows 7, with already installed Apache 2.2 and PHP 5.4.11. Installation process looked like this:
Documentation for Web ServicesAt the time of this writing, web services documentation for my version of Moodle was at http://docs.moodle.org/24/en/Web_servicesEnable Web Services
Create a Web Service
![]() Adding functions to a web service On the picture above, notice the location of the page in the Site administration tree, see the 6 functions added to the service and pay attention to the Required capabilities column - these are access rights required to execute the corresponding functions. Create a User Account to Use Web ServicesGo to Home - Site administration - Users - Accounts - Browse list of users. Click the Add a new user link. Create a user, for example, web_service_user with all default other values.![]() Creating a user account to access the web service Create a New System RoleGo to Home - Site Administration - Users - Permissions - Define roles and create a new role, for example Web Services Users.![]() Creating a new system role in Moodle Make sure the role can be assigned in the "System" context by editing the role and marking the "System" checkbox in "Context types where this role can be assigned". Assign Capabilities to the RoleNow we need to assign capabilities to this role. It is critical for this role to have all the required capabilities in order to execute the functions above. When a capability is missing, Moodle generates and catches exceptions internally, leaving you in the dark about what may be wrong, see debugging section below. Here is a list of capabilities we need:
Allow to Assign the Student RoleThere is an additional step that is required for the enroll function to work. We need to allow Web Services Users to assign the Student role to accounts. This is done on the Allow role assignments tab in Site administration - Users - Permissions - Define Roles.![]() Allow Web Services Users to assign the Student role Make sure that the Student checkbox is checked for Web Services Users role. By now we defined a system role that is capable to execute our functions. Assign User to the RoleAssign web_service_user account to the Web Services Users role by going to Home - Site administration - Users - Permissions - Assign system roles. Click on the Web Services Users and add the web_service_user to the role on the next page. This makes web_service_user to have the capabilities assigned to the Web Services Users role in the previous steps. By now we have a Moodle user account with the capabilities necessary to execute our functions.Create a TokenWe'll need a token for programmatic access to the web service we created. Go to Home - Site administration - Plugins - Web services - Manage tokens. Click Add. Select a user and a service and generate token.![]() Create a token to access the web service programmatically SummaryBy this point we have:
Debugging and TroubleshootingAs you can see from the above description, the setup procedure is quite demanding. However, in addition to a potential configuration issue (in case you did everything right with Moodle setup, as described above, with adjustments for your version), many other points of failure are also possible, here are a few possibilities:
![]() Observing XML that hits Moodle server However, if you use xmlrpc_encode_request as I do, you can also see the XML right in PHP client by doing var_dump:
// Create XML for Moodle.
$request = xmlrpc_encode_request('core_user_get_users_by_id', array(array((string) $user_id)), array('encoding'=>'UTF-8'));
var_dump($request); // In case you want to see XML.
ob_start(); print_r($sigParams); $contents = ob_get_contents(); ob_end_clean(); error_log($contents); Solving ProblemsHere are three problems that were most challenging for me in the scope of this project, and how I solved them.1) My previous code samples used PEAR XML_RPC package to do XML-RPC communications. Things kind of worked but not always. In the end and after a long struggle, I dumped the content of replies that Moodle sends back to error.log and saw this: <?xml version="1.0" encoding="UTF-8"?>\n
<methodResponse>
<params>
<param>
<value><nil/></value>
</param>
</params>
</methodResponse>
See the <nil/> inside the <value> element. I turned out that the XML_RPC package on the client could not decode such elements. The solution was to abandon XML_RPC package and use the experimental xmlrpc functions that are a part of PHP. 2) The second, and probably the most important problem, was to figure out how to construct parameters in XML for each function call. They need to be built in a certain fashion. For me, the difficult function was enrol_manual_enrol_users, as I could not figure it out, and the docs as well as code seemed conflicting to me. So, I narrowed the problem down to a place where Moodle compares a signature for a function with the actual structure received. Dumped the (beginnings of...) signatures and saw some differences. Finally, I figured out that the parameters need to be like:
$params = array(array(array('roleid'=>'5', 'userid'=>'42', 'courseid'=>'2')));
$request = xmlrpc_encode_request('enrol_manual_enrol_users', $params, array('encoding'=>'UTF-8'));
var_dump($request); // In case you want to see XML.
3) Missing capabilities for the web service account. When this happens, Moodle generates exceptions. You can localize them in web server logs and then use the technique above to find out the exact reason. Well, there is a thing that may save you a lot of time: in Moodle look at the Home - Site administration - Plugins - Web services - Manage tokens page. If any capabilities are missing for your functions you will see them there. Code Samples with Moodle ClassI wrote a class called Moodle that you can instantiate and call. Something like this:
$token = 'cface2c6d6e7888f5e33485008dbda88';
$server = 'http://localhost:8080';
$dir = '/moodle'; // May be null if moodle runs in the root directory in the server.
// To do things with Moodle, we create a new Moodle class, initialize it, and then call its functions.
$moodle = new Moodle();
// Initialize the class.
$fields = array('token'=>$token, 'server'=>$server, 'dir'=>$dir);
$moodle->init($fields);
// Usage examples.
// Normally, a function returns something useful, such as an array of user properties, or TRUE, on success.
// When something happens the return is FALSE, and the last error string is in $moodle->error string.
// A lot of things need to be done in Moodle for web services API to work properly. See readme in this project.
// Get user information.
$id = 1; // User id in Moodle. 1 for guest user.
$user = $moodle->getUser($id);
if ($user)
var_dump($user); // Success, normal result.
else
var_dump($moodle->error); // Error.
The following functions are supported by the class at the moment:
Code for the Moodle Class
<?php
class Moodle {
var $token = null; // Token to access Moodle server. Must be configured in Moodle. See readme.
var $server = null; // Moodle URL, for example http://localhost:8080.
var $dir = null; // Directory on the server. For example, /moodle. If your moodle runs as root, this is empty.
var $error = ''; // Last error of the class. We'll write the last error here when something wrong happens.
// The init function initializes the variable of class (so that it can be used).
function init($fields) {
$this->token = $fields['token'];
$this->server = $fields['server'];
$this->dir = $fields['dir'];
}
// The getUser function obtains information for a Moodle user identified by its id.
function getUser($user_id) {
// Clear last error.
$this->error = null;
// Create XML for the request. XML must be set properly for this to work.
$request = xmlrpc_encode_request('core_user_get_users_by_id', array(array((string) $user_id)), array('encoding'=>'UTF-8'));
// var_dump($request); // In case you want to see XML.
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$path = $this->server.$this->dir."/webservice/xmlrpc/server.php?wstoken=".$this->token;
// Send XML to server and get a reply from it.
$file = file_get_contents($path, false, $context); // $file is the reply from server.
// Decode the reply.
$response = xmlrpc_decode($file);
// Note: lack of permissions on Moodle will get us an XML-formatted response with NULL values.
// In other words, one must be absolutely sure to give all the required capabilities to web services account
// in order to execute this function successfully. Moodle says that we need the following:
// moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update
// for core_user_get_users_by_id call.
// Handle errors.
if (!is_array($response) || !is_array($response[0]) || !array_key_exists('id', $response[0])) {
// We have an error.
if ($response[faultCode])
$this->error = 'Moodle error: ' . $response[faultString] . ". Fault code: ".$response[faultCode]. ".";
else
$this->error = 'Moodle returned no info. Check if user id exists and whether the web service
account has capabilities required to execute core_user_get_users_by_id call.';
$this->error .= " Actual reply from server: ".$file;
return false;
}
// This is our normal exit (returning an array of user properties).
$user = $response[0];
return $user;
}
// The createUser function tries to create a new Moodle user.
function createUser($fields) {
// Clear last error.
$this->error = null;
// Construct user fields array.
$userFields = array();
if (isset($fields['username'])) $userFields['username'] = $fields['username'];
if (isset($fields['password'])) $userFields['password'] = $fields['password'];
if (isset($fields['firstname'])) $userFields['firstname'] = $fields['firstname'];
if (isset($fields['lastname'])) $userFields['lastname'] = $fields['lastname'];
if (isset($fields['email'])) $userFields['email'] = $fields['email'];
if (isset($fields['city'])) $userFields['city'] = $fields['city'];
if (isset($fields['country'])) $userFields['country'] = $fields['country'];
// if (isset($fields['auth'])) $userFields['auth'] = $fields['auth'];
if (isset($fields['preferences'])) $userFields['preferences'] = $fields['preferences'];
// Create XML for the request. XML must be set properly for this to work.
$request = xmlrpc_encode_request('core_user_create_users', array(array($userFields)), array('encoding'=>'UTF-8'));
// var_dump($request); // In case you want to see XML.
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$path = $this->server.$this->dir."/webservice/xmlrpc/server.php?wstoken=".$this->token;
// Send XML to server and get a reply from it.
$file = file_get_contents($path, false, $context); // $file is the reply from server.
// Decode the reply.
$response = xmlrpc_decode($file);
// Note: lack of permissions on Moodle will get us an error.
// moodle/user:create capability is required for web service account to call core_user_create_users.
// Handle errors.
if (!is_array($response) || !is_array($response[0]) || !array_key_exists('id', $response[0])) {
// We have an error.
if ($response[faultCode])
$this->error = 'Moodle error: ' . $response[faultString] . ". Fault code: ".$response[faultCode]. ".";
else
$this->error = 'Moodle returned no info. Check if Moodle is set up properly (see readme).';
$this->error .= " Actual reply from server: ".$file;
return false;
}
// This is our normal exit. Returning a 2-member array with new user id and username.
$user = $response[0];
return $user;
}
// The createUser function tries to update an existing Moodle user.
function updateUser($fields) {
// Clear last error.
$this->error = null;
// Check if user exists.
$user = $this->getUser($fields['id']);
if (!$user)
return false;
// Construct user fields array.
$userFields = array();
if (isset($fields['id'])) $userFields['id'] = $fields['id'];
if (isset($fields['username'])) $userFields['username'] = $fields['username'];
if (isset($fields['password'])) $userFields['password'] = $fields['password'];
if (isset($fields['firstname'])) $userFields['firstname'] = $fields['firstname'];
if (isset($fields['lastname'])) $userFields['lastname'] = $fields['lastname'];
if (isset($fields['email'])) $userFields['email'] = $fields['email'];
if (isset($fields['city'])) $userFields['city'] = $fields['city'];
if (isset($fields['country'])) $userFields['country'] = $fields['country'];
if (isset($fields['auth'])) $userFields['auth'] = $fields['auth'];
if (isset($fields['preferences'])) $userFields['preferences'] = $fields['preferences'];
// Create XML for the request. XML must be set properly for this to work.
$request = xmlrpc_encode_request('core_user_update_users', array(array($userFields)), array('encoding'=>'UTF-8'));
// var_dump($request); // In case you want to see XML.
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$path = $this->server.$this->dir."/webservice/xmlrpc/server.php?wstoken=".$this->token;
// Send XML to server and get a reply from it.
$file = file_get_contents($path, false, $context); // $file is the reply from server.
// Decode the reply.
$response = xmlrpc_decode($file);
// Note: lack of permissions on Moodle will get us an error.
// moodle/user:update capability is required for web service account to call core_user_update_users.
if ($response && xmlrpc_is_fault($response)) {
$this->error = 'Moodle error: ' . $response[faultString] . ". Fault code: ".$response[faultCode]. ".";
$this->error .= " Actual reply from server: ".$file;
return false;
}
// This is our normal exit after a successful update.
return true;
}
// The deleteUser function tries to delete an existing Moodle user.
function deleteUser($user_id) {
// Clear last error.
$this->error = null;
// Check if user exists.
$user = $this->getUser($user_id);
if (!$user)
return false;
// Create XML for the request. XML must be set properly for this to work.
$request = xmlrpc_encode_request('core_user_delete_users', array(array((string) $user_id)), array('encoding' => 'UTF-8'));
// var_dump($request); // In case you want to see XML.
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$path = $this->server.$this->dir."/webservice/xmlrpc/server.php?wstoken=".$this->token;
// Send XML to server and get a reply from it.
$file = file_get_contents($path, false, $context); // $file is the reply from server.
// Decode the reply.
$response = xmlrpc_decode($file);
if ($response && xmlrpc_is_fault($response)) {
$this->error = "Moodle error: " . $response[faultString]." Fault code: " . $response[faultCode];
return false;
}
// This is our normal exit after a successful delete.
return true;
}
// The getCourse function obtains information for a Moodle course identified by its id.
function getCourse($id) {
// Clear last error.
$this->error = null;
// Create XML for the request. XML must be set properly for this to work.
$courseids = array( $id );
// $params = array('options'=>array('ids'=>$courseids)); // This does not work, gets us an exception inside Moodle.
$params = array(array('ids'=>$courseids)); // This works.
$request = xmlrpc_encode_request('core_course_get_courses', $params, array('encoding'=>'UTF-8'));
// var_dump($request); // In case you want to see XML.
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$path = $this->server.$this->dir."/webservice/xmlrpc/server.php?wstoken=".$this->token;
// Send XML to server and get a reply from it.
$file = file_get_contents($path, false, $context); // $file is the reply from server.
// Decode the reply.
$response = xmlrpc_decode($file);
// Note: lack of permissions on Moodle will get us an error.
// Required capabilities for core_course_get_courses call:
// moodle/course:view,moodle/course:update,moodle/course:viewhiddencourses
// Make sure that your web service account role has those.
// Handle errors.
if (!is_array($response) || !is_array($response[0]) || !array_key_exists('id', $response[0])) {
// We have an error.
if ($response[faultCode])
$this->error = 'Moodle error: ' . $response[faultString] . ". Fault code: ".$response[faultCode]. ".";
else
$this->error = "Moodle returned no info. Check if course id exists and whether the web service
account has capabilities required to execute core_course_get_courses call.";
$this->error .= " Actual reply from server: ".$file;
return false;
}
// This is our normal exit (returning an array of course properties).
$course = $response[0];
return $course;
}
// The enrollUser function tries to enroll user in a course.
function enrollUser($user_id, $course_id) {
// Clear last error.
$this->error = null;
// Check whether user exists.
$user = $this->getUser($user_id);
if (!$user)
return false;
// Here, you may wish to check $user['enrolledcourses'] to see if a user is already enrolled in a course.
// Check whether course exists.
$course = $this->getCourse($course_id);
if (!$course)
return false;
// Create XML for the request. XML must be set properly for this to work. This format was hard to figure out.
// I needed to debug the server code so see why method signatures did not match.
$params = array(array(array('roleid'=>'5', 'userid'=>$user_id, 'courseid'=>$course_id))); // roleid 5 is "student".
$request = xmlrpc_encode_request('enrol_manual_enrol_users', $params, array('encoding'=>'UTF-8'));
// var_dump($request); // In case you want to see XML.
$context = stream_context_create(array('http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)));
$path = $this->server.$this->dir."/webservice/xmlrpc/server.php?wstoken=".$this->token;
// Send XML to server and get a reply from it.
$file = file_get_contents($path, false, $context); // $file is the reply from server.
// Decode the reply.
$response = xmlrpc_decode($file);
// enrol/manual:enrol capability is required for the web services account.
// Also, the account must be abble to assign the "Student" role - this is configured in
// Site administration - Users - Permissions - Define roles - Allow role assignments (make sure that the "Student" role
// is checked for Web Services Users category (this is my custom role for web services account).
if ($response && xmlrpc_is_fault($response)) {
$this->error = "Moodle error: " . $response[faultString]." Fault code: " . $response[faultCode];
return false;
}
// Here, you may wish to check $user['enrolledcourses'] to see if a user gor enrolled, just to be safe.
// $user = $this->getUser($user_id);
// This is our normal exit after a successful enrollment.
return true;
}
}
?>
Below are some code samples that show how to use the Moodle class. Do not forget to initialize the class first. Code Example: Creating a New Moodle User
$fields = array(
'username' => 'test',
'password' => '_aA1234567_',
'firstname' => 'Test',
'lastname' => 'via Moodle class',
'email' => 'test@example.com',
'city' => 'Hope, BC',
'country' => 'CA',
'preferences' => array(array('type' => 'auth_forcepasswordchange', 'value' => true)), // This forces user to change password on first login.
);
$user = $moodle->createUser($fields);
if ($user)
var_dump($user); // Normal result contains a 2-member array with new user id and username.
else
var_dump($moodle->error); // Error.
Code Example: Update and Existing Moodle User$fields = array( 'id' => '42', 'lastname' => 'via Moodle class, Updated!', ); $result = $moodle->updateUser($fields); if ($result) var_dump($result); // Normal result. else var_dump($moodle->error); // Error. Code Example: Set User for LDAP AuthenticationYou may want to set Moodle users to authenticate against an LDAP server. One would normally try the core_user_create_user API with "auth" parameter set to "ldap". What appears to be happening in this case (tried with Moodle 2.4.3) - is that Moodle tries to set a provided password on LDAP server, while your environment may not allow for it. A workaround is to create user first as above, and then update its authentication method as below.$fields = array( 'id' => '42', 'auth' => 'ldap', ); $result = $moodle->updateUser($fields); if ($result) var_dump($result); // Normal result. else var_dump($moodle->error); // Error. Code Example: Delete a Moodle User// Delete user. $id = 1222; // User id in Moodle. $deleted = $moodle->deleteUser($id); if ($deleted) var_dump($deleted); // Normal result. We have deleted a user. else var_dump($moodle->error); // Error. Code Example: Get Course Information// Get course information. $id = 2; // Course id in Moodle. $course = $moodle->getCourse($id); if ($course) var_dump($course); // Success, normal result. else var_dump($moodle->error); // Error. Code Example: Enroll User in a Course// Enroll a user in a course. $user_id = 42; $course_id = 2; $enrolled = $moodle->enrollUser($user_id, $course_id); if ($enrolled) var_dump($enrolled); // Success, normal result. else var_dump($moodle->error); // Error. |