MDL-12886 refactored function return full external function info and improved reporti...
[moodle.git] / lib / externallib.php
CommitLineData
9a0df45a 1<?php
2
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/>.
17
18/**
19 * Support for external API
20 *
21 * @package moodlecore
22 * @subpackage webservice
551f4420 23 * @copyright 2009 Moodle Pty Ltd (http://moodle.com)
9a0df45a 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 */
26
5593d2dc 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 */
34function external_function_info($function, $strictness=MUST_EXIST) {
35 global $DB, $CFG;
36
37 if (!is_object($function)) {
38 if (!$function = $DB->get_record('external_functions', array('name'=>$function), '*', $strictness)) {
39 return false;
40 }
41 }
42
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);
49
50 $function->parameters_method = $function->methodname.'_parameters';
51 $function->returns_method = $function->methodname.'_returns';
52
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 }
63
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 }
69
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 }
76
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 }
91
92 return $function;
93}
94
9a0df45a 95/**
96 * Exception indicating user is not allowed to use external function in
97 * the current context.
98 */
99class restricted_context_exception extends moodle_exception {
100 /**
101 * Constructor
102 */
103 function __construct() {
104 parent::__construct('restrictedcontextexception', 'error');
105 }
106}
107
108/**
109 * Base class for external api methods.
110 */
111class external_api {
9a0df45a 112 private static $contextrestriction;
113
1bea0c27 114 /**
115 * Set context restriction for all folowing subsequent function calls.
116 * @param stdClass $contex
117 * @return void
118 */
2965d271 119 public static function set_context_restriction($context) {
9a0df45a 120 self::$contextrestriction = $context;
121 }
122
2965d271 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 }
134
1bea0c27 135 /**
c9c5cc81 136 * Validates submitted function parameters, if anything is incorrect
1bea0c27 137 * invalid_parameter_exception is thrown.
1d7db36f 138 * This is a simple recursive method which is intended to be called from
139 * each implementation method of external API.
c9c5cc81 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
1bea0c27 143 */
c9c5cc81 144 public static function validate_parameters(external_description $description, $params) {
04d212ce 145 if ($description instanceof external_value) {
c9c5cc81 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');
150
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 }
04d212ce 161 if ($subdesc instanceof external_value) {
c9c5cc81 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;
1bea0c27 173
c9c5cc81 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;
183
184 } else {
185 throw new invalid_parameter_exception('Invalid external api description.');
186 }
1bea0c27 187 }
188
9a0df45a 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) {
ab9a01f2 195 if (empty($context)) {
196 throw new invalid_parameter_exception('Context does not exist');
197 }
9a0df45a 198 if (empty(self::$contextrestriction)) {
199 self::$contextrestriction = get_context_instance(CONTEXT_SYSTEM);
200 }
201 $rcontext = self::$contextrestriction;
202
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 }
215
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)
c9c5cc81 221 // oh - did I say we need to rewrite enrolments in 2.0
9a0df45a 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 }
9a0df45a 229}
230
b038c32c 231/**
232 * Common ancestor of all parameter description classes
233 */
234abstract 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;
239
240 /**
241 * Contructor
242 * @param string $desc
243 * @param bool $required
244 */
c9c5cc81 245 public function __construct($desc, $required) {
b038c32c 246 $this->desc = $desc;
247 $this->required = $required;
248 }
249}
250
251/**
04d212ce 252 * Scalar alue description class
b038c32c 253 */
04d212ce 254class external_value extends external_description {
255 /** @property mixed $type value type PARAM_XX */
b038c32c 256 public $type;
257 /** @property mixed $default default value */
258 public $default;
259 /** @property bool $allownull allow null values */
260 public $allownull;
261
262 /**
263 * Constructor
264 * @param mixed $type
265 * @param string $desc
266 * @param bool $required
267 * @param mixed $default
268 * @param bool $allownull
269 */
c9c5cc81 270 public function __construct($type, $desc='', $required=true, $default=null, $allownull=true) {
271 parent::__construct($desc, $required);
b038c32c 272 $this->type = $type;
273 $this->default = $default;
274 $this->allownull = $allownull;
275 }
276}
277
278/**
279 * Associative array description class
280 */
281class external_single_structure extends external_description {
282 /** @property array $keys description of array keys key=>external_description */
283 public $keys;
284
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) {
c9c5cc81 292 parent::__construct($desc, $required);
b038c32c 293 $this->keys = $keys;
294 }
295}
296
297/**
298 * Bulk array description class.
299 */
300class external_multiple_structure extends external_description {
301 /** @property external_description $content */
302 public $content;
303
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) {
c9c5cc81 311 parent::__construct($desc, $required);
b038c32c 312 $this->content = $content;
313 }
314}
c29cca30 315
316/**
317 * Description of top level - PHP function parameters.
318 * @author skodak
319 *
320 */
321class external_function_parameters extends external_single_structure {
322}