Creating a Moodle web service with mNet, August 2007 The Open University Creating a Moodle web service with mNet 1.0 Introduction ............................................................................................ 2 2.0 Create Peer to Peer Network ........................................................................ 3 3.0 Create a Web Service ................................................................................. 4 3.1 Create Service ...................................................................................... 4 3.2 Enable service through mNet .................................................................... 4 3.3 Register Service .................................................................................... 5 3.4 Publish service ...................................................................................... 5 3.5 Consume Service ................................................................................... 5 Conclusion ................................................................................................... 7 Appendix A................................................................................................... 8 Appendix B .................................................................................................. 11 Appendix C.................................................................................................. 14 Appendix D ................................................................................................. 15 Page 1 of 18 Creating a Moodle web service with mNet, August 2007 The Open University 1.0 Introduction This documentation comes as part of the Open University Offline Moodle project which is aimed at providing the facility to synchronise one Moodle application with another. Web Services are used where it is necessary to call methods of a remote application as though they are available in the local application. This is necessary within the Offline Moodle context because both Moodle instances must share data frequently and the data must come from many different locations within the Moodle infrastructure. The Mobile Network (mNet) feature of Moodle has been chosen to provide web services for Offline Moodle however this feature is relatively new in the latest 1.9 release. For this reason there is not currently any documentation as to how to implement a web service using the mNet architecture. A basic web service has now been created using mNet and this documentation will explain how web services were enabled for the Offline Moodle project Page 2 of 18 Creating a Moodle web service with mNet, August 2007 The Open University 2.0 Create Peer to Peer Network 2.1 Install Moodles To implement a web service two applications must talk to each other. For the purposes of this document our 2 applications will be 1. Moodle Offline 2. Moodle Hub Moodle Offline is the name given to an instance of Moodle operating on a desktop personal computer. Moodle Hub is the name given to an instance of Moodle operating on a hosted server environment. Details on how to install Moodle as well as how to set up an environment to run it can be found at http://docs.moodle.org/en/Installing_Moodle. 2.2 Enable mNet Both instances must have mNet enabled and trust each other. The procedure for achieving this is at http://docs.moodle.org/en/Moodle_Network. We enabled mNet to the point where Single Sign On was working and we could roam around both sites as one user. Page 3 of 18 Creating a Moodle web service with mNet, August 2007 The Open University 3.0 Create a Web Service mNet defines a process for enabling web services. This section refers heavily to the guidance given at http://docs.moodle.org/en/Web_Services_API. Enabling a new service involves a few shorts steps: 1. Create Service: create a service for mNet to use. 2. Enable service through mNet: tell mNet how to use your new service 3. Register Service: Let Moodle and mNet know the service is available 4. Publish Service: Allow the service to be used by selected trusted applications 5. Consume Service: Get a trusted application to call and make use of the service you just created and published 3.1 Create Service mNet requires that a proxy class be defined that exposes the web services. This class must adhere to the mNet interface specification and must be in place for Moodle to recognise any methods that must be exposed. It is through these methods that you can expose the inner methods of Moodle as you require. To create our class we simply used the auth/mnet/auth.php file as a template. Appendix A contains the code for our file located at synch/mnet/synch.php. The class simply exposes two methods. The methods you wish to expose must be listed in the method mnet_publishes as shown. This information is used later to register with mNet that these methods are available as part of a web service. Once the class and its methods have been ‘registered’ in Moodle they will become available for publishing. Only when they are published can the be used, or consumed. Note: You will notice remnants of the auth class within the code. I leave them there as reminders of what else can be done. 3.2 Enable service through mNet This process involves incorporating our class into the mNet framework so that mNet knows what to do when methods from our class get called. Appendix B contains the code to add to the core mNet files and those files used by the admin feature in admin/mnet. Page 4 of 18 Creating a Moodle web service with mNet, August 2007 The Open University 3.3 Register Service Now that we have made alterations to the mNet code, it will now know where to look when the service and its methods are called. At this point the service itself is not recognised within Moodle. Our next step is to go to the admin home page which is easily reached from the ‘notifications’ option of the ‘Site Administration’ menu. This calls the methods we updated in the previous step and should find our new class. It will then add the methods to the database. The table it changes is ‘mnet_rpc’. If you are interested you should now see the new methods as the most recent additions to this table. You will notice that the help field is empty. This is because we have been lazy and not added comments to the methods. If we do these will also be recorded in the help field and made available to applications that ask Moodle which methods it exposes. 3.4 Publish service Even though Moodle is now aware that these methods actually exist, it is still not possible to call them as web services. The final step we have left is to publish the service. This involves telling Moodle which of its trusted hosts are allowed to use this service. Now we’ve been a bit naughty and missed one step of adding the language files. Not to worry we didn’t need them until now. Appendix C contains the code for the file lang/en_utf8/synch_mnet.php. This is required to provide the text to explain how to publish the new service. In the same way you enabled the services to allow single sign on, you will enable the synch service. On the ’Site Administration’ menu go to networking/peers and click on the peer that you wish to have access to the service and click on the services tab when it loads. At the bottom of the page you should now see a section named ‘Synch(test)’ along with publish and subscribe options. Tick them both to enable the service. 3.5 Consume Service To consume a web service is use to it. So let’s go and check it’s working. Appendix D has code for a very simple file that shows how the new service works. Of course this is as simple as you can get but I am just trying to show you enough to get going. Page 5 of 18 Creating a Moodle web service with mNet, August 2007 The Open University Create the file where you like as long as it’s within the Moodle framework code. Correct the link to the config.php file. Then navigate to the file you just created in a browser. You will find initially that the webservice that is called is the local service, the one within the Moodle application the file resides in. this just proves whether the service is enabled locally. Connecting to a remote service is as simple as setting the correct host id. An example of this is included in the file but is commented out. Just ensure you use the correct id. Either look in the database or check the url of the peers you can connect to via the Network Servers block on your home page for the host id. (See the documentation re setting up mnet referenced earlier). If it doesn’t work and you get errors check that the new Synch service is enabled on both applications. I enabled it for both all hosts and the trusted client on each instance. Until I know the service better I have left it open like this. Note: unit tests have been created but they were too cumbersome to include in this document. To find out more just email me at c.chambers@open.ac.uk Page 6 of 18 Creating a Moodle web service with mNet, August 2007 The Open University Conclusion So there you have it. The actual service you have enabled doesn’t do much but that’s not the point. You now have the tools to add as many as you want and do anything you like. The examples I have given just show the basic way to create new classes and methods and pass parameters back and forth between each. I have tested the following types: String Integer Double/float Array Object (These are handled as arrays) I haven’t tested any further types but I understand it simply follows the xmlrpc spec at http://www.xmlrpc.com/spec. This reference is useful for a more thorough understanding of the xml rpc standards that the catalyst guys who built mNet are adhering to. Page 7 of 18 Creating a Moodle web service with mNet, August 2007 The Open University Appendix A <?php if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page } //require_once($CFG->libdir.'/authlib.php'); /** * Moodle Network authentication plugin. */ class synch_plugin_mnet { /** * Constructor. */ function __construct() { /* $this->authtype = 'mnet'; $this->config = get_config('auth/mnet'); */ } /** * Provides the allowed RPC services from this class as an array. * @return array Allowed RPC services. */ function mnet_publishes() { $sso_idp = array(); $sso_idp['name'] = 'synch_test'; // Name & Description go in lang file $sso_idp['apiversion'] = 1; $sso_idp['methods'] = array('test', 'testResponse'); Page 8 of 18 Creating a Moodle web service with mNet, August 2007 The Open University return array($sso_idp); } function test(){ //return 'test method is working on the client baby!.'; /* // Testing which objects can be passed via the interface. $testObject = new Object(); $testObject->test = 'just testing what can be passed'; $testObject->testArray = Array('this','is','a','test'); $testObject->testObject = new Object(); $testObject->testObject->field = 'just a field'; $testObject->testsynch_plugin_mnet = new synch_plugin_mnet(); $testObject->testDouble = 56.56; $testObject->testInt = 56; return $testObject; */ /* // Testing that 1000 records can be passed via the web service interface $testRecordSet = array(); $record; for($i=0;$i<1000;$i++){ $record = new Object(); $record->name = 'My name is '.$i; $record->id = '314325415345'.$i; $record->table = 'mdl_some_table_here'; $testRecordSet[] = $record; } return $testRecordSet; */ Page 9 of 18 Creating a Moodle web service with mNet, August 2007 The Open University // testing the characters that can be passed $testCharacters = "!\"£$%^&*()_+-={}[]:@~;'#<>?,./|\\¬`"; return $testCharacters; } /* * @method testResponse designed for unit testing to return the paramater * provided. Tests the web service data transfer * @param mixed $input value passed into the method * @return mixed */ function testResponse($input, $input2=null){ if($input2){ $input = array($input, $input2); } //return $input; return array('response'=>$input, 'host'=>array('wwwroot'=>'http://urlof this server')); } } ?> Page 10 of 18 Creating a Moodle web service with mNet, August 2007 The Open University Appendix B Core Changes The following code must be added to the file mnet/xmlrpc/server.php to the method mnet_server_dispatch at the end of the if..else checks for dispatched settings. Our code was placed just after the if…else block beginning } elseif ('promiscuous' == $CFG->mnet_dispatcher_mode && $MNET_REMOTE_CLIENT->plaintext_is_ok()) { Here is the code to add. elseif ($callstack[0] == 'synch') { // Break out the callstack into its elements list($base, $plugin, $filename, $methodname) = $callstack; // We refuse to include anything that is not synch.php // TODO: use method is_enabled_synch to check which modules // support synching. if ($filename == 'synch.php') { $authclass = 'synch_plugin_'.$plugin; $includefile = '/synch/'.$plugin.'/synch.php'; $response = mnet_server_invoke_method($includefile, $methodname, $method, $payload, $authclass); $response = mnet_server_prepare_response($response); echo $response; } else { // Generate error response - unable to locate function exit(mnet_server_fault(702, 'nosuchfunction')); } ////////////////////////////////////// NO FUNCTION } Admin Changes These are the changes that need to be made to the file admin/mnet/adminlib.php. Page 11 of 18 Creating a Moodle web service with mNet, August 2007 The Open University The following code was placed in the method upgrade_RPC_functions at the end of the method. When visiting the home page of the admin section of Moodle, or clicking ‘notifications’ in the site administration menu, you reach the page admin/index.php. this page calls the method upgrade_RPC_functions as part of its check for upgraded components. This code makes the rpc framework of mNet aware of the synch folder and consequently it’s contents. $basedir = $CFG->dirroot.'/synch'; if (file_exists($basedir) && filetype($basedir) == 'dir') { $dirhandle = opendir($basedir); while (false !== ($dir = readdir($dirhandle))) { $firstchar = substr($dir, 0, 1); if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf') { continue; } if (filetype($basedir .'/'. $dir) != 'dir') { continue; } mnet_get_functions('synch', $dir); } } The next bit of code was placed in the method mnet_get_functions between the if….else statements for ‘mod’ and ‘auth or enrol’. It’s purpose is to specify where to look within the synch folder structure for the file that contains the webservice class. It is called by the previous method upgrade_RPC_functions. else if('synch' == $type){ // synch $relname = '/'.$type.'/'.$parentname.'/'.$docname; $filename = $CFG->dirroot.$relname; Page 12 of 18 Creating a Moodle web service with mNet, August 2007 The Open University if (file_exists($filename)) include $filename; $class = $type.($type=='enrol'? 'ment':'').'_plugin_'.$parentname; if (class_exists($class)) { $object = new $class(); if (method_exists($object, 'mnet_publishes')) { (array)$publishes = $object->mnet_publishes(); } } } Page 13 of 18 Creating a Moodle web service with mNet, August 2007 The Open University Appendix C <?php $string['synch_test_name'] = 'Synch (Test)'; $string['synch_test_description'] = 'Publish this service to allow your users to synch with the $a Moodle site. '. '<ul><li><em>Dependency</em>: You must also <strong>subscribe</strong> to the Synchronisation service on $a.</li></ul><br />'. 'Subscribe to this service to allow synchronisation with $a. '. '<ul><li><em>Dependency</em>: You must also <strong>publish</strong> the Synchronisation service to $a.</li></ul><br />'; $string['synch_mnet_login_refused'] = 'Username $a[0] is not permitted to login from $a[1].'; ?> Page 14 of 18 Creating a Moodle web service with mNet, August 2007 The Open University Appendix D This section contains the code for a page that tests the web service we have created. The include to the config.php file must be adjusted depending on where you put the file. <?php /** * A template to test Moodle's XML-RPC feature * * This script 'remotely' executes the mnet_concatenate_strings function in * mnet/testlib.php * It steps through each stage of the process, printing some data as it goes * along. It should help you to get your remote method working. * * @author Colin Chambers c.chambers@open.ac.uk * @version 0.0.1 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ require_once(dirname(__FILE__) . '/../../../config.php'); //require_once dirname(__FILE__)."/../../admin/synch-setup.php"; require_once $CFG->dirroot.'/mnet/xmlrpc/client.php'; error_reporting(E_ALL); if (isset($_GET['func']) && is_numeric($_GET['func'])) { $func = $_GET['func']; // Some HTML sugar echo '<?xml version="1.0" encoding="utf-8"?>'; ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> <head><title>Moodle MNET Test Client</title></head><body> Page 15 of 18 Creating a Moodle web service with mNet, August 2007 The Open University <?php // For the demo, our 'remote' host is actually our local host. $wwwroot = $CFG->wwwroot; // Enter the complete path to the file that contains the function you want to // call on the remote server. In our example the function is in // mnet/testlib/ // The function itself is added to that path to complete the $path_to_function // variable $path_to_function[0] = 'synch/mnet/synch.php/test'; $path_to_function[1] = 'synch/mnet/synch.php/testResponse'; $paramArray[0] = array(); $paramArray[1] = array( array('testing just to see if it is working correctly from the server', 'string'), array(array('title'=>'You got the love','artist'=>'Candi Staton','format','CD'), 'array'), array(123456, 'int'), array(12.3456, 'double'), ); echo 'Your local wwwroot appears to be <strong>'. $wwwroot ."</strong>.<br />\n"; echo "We will use this as the local <em>and</em> remote hosts.<br /><br />\n"; flush(); // mnet_peer pulls information about a remote host from the database. $mnet_peer = new mnet_peer(); $mnet_peer->set_wwwroot($wwwroot); $mnethostid = 1010000003; // Choose a valid id from your set up //$mnet_peer->set_id($mnethostid); // // Create a new request object Page 16 of 18 Creating a Moodle web service with mNet, August 2007 The Open University $mnet_request = new mnet_xmlrpc_client(); // Tell it the path to the method that we want to execute $mnet_request->set_method($path_to_function[$func]); // Add parameters for your function. The mnet_concatenate_strings takes three // parameters, like mnet_concatenate_strings($string1, $string2, $string3) // PHP is weakly typed, so you can get away with calling most things strings, // unless it's non-scalar (i.e. an array or object or something). foreach($paramArray[$func] as $param) { $mnet_request->add_param($param[0], $param[1]); } if (count($mnet_request->params)) { echo 'Your parameters are:<br />'; while(list($key, $val) = each($mnet_request->params)) { echo '&nbsp;&nbsp; <strong>'.$key.':</strong> '. $val."<br/>\n"; } } flush(); // We send the request: $mnet_request->send($mnet_peer); ?> A var_dump of the decoded response: <strong><pre><?php var_dump($mnet_request->response); ?></pre></strong><br /> <?php if (count($mnet_request->params)) { ?> A var_dump of the parameters you sent: <strong><pre><?php var_dump($mnet_request->params); ?></pre></strong><br /> <?php Page 17 of 18 Creating a Moodle web service with mNet, August 2007 The Open University } } ?> <p> Choose a function to call:<br /> <a href="testclient.php?func=0">synch/test</a><br /> <a href="testclient.php?func=1">synch/testResponse</a><br /> </body></html> Page 18 of 18