bb5330dcb172dae300cbde6b23b70c2650b5da8c
[moodle.git] / lib / externallib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Support for external API
20  *
21  * @package    moodlecore
22  * @subpackage webservice
23  * @copyright  2009 Moodle Pty Ltd (http://moodle.com)
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25  */
27 /**
28  * Returns detailed functio information
29  * @param string|object $function name of external function or record from external_function
30  * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
31  *                        MUST_EXIST means throw exception if no record or multiple records found
32  * @return object description or false if not found or exception thrown
33  */
34 function external_function_info($function, $strictness=MUST_EXIST) {
35     global $DB, $CFG;
37     if (!is_object($function)) {
38         if (!$function = $DB->get_record('external_functions', array('name'=>$function), '*', $strictness)) {
39             return false;
40         }
41     }
43     //first find and include the ext implementation class
44     $function->classpath = empty($function->classpath) ? get_component_directory($function->component).'/externallib.php' : $CFG->dirroot.'/'.$function->classpath;
45     if (!file_exists($function->classpath)) {
46         throw new coding_exception('Can not find file with external function implementation');
47     }
48     require_once($function->classpath);
50     $function->parameters_method = $function->methodname.'_parameters';
51     $function->returns_method    = $function->methodname.'_returns';
53     // make sure the implementaion class is ok
54     if (!method_exists($function->classname, $function->methodname)) {
55         throw new coding_exception('Missing implementation method');
56     }
57     if (!method_exists($function->classname, $function->parameters_method)) {
58         throw new coding_exception('Missing parameters description');
59     }
60     if (!method_exists($function->classname, $function->returns_method)) {
61         throw new coding_exception('Missing returned values description');
62     }
64     // fetch the parameters description
65     $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method));
66     if (!($function->parameters_desc instanceof external_function_parameters)) {
67         throw new coding_exception('Invalid parameters description');
68     }
70     // fetch the return values description
71     $function->returns_desc = call_user_func(array($function->classname, $function->returns_method));
72     // null means void result or result is ignored
73     if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) {
74         throw new coding_exception('Invalid return description');
75     }
77     //now get the function description
78     //TODO: use localised lang pack descriptions, it would be nice to have
79     //      easy to understand descriptiosn in admin UI,
80     //      on the other hand this is still a bit in a flux and we need to find some new naming
81     //      conventions for these descriptions in lang packs
82     $function->description = null;
83     $servicesfile = get_component_directory($function->component).'/db/services.php';
84     if (file_exists($servicesfile)) {
85         $functions = null;
86         include($servicesfile);
87         if (isset($functions[$function->name]['description'])) {
88             $function->description = $functions[$function->name]['description'];
89         }
90     }
92     return $function;
93 }
95 /**
96  * Exception indicating user is not allowed to use external function in
97  * the current context.
98  */
99 class restricted_context_exception extends moodle_exception {
100     /**
101      * Constructor
102      */
103     function __construct() {
104         parent::__construct('restrictedcontextexception', 'error');
105     }
108 /**
109  * Base class for external api methods.
110  */
111 class external_api {
112     private static $contextrestriction;
114     /**
115      * Set context restriction for all folowing subsequent function calls.
116      * @param stdClass $contex
117      * @return void
118      */
119     public static function set_context_restriction($context) {
120         self::$contextrestriction = $context;
121     }
123     /**
124      * This method has to be called before every operation
125      * that takes a longer time to finish!
126      *
127      * @param int $seconds max expected time the next operation needs
128      * @return void
129      */
130     public static function set_timeout($seconds=360) {
131         $seconds = ($seconds < 300) ? 300 : $seconds;
132         set_time_limit($seconds);
133     }
135     /**
136      * Validates submitted function parameters, if anything is incorrect
137      * invalid_parameter_exception is thrown.
138      * This is a simple recursive method which is intended to be called from
139      * each implementation method of external API.
140      * @param external_description $description description of parameters
141      * @param mixed $params the actual parameters
142      * @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found
143      */
144     public static function validate_parameters(external_description $description, $params) {
145         if ($description instanceof external_value) {
146             if (is_array($params) or is_object($params)) {
147                 throw new invalid_parameter_exception('Scalar type expected, array or object received.');
148             }
149             return validate_param($params, $description->type, $description->allownull, 'Invalid external api parameter');
151         } else if ($description instanceof external_single_structure) {
152             if (!is_array($params)) {
153                 throw new invalid_parameter_exception('Only arrays accepted.');
154             }
155             $result = array();
156             foreach ($description->keys as $key=>$subdesc) {
157                 if (!array_key_exists($key, $params)) {
158                     if ($subdesc->required) {
159                         throw new invalid_parameter_exception('Missing required key in single structure.');
160                     }
161                     if ($subdesc instanceof external_value) {
162                         $result[$key] = self::validate_parameters($subdesc, $subdesc->default);
163                     }
164                 } else {
165                     $result[$key] = self::validate_parameters($subdesc, $params[$key]);
166                 }
167                 unset($params[$key]);
168             }
169             if (!empty($params)) {
170                 throw new invalid_parameter_exception('Unexpected keys detected in parameter array.');
171             }
172             return $result;
174         } else if ($description instanceof external_multiple_structure) {
175             if (!is_array($params)) {
176                 throw new invalid_parameter_exception('Only arrays accepted.');
177             }
178             $result = array();
179             foreach ($params as $param) {
180                 $result[] = self::validate_parameters($description->content, $param);
181             }
182             return $result;
184         } else {
185             throw new invalid_parameter_exception('Invalid external api description.');
186         }
187     }
189     /**
190      * Makes sure user may execute functions in this context.
191      * @param object $context
192      * @return void
193      */
194     protected static function validate_context($context) {
195         if (empty($context)) {
196             throw new invalid_parameter_exception('Context does not exist');
197         }
198         if (empty(self::$contextrestriction)) {
199             self::$contextrestriction = get_context_instance(CONTEXT_SYSTEM);
200         }
201         $rcontext = self::$contextrestriction;
203         if ($rcontext->contextlevel == $context->contextlevel) {
204             if ($rcontex->id != $context->id) {
205                 throw new restricted_context_exception();
206             }
207         } else if ($rcontext->contextlevel > $context->contextlevel) {
208             throw new restricted_context_exception();
209         } else {
210             $parents = get_parent_contexts($context);
211             if (!in_array($rcontext->id, $parents)) {
212                 throw new restricted_context_exception();
213             }
214         }
216         if ($context->contextlevel >= CONTEXT_COURSE) {
217             //TODO: temporary bloody hack, this needs to be replaced by
218             //      proper enrolment and course visibility check
219             //      similar to require_login() (which can not be used
220             //      because it can be used only once and redirects)
221             //      oh - did I say we need to rewrite enrolments in 2.0
222             //      to solve this bloody mess?
223             //
224             //      missing: hidden courses and categories, groupmembersonly,
225             //      conditional activities, etc.
226             require_capability('moodle/course:view', $context);
227         }
228     }
231 /**
232  * Common ancestor of all parameter description classes
233  */
234 abstract class external_description {
235     /** @property string $description description of element */
236     public $desc;
237     /** @property bool $required element value required, null not alowed */
238     public $required;
240     /**
241      * Contructor
242      * @param string $desc
243      * @param bool $required
244      */
245     public function __construct($desc, $required) {
246         $this->desc = $desc;
247         $this->required = $required;
248     }
251 /**
252  * Scalar alue description class
253  */
254 class external_value extends external_description {
255     /** @property mixed $type value type PARAM_XX */
256     public $type;
257     /** @property mixed $default default value */
258     public $default;
259     /** @property bool $allownull allow null values */
260     public $allownull;
262     /**
263      * Constructor
264      * @param mixed $type
265      * @param string $desc
266      * @param bool $required
267      * @param mixed $default
268      * @param bool $allownull
269      */
270     public function __construct($type, $desc='', $required=true, $default=null, $allownull=true) {
271         parent::__construct($desc, $required);
272         $this->type      = $type;
273         $this->default   = $default;
274         $this->allownull = $allownull;
275     }
278 /**
279  * Associative array description class
280  */
281 class external_single_structure extends external_description {
282      /** @property array $keys description of array keys key=>external_description */
283     public $keys;
285     /**
286      * Constructor
287      * @param array $keys
288      * @param string $desc
289      * @param bool $required
290      */
291     public function __construct(array $keys, $desc='', $required=true) {
292         parent::__construct($desc, $required);
293         $this->keys = $keys;
294     }
297 /**
298  * Bulk array description class.
299  */
300 class external_multiple_structure extends external_description {
301      /** @property external_description $content */
302     public $content;
304     /**
305      * Constructor
306      * @param external_description $content
307      * @param string $desc
308      * @param bool $required
309      */
310     public function __construct(external_description $content, $desc='', $required=true) {
311         parent::__construct($desc, $required);
312         $this->content = $content;
313     }
316 /**
317  * Description of top level - PHP function parameters.
318  * @author skodak
319  *
320  */
321 class external_function_parameters extends external_single_structure {