webservice MDL-21580 remove personal log function call
[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
1942103f 27
5593d2dc 28/**
bff11d29 29 * Returns detailed function information
5593d2dc 30 * @param string|object $function name of external function or record from external_function
31 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
32 * MUST_EXIST means throw exception if no record or multiple records found
33 * @return object description or false if not found or exception thrown
34 */
35function external_function_info($function, $strictness=MUST_EXIST) {
36 global $DB, $CFG;
37
38 if (!is_object($function)) {
39 if (!$function = $DB->get_record('external_functions', array('name'=>$function), '*', $strictness)) {
40 return false;
41 }
42 }
43
44 //first find and include the ext implementation class
45 $function->classpath = empty($function->classpath) ? get_component_directory($function->component).'/externallib.php' : $CFG->dirroot.'/'.$function->classpath;
46 if (!file_exists($function->classpath)) {
47 throw new coding_exception('Can not find file with external function implementation');
48 }
49 require_once($function->classpath);
50
51 $function->parameters_method = $function->methodname.'_parameters';
52 $function->returns_method = $function->methodname.'_returns';
53
54 // make sure the implementaion class is ok
55 if (!method_exists($function->classname, $function->methodname)) {
56 throw new coding_exception('Missing implementation method');
57 }
58 if (!method_exists($function->classname, $function->parameters_method)) {
59 throw new coding_exception('Missing parameters description');
60 }
61 if (!method_exists($function->classname, $function->returns_method)) {
62 throw new coding_exception('Missing returned values description');
63 }
64
65 // fetch the parameters description
66 $function->parameters_desc = call_user_func(array($function->classname, $function->parameters_method));
67 if (!($function->parameters_desc instanceof external_function_parameters)) {
68 throw new coding_exception('Invalid parameters description');
69 }
70
71 // fetch the return values description
72 $function->returns_desc = call_user_func(array($function->classname, $function->returns_method));
73 // null means void result or result is ignored
74 if (!is_null($function->returns_desc) and !($function->returns_desc instanceof external_description)) {
75 throw new coding_exception('Invalid return description');
76 }
77
78 //now get the function description
79 //TODO: use localised lang pack descriptions, it would be nice to have
80 // easy to understand descriptiosn in admin UI,
81 // on the other hand this is still a bit in a flux and we need to find some new naming
82 // conventions for these descriptions in lang packs
83 $function->description = null;
84 $servicesfile = get_component_directory($function->component).'/db/services.php';
85 if (file_exists($servicesfile)) {
86 $functions = null;
87 include($servicesfile);
88 if (isset($functions[$function->name]['description'])) {
89 $function->description = $functions[$function->name]['description'];
90 }
91 }
92
93 return $function;
94}
95
9a0df45a 96/**
97 * Exception indicating user is not allowed to use external function in
98 * the current context.
99 */
100class restricted_context_exception extends moodle_exception {
101 /**
102 * Constructor
103 */
104 function __construct() {
105 parent::__construct('restrictedcontextexception', 'error');
106 }
107}
108
109/**
110 * Base class for external api methods.
111 */
112class external_api {
9a0df45a 113 private static $contextrestriction;
114
1bea0c27 115 /**
116 * Set context restriction for all folowing subsequent function calls.
117 * @param stdClass $contex
118 * @return void
119 */
2965d271 120 public static function set_context_restriction($context) {
9a0df45a 121 self::$contextrestriction = $context;
122 }
123
2965d271 124 /**
125 * This method has to be called before every operation
126 * that takes a longer time to finish!
127 *
128 * @param int $seconds max expected time the next operation needs
129 * @return void
130 */
131 public static function set_timeout($seconds=360) {
132 $seconds = ($seconds < 300) ? 300 : $seconds;
133 set_time_limit($seconds);
134 }
135
1bea0c27 136 /**
c9c5cc81 137 * Validates submitted function parameters, if anything is incorrect
1bea0c27 138 * invalid_parameter_exception is thrown.
1d7db36f 139 * This is a simple recursive method which is intended to be called from
140 * each implementation method of external API.
c9c5cc81 141 * @param external_description $description description of parameters
142 * @param mixed $params the actual parameters
143 * @return mixed params with added defaults for optional items, invalid_parameters_exception thrown if any problem found
1bea0c27 144 */
c9c5cc81 145 public static function validate_parameters(external_description $description, $params) {
04d212ce 146 if ($description instanceof external_value) {
c9c5cc81 147 if (is_array($params) or is_object($params)) {
eae18ab6 148 throw new invalid_parameter_exception(get_string('errorscalartype', 'webservice'));
c9c5cc81 149 }
4f0c6ad1
PS
150
151 if ($description->type == PARAM_BOOL) {
152 // special case for PARAM_BOOL - we want true/false instead of the usual 1/0 - we can not be too strict here ;-)
153 if (is_bool($params) or $params === 0 or $params === 1 or $params === '0' or $params === '1') {
154 return (bool)$params;
155 }
156 }
eae18ab6 157 return validate_param($params, $description->type, $description->allownull, get_string('errorinvalidparamsapi', 'webservice'));
382b9cea 158
c9c5cc81 159 } else if ($description instanceof external_single_structure) {
160 if (!is_array($params)) {
eae18ab6 161 throw new invalid_parameter_exception(get_string('erroronlyarray', 'webservice'));
c9c5cc81 162 }
163 $result = array();
164 foreach ($description->keys as $key=>$subdesc) {
165 if (!array_key_exists($key, $params)) {
382b9cea 166 if ($subdesc->required == VALUE_REQUIRED) {
eae18ab6 167 throw new invalid_parameter_exception(get_string('errormissingkey', 'webservice', $key));
c9c5cc81 168 }
04d212ce 169 if ($subdesc instanceof external_value) {
382b9cea 170 if ($subdesc->required == VALUE_DEFAULT) {
559a5dbd 171 try {
172 $result[$key] = self::validate_parameters($subdesc, $subdesc->default);
173 } catch (invalid_parameter_exception $e) {
174 throw new webservice_parameter_exception('invalidextparam',$key);
175 }
382b9cea 176 }
177 }
c9c5cc81 178 } else {
559a5dbd 179 try {
180 $result[$key] = self::validate_parameters($subdesc, $params[$key]);
181 } catch (invalid_parameter_exception $e) {
e5752b7d 182 //it's ok to display debug info as here the information is useful for ws client/dev
183 throw new webservice_parameter_exception('invalidextparam',$key." (".$e->debuginfo.")");
559a5dbd 184 }
c9c5cc81 185 }
186 unset($params[$key]);
187 }
188 if (!empty($params)) {
eae18ab6 189 throw new invalid_parameter_exception(get_string('errorunexpectedkey', 'webservice'));
c9c5cc81 190 }
191 return $result;
1bea0c27 192
c9c5cc81 193 } else if ($description instanceof external_multiple_structure) {
194 if (!is_array($params)) {
eae18ab6 195 throw new invalid_parameter_exception(get_string('erroronlyarray', 'webservice'));
c9c5cc81 196 }
197 $result = array();
198 foreach ($params as $param) {
199 $result[] = self::validate_parameters($description->content, $param);
200 }
201 return $result;
202
203 } else {
eae18ab6 204 throw new invalid_parameter_exception(get_string('errorinvalidparamsdesc', 'webservice'));
c9c5cc81 205 }
1bea0c27 206 }
207
9a0df45a 208 /**
209 * Makes sure user may execute functions in this context.
210 * @param object $context
211 * @return void
212 */
213 protected static function validate_context($context) {
ab9a01f2 214 if (empty($context)) {
215 throw new invalid_parameter_exception('Context does not exist');
216 }
9a0df45a 217 if (empty(self::$contextrestriction)) {
218 self::$contextrestriction = get_context_instance(CONTEXT_SYSTEM);
219 }
220 $rcontext = self::$contextrestriction;
221
222 if ($rcontext->contextlevel == $context->contextlevel) {
aa7fbebd 223 if ($rcontext->id != $context->id) {
9a0df45a 224 throw new restricted_context_exception();
225 }
226 } else if ($rcontext->contextlevel > $context->contextlevel) {
227 throw new restricted_context_exception();
228 } else {
229 $parents = get_parent_contexts($context);
230 if (!in_array($rcontext->id, $parents)) {
231 throw new restricted_context_exception();
232 }
233 }
234
235 if ($context->contextlevel >= CONTEXT_COURSE) {
236 //TODO: temporary bloody hack, this needs to be replaced by
237 // proper enrolment and course visibility check
238 // similar to require_login() (which can not be used
239 // because it can be used only once and redirects)
c9c5cc81 240 // oh - did I say we need to rewrite enrolments in 2.0
9a0df45a 241 // to solve this bloody mess?
242 //
243 // missing: hidden courses and categories, groupmembersonly,
244 // conditional activities, etc.
245 require_capability('moodle/course:view', $context);
246 }
247 }
9a0df45a 248}
249
b038c32c 250/**
251 * Common ancestor of all parameter description classes
252 */
253abstract class external_description {
254 /** @property string $description description of element */
255 public $desc;
256 /** @property bool $required element value required, null not alowed */
257 public $required;
258
259 /**
260 * Contructor
261 * @param string $desc
262 * @param bool $required
263 */
c9c5cc81 264 public function __construct($desc, $required) {
b038c32c 265 $this->desc = $desc;
266 $this->required = $required;
267 }
268}
269
270/**
04d212ce 271 * Scalar alue description class
b038c32c 272 */
04d212ce 273class external_value extends external_description {
274 /** @property mixed $type value type PARAM_XX */
b038c32c 275 public $type;
276 /** @property mixed $default default value */
277 public $default;
278 /** @property bool $allownull allow null values */
279 public $allownull;
280
281 /**
282 * Constructor
283 * @param mixed $type
284 * @param string $desc
285 * @param bool $required
286 * @param mixed $default
287 * @param bool $allownull
288 */
5a1861ee 289 public function __construct($type, $desc='', $required=VALUE_REQUIRED, $default=null, $allownull=NULL_ALLOWED) {
c9c5cc81 290 parent::__construct($desc, $required);
b038c32c 291 $this->type = $type;
292 $this->default = $default;
293 $this->allownull = $allownull;
294 }
295}
296
297/**
298 * Associative array description class
299 */
300class external_single_structure extends external_description {
301 /** @property array $keys description of array keys key=>external_description */
302 public $keys;
303
304 /**
305 * Constructor
306 * @param array $keys
307 * @param string $desc
308 * @param bool $required
309 */
382b9cea 310 public function __construct(array $keys, $desc='', $required=VALUE_REQUIRED) {
c9c5cc81 311 parent::__construct($desc, $required);
b038c32c 312 $this->keys = $keys;
313 }
314}
315
316/**
317 * Bulk array description class.
318 */
319class external_multiple_structure extends external_description {
320 /** @property external_description $content */
321 public $content;
322
323 /**
324 * Constructor
325 * @param external_description $content
326 * @param string $desc
327 * @param bool $required
328 */
382b9cea 329 public function __construct(external_description $content, $desc='', $required=VALUE_REQUIRED) {
c9c5cc81 330 parent::__construct($desc, $required);
b038c32c 331 $this->content = $content;
332 }
333}
c29cca30 334
335/**
336 * Description of top level - PHP function parameters.
337 * @author skodak
338 *
339 */
340class external_function_parameters extends external_single_structure {
341}