Personal tools
You are here: Home Members ppollet public MoodleWS How to extend Moodle Web Services (the hard way)
Document Actions

How to extend Moodle Web Services (the hard way)

How to add an operation to this Web service

Starting at revision 1.8, you should consider using the new WSDL automatically generated and consult this page

The recipe given below is the most complex example possible, since we need to add new data types for input and output of our new operation. Some steps may be omitted if you find that one of the current datatypes match your needs.

Let's suppose we want to add a call named get_activities () to fetch recent activities of an user, identified by Moodle's id or SIS based idnumber or unique login ( Moodle username) on a course, also identified by either Moodle's Id or SIS based courseid. This is purely demonstrative since this call is present starting at revision 1.5.5 of the Web Service.

Step 0: Design the input and output datatypes

input :

Try to keep this order for consistency
  • client id : the required integer returned by a previous call to login()
  • sessionkey : the required string returned by a previous call to login()
  • userid : the value of the user identifier ( must be unique in Moodle's table mdl_user ) , must be not empty
  • useridfield : the relevant column of Moodle's mdl_user table we are going to search , default if empty "idnumber" (internal SIS number)
  • courseid : the value of the course identifier ( must be unique in Moodle's table mdl_course ) , can be empty. In that case returns user's activities on all site.
  • courseidfield : the relevant column of Moodle's mdl_course table we are going to search , ignored if empty "courseid", default "idnumber" (internal SIS number)
  • limit : an integer to limit the number of results , default 99

output :

Since Moodle API does not have such an entry, directly callable from PHP, we will use a SQL query from the table mdl_log, joined to table mdl_user. It is often easier since Moodle API is quite rich ... see http://xref.moodle.org/nav.html?index.html

SELECT mdl_log.*,FROM_UNIXTIME(mdl_log.time,'%d/%m/%Y %H:%i:%s' )as DATE,auth,firstname,lastname,email,
firstaccess, lastaccess, lastlogin, currentlogin, 
FROM_UNIXTIME(lastaccess,'%d/%m/%Y %H:%i:%s' )as DLA,
FROM_UNIXTIME(firstaccess,'%d/%m/%Y %H:%i:%s' )as DFA,
FROM_UNIXTIME(lastlogin,'%d/%m/%Y %H:%i:%s' )as DLL,
FROM_UNIXTIME(currentlogin,'%d/%m/%Y %H:%i:%s' )as DCL
 
FROM mdl_log , mdl_user 
WHERE mdl_log.userid = mdl_user.id
AND mdl_user.id = xxxx      <--- required 

AND mdl_log.course= zzz   <--- optional
  
ORDER BY time DESC 
LIMIT yyy     <--- parameter or default value 
Testing the query in some MySQL GUI utility (phpMyAdmin ...) gives :
id  	time  	userid  	ip  	course  	module  	cmid  	action  	url  	info  	DATE  	auth  	firstname  	lastname  	email  	firstaccess  	lastaccess  	lastlogin  	currentlogin  	DLA  	DFA  	DLL  	DCL

468597 	1177155796 	9 	134.214.152.108 	13 	course 	0 	view 	view.php?id=13 	13 	21/04/2007 13:43:16 	ldap 	Patrick 	Pollet 	patrick.pollet@insa-lyon.fr 	0 	1178817898 	1178782717 	1178817881 	10/05/2007 19:24:58 	01/01/1970 01:00:00 	10/05/2007 09:38:37 	10/05/2007 19:24:41

468596 	1177155773 	9 	134.214.152.108 	13 	course 	0 	view 	view.php?id=13 	13 	21/04/2007 13:42:53 	ldap 	Patrick 	Pollet 	patrick.pollet@insa-lyon.fr 	0 	1178817898 	1178782717 	1178817881 	10/05/2007 19:24:58 	01/01/1970 01:00:00 	10/05/2007 09:38:37 	10/05/2007 19:24:41

...

Note the names of the returned columns (watch case) ; the returned data record must have the very same attributes for automatic conversion by the Web Service . In the real code, we do not convert dates by SQL but use Moodle's API functions. It does not make any difference as long as we fill in the correctly named attributes (DATE, DLA, DFA, DLL and DCL) of the returned array of objects ...

warning DON'T FORGET TO HAVE an unique value (usually Moodle's id key of the main target table) as the first returned column of your SQL ! Currently Moodle's API functions will not work (or lose records) if you do not do it !!!!

Step 1 : Edit the moodlewsdl.xml script

In this example, we have to add XML information to all sections of the WSDL file. Ctrl-C and Ctrl-V are your friends ;-)

As per revision 1.6, this file is a pure XML file, so usage of an XML editor, such as eclipse is recommended.

In the <xsd:schema> section of the <types> section we must add three complexType entries :

One for the activity Record to be returned : just enumerate in order the fields and types.
<xsd:complexType name="activityRecord">
        <xsd:all>
          <xsd:element name="error" type="xsd:string" />      <-- MUST be here for error messages on invalid records 
          <xsd:element name="id" type="xsd:integer" />         <-- must be in Moodle's SQL calls, so why not returning it to check ...
          <xsd:element name="time" type="xsd:integer" />     <--- too lazy to learn Soap date types ;-)
          <xsd:element name="userid" type="xsd:integer" />
          <xsd:element name="ip" type="xsd:string" />
          <xsd:element name="course" type="xsd:integer" />
          <xsd:element name="module" type="xsd:integer" />
          <xsd:element name="cmid" type="xsd:integer" />
          <xsd:element name="action" type="xsd:string" />
          <xsd:element name="url" type="xsd:string" />
          <xsd:element name="info" type="xsd:string" />
          <xsd:element name="DATE" type="xsd:string" />
          <xsd:element name="auth" type="xsd:string" />
          <xsd:element name="firstname" type="xsd:string" />
          <xsd:element name="lastname" type="xsd:string" />
          <xsd:element name="email" type="xsd:string" />
          <xsd:element name="firstaccess" type="xsd:integer" />      
          <xsd:element name="lastaccess" type="xsd:integer" />
          <xsd:element name="lastlogin" type="xsd:integer" />
          <xsd:element name="currentlogin" type="xsd:integer" />
          <xsd:element name="DLA" type="xsd:string" />
          <xsd:element name="DFA" type="xsd:string" />
          <xsd:element name="DLL" type="xsd:string" />
          <xsd:element name="DCL" type="xsd:string" />
        </xsd:all>
      </xsd:complexType>

One for the array of these records (copy and paste an existing one) and adjust :


    <xsd:complexType name="activityRecords">                <-- here 
        <xsd:complexContent>
          <xsd:restriction base="SOAP-ENC:Array">
            <xsd:attribute ref="SOAP-ENC:arrayType"
              wsdl:arrayType="tns:activityRecord[]" />       <-- and here 
          </xsd:restriction>
        </xsd:complexContent>
      </xsd:complexType>



And finally one for the returned data :

     <xsd:complexType name="getActivitiesReturn">
        <xsd:all>
          <xsd:element name="activities" type="tns:activityRecords" />     <--- defined the name and type of the returned data, match the array name above 
        </xsd:all>
      </xsd:complexType>

    </xsd:schema>     <--- end of section (do not edit !)
  </types>

In the messages area (there is no specific XML tag that surrounds messages), we must add two entries:

One for the input data, in which we enumerate in proper order the fields and types to be sent :

Our php function will have that many parameters (easier to define default values) in the very same order. Names of parts do not need to match the php parameter names


    <message name="get_activitiesRequest">
    <part name="client" type="xsd:integer" />
    <part name="sesskey" type="xsd:string" />
    <part name="iduser" type="xsd:string" />
    <part name="iduserfield" type="xsd:string" />
    <part name="idcourse" type="xsd:string" />
    <part name="idcoursefield" type="xsd:string" />
    <part name="idlimit" type="xsd:integer" />
  </message>

  <portType name="MoodleWSPortType">      <--- just before the beginning tag of the portType section 

One for the output data , a simple reference to the complexType array of activity records described above :

   <message name="get_activitiesResponse">
    <part name="return" type="tns:getActivitiesReturn" />       <-- reference to the array of activityRecord described above 
  </message>

  <portType name="MoodleWSPortType">    <--- just before the beginning tag of the portType section 

In the <portType> section we must add one entry for the new operation


  <operation name="get_activities">        <--- must match the choosen name for the operation 
      <documentation>MoodleWS: Get user most recent activities in a Moodle course</documentation>    <-- optional doc string 
      <input message="tns:get_activitiesRequest" />           <---   must match the message's name  described  above 
      <output message="tns:get_activitiesResponse" />       <---  must match the message's name  described  above
    </operation>

  </portType>     <--- the end tag of the portType section

And finally in the <binding> section, we add one entry for the new call . Copy and paste an existing entry and edit the XML tags name and soap:operation

. Note the CFGWWWROOT 'macro' that will be replaced by the actual url of your Moodle server when this XML document will be sent...
    <operation name="get_activities"> 
      <soap:operation
        soapAction="CFGWWWROOT/wspp/wsdl#get_activities"     <--- set the same name here after the '#'
        style="rpc" />
      <input>
        <soap:body use="encoded"
          namespace="CFGWWWROOT/wspp/wsdl"
          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
      </input>
      <output>
        <soap:body use="encoded"
          namespace="CFGWWWROOT/wspp/wsdl"
          encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
      </output>
     </operation>

  </binding>     <--- the end tag of the binding section. 

Step 3: Test and retest the WSDL

Using a WSDL analyzer such as soapUI or a crafted python program (see this one for an example ) by calling http://yourmoodle/wspp/wsdl_pp.php ...

Be warned that, as the time of this writing (May 2007), the WSDL Web Validator at http://xmethods.net/ve2/Tools.po does not catch missing complex types ;-(( An issue has been sent to them

The new operation and its associated data types should appear without any error message.

Method Name: get_activities

   In #0: client  ((u'http://www.w3.org/2001/XMLSchema', u'integer'))
   In #1: sesskey  ((u'http://www.w3.org/2001/XMLSchema', u'string'))
   In #2: iduser  ((u'http://www.w3.org/2001/XMLSchema', u'string'))
   In #3: iduserfield  ((u'http://www.w3.org/2001/XMLSchema', u'string'))
   In #4: idcourse  ((u'http://www.w3.org/2001/XMLSchema', u'string'))
   In #5: idcoursefield  ((u'http://www.w3.org/2001/XMLSchema', u'string'))
   In #6: idlimit  ((u'http://www.w3.org/2001/XMLSchema', u'integer'))

   Out #0: return  ((u'http://yourmoodle/wspp/wsdl', u'getActivitiesReturn'))

Step 4 : Edit the mdl_soapserver.php protocol script.

Add a php function by copy-paste another one and adjust the name (that must match the operation name), parameters (in the same order as the get_activitiesRequest declaration) and optional default values in the function heading and the future call to the parent function (that will be in server.class.php script) with the same name, and the same parameters, in the same order .

We recommand first to add the test return value array() instead of the real call. So you can test your new Web Service right away , and it will return an array named "activities" with a single empty activityRecord with error attribute set to your error message ( python, soapUI ...)

public function get_activities($client,$sesskey,
                                      $userid,$useridfield='idnumber',
                                      $courseid=0,$courseidfield='idnumber',$limit=99) {
                 return $this->send($this->to_soap_array(
                        array(),                <------ TESTING CODE 
                        //parent::get_activities($client,$sesskey,          <---- THE REAL CODE 
                        //                          $userid,$useridfield,
                        //                          $courseid,$courseidfield,$limit),
                        'activities',                         <--- name of the associative array as declared in getActivitiesReturn
                        'activityRecord',                 <--- name of the php class generated by wsdl2php from complex type declaration activityRecord 
                        'nothing new'));                  <---  message to put in error field of returned invalid records  or if empty result 
        }

Typical error : I forgot to run wsdl2php.php on the server side to generate the new php class activityRecord. The Web service returns a fatal SoapFault catched by our python test script :

>>> a,b=proxy.login ("me","mypass")
>>> proxy.get_activities(a,b,"ppollet","username",2,"id",99)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/opt/Plone-2.5/Python-2.4.3/lib/python2.4/site-packages/SOAPpy/Client.py", line 470, in __call__
    return self.__r_call(*args, **kw)
  File "/opt/Plone-2.5/Python-2.4.3/lib/python2.4/site-packages/SOAPpy/Client.py", line 492, in __r_call
    self.__hd, self.__ma)
  File "/opt/Plone-2.5/Python-2.4.3/lib/python2.4/site-packages/SOAPpy/Client.py", line 406, in __call
    raise p
SOAPpy.Types.faultType: <Fault SOAP-ENV:Server: internal error :class activityRecord not found !>

go to yourmoodle/wspp directory and run the script mkclasses.sh (or php wsdl2php.php http://yourmoodle/wspp/wsdl_pp.php) to generate the new php classes . This step must be done at every changes of the complexTypes in the WSDL.

Now we get a returned array (list) with an empty activityREcord structure with error attribute set and all other fields blanked to neutral values ; Looks good ...

( with python's SOAPPy, if an list has only one record, the returned data is converted automatically to a single record ; this should not be the case with other languages , see below for PHP5 )...
>>> proxy.get_activities(a,b,"ppollet","username",2,"id",99)
<SOAPpy.Types.structType return at -1215131700>: {'activities': <SOAPpy.Types.structType activities at -1215133044>: {'item': 
<SOAPpy.Types.structType item at -1215253460>: {'ip': '', 'module': 0, 'cmid': 0, 'course': 0, 'DATE': '', 'lastaccess': 0, 'id': 0, 'DCL': '',
 'firstaccess': 0, 'email': '', 'DLA': '', 'firstname': '', 'lastname': '', 'time': 0, 'auth': '', 'DLL': '', 'lastlogin': 0, 'DFA': '', 'info': '', 'url': '', 'currentlogin': 0, 
'userid': 0, 'error': 'nothing new', 'action': ''}}}

>>>     
Sample call in php5 with SoapClient : a class with an attribute activities as an array having one empty activityRecord instance
stdClass Object
(
    [activities] => Array
        (
            [0] => stdClass Object
                (
                    [error] => nothing new
                    [id] => 0
                    [time] => 0
                    [userid] => 0
                    [ip] =>
                    [course] => 0
                    [module] => 0
                    [cmid] => 0
                    [action] =>
                    [url] =>
                    [info] =>
                    [DATE] =>
                    [auth] =>
                    [firstname] =>
                    [lastname] =>
                    [email] =>
                    [firstaccess] => 0
                    [lastaccess] => 0
                    [lastlogin] => 0
                    [currentlogin] => 0
                    [DLA] =>
                    [DFA] =>
                    [DLL] =>
                    [DCL] =>
                )

        )

)

Step 5 : Create the real function call to Moodle's API

  • Comment the testing call (array()) and uncomment the real call in the mdl_soapserver::get_activities function.
  • Create the real function in server.class.php (same name, same parameters) with the appropriate calls to Moodle's API.
  • Now you are on your own ;-) See the real code of server.class::get_activities ...

Deploying

On remote clients using "static classes" generated by some wsdl2xxxx utilities, don't forget to update these classes ... (PHP5, Java, Python's ZIS ...). You have nothing to do with UI based clients (soapUI, Visual Studio ...) since they should read again the new WSDL file from your Moodle server ...

Adding testing code in wspp/clients

  • Go to test clients directory of yourmoodle/wspp/clients and run the script mkclasses.sh
  • Add some call examples to get_activities in the scripts there : ppdemo.php for PHP5/SoapClient, ws-test.php for PHP5/nusoap ....
by Pollet Patrick last modified 2011-04-19 14:15

Extend Moodle Web Services

Posted by Anonymous User at 2011-02-13 03:35

Muchas gracias.. muy buena guia

Message Permissions

Posted by Anonymous User at 2011-04-11 07:48

When I tasted to SOAP-UI I get the following message "You Do not Have the permissions to do this"


This site conforms to the following standards:

Mentions légales
Pour ceux qui vont chercher midi à 14 heures, la minute de vérité risque de se faire attendre longtemps. (Pierre DAC)
Avis de la CNIL numéro 370298 : Il est rappelé que les droits des personnes figurant sur ce serveur sont garantis et protégés par la législation française et qu'il est interdit de capturer les informations nominatives pour les utiliser à des fins commerciales, publicitaires ou autres (cf. loi du 6/01/1978)