Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403]

   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   * The abstract custom fields handler
  19   *
  20   * @package   core_customfield
  21   * @copyright 2018 David Matamoros <davidmc@moodle.com>
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_customfield;
  26  
  27  use core_customfield\output\field_data;
  28  use stdClass;
  29  
  30  defined('MOODLE_INTERNAL') || die;
  31  
  32  /**
  33   * Base class for custom fields handlers
  34   *
  35   * This handler provides callbacks for field configuration form and also allows to add the fields to the instance editing form
  36   *
  37   * Every plugin that wants to use custom fields must define a handler class:
  38   * <COMPONENT_OR_PLUGIN>\customfield\<AREA>_handler extends \core_customfield\handler
  39   *
  40   * To initiate a class use an appropriate static method:
  41   * - <handlerclass>::create - to create an instance of a known handler
  42   * - \core_customfield\handler::get_handler - to create an instance of a handler for given component/area/itemid
  43   *
  44   * Also handler is automatically created when the following methods are called:
  45   * - \core_customfield\api::get_field($fieldid)
  46   * - \core_customfield\api::get_category($categoryid)
  47   *
  48   * @package core_customfield
  49   * @copyright 2018 David Matamoros <davidmc@moodle.com>
  50   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  51   */
  52  abstract class handler {
  53  
  54      /**
  55       * The component this handler handles
  56       *
  57       * @var string $component
  58       */
  59      private $component;
  60  
  61      /**
  62       * The area within the component
  63       *
  64       * @var string $area
  65       */
  66      private $area;
  67  
  68      /**
  69       * The id of the item within the area and component
  70  
  71       * @var int $itemid
  72       */
  73      private $itemid;
  74  
  75      /**
  76       * @var category_controller[]
  77       */
  78      protected $categories = null;
  79  
  80      /**
  81       * Handler constructor.
  82       *
  83       * @param int $itemid
  84       */
  85      protected final function __construct(int $itemid = 0) {
  86          if (!preg_match('|^(\w+_[\w_]+)\\\\customfield\\\\([\w_]+)_handler$|', static::class, $matches)) {
  87              throw new \coding_exception('Handler class name must have format: <PLUGIN>\\customfield\\<AREA>_handler');
  88          }
  89          $this->component = $matches[1];
  90          $this->area = $matches[2];
  91          $this->itemid = $itemid;
  92      }
  93  
  94      /**
  95       * Returns an instance of the handler
  96       *
  97       * Some areas may choose to use singleton/caching here
  98       *
  99       * @param int $itemid
 100       * @return handler
 101       */
 102      public static function create(int $itemid = 0) : handler {
 103          return new static($itemid);
 104      }
 105  
 106      /**
 107       * Returns an instance of handler by component/area/itemid
 108       *
 109       * @param string $component component name of full frankenstyle plugin name
 110       * @param string $area name of the area (each component/plugin may define handlers for multiple areas)
 111       * @param int $itemid item id if the area uses them (usually not used)
 112       * @return handler
 113       */
 114      public static function get_handler(string $component, string $area, int $itemid = 0) : handler {
 115          $classname = $component . '\\customfield\\' . $area . '_handler';
 116          if (class_exists($classname) && is_subclass_of($classname, self::class)) {
 117              return $classname::create($itemid);
 118          }
 119          $a = ['component' => s($component), 'area' => s($area)];
 120          throw new \moodle_exception('unknownhandler', 'core_customfield', '', $a);
 121      }
 122  
 123      /**
 124       * Get component
 125       *
 126       * @return string
 127       */
 128      public function get_component() : string {
 129          return $this->component;
 130      }
 131  
 132      /**
 133       * Get area
 134       *
 135       * @return string
 136       */
 137      public function get_area() : string {
 138          return $this->area;
 139      }
 140  
 141      /**
 142       * Context that should be used for new categories created by this handler
 143       *
 144       * @return \context
 145       */
 146      abstract public function get_configuration_context() : \context;
 147  
 148      /**
 149       * URL for configuration of the fields on this handler.
 150       *
 151       * @return \moodle_url
 152       */
 153      abstract public function get_configuration_url() : \moodle_url;
 154  
 155      /**
 156       * Context that should be used for data stored for the given record
 157       *
 158       * @param int $instanceid id of the instance or 0 if the instance is being created
 159       * @return \context
 160       */
 161      abstract public function get_instance_context(int $instanceid = 0) : \context;
 162  
 163      /**
 164       * Get itemid
 165       *
 166       * @return int|null
 167       */
 168      public function get_itemid() : int {
 169          return $this->itemid;
 170      }
 171  
 172      /**
 173       * Uses categories
 174       *
 175       * @return bool
 176       */
 177      public function uses_categories() : bool {
 178          return true;
 179      }
 180  
 181      /**
 182       * Generates a name for the new category
 183       *
 184       * @param int $suffix
 185       * @return string
 186       */
 187      protected function generate_category_name($suffix = 0) : string {
 188          if ($suffix) {
 189              return get_string('otherfieldsn', 'core_customfield', $suffix);
 190          } else {
 191              return get_string('otherfields', 'core_customfield');
 192          }
 193      }
 194  
 195      /**
 196       * Creates a new category and inserts it to the database
 197       *
 198       * @param string $name name of the category, null to generate automatically
 199       * @return int id of the new category
 200       */
 201      public function create_category(string $name = null) : int {
 202          global $DB;
 203          $params = ['component' => $this->get_component(), 'area' => $this->get_area(), 'itemid' => $this->get_itemid()];
 204  
 205          if (empty($name)) {
 206              for ($suffix = 0; $suffix < 100; $suffix++) {
 207                  $name = $this->generate_category_name($suffix);
 208                  if (!$DB->record_exists(category::TABLE, $params + ['name' => $name])) {
 209                      break;
 210                  }
 211              }
 212          }
 213  
 214          $category = category_controller::create(0, (object)['name' => $name], $this);
 215          api::save_category($category);
 216          $this->clear_configuration_cache();
 217          return $category->get('id');
 218      }
 219  
 220      /**
 221       * Validate that the given category belongs to this handler
 222       *
 223       * @param category_controller $category
 224       * @return category_controller
 225       * @throws \moodle_exception
 226       */
 227      protected function validate_category(category_controller $category) : category_controller {
 228          $categories = $this->get_categories_with_fields();
 229          if (!array_key_exists($category->get('id'), $categories)) {
 230              throw new \moodle_exception('categorynotfound', 'core_customfield');
 231          }
 232          return $categories[$category->get('id')];
 233      }
 234  
 235      /**
 236       * Validate that the given field belongs to this handler
 237       *
 238       * @param field_controller $field
 239       * @return field_controller
 240       * @throws \moodle_exception
 241       */
 242      protected function validate_field(field_controller $field) : field_controller {
 243          if (!array_key_exists($field->get('categoryid'), $this->get_categories_with_fields())) {
 244              throw new \moodle_exception('fieldnotfound', 'core_customfield');
 245          }
 246          $category = $this->get_categories_with_fields()[$field->get('categoryid')];
 247          if (!array_key_exists($field->get('id'), $category->get_fields())) {
 248              throw new \moodle_exception('fieldnotfound', 'core_customfield');
 249          }
 250          return $category->get_fields()[$field->get('id')];
 251      }
 252  
 253      /**
 254       * Change name for a field category
 255       *
 256       * @param category_controller $category
 257       * @param string $name
 258       */
 259      public function rename_category(category_controller $category, string $name) {
 260          $this->validate_category($category);
 261          $category->set('name', $name);
 262          api::save_category($category);
 263          $this->clear_configuration_cache();
 264      }
 265  
 266      /**
 267       * Change sort order of the categories
 268       *
 269       * @param category_controller $category category that needs to be moved
 270       * @param int $beforeid id of the category this category needs to be moved before, 0 to move to the end
 271       */
 272      public function move_category(category_controller $category, int $beforeid = 0) {
 273          $category = $this->validate_category($category);
 274          api::move_category($category, $beforeid);
 275          $this->clear_configuration_cache();
 276      }
 277  
 278      /**
 279       * Permanently delete category, all fields in it and all associated data
 280       *
 281       * @param category_controller $category
 282       * @return bool
 283       */
 284      public function delete_category(category_controller $category) : bool {
 285          $category = $this->validate_category($category);
 286          $result = api::delete_category($category);
 287          $this->clear_configuration_cache();
 288          return $result;
 289      }
 290  
 291      /**
 292       * Deletes all data and all fields and categories defined in this handler
 293       */
 294      public function delete_all() {
 295          $categories = $this->get_categories_with_fields();
 296          foreach ($categories as $category) {
 297              api::delete_category($category);
 298          }
 299          $this->clear_configuration_cache();
 300      }
 301  
 302      /**
 303       * Permanently delete a custom field configuration and all associated data
 304       *
 305       * @param field_controller $field
 306       * @return bool
 307       */
 308      public function delete_field_configuration(field_controller $field) : bool {
 309          $field = $this->validate_field($field);
 310          $result = api::delete_field_configuration($field);
 311          $this->clear_configuration_cache();
 312          return $result;
 313      }
 314  
 315      /**
 316       * Change fields sort order, move field to another category
 317       *
 318       * @param field_controller $field field that needs to be moved
 319       * @param int $categoryid category that needs to be moved
 320       * @param int $beforeid id of the category this category needs to be moved before, 0 to move to the end
 321       */
 322      public function move_field(field_controller $field, int $categoryid, int $beforeid = 0) {
 323          $field = $this->validate_field($field);
 324          api::move_field($field, $categoryid, $beforeid);
 325          $this->clear_configuration_cache();
 326      }
 327  
 328      /**
 329       * The current user can configure custom fields on this component.
 330       *
 331       * @return bool
 332       */
 333      abstract public function can_configure() : bool;
 334  
 335      /**
 336       * The current user can edit given custom fields on the given instance
 337       *
 338       * Called to filter list of fields displayed on the instance edit form
 339       *
 340       * Capability to edit/create instance is checked separately
 341       *
 342       * @param field_controller $field
 343       * @param int $instanceid id of the instance or 0 if the instance is being created
 344       * @return bool
 345       */
 346      abstract public function can_edit(field_controller $field, int $instanceid = 0) : bool;
 347  
 348      /**
 349       * The current user can view the value of the custom field for a given custom field and instance
 350       *
 351       * Called to filter list of fields returned by methods get_instance_data(), get_instances_data(),
 352       * export_instance_data(), export_instance_data_object()
 353       *
 354       * Access to the instance itself is checked by handler before calling these methods
 355       *
 356       * @param field_controller $field
 357       * @param int $instanceid
 358       * @return bool
 359       */
 360      abstract public function can_view(field_controller $field, int $instanceid) : bool;
 361  
 362      /**
 363       * Returns the custom field values for an individual instance
 364       *
 365       * The caller must check access to the instance itself before invoking this method
 366       *
 367       * The result is an array of data_controller objects
 368       *
 369       * @param int $instanceid
 370       * @param bool $returnall return data for all fields (by default only visible fields)
 371       * @return data_controller[] array of data_controller objects indexed by fieldid. All fields are present,
 372       *     some data_controller objects may have 'id', some not
 373       *     In the last case data_controller::get_value() and export_value() functions will return default values.
 374       */
 375      public function get_instance_data(int $instanceid, bool $returnall = false) : array {
 376          $fields = $returnall ? $this->get_fields() : $this->get_visible_fields($instanceid);
 377          return api::get_instance_fields_data($fields, $instanceid);
 378      }
 379  
 380      /**
 381       * Returns the custom fields values for multiple instances
 382       *
 383       * The caller must check access to the instance itself before invoking this method
 384       *
 385       * The result is an array of data_controller objects
 386       *
 387       * @param int[] $instanceids
 388       * @param bool $returnall return data for all fields (by default only visible fields)
 389       * @return data_controller[][] 2-dimension array, first index is instanceid, second index is fieldid.
 390       *     All instanceids and all fieldids are present, some data_controller objects may have 'id', some not.
 391       *     In the last case data_controller::get_value() and export_value() functions will return default values.
 392       */
 393      public function get_instances_data(array $instanceids, bool $returnall = false) : array {
 394          $result = api::get_instances_fields_data($this->get_fields(), $instanceids);
 395  
 396          if (!$returnall) {
 397              // Filter only by visible fields (list of visible fields may be different for each instance).
 398              $handler = $this;
 399              foreach ($instanceids as $instanceid) {
 400                  $result[$instanceid] = array_filter($result[$instanceid], function(data_controller $d) use ($handler) {
 401                      return $handler->can_view($d->get_field(), $d->get('instanceid'));
 402                  });
 403              }
 404          }
 405          return $result;
 406      }
 407  
 408      /**
 409       * Returns the custom field values for an individual instance ready to be displayed
 410       *
 411       * The caller must check access to the instance itself before invoking this method
 412       *
 413       * The result is an array of \core_customfield\output\field_data objects
 414       *
 415       * @param int $instanceid
 416       * @param bool $returnall
 417       * @return \core_customfield\output\field_data[]
 418       */
 419      public function export_instance_data(int $instanceid, bool $returnall = false) : array {
 420          return array_map(function($d) {
 421              return new field_data($d);
 422          }, $this->get_instance_data($instanceid, $returnall));
 423      }
 424  
 425      /**
 426       * Returns the custom field values for an individual instance ready to be displayed
 427       *
 428       * The caller must check access to the instance itself before invoking this method
 429       *
 430       * The result is a class where properties are fields short names and the values their export values for this instance
 431       *
 432       * @param int $instanceid
 433       * @param bool $returnall
 434       * @return stdClass
 435       */
 436      public function export_instance_data_object(int $instanceid, bool $returnall = false) : stdClass {
 437          $rv = new stdClass();
 438          foreach ($this->export_instance_data($instanceid, $returnall) as $d) {
 439              $rv->{$d->get_shortname()} = $d->get_value();
 440          }
 441          return $rv;
 442      }
 443  
 444      /**
 445       * Display visible custom fields.
 446       * This is a sample implementation that can be overridden in each handler.
 447       *
 448       * @param data_controller[] $fieldsdata
 449       * @return string
 450       */
 451      public function display_custom_fields_data(array $fieldsdata) : string {
 452          global $PAGE;
 453          $output = $PAGE->get_renderer('core_customfield');
 454          $content = '';
 455          foreach ($fieldsdata as $data) {
 456              $fd = new field_data($data);
 457              $content .= $output->render($fd);
 458          }
 459  
 460          return $content;
 461      }
 462  
 463      /**
 464       * Returns array of categories, each of them contains a list of fields definitions.
 465       *
 466       * @return category_controller[]
 467       */
 468      public function get_categories_with_fields() : array {
 469          if ($this->categories === null) {
 470              $this->categories = api::get_categories_with_fields($this->get_component(), $this->get_area(), $this->get_itemid());
 471          }
 472          $handler = $this;
 473          array_walk($this->categories, function(category_controller $c) use ($handler) {
 474              $c->set_handler($handler);
 475          });
 476          return $this->categories;
 477      }
 478  
 479      /**
 480       * Clears a list of categories with corresponding fields definitions.
 481       */
 482      protected function clear_configuration_cache() {
 483          $this->categories = null;
 484      }
 485  
 486      /**
 487       * Checks if current user can backup a given field
 488       *
 489       * Capability to backup the instance does not need to be checked here
 490       *
 491       * @param field_controller $field
 492       * @param int $instanceid
 493       * @return bool
 494       */
 495      protected function can_backup(field_controller $field, int $instanceid) : bool {
 496          return $this->can_view($field, $instanceid) || $this->can_edit($field, $instanceid);
 497      }
 498  
 499      /**
 500       * Get raw data associated with all fields current user can view or edit
 501       *
 502       * @param int $instanceid
 503       * @return array
 504       */
 505      public function get_instance_data_for_backup(int $instanceid) : array {
 506          $finalfields = [];
 507          $data = $this->get_instance_data($instanceid, true);
 508          foreach ($data as $d) {
 509              if ($d->get('id') && $this->can_backup($d->get_field(), $instanceid)) {
 510                  $finalfields[] = [
 511                      'id' => $d->get('id'),
 512                      'shortname' => $d->get_field()->get('shortname'),
 513                      'type' => $d->get_field()->get('type'),
 514                      'value' => $d->get_value(),
 515                      'valueformat' => $d->get('valueformat')];
 516              }
 517          }
 518          return $finalfields;
 519      }
 520  
 521      /**
 522       * Form data definition callback.
 523       *
 524       * This method is called from moodleform::definition_after_data and allows to tweak
 525       * mform with some data coming directly from the field plugin data controller.
 526       *
 527       * @param \MoodleQuickForm $mform
 528       * @param int $instanceid
 529       */
 530      public function instance_form_definition_after_data(\MoodleQuickForm $mform, int $instanceid = 0) {
 531          $editablefields = $this->get_editable_fields($instanceid);
 532          $fields = api::get_instance_fields_data($editablefields, $instanceid);
 533  
 534          foreach ($fields as $formfield) {
 535              $formfield->instance_form_definition_after_data($mform);
 536          }
 537      }
 538  
 539      /**
 540       * Prepares the custom fields data related to the instance to pass to mform->set_data()
 541       *
 542       * Example:
 543       *   $instance = $DB->get_record(...);
 544       *   // .... prepare editor, filemanager, add tags, etc.
 545       *   $handler->instance_form_before_set_data($instance);
 546       *   $form->set_data($instance);
 547       *
 548       * @param stdClass $instance the instance that has custom fields, if 'id' attribute is present the custom
 549       *    fields for this instance will be added, otherwise the default values will be added.
 550       */
 551      public function instance_form_before_set_data(stdClass $instance) {
 552          $instanceid = !empty($instance->id) ? $instance->id : 0;
 553          $fields = api::get_instance_fields_data($this->get_editable_fields($instanceid), $instanceid);
 554  
 555          foreach ($fields as $formfield) {
 556              $formfield->instance_form_before_set_data($instance);
 557          }
 558      }
 559  
 560      /**
 561       * Saves the given data for custom fields, must be called after the instance is saved and id is present
 562       *
 563       * Example:
 564       *   if ($data = $form->get_data()) {
 565       *     // ... save main instance, set $data->id if instance was created.
 566       *     $handler->instance_form_save($data);
 567       *     redirect(...);
 568       *   }
 569       *
 570       * @param stdClass $instance data received from a form
 571       * @param bool $isnewinstance if this is call is made during instance creation
 572       */
 573      public function instance_form_save(stdClass $instance, bool $isnewinstance = false) {
 574          if (empty($instance->id)) {
 575              throw new \coding_exception('Caller must ensure that id is already set in data before calling this method');
 576          }
 577          if (!preg_grep('/^customfield_/', array_keys((array)$instance))) {
 578              // For performance.
 579              return;
 580          }
 581          $editablefields = $this->get_editable_fields($isnewinstance ? 0 : $instance->id);
 582          $fields = api::get_instance_fields_data($editablefields, $instance->id);
 583          foreach ($fields as $data) {
 584              if (!$data->get('id')) {
 585                  $data->set('contextid', $this->get_instance_context($instance->id)->id);
 586              }
 587              $data->instance_form_save($instance);
 588          }
 589      }
 590  
 591      /**
 592       * Validates the given data for custom fields, used in moodleform validation() function
 593       *
 594       * Example:
 595       *   public function validation($data, $files) {
 596       *     $errors = [];
 597       *     // .... check other fields.
 598       *     $errors = array_merge($errors, $handler->instance_form_validation($data, $files));
 599       *     return $errors;
 600       *   }
 601       *
 602       * @param array $data
 603       * @param array $files
 604       * @return array validation errors
 605       */
 606      public function instance_form_validation(array $data, array $files) {
 607          $instanceid = empty($data['id']) ? 0 : $data['id'];
 608          $editablefields = $this->get_editable_fields($instanceid);
 609          $fields = api::get_instance_fields_data($editablefields, $instanceid);
 610          $errors = [];
 611          foreach ($fields as $formfield) {
 612              $errors += $formfield->instance_form_validation($data, $files);
 613          }
 614          return $errors;
 615      }
 616  
 617      /**
 618       * Adds custom fields to instance editing form
 619       *
 620       * Example:
 621       *   public function definition() {
 622       *     // ... normal instance definition, including hidden 'id' field.
 623       *     $handler->instance_form_definition($this->_form, $instanceid);
 624       *     $this->add_action_buttons();
 625       *   }
 626       *
 627       * @param \MoodleQuickForm $mform
 628       * @param int $instanceid id of the instance, can be null when instance is being created
 629       * @param string $headerlangidentifier If specified, a lang string will be used for field category headings
 630       * @param string $headerlangcomponent
 631       */
 632      public function instance_form_definition(\MoodleQuickForm $mform, int $instanceid = 0,
 633              ?string $headerlangidentifier = null, ?string $headerlangcomponent = null) {
 634  
 635          $editablefields = $this->get_editable_fields($instanceid);
 636          $fieldswithdata = api::get_instance_fields_data($editablefields, $instanceid);
 637          $lastcategoryid = null;
 638          foreach ($fieldswithdata as $data) {
 639              $categoryid = $data->get_field()->get_category()->get('id');
 640              if ($categoryid != $lastcategoryid) {
 641                  $categoryname = $data->get_field()->get_category()->get_formatted_name();
 642  
 643                  // Load category header lang string if specified.
 644                  if (!empty($headerlangidentifier)) {
 645                      $categoryname = get_string($headerlangidentifier, $headerlangcomponent, $categoryname);
 646                  }
 647  
 648                  $mform->addElement('header', 'category_' . $categoryid, $categoryname);
 649                  $lastcategoryid = $categoryid;
 650              }
 651              $data->instance_form_definition($mform);
 652              $field = $data->get_field()->to_record();
 653              if (strlen($field->description)) {
 654                  // Add field description.
 655                  $context = $this->get_configuration_context();
 656                  $value = file_rewrite_pluginfile_urls($field->description, 'pluginfile.php',
 657                      $context->id, 'core_customfield', 'description', $field->id);
 658                  $value = format_text($value, $field->descriptionformat, ['context' => $context]);
 659                  $mform->addElement('static', 'customfield_' . $field->shortname . '_static', '', $value);
 660              }
 661          }
 662      }
 663  
 664      /**
 665       * Get field types array
 666       *
 667       * @return array
 668       */
 669      public function get_available_field_types() :array {
 670          return api::get_available_field_types();
 671      }
 672  
 673      /**
 674       * Options for processing embedded files in the field description.
 675       *
 676       * Handlers may want to extend it to disable files support and/or specify 'noclean'=>true
 677       * Context is not necessary here
 678       *
 679       * @return array
 680       */
 681      public function get_description_text_options() : array {
 682          global $CFG;
 683          require_once($CFG->libdir.'/formslib.php');
 684          return [
 685              'maxfiles' => EDITOR_UNLIMITED_FILES,
 686              'maxbytes' => $CFG->maxbytes,
 687              'context' => $this->get_configuration_context()
 688          ];
 689      }
 690  
 691      /**
 692       * Save the field configuration with the data from the form
 693       *
 694       * @param field_controller $field
 695       * @param stdClass $data data from the form
 696       */
 697      public function save_field_configuration(field_controller $field, stdClass $data) {
 698          if ($field->get('id')) {
 699              $field = $this->validate_field($field);
 700          } else {
 701              $this->validate_category($field->get_category());
 702          }
 703          api::save_field_configuration($field, $data);
 704          $this->clear_configuration_cache();
 705      }
 706  
 707      /**
 708       * Creates or updates custom field data for a instanceid from backup data.
 709       *
 710       * The handlers have to override it if they support backup
 711       *
 712       * @param \restore_task $task
 713       * @param array $data
 714       */
 715      public function restore_instance_data_from_backup(\restore_task $task, array $data) {
 716          throw new \coding_exception('Must be implemented in the handler');
 717      }
 718  
 719      /**
 720       * Returns list of fields defined for this instance as an array (not groupped by categories)
 721       *
 722       * Fields are sorted in the same order they would appear on the instance edit form
 723       *
 724       * Note that this function returns all fields in all categories regardless of whether the current user
 725       * can view or edit data associated with them
 726       *
 727       * @return field_controller[]
 728       */
 729      public function get_fields() : array {
 730          $categories = $this->get_categories_with_fields();
 731          $fields = [];
 732          foreach ($categories as $category) {
 733              foreach ($category->get_fields() as $field) {
 734                  $fields[$field->get('id')] = $field;
 735              }
 736          }
 737          return $fields;
 738      }
 739  
 740      /**
 741       * Get visible fields
 742       *
 743       * @param int $instanceid
 744       * @return field_controller[]
 745       */
 746      protected function get_visible_fields(int $instanceid) : array {
 747          $handler = $this;
 748          return array_filter($this->get_fields(),
 749              function($field) use($handler, $instanceid) {
 750                  return $handler->can_view($field, $instanceid);
 751              }
 752          );
 753      }
 754  
 755      /**
 756       * Get editable fields
 757       *
 758       * @param int $instanceid
 759       * @return field_controller[]
 760       */
 761      public function get_editable_fields(int $instanceid) : array {
 762          $handler = $this;
 763          return array_filter($this->get_fields(),
 764              function($field) use($handler, $instanceid) {
 765                  return $handler->can_edit($field, $instanceid);
 766              }
 767          );
 768      }
 769  
 770      /**
 771       * Allows to add custom controls to the field configuration form that will be saved in configdata
 772       *
 773       * @param \MoodleQuickForm $mform
 774       */
 775      public function config_form_definition(\MoodleQuickForm $mform) {
 776      }
 777  
 778      /**
 779       * Deletes all data related to all fields of an instance.
 780       *
 781       * @param int $instanceid
 782       */
 783      public function delete_instance(int $instanceid) {
 784          $fielddata = api::get_instance_fields_data($this->get_fields(), $instanceid, false);
 785          foreach ($fielddata as $data) {
 786              $data->delete();
 787          }
 788      }
 789  }