It took me quite a while to figure our how to troubleshoot web services. I hope this summary will help someone to fix their web service related issues. Sorry for a long post.
My project was: investigate how to create Moodle users and enroll them in courses programmatically via its web services API. This procedure describes how to configure Moodle to use web services API and troubleshooting techniques I used to resolve problems.
Moodle 2.4.3, running on Windows 7 with PHP 5.4.11, MySQL 5.5.27.
For me, there were many steps to get through before things started to work. Also, from my experience, Moodle web-service related stuff tends to change from version to version. For example: methods get changed and it may be difficult to construct function parameters correctly. What it means is that the code that works now will break when things change. You may use the troubleshooting techniques described below to adjust things when necessary.
* Installed Moodle 2.4.3 using php 5.4.11. Installation process:
** Download and deploy code on web server.
** Create database, grant access permissions to the database.
** Access the site via web browser and follow a lengthy process of Moodle install.
* Configuring Web Services.
** Go to Home - Site administration - Advanced features. Mark the "Enable web services" option. Click "Save changes" on the bottom.
** Go to Home - Site administration - Plugins - Web services - Manage protocols. Enable the XML-RPC protocol (by clicking on the marker in the "Enable" column). Click "Save changes" on the bottom.
* Create a web service.
** Go to Home - Site administration - Plugins - Web services - External services. Click "Add". Specify a name (for example, nik_test) and mark the "Enabled" box.
** Add functions to just created web service. Go to Home - Site administration - Plugins - Web services - External services - Functions. Click "Add functions". I needed to add the following functions:
*** core_user_get_users_by_id - to get information about users.
*** core_user_create_users - to create users.
*** core_user_update_users - to update users.
*** core_user_delete_users - to update users.
*** core_course_get_courses - to get information about courses.
*** enrol_manual_enrol_users - to enroll users in courses.
* Create a user to access web services by going 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.
* Go to Home - Site Administration - Users - Permissions - Define roles and create a new role, for example "Web Services Users".
* Now 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:
** webservice/xmlrpc:use - or "Use XML-RPC protocol" - required to process XML-RPC communications.
** moodle/user:viewdetails - or "View user profiles" - required to view user info.
** moodle/user:viewhiddendetails - or "View hidden details of users" - required to view user info.
** moodle/course:useremail - or "Enable/disable email address" - required to view user info.
** moodle/user:create - or "Create users" - required to create user accounts.
** moodle/user:delete - or "Delete users" - required to delete user accounts.
** moodle/user:update - or "Update user profiles" - required to update user accounts.
** moodle/course:view - or "View courses without participation" - required to get course info.
** moodle/course:viewhiddencourses - or "View hidden courses" - required to get course info.
** moodle/course:update - or "Update course settings" - required to get course info.
** enrol/manual:enrol - or "Enrol users" - required to enroll users in courses.
* There is an additional step that is required for enroll function to work. We need to allow "Web Services Users" to assign a "Student" role to accounts. This is done on the "Allow role assignments" tab in Site administration - Users - Permissions - Define Roles. 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 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 account with the capabilities necessary to execute our functions.
* Create a token to access web service (we'll need it in code examples). Go to Home - Site administration - Plugins - Web services - Manage tokens. Click "Add". Select a user and a service and generate token.
By this point we have:
* Installed Moodle 2.4.3.
* Enabled web services in it.
* Created nik_test web service with a few functions.
* Created web_service_user user account.
* Created "Web Services Users" system role with necessary capabilities to execute our functions and also a capability to assign the "Student" role to other accounts.
* Created a token for web_service_user account for programmatic access to nik_test service.
DEBUGGING AND TROUBLESHOOTING
As you can see from the above lists, the setup procedure is quite demanding. Numerous points of failure are possible, here are just a few possibilities.
* Moodle not set up correctly.
* Moodle expects parameters in a certain way, documentation conflicting, incorrect, missing, or deprecated.
* Coding errors.
* Component errors. For example, PEAR XML_RPC package that I used in earlier samples could not decode some replies from Moodle, such as containing <value><nil/></value> elements.
* Moodle returns a call that contains nothing and it is unclear what's wrong.
Here are two troubleshooting ideas that worked for me.
* Output additional info to web server log from the modified Moodle code. This was the primary thing.
* Had a special tool to observe incoming XML that hits the server.
I tried to work like this:
* Understand the XML format of the request that I need to send to Moodle.
* Create such request and observe it somewhere to make sure the format is right. I did it by writing a simple Java servlet running under Tomcat that was dumping the POST body to its console. Then configured my client to talk to it, and looked at XML. When you see XML everything becomes so much easier. 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.
* When we are sure the request is correct we fire it at Moodle and see what happens. At this point, in case of problems, we need to examine web server error log. It will help if you start with a clean error log before making a call. From that point on, we determine where in Moodle the problem occurs, and insert additional debug output in Moodle code around the place to localize the issue precisely.
* You can dump simple values and strings using the error_log() function but dumping arrays does not work, until you know this trick, which works a bit better:
$contents = ob_get_contents();
Here 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
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 PEAR XML_RPC PACKAGE
My earlier code samples for Moodle API used PEAR XML_RPC package (version 1.5.3). They are not very much useful because of the decoding problem for elements like <value><nil/></value> that Moodle likes to send back sometimes.
CODE SAMPLE WITH MOODLE CLASS
I wrote a class called Moodle that you can instantiate and call. Something like this:
$token = 'cface2c6d6e7888f5e33485008dbda88'; $server = 'url_to_server:port'; $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. // 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:
* enrollUser($user_id, $course_id)
They all work in a similar fashion: they return something useful such as the result of the requested operation. If an error occurs, the return is FALSE, and you can look at the $error member for information about the last error.
TO BE CONTINUED... If needed, I will post the code for the class.