1 |
efrain |
1 |
<?php
|
|
|
2 |
// This file is part of Moodle - http://moodle.org/
|
|
|
3 |
//
|
|
|
4 |
// Moodle is free software: you can redistribute it and/or modify
|
|
|
5 |
// it under the terms of the GNU General Public License as published by
|
|
|
6 |
// the Free Software Foundation, either version 3 of the License, or
|
|
|
7 |
// (at your option) any later version.
|
|
|
8 |
//
|
|
|
9 |
// Moodle is distributed in the hope that it will be useful,
|
|
|
10 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
11 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
12 |
// GNU General Public License for more details.
|
|
|
13 |
//
|
|
|
14 |
// You should have received a copy of the GNU General Public License
|
|
|
15 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16 |
|
|
|
17 |
/**
|
|
|
18 |
* Generic exporter to take a stdClass and prepare it for return by webservice.
|
|
|
19 |
*
|
|
|
20 |
* @package core
|
|
|
21 |
* @copyright 2015 Damyon Wiese
|
|
|
22 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
23 |
*/
|
|
|
24 |
namespace core\external;
|
|
|
25 |
|
|
|
26 |
use stdClass;
|
|
|
27 |
use renderer_base;
|
|
|
28 |
use context;
|
|
|
29 |
use coding_exception;
|
|
|
30 |
use core_external\external_format_value;
|
|
|
31 |
use core_external\external_multiple_structure;
|
|
|
32 |
use core_external\external_single_structure;
|
|
|
33 |
use core_external\external_value;
|
|
|
34 |
|
|
|
35 |
/**
|
|
|
36 |
* Generic exporter to take a stdClass and prepare it for return by webservice, or as the context for a template.
|
|
|
37 |
*
|
|
|
38 |
* templatable classes implementing export_for_template, should always use a standard exporter if it exists.
|
|
|
39 |
* External functions should always use a standard exporter if it exists.
|
|
|
40 |
*
|
|
|
41 |
* @copyright 2015 Damyon Wiese
|
|
|
42 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
43 |
*/
|
|
|
44 |
abstract class exporter {
|
|
|
45 |
|
|
|
46 |
/** @var array $related List of related objects used to avoid DB queries. */
|
|
|
47 |
protected $related = array();
|
|
|
48 |
|
|
|
49 |
/** @var stdClass|array The data of this exporter. */
|
|
|
50 |
protected $data = null;
|
|
|
51 |
|
|
|
52 |
/**
|
|
|
53 |
* Constructor - saves the persistent object, and the related objects.
|
|
|
54 |
*
|
|
|
55 |
* @param mixed $data - Either an stdClass or an array of values.
|
|
|
56 |
* @param array $related - An optional list of pre-loaded objects related to this object.
|
|
|
57 |
*/
|
|
|
58 |
public function __construct($data, $related = array()) {
|
|
|
59 |
$this->data = $data;
|
|
|
60 |
// Cache the valid related objects.
|
|
|
61 |
foreach (static::define_related() as $key => $classname) {
|
|
|
62 |
$isarray = false;
|
|
|
63 |
$nullallowed = false;
|
|
|
64 |
|
|
|
65 |
// Allow ? to mean null is allowed.
|
|
|
66 |
if (substr($classname, -1) === '?') {
|
|
|
67 |
$classname = substr($classname, 0, -1);
|
|
|
68 |
$nullallowed = true;
|
|
|
69 |
}
|
|
|
70 |
|
|
|
71 |
// Allow [] to mean an array of values.
|
|
|
72 |
if (substr($classname, -2) === '[]') {
|
|
|
73 |
$classname = substr($classname, 0, -2);
|
|
|
74 |
$isarray = true;
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
$missingdataerr = 'Exporter class is missing required related data: (' . get_called_class() . ') ';
|
|
|
78 |
$scalartypes = ['string', 'int', 'bool', 'float'];
|
|
|
79 |
$scalarcheck = 'is_' . $classname;
|
|
|
80 |
|
|
|
81 |
if ($nullallowed && (!array_key_exists($key, $related) || $related[$key] === null)) {
|
|
|
82 |
$this->related[$key] = null;
|
|
|
83 |
|
|
|
84 |
} else if ($isarray) {
|
|
|
85 |
if (array_key_exists($key, $related) && is_array($related[$key])) {
|
|
|
86 |
foreach ($related[$key] as $index => $value) {
|
|
|
87 |
if (!$value instanceof $classname && !$scalarcheck($value)) {
|
|
|
88 |
throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]');
|
|
|
89 |
}
|
|
|
90 |
}
|
|
|
91 |
$this->related[$key] = $related[$key];
|
|
|
92 |
} else {
|
|
|
93 |
throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]');
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
} else {
|
|
|
97 |
if (array_key_exists($key, $related) &&
|
|
|
98 |
((in_array($classname, $scalartypes) && $scalarcheck($related[$key])) ||
|
|
|
99 |
($related[$key] instanceof $classname))) {
|
|
|
100 |
$this->related[$key] = $related[$key];
|
|
|
101 |
} else {
|
|
|
102 |
throw new coding_exception($missingdataerr . $key . ' => ' . $classname);
|
|
|
103 |
}
|
|
|
104 |
}
|
|
|
105 |
}
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
/**
|
|
|
109 |
* Function to export the renderer data in a format that is suitable for a
|
|
|
110 |
* mustache template. This means raw records are generated as in to_record,
|
|
|
111 |
* but all strings are correctly passed through \core_external\util::format_text (or \core_external\util::format_string).
|
|
|
112 |
*
|
|
|
113 |
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
|
|
|
114 |
* @return stdClass
|
|
|
115 |
*/
|
|
|
116 |
final public function export(renderer_base $output) {
|
|
|
117 |
$data = new stdClass();
|
|
|
118 |
$properties = self::read_properties_definition();
|
|
|
119 |
$values = (array) $this->data;
|
|
|
120 |
|
|
|
121 |
$othervalues = $this->get_other_values($output);
|
|
|
122 |
if (array_intersect_key($values, $othervalues)) {
|
|
|
123 |
// Attempt to replace a standard property.
|
|
|
124 |
throw new coding_exception('Cannot override a standard property value.');
|
|
|
125 |
}
|
|
|
126 |
$values += $othervalues;
|
|
|
127 |
$record = (object) $values;
|
|
|
128 |
|
|
|
129 |
foreach ($properties as $property => $definition) {
|
|
|
130 |
if (isset($data->$property)) {
|
|
|
131 |
// This happens when we have already defined the format properties.
|
|
|
132 |
continue;
|
|
|
133 |
} else if (!property_exists($record, $property) && array_key_exists('default', $definition)) {
|
|
|
134 |
// We have a default value for this property.
|
|
|
135 |
$record->$property = $definition['default'];
|
|
|
136 |
} else if (!property_exists($record, $property) && !empty($definition['optional'])) {
|
|
|
137 |
// Fine, this property can be omitted.
|
|
|
138 |
continue;
|
|
|
139 |
} else if (!property_exists($record, $property)) {
|
|
|
140 |
// Whoops, we got something that wasn't defined.
|
|
|
141 |
throw new coding_exception('Unexpected property ' . $property);
|
|
|
142 |
}
|
|
|
143 |
|
|
|
144 |
$data->$property = $record->$property;
|
|
|
145 |
|
|
|
146 |
// If the field is PARAM_RAW and has a format field.
|
|
|
147 |
if ($propertyformat = self::get_format_field($properties, $property)) {
|
|
|
148 |
$formatdefinition = $properties[$propertyformat];
|
|
|
149 |
if (!property_exists($record, $propertyformat) && !array_key_exists('default', $formatdefinition)) {
|
|
|
150 |
// Whoops, we got something that wasn't defined.
|
|
|
151 |
throw new coding_exception('Unexpected property ' . $propertyformat);
|
|
|
152 |
}
|
|
|
153 |
|
|
|
154 |
$formatparams = $this->get_format_parameters($property);
|
|
|
155 |
$format = $record->$propertyformat ?? $formatdefinition['default'];
|
|
|
156 |
|
|
|
157 |
list($text, $format) = \core_external\util::format_text($data->$property, $format, $formatparams['context'],
|
|
|
158 |
$formatparams['component'], $formatparams['filearea'], $formatparams['itemid'], $formatparams['options']);
|
|
|
159 |
|
|
|
160 |
$data->$property = $text;
|
|
|
161 |
$data->$propertyformat = $format;
|
|
|
162 |
|
|
|
163 |
} else if ($definition['type'] === PARAM_TEXT) {
|
|
|
164 |
$formatparams = $this->get_format_parameters($property);
|
|
|
165 |
|
|
|
166 |
if (!empty($definition['multiple'])) {
|
|
|
167 |
foreach ($data->$property as $key => $value) {
|
|
|
168 |
$data->{$property}[$key] = \core_external\util::format_string($value, $formatparams['context'],
|
|
|
169 |
$formatparams['striplinks'], $formatparams['options']);
|
|
|
170 |
}
|
|
|
171 |
} else {
|
|
|
172 |
$data->$property = \core_external\util::format_string($data->$property, $formatparams['context'],
|
|
|
173 |
$formatparams['striplinks'], $formatparams['options']);
|
|
|
174 |
}
|
|
|
175 |
}
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
return $data;
|
|
|
179 |
}
|
|
|
180 |
|
|
|
181 |
/**
|
|
|
182 |
* Get the format parameters.
|
|
|
183 |
*
|
|
|
184 |
* This method returns the parameters to use with the functions \core_external\util::format_text(), and
|
|
|
185 |
* \core_external\util::format_string(). To override the default parameters, you can define a protected method
|
|
|
186 |
* called 'get_format_parameters_for_<propertyName>'. For example, 'get_format_parameters_for_description',
|
|
|
187 |
* if your property is 'description'.
|
|
|
188 |
*
|
|
|
189 |
* Your method must return an array containing any of the following keys:
|
|
|
190 |
* - context: The context to use. Defaults to $this->related['context'] if defined, else throws an exception.
|
|
|
191 |
* - component: The component to use with \core_external\util::format_text(). Defaults to null.
|
|
|
192 |
* - filearea: The filearea to use with \core_external\util::format_text(). Defaults to null.
|
|
|
193 |
* - itemid: The itemid to use with \core_external\util::format_text(). Defaults to null.
|
|
|
194 |
* - options: An array of options accepted by \core_external\util::format_text()
|
|
|
195 |
* or \core_external\util::format_string().
|
|
|
196 |
* Defaults to [].
|
|
|
197 |
* - striplinks: Whether to strip the links with \core_external\util::format_string(). Defaults to true.
|
|
|
198 |
*
|
|
|
199 |
* @param string $property The property to get the parameters for.
|
|
|
200 |
* @return array
|
|
|
201 |
*/
|
|
|
202 |
final protected function get_format_parameters($property) {
|
|
|
203 |
$parameters = [
|
|
|
204 |
'component' => null,
|
|
|
205 |
'filearea' => null,
|
|
|
206 |
'itemid' => null,
|
|
|
207 |
'options' => [],
|
|
|
208 |
'striplinks' => true,
|
|
|
209 |
];
|
|
|
210 |
|
|
|
211 |
$candidate = 'get_format_parameters_for_' . $property;
|
|
|
212 |
if (method_exists($this, $candidate)) {
|
|
|
213 |
$parameters = array_merge($parameters, $this->{$candidate}());
|
|
|
214 |
}
|
|
|
215 |
|
|
|
216 |
if (!isset($parameters['context'])) {
|
|
|
217 |
if (!isset($this->related['context']) || !($this->related['context'] instanceof context)) {
|
|
|
218 |
throw new coding_exception("Unknown context to use for formatting the property '$property' in the " .
|
|
|
219 |
"exporter '" . get_class($this) . "'. You either need to add 'context' to your related objects, " .
|
|
|
220 |
"or create the method '$candidate' and return the context from there.");
|
|
|
221 |
}
|
|
|
222 |
$parameters['context'] = $this->related['context'];
|
|
|
223 |
|
|
|
224 |
} else if (!($parameters['context'] instanceof context)) {
|
|
|
225 |
throw new coding_exception("The context given to format the property '$property' in the exporter '" .
|
|
|
226 |
get_class($this) . "' is invalid.");
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
return $parameters;
|
|
|
230 |
}
|
|
|
231 |
|
|
|
232 |
/**
|
|
|
233 |
* Get the additional values to inject while exporting.
|
|
|
234 |
*
|
|
|
235 |
* These are additional generated values that are not passed in through $data
|
|
|
236 |
* to the exporter. For a persistent exporter - these are generated values that
|
|
|
237 |
* do not exist in the persistent class. For your convenience the format_text or
|
|
|
238 |
* format_string functions do not need to be applied to PARAM_TEXT fields,
|
|
|
239 |
* it will be done automatically during export.
|
|
|
240 |
*
|
|
|
241 |
* These values are only used when returning data via {@link self::export()},
|
|
|
242 |
* they are not used when generating any of the different external structures.
|
|
|
243 |
*
|
|
|
244 |
* Note: These must be defined in {@link self::define_other_properties()}.
|
|
|
245 |
*
|
|
|
246 |
* @param renderer_base $output The renderer.
|
|
|
247 |
* @return array Keys are the property names, values are their values.
|
|
|
248 |
*/
|
|
|
249 |
protected function get_other_values(renderer_base $output) {
|
|
|
250 |
return array();
|
|
|
251 |
}
|
|
|
252 |
|
|
|
253 |
/**
|
|
|
254 |
* Get the read properties definition of this exporter. Read properties combines the
|
|
|
255 |
* default properties from the model (persistent or stdClass) with the properties defined
|
|
|
256 |
* by {@link self::define_other_properties()}.
|
|
|
257 |
*
|
|
|
258 |
* @return array Keys are the property names, and value their definition.
|
|
|
259 |
*/
|
|
|
260 |
final public static function read_properties_definition() {
|
|
|
261 |
$properties = static::properties_definition();
|
|
|
262 |
$customprops = static::define_other_properties();
|
|
|
263 |
$customprops = static::format_properties($customprops);
|
|
|
264 |
$properties += $customprops;
|
|
|
265 |
return $properties;
|
|
|
266 |
}
|
|
|
267 |
|
|
|
268 |
/**
|
|
|
269 |
* Recursively formats a given property definition with the default fields required.
|
|
|
270 |
*
|
|
|
271 |
* @param array $properties List of properties to format
|
|
|
272 |
* @return array Formatted array
|
|
|
273 |
*/
|
|
|
274 |
final public static function format_properties($properties) {
|
|
|
275 |
foreach ($properties as $property => $definition) {
|
|
|
276 |
// Ensures that null is set to its default.
|
|
|
277 |
if (!isset($definition['null'])) {
|
|
|
278 |
$properties[$property]['null'] = NULL_NOT_ALLOWED;
|
|
|
279 |
}
|
|
|
280 |
if (!isset($definition['description'])) {
|
|
|
281 |
$properties[$property]['description'] = $property;
|
|
|
282 |
}
|
|
|
283 |
|
|
|
284 |
// If an array is provided, it may be a nested array that is unformatted so rinse and repeat.
|
|
|
285 |
if (is_array($definition['type'])) {
|
|
|
286 |
$properties[$property]['type'] = static::format_properties($definition['type']);
|
|
|
287 |
}
|
|
|
288 |
}
|
|
|
289 |
return $properties;
|
|
|
290 |
}
|
|
|
291 |
|
|
|
292 |
/**
|
|
|
293 |
* Get the properties definition of this exporter used for create, and update structures.
|
|
|
294 |
* The read structures are returned by: {@link self::read_properties_definition()}.
|
|
|
295 |
*
|
|
|
296 |
* @return array Keys are the property names, and value their definition.
|
|
|
297 |
*/
|
|
|
298 |
final public static function properties_definition() {
|
|
|
299 |
$properties = static::define_properties();
|
|
|
300 |
foreach ($properties as $property => $definition) {
|
|
|
301 |
// Ensures that null is set to its default.
|
|
|
302 |
if (!isset($definition['null'])) {
|
|
|
303 |
$properties[$property]['null'] = NULL_NOT_ALLOWED;
|
|
|
304 |
}
|
|
|
305 |
if (!isset($definition['description'])) {
|
|
|
306 |
$properties[$property]['description'] = $property;
|
|
|
307 |
}
|
|
|
308 |
}
|
|
|
309 |
return $properties;
|
|
|
310 |
}
|
|
|
311 |
|
|
|
312 |
/**
|
|
|
313 |
* Return the list of additional properties used only for display.
|
|
|
314 |
*
|
|
|
315 |
* Additional properties are only ever used for the read structure, and during
|
|
|
316 |
* export of the persistent data.
|
|
|
317 |
*
|
|
|
318 |
* The format of the array returned by this method has to match the structure
|
|
|
319 |
* defined in {@link \core\persistent::define_properties()}. The display properties
|
|
|
320 |
* can however do some more fancy things. They can define 'multiple' => true to wrap
|
|
|
321 |
* values in an external_multiple_structure automatically - or they can define the
|
|
|
322 |
* type as a nested array of more properties in order to generate a nested
|
|
|
323 |
* external_single_structure.
|
|
|
324 |
*
|
|
|
325 |
* You can specify an array of values by including a 'multiple' => true array value. This
|
|
|
326 |
* will result in a nested external_multiple_structure.
|
|
|
327 |
* E.g.
|
|
|
328 |
*
|
|
|
329 |
* 'arrayofbools' => array(
|
|
|
330 |
* 'type' => PARAM_BOOL,
|
|
|
331 |
* 'multiple' => true
|
|
|
332 |
* ),
|
|
|
333 |
*
|
|
|
334 |
* You can return a nested array in the type field, which will result in a nested external_single_structure.
|
|
|
335 |
* E.g.
|
|
|
336 |
* 'competency' => array(
|
|
|
337 |
* 'type' => competency_exporter::read_properties_definition()
|
|
|
338 |
* ),
|
|
|
339 |
*
|
|
|
340 |
* Other properties can be specifically marked as optional, in which case they do not need
|
|
|
341 |
* to be included in the export in {@link self::get_other_values()}. This is useful when exporting
|
|
|
342 |
* a substructure which cannot be set as null due to webservices protocol constraints.
|
|
|
343 |
* E.g.
|
|
|
344 |
* 'competency' => array(
|
|
|
345 |
* 'type' => competency_exporter::read_properties_definition(),
|
|
|
346 |
* 'optional' => true
|
|
|
347 |
* ),
|
|
|
348 |
*
|
|
|
349 |
* @return array
|
|
|
350 |
*/
|
|
|
351 |
protected static function define_other_properties() {
|
|
|
352 |
return array();
|
|
|
353 |
}
|
|
|
354 |
|
|
|
355 |
/**
|
|
|
356 |
* Return the list of properties.
|
|
|
357 |
*
|
|
|
358 |
* The format of the array returned by this method has to match the structure
|
|
|
359 |
* defined in {@link \core\persistent::define_properties()}. Howewer you can
|
|
|
360 |
* add a new attribute "description" to describe the parameter for documenting the API.
|
|
|
361 |
*
|
|
|
362 |
* Note that the type PARAM_TEXT should ONLY be used for strings which need to
|
|
|
363 |
* go through filters (multilang, etc...) and do not have a FORMAT_* associated
|
|
|
364 |
* to them. Typically strings passed through to format_string().
|
|
|
365 |
*
|
|
|
366 |
* Other filtered strings which use a FORMAT_* constant (hear used with format_text)
|
|
|
367 |
* must be defined as PARAM_RAW.
|
|
|
368 |
*
|
|
|
369 |
* @return array
|
|
|
370 |
*/
|
|
|
371 |
protected static function define_properties() {
|
|
|
372 |
return array();
|
|
|
373 |
}
|
|
|
374 |
|
|
|
375 |
/**
|
|
|
376 |
* Returns a list of objects that are related to this persistent.
|
|
|
377 |
*
|
|
|
378 |
* Only objects listed here can be cached in this object.
|
|
|
379 |
*
|
|
|
380 |
* The class name can be suffixed:
|
|
|
381 |
* - with [] to indicate an array of values.
|
|
|
382 |
* - with ? to indicate that 'null' is allowed.
|
|
|
383 |
*
|
|
|
384 |
* @return array of 'propertyname' => array('type' => classname, 'required' => true)
|
|
|
385 |
*/
|
|
|
386 |
protected static function define_related() {
|
|
|
387 |
return array();
|
|
|
388 |
}
|
|
|
389 |
|
|
|
390 |
/**
|
|
|
391 |
* Get the context structure.
|
|
|
392 |
*
|
|
|
393 |
* @return array
|
|
|
394 |
*/
|
|
|
395 |
final protected static function get_context_structure() {
|
|
|
396 |
return array(
|
|
|
397 |
'contextid' => new external_value(PARAM_INT, 'The context id', VALUE_OPTIONAL),
|
|
|
398 |
'contextlevel' => new external_value(PARAM_ALPHA, 'The context level', VALUE_OPTIONAL),
|
|
|
399 |
'instanceid' => new external_value(PARAM_INT, 'The Instance id', VALUE_OPTIONAL),
|
|
|
400 |
);
|
|
|
401 |
}
|
|
|
402 |
|
|
|
403 |
/**
|
|
|
404 |
* Get the format field name.
|
|
|
405 |
*
|
|
|
406 |
* @param array $definitions List of properties definitions.
|
|
|
407 |
* @param string $property The name of the property that may have a format field.
|
|
|
408 |
* @return bool|string False, or the name of the format property.
|
|
|
409 |
*/
|
|
|
410 |
final protected static function get_format_field($definitions, $property) {
|
|
|
411 |
$formatproperty = $property . 'format';
|
|
|
412 |
if (($definitions[$property]['type'] == PARAM_RAW || $definitions[$property]['type'] == PARAM_CLEANHTML)
|
|
|
413 |
&& isset($definitions[$formatproperty])
|
|
|
414 |
&& $definitions[$formatproperty]['type'] == PARAM_INT) {
|
|
|
415 |
return $formatproperty;
|
|
|
416 |
}
|
|
|
417 |
return false;
|
|
|
418 |
}
|
|
|
419 |
|
|
|
420 |
/**
|
|
|
421 |
* Get the format structure.
|
|
|
422 |
*
|
|
|
423 |
* @param string $property The name of the property on which the format applies.
|
|
|
424 |
* @param array $definition The definition of the format property.
|
|
|
425 |
* @param int $required Constant VALUE_*.
|
|
|
426 |
* @return external_format_value
|
|
|
427 |
*/
|
|
|
428 |
final protected static function get_format_structure($property, $definition, $required = VALUE_REQUIRED) {
|
|
|
429 |
$default = null;
|
|
|
430 |
if (array_key_exists('default', $definition)) {
|
|
|
431 |
$required = VALUE_DEFAULT;
|
|
|
432 |
$default = $definition['default'];
|
|
|
433 |
}
|
|
|
434 |
return new external_format_value($property, $required, $default);
|
|
|
435 |
}
|
|
|
436 |
|
|
|
437 |
/**
|
|
|
438 |
* Returns the create structure.
|
|
|
439 |
*
|
|
|
440 |
* @return external_single_structure
|
|
|
441 |
*/
|
|
|
442 |
final public static function get_create_structure() {
|
|
|
443 |
$properties = self::properties_definition();
|
|
|
444 |
$returns = array();
|
|
|
445 |
|
|
|
446 |
foreach ($properties as $property => $definition) {
|
|
|
447 |
if ($property == 'id') {
|
|
|
448 |
// The can not be set on create.
|
|
|
449 |
continue;
|
|
|
450 |
|
|
|
451 |
} else if (isset($returns[$property]) && substr($property, -6) === 'format') {
|
|
|
452 |
// We've already treated the format.
|
|
|
453 |
continue;
|
|
|
454 |
}
|
|
|
455 |
|
|
|
456 |
$required = VALUE_REQUIRED;
|
|
|
457 |
$default = null;
|
|
|
458 |
|
|
|
459 |
// We cannot use isset here because we want to detect nulls.
|
|
|
460 |
if (array_key_exists('default', $definition)) {
|
|
|
461 |
$required = VALUE_DEFAULT;
|
|
|
462 |
$default = $definition['default'];
|
|
|
463 |
}
|
|
|
464 |
|
|
|
465 |
// Magically treat the contextid fields.
|
|
|
466 |
if ($property == 'contextid') {
|
|
|
467 |
if (isset($properties['context'])) {
|
|
|
468 |
throw new coding_exception('There cannot be a context and a contextid column');
|
|
|
469 |
}
|
|
|
470 |
$returns += self::get_context_structure();
|
|
|
471 |
|
|
|
472 |
} else {
|
|
|
473 |
$returns[$property] = new external_value($definition['type'], $definition['description'], $required, $default,
|
|
|
474 |
$definition['null']);
|
|
|
475 |
|
|
|
476 |
// Magically treat the format properties.
|
|
|
477 |
if ($formatproperty = self::get_format_field($properties, $property)) {
|
|
|
478 |
if (isset($returns[$formatproperty])) {
|
|
|
479 |
throw new coding_exception('The format for \'' . $property . '\' is already defined.');
|
|
|
480 |
}
|
|
|
481 |
$returns[$formatproperty] = self::get_format_structure($property,
|
|
|
482 |
$properties[$formatproperty], VALUE_REQUIRED);
|
|
|
483 |
}
|
|
|
484 |
}
|
|
|
485 |
}
|
|
|
486 |
|
|
|
487 |
return new external_single_structure($returns);
|
|
|
488 |
}
|
|
|
489 |
|
|
|
490 |
/**
|
|
|
491 |
* Returns the read structure.
|
|
|
492 |
*
|
|
|
493 |
* @param int $required Whether is required.
|
|
|
494 |
* @param mixed $default The default value.
|
|
|
495 |
*
|
|
|
496 |
* @return external_single_structure
|
|
|
497 |
*/
|
|
|
498 |
final public static function get_read_structure($required = VALUE_REQUIRED, $default = null) {
|
|
|
499 |
$properties = self::read_properties_definition();
|
|
|
500 |
|
|
|
501 |
return self::get_read_structure_from_properties($properties, $required, $default);
|
|
|
502 |
}
|
|
|
503 |
|
|
|
504 |
/**
|
|
|
505 |
* Returns the read structure from a set of properties (recursive).
|
|
|
506 |
*
|
|
|
507 |
* @param array $properties The properties.
|
|
|
508 |
* @param int $required Whether is required.
|
|
|
509 |
* @param mixed $default The default value.
|
|
|
510 |
* @return external_single_structure
|
|
|
511 |
*/
|
|
|
512 |
final protected static function get_read_structure_from_properties($properties, $required = VALUE_REQUIRED, $default = null) {
|
|
|
513 |
$returns = array();
|
|
|
514 |
foreach ($properties as $property => $definition) {
|
|
|
515 |
if (isset($returns[$property]) && substr($property, -6) === 'format') {
|
|
|
516 |
// We've already treated the format.
|
|
|
517 |
continue;
|
|
|
518 |
}
|
|
|
519 |
$thisvalue = null;
|
|
|
520 |
|
|
|
521 |
$type = $definition['type'];
|
|
|
522 |
$proprequired = VALUE_REQUIRED;
|
|
|
523 |
$propdefault = null;
|
|
|
524 |
if (array_key_exists('default', $definition)) {
|
|
|
525 |
$propdefault = $definition['default'];
|
|
|
526 |
}
|
|
|
527 |
if (array_key_exists('optional', $definition)) {
|
|
|
528 |
// Mark as optional. Note that this should only apply to "reading" "other" properties.
|
|
|
529 |
$proprequired = VALUE_OPTIONAL;
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
if (is_array($type)) {
|
|
|
533 |
// This is a nested array of more properties.
|
|
|
534 |
$thisvalue = self::get_read_structure_from_properties($type, $proprequired, $propdefault);
|
|
|
535 |
} else {
|
|
|
536 |
if ($definition['type'] == PARAM_TEXT || $definition['type'] == PARAM_CLEANHTML) {
|
|
|
537 |
// PARAM_TEXT always becomes PARAM_RAW because filters may be applied.
|
|
|
538 |
$type = PARAM_RAW;
|
|
|
539 |
}
|
|
|
540 |
$thisvalue = new external_value($type, $definition['description'], $proprequired, $propdefault, $definition['null']);
|
|
|
541 |
}
|
|
|
542 |
if (!empty($definition['multiple'])) {
|
|
|
543 |
$returns[$property] = new external_multiple_structure($thisvalue, $definition['description'], $proprequired,
|
|
|
544 |
$propdefault);
|
|
|
545 |
} else {
|
|
|
546 |
$returns[$property] = $thisvalue;
|
|
|
547 |
|
|
|
548 |
// Magically treat the format properties (not possible for arrays).
|
|
|
549 |
if ($formatproperty = self::get_format_field($properties, $property)) {
|
|
|
550 |
if (isset($returns[$formatproperty])) {
|
|
|
551 |
throw new coding_exception('The format for \'' . $property . '\' is already defined.');
|
|
|
552 |
}
|
|
|
553 |
$formatpropertydef = $properties[$formatproperty];
|
|
|
554 |
$formatpropertyrequired = VALUE_REQUIRED;
|
|
|
555 |
if (!empty($formatpropertydef['optional'])) {
|
|
|
556 |
$formatpropertyrequired = VALUE_OPTIONAL;
|
|
|
557 |
}
|
|
|
558 |
$returns[$formatproperty] = self::get_format_structure($property, $formatpropertydef, $formatpropertyrequired);
|
|
|
559 |
}
|
|
|
560 |
}
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
return new external_single_structure($returns, '', $required, $default);
|
|
|
564 |
}
|
|
|
565 |
|
|
|
566 |
/**
|
|
|
567 |
* Returns the update structure.
|
|
|
568 |
*
|
|
|
569 |
* This structure can never be included at the top level for an external function signature
|
|
|
570 |
* because it contains optional parameters.
|
|
|
571 |
*
|
|
|
572 |
* @return external_single_structure
|
|
|
573 |
*/
|
|
|
574 |
final public static function get_update_structure() {
|
|
|
575 |
$properties = self::properties_definition();
|
|
|
576 |
$returns = array();
|
|
|
577 |
|
|
|
578 |
foreach ($properties as $property => $definition) {
|
|
|
579 |
if (isset($returns[$property]) && substr($property, -6) === 'format') {
|
|
|
580 |
// We've already treated the format.
|
|
|
581 |
continue;
|
|
|
582 |
}
|
|
|
583 |
|
|
|
584 |
$default = null;
|
|
|
585 |
$required = VALUE_OPTIONAL;
|
|
|
586 |
if ($property == 'id') {
|
|
|
587 |
$required = VALUE_REQUIRED;
|
|
|
588 |
}
|
|
|
589 |
|
|
|
590 |
// Magically treat the contextid fields.
|
|
|
591 |
if ($property == 'contextid') {
|
|
|
592 |
if (isset($properties['context'])) {
|
|
|
593 |
throw new coding_exception('There cannot be a context and a contextid column');
|
|
|
594 |
}
|
|
|
595 |
$returns += self::get_context_structure();
|
|
|
596 |
|
|
|
597 |
} else {
|
|
|
598 |
$returns[$property] = new external_value($definition['type'], $definition['description'], $required, $default,
|
|
|
599 |
$definition['null']);
|
|
|
600 |
|
|
|
601 |
// Magically treat the format properties.
|
|
|
602 |
if ($formatproperty = self::get_format_field($properties, $property)) {
|
|
|
603 |
if (isset($returns[$formatproperty])) {
|
|
|
604 |
throw new coding_exception('The format for \'' . $property . '\' is already defined.');
|
|
|
605 |
}
|
|
|
606 |
$returns[$formatproperty] = self::get_format_structure($property,
|
|
|
607 |
$properties[$formatproperty], VALUE_OPTIONAL);
|
|
|
608 |
}
|
|
|
609 |
}
|
|
|
610 |
}
|
|
|
611 |
|
|
|
612 |
return new external_single_structure($returns);
|
|
|
613 |
}
|
|
|
614 |
|
|
|
615 |
}
|