[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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_competency 21 * @copyright 2015 Damyon Wiese 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_competency\external; 25 defined('MOODLE_INTERNAL') || die(); 26 27 require_once($CFG->libdir . '/externallib.php'); 28 29 use stdClass; 30 use renderer_base; 31 use context; 32 use context_system; 33 use coding_exception; 34 use external_single_structure; 35 use external_multiple_structure; 36 use external_value; 37 use external_format_value; 38 39 /** 40 * Generic exporter to take a stdClass and prepare it for return by webservice, or as the context for a template. 41 * 42 * templatable classes implementing export_for_template, should always use a standard exporter if it exists. 43 * External functions should always use a standard exporter if it exists. 44 * 45 * @copyright 2015 Damyon Wiese 46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 47 */ 48 abstract class exporter { 49 50 /** @var array $related List of related objects used to avoid DB queries. */ 51 protected $related = array(); 52 53 /** @var stdClass|array The data of this exporter. */ 54 protected $data = null; 55 56 /** 57 * Constructor - saves the persistent object, and the related objects. 58 * 59 * @param mixed $data - Either an stdClass or an array of values. 60 * @param array $related - An optional list of pre-loaded objects related to this object. 61 */ 62 public function __construct($data, $related = array()) { 63 $this->data = $data; 64 // Cache the valid related objects. 65 foreach (static::define_related() as $key => $classname) { 66 $isarray = false; 67 $nullallowed = false; 68 69 // Allow ? to mean null is allowed. 70 if (substr($classname, -1) === '?') { 71 $classname = substr($classname, 0, -1); 72 $nullallowed = true; 73 } 74 75 // Allow [] to mean an array of values. 76 if (substr($classname, -2) === '[]') { 77 $classname = substr($classname, 0, -2); 78 $isarray = true; 79 } 80 81 $missingdataerr = 'Exporter class is missing required related data: (' . get_called_class() . ') '; 82 83 if ($nullallowed && array_key_exists($key, $related) && $related[$key] === null) { 84 $this->related[$key] = $related[$key]; 85 86 } else if ($isarray) { 87 if (array_key_exists($key, $related) && is_array($related[$key])) { 88 foreach ($related[$key] as $index => $value) { 89 if (!$value instanceof $classname) { 90 throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]'); 91 } 92 } 93 $this->related[$key] = $related[$key]; 94 } else { 95 throw new coding_exception($missingdataerr . $key . ' => ' . $classname . '[]'); 96 } 97 98 } else { 99 if (array_key_exists($key, $related) && $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 external_format_text (or external_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 $context = $this->get_context(); 120 $values = (array) $this->data; 121 122 $othervalues = $this->get_other_values($output); 123 if (array_intersect_key($values, $othervalues)) { 124 // Attempt to replace a standard property. 125 throw new coding_exception('Cannot override a standard property value.'); 126 } 127 $values += $othervalues; 128 $record = (object) $values; 129 130 foreach ($properties as $property => $definition) { 131 if (isset($data->$property)) { 132 // This happens when we have already defined the format properties. 133 continue; 134 } else if (!property_exists($record, $property) && array_key_exists('default', $definition)) { 135 // We have a default value for this property. 136 $record->$property = $definition['default']; 137 } else if (!property_exists($record, $property) && !empty($definition['optional'])) { 138 // Fine, this property can be omitted. 139 continue; 140 } else if (!property_exists($record, $property)) { 141 // Whoops, we got something that wasn't defined. 142 throw new coding_exception('Unexpected property ' . $property); 143 } 144 145 $data->$property = $record->$property; 146 147 // If the field is PARAM_RAW and has a format field. 148 if ($propertyformat = self::get_format_field($properties, $property)) { 149 if (!property_exists($record, $propertyformat)) { 150 // Whoops, we got something that wasn't defined. 151 throw new coding_exception('Unexpected property ' . $propertyformat); 152 } 153 $format = $record->$propertyformat; 154 list($text, $format) = external_format_text($data->$property, $format, $context->id, 'core_competency', '', 0); 155 $data->$property = $text; 156 $data->$propertyformat = $format; 157 158 } else if ($definition['type'] === PARAM_TEXT) { 159 if (!empty($definition['multiple'])) { 160 foreach ($data->$property as $key => $value) { 161 $data->$property[$key] = external_format_string($value, $context->id); 162 } 163 } else { 164 $data->$property = external_format_string($data->$property, $context->id); 165 } 166 } 167 } 168 169 return $data; 170 } 171 172 /** 173 * Function to guess the correct context, falling back to system context. 174 * 175 * @return context 176 */ 177 protected function get_context() { 178 $context = null; 179 if (isset($this->related['context']) && $this->related['context'] instanceof context) { 180 $context = $this->related['context']; 181 } else { 182 $context = context_system::instance(); 183 } 184 return $context; 185 } 186 187 /** 188 * Get the additional values to inject while exporting. 189 * 190 * These are additional generated values that are not passed in through $data 191 * to the exporter. For a persistent exporter - these are generated values that 192 * do not exist in the persistent class. For your convenience the format_text or 193 * format_string functions do not need to be applied to PARAM_TEXT fields, 194 * it will be done automatically during export. 195 * 196 * These values are only used when returning data via {@link self::export()}, 197 * they are not used when generating any of the different external structures. 198 * 199 * Note: These must be defined in {@link self::define_other_properties()}. 200 * 201 * @param renderer_base $output The renderer. 202 * @return array Keys are the property names, values are their values. 203 */ 204 protected function get_other_values(renderer_base $output) { 205 return array(); 206 } 207 208 /** 209 * Get the read properties definition of this exporter. Read properties combines the 210 * default properties from the model (persistent or stdClass) with the properties defined 211 * by {@link self::define_other_properties()}. 212 * 213 * @return array Keys are the property names, and value their definition. 214 */ 215 final public static function read_properties_definition() { 216 $properties = static::properties_definition(); 217 $customprops = static::define_other_properties(); 218 foreach ($customprops as $property => $definition) { 219 // Ensures that null is set to its default. 220 if (!isset($definition['null'])) { 221 $customprops[$property]['null'] = NULL_NOT_ALLOWED; 222 } 223 } 224 $properties += $customprops; 225 return $properties; 226 } 227 228 /** 229 * Get the properties definition of this exporter used for create, and update structures. 230 * The read structures are returned by: {@link self::read_properties_definition()}. 231 * 232 * @return array Keys are the property names, and value their definition. 233 */ 234 final public static function properties_definition() { 235 $properties = static::define_properties(); 236 foreach ($properties as $property => $definition) { 237 // Ensures that null is set to its default. 238 if (!isset($definition['null'])) { 239 $properties[$property]['null'] = NULL_NOT_ALLOWED; 240 } 241 } 242 return $properties; 243 } 244 245 /** 246 * Return the list of additional properties used only for display. 247 * 248 * Additional properties are only ever used for the read structure, and during 249 * export of the persistent data. 250 * 251 * The format of the array returned by this method has to match the structure 252 * defined in {@link \core_competency\persistent::define_properties()}. The display properties 253 * can however do some more fancy things. They can define 'multiple' => true to wrap 254 * values in an external_multiple_structure automatically - or they can define the 255 * type as a nested array of more properties in order to generate a nested 256 * external_single_structure. 257 * 258 * You can specify an array of values by including a 'multiple' => true array value. This 259 * will result in a nested external_multiple_structure. 260 * E.g. 261 * 262 * 'arrayofbools' => array( 263 * 'type' => PARAM_BOOL, 264 * 'multiple' => true 265 * ), 266 * 267 * You can return a nested array in the type field, which will result in a nested external_single_structure. 268 * E.g. 269 * 'competency' => array( 270 * 'type' => competency_exporter::read_properties_definition() 271 * ), 272 * 273 * Other properties can be specifically marked as optional, in which case they do not need 274 * to be included in the export in {@link self::get_other_values()}. This is useful when exporting 275 * a substructure which cannot be set as null due to webservices protocol constraints. 276 * E.g. 277 * 'competency' => array( 278 * 'type' => competency_exporter::read_properties_definition(), 279 * 'optional' => true 280 * ), 281 * 282 * @return array 283 */ 284 protected static function define_other_properties() { 285 return array(); 286 } 287 288 /** 289 * Return the list of properties. 290 * 291 * The format of the array returned by this method has to match the structure 292 * defined in {@link \core_competency\persistent::define_properties()}. 293 * 294 * @return array 295 */ 296 protected static function define_properties() { 297 return array(); 298 } 299 300 /** 301 * Returns a list of objects that are related to this persistent. 302 * 303 * Only objects listed here can be cached in this object. 304 * 305 * The class name can be suffixed: 306 * - with [] to indicate an array of values. 307 * - with ? to indicate that 'null' is allowed. 308 * 309 * @return array of 'propertyname' => array('type' => classname, 'required' => true) 310 */ 311 protected static function define_related() { 312 return array(); 313 } 314 315 /** 316 * Get the context structure. 317 * 318 * @return external_single_structure 319 */ 320 final protected static function get_context_structure() { 321 return array( 322 'contextid' => new external_value(PARAM_INT, 'The context id', VALUE_OPTIONAL), 323 'contextlevel' => new external_value(PARAM_ALPHA, 'The context level', VALUE_OPTIONAL), 324 'instanceid' => new external_value(PARAM_INT, 'The Instance id', VALUE_OPTIONAL), 325 ); 326 } 327 328 /** 329 * Get the format field name. 330 * 331 * @param array $definitions List of properties definitions. 332 * @param string $property The name of the property that may have a format field. 333 * @return bool|string False, or the name of the format property. 334 */ 335 final protected static function get_format_field($definitions, $property) { 336 $formatproperty = $property . 'format'; 337 if ($definitions[$property]['type'] == PARAM_RAW && isset($definitions[$formatproperty]) 338 && $definitions[$formatproperty]['type'] == PARAM_INT) { 339 return $formatproperty; 340 } 341 return false; 342 } 343 344 /** 345 * Get the format structure. 346 * 347 * @param string $property The name of the property on which the format applies. 348 * @param array $definition The definition of the format property. 349 * @param int $required Constant VALUE_*. 350 * @return external_format_value 351 */ 352 final protected static function get_format_structure($property, $definition, $required = VALUE_REQUIRED) { 353 if (array_key_exists('default', $definition)) { 354 $required = VALUE_DEFAULT; 355 } 356 return new external_format_value($property, $required); 357 } 358 359 /** 360 * Returns the create structure. 361 * 362 * @return external_single_structure 363 */ 364 final public static function get_create_structure() { 365 $properties = self::properties_definition(); 366 $returns = array(); 367 368 foreach ($properties as $property => $definition) { 369 if ($property == 'id') { 370 // The can not be set on create. 371 continue; 372 373 } else if (isset($returns[$property]) && substr($property, -6) === 'format') { 374 // We've already treated the format. 375 continue; 376 } 377 378 $required = VALUE_REQUIRED; 379 $default = null; 380 381 // We cannot use isset here because we want to detect nulls. 382 if (array_key_exists('default', $definition)) { 383 $required = VALUE_DEFAULT; 384 $default = $definition['default']; 385 } 386 387 // Magically treat the contextid fields. 388 if ($property == 'contextid') { 389 if (isset($properties['context'])) { 390 throw new coding_exception('There cannot be a context and a contextid column'); 391 } 392 $returns += self::get_context_structure(); 393 394 } else { 395 $returns[$property] = new external_value($definition['type'], $property, $required, $default, $definition['null']); 396 397 // Magically treat the format properties. 398 if ($formatproperty = self::get_format_field($properties, $property)) { 399 if (isset($returns[$formatproperty])) { 400 throw new coding_exception('The format for \'' . $property . '\' is already defined.'); 401 } 402 $returns[$formatproperty] = self::get_format_structure($property, 403 $properties[$formatproperty], VALUE_REQUIRED); 404 } 405 } 406 } 407 408 return new external_single_structure($returns); 409 } 410 411 /** 412 * Returns the read structure. 413 * 414 * @return external_single_structure 415 */ 416 final public static function get_read_structure() { 417 $properties = self::read_properties_definition(); 418 419 return self::get_read_structure_from_properties($properties); 420 } 421 422 /** 423 * Returns the read structure from a set of properties (recursive). 424 * 425 * @param array $properties The properties. 426 * @param int $required Whether is required. 427 * @param mixed $default The default value. 428 * @return external_single_structure 429 */ 430 final protected static function get_read_structure_from_properties($properties, $required = VALUE_REQUIRED, $default = null) { 431 $returns = array(); 432 foreach ($properties as $property => $definition) { 433 if (isset($returns[$property]) && substr($property, -6) === 'format') { 434 // We've already treated the format. 435 continue; 436 } 437 $thisvalue = null; 438 439 $type = $definition['type']; 440 $proprequired = VALUE_REQUIRED; 441 $propdefault = null; 442 if (array_key_exists('default', $definition)) { 443 $propdefault = $definition['default']; 444 } 445 if (array_key_exists('optional', $definition)) { 446 // Mark as optional. Note that this should only apply to "reading" "other" properties. 447 $proprequired = VALUE_OPTIONAL; 448 } 449 450 if (is_array($type)) { 451 // This is a nested array of more properties. 452 $thisvalue = self::get_read_structure_from_properties($type, $proprequired, $propdefault); 453 } else { 454 if ($definition['type'] == PARAM_TEXT) { 455 // PARAM_TEXT always becomes PARAM_RAW because filters may be applied. 456 $type = PARAM_RAW; 457 } 458 $thisvalue = new external_value($type, $property, $proprequired, $propdefault, $definition['null']); 459 } 460 if (!empty($definition['multiple'])) { 461 $returns[$property] = new external_multiple_structure($thisvalue, '', $proprequired, $propdefault); 462 } else { 463 $returns[$property] = $thisvalue; 464 465 // Magically treat the format properties (not possible for arrays). 466 if ($formatproperty = self::get_format_field($properties, $property)) { 467 if (isset($returns[$formatproperty])) { 468 throw new coding_exception('The format for \'' . $property . '\' is already defined.'); 469 } 470 $returns[$formatproperty] = self::get_format_structure($property, $properties[$formatproperty]); 471 } 472 } 473 } 474 475 return new external_single_structure($returns, '', $required, $default); 476 } 477 478 /** 479 * Returns the update structure. 480 * 481 * This structure can never be included at the top level for an external function signature 482 * because it contains optional parameters. 483 * 484 * @return external_single_structure 485 */ 486 final public static function get_update_structure() { 487 $properties = self::properties_definition(); 488 $returns = array(); 489 490 foreach ($properties as $property => $definition) { 491 if (isset($returns[$property]) && substr($property, -6) === 'format') { 492 // We've already treated the format. 493 continue; 494 } 495 496 $default = null; 497 $required = VALUE_OPTIONAL; 498 if ($property == 'id') { 499 $required = VALUE_REQUIRED; 500 } 501 502 // Magically treat the contextid fields. 503 if ($property == 'contextid') { 504 if (isset($properties['context'])) { 505 throw new coding_exception('There cannot be a context and a contextid column'); 506 } 507 $returns += self::get_context_structure(); 508 509 } else { 510 $returns[$property] = new external_value($definition['type'], $property, $required, $default, $definition['null']); 511 512 // Magically treat the format properties. 513 if ($formatproperty = self::get_format_field($properties, $property)) { 514 if (isset($returns[$formatproperty])) { 515 throw new coding_exception('The format for \'' . $property . '\' is already defined.'); 516 } 517 $returns[$formatproperty] = self::get_format_structure($property, 518 $properties[$formatproperty], VALUE_OPTIONAL); 519 } 520 } 521 } 522 523 return new external_single_structure($returns); 524 } 525 526 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |