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 | */ |
34 | function 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 | */ |
99 | class 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 | */ |
111 | class 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 | */ |
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; |
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 |
254 | class 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 | */ |
281 | class 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 | */ |
300 | class 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 | */ |
321 | class external_function_parameters extends external_single_structure { |
322 | } |