Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
/mod/data/ -> lib.php (source)

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [Versions 402 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   * @package   mod_data
  19   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  20   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  21   */
  22  
  23  use mod_data\manager;
  24  use mod_data\preset;
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  // Some constants
  29  define ('DATA_MAX_ENTRIES', 50);
  30  define ('DATA_PERPAGE_SINGLE', 1);
  31  
  32  define ('DATA_FIRSTNAME', -1);
  33  define ('DATA_LASTNAME', -2);
  34  define ('DATA_APPROVED', -3);
  35  define ('DATA_TIMEADDED', 0);
  36  define ('DATA_TIMEMODIFIED', -4);
  37  define ('DATA_TAGS', -5);
  38  
  39  define ('DATA_CAP_EXPORT', 'mod/data:viewalluserpresets');
  40  // Users having assigned the default role "Non-editing teacher" can export database records
  41  // Using the mod/data capability "viewalluserpresets" existing in Moodle 1.9.x.
  42  // In Moodle >= 2, new roles may be introduced and used instead.
  43  
  44  define('DATA_PRESET_COMPONENT', 'mod_data');
  45  define('DATA_PRESET_FILEAREA', 'site_presets');
  46  define('DATA_PRESET_CONTEXT', SYSCONTEXTID);
  47  
  48  define('DATA_EVENT_TYPE_OPEN', 'open');
  49  define('DATA_EVENT_TYPE_CLOSE', 'close');
  50  
  51  require_once (__DIR__ . '/deprecatedlib.php');
  52  
  53  /**
  54   * @package   mod_data
  55   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  56   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  57   */
  58  class data_field_base {     // Base class for Database Field Types (see field/*/field.class.php)
  59  
  60      /** @var string Subclasses must override the type with their name */
  61      var $type = 'unknown';
  62      /** @var object The database object that this field belongs to */
  63      var $data = NULL;
  64      /** @var object The field object itself, if we know it */
  65      var $field = NULL;
  66      /** @var int Width of the icon for this fieldtype */
  67      var $iconwidth = 16;
  68      /** @var int Width of the icon for this fieldtype */
  69      var $iconheight = 16;
  70      /** @var object course module or cmifno */
  71      var $cm;
  72      /** @var object activity context */
  73      var $context;
  74      /** @var priority for globalsearch indexing */
  75      protected static $priority = self::NO_PRIORITY;
  76      /** priority value for invalid fields regarding indexing */
  77      const NO_PRIORITY = 0;
  78      /** priority value for minimum priority */
  79      const MIN_PRIORITY = 1;
  80      /** priority value for low priority */
  81      const LOW_PRIORITY = 2;
  82      /** priority value for high priority */
  83      const HIGH_PRIORITY = 3;
  84      /** priority value for maximum priority */
  85      const MAX_PRIORITY = 4;
  86  
  87      /** @var bool whether the field is used in preview mode. */
  88      protected $preview = false;
  89  
  90      /**
  91       * Constructor function
  92       *
  93       * @global object
  94       * @uses CONTEXT_MODULE
  95       * @param int $field
  96       * @param int $data
  97       * @param int $cm
  98       */
  99      function __construct($field=0, $data=0, $cm=0) {   // Field or data or both, each can be id or object
 100          global $DB;
 101  
 102          if (empty($field) && empty($data)) {
 103              throw new \moodle_exception('missingfield', 'data');
 104          }
 105  
 106          if (!empty($field)) {
 107              if (is_object($field)) {
 108                  $this->field = $field;  // Programmer knows what they are doing, we hope
 109              } else if (!$this->field = $DB->get_record('data_fields', array('id'=>$field))) {
 110                  throw new \moodle_exception('invalidfieldid', 'data');
 111              }
 112              if (empty($data)) {
 113                  if (!$this->data = $DB->get_record('data', array('id'=>$this->field->dataid))) {
 114                      throw new \moodle_exception('invalidid', 'data');
 115                  }
 116              }
 117          }
 118  
 119          if (empty($this->data)) {         // We need to define this properly
 120              if (!empty($data)) {
 121                  if (is_object($data)) {
 122                      $this->data = $data;  // Programmer knows what they are doing, we hope
 123                  } else if (!$this->data = $DB->get_record('data', array('id'=>$data))) {
 124                      throw new \moodle_exception('invalidid', 'data');
 125                  }
 126              } else {                      // No way to define it!
 127                  throw new \moodle_exception('missingdata', 'data');
 128              }
 129          }
 130  
 131          if ($cm) {
 132              $this->cm = $cm;
 133          } else {
 134              $this->cm = get_coursemodule_from_instance('data', $this->data->id);
 135          }
 136  
 137          if (empty($this->field)) {         // We need to define some default values
 138              $this->define_default_field();
 139          }
 140  
 141          $this->context = context_module::instance($this->cm->id);
 142      }
 143  
 144      /**
 145       * Return the field type name.
 146       *
 147       * @return string the filed type.
 148       */
 149      public function get_name(): string {
 150          return $this->field->name;
 151      }
 152  
 153      /**
 154       * Return if the field type supports preview.
 155       *
 156       * Fields without a preview cannot be displayed in the preset preview.
 157       *
 158       * @return bool if the plugin supports preview.
 159       */
 160      public function supports_preview(): bool {
 161          return false;
 162      }
 163  
 164      /**
 165       * Generate a fake data_content for this field to be used in preset previews.
 166       *
 167       * Data plugins must override this method and support_preview in order to enable
 168       * preset preview for this field.
 169       *
 170       * @param int $recordid the fake record id
 171       * @return stdClass the fake record
 172       */
 173      public function get_data_content_preview(int $recordid): stdClass {
 174          $message = get_string('nopreviewavailable', 'mod_data', $this->field->name);
 175          return (object)[
 176              'id' => 0,
 177              'fieldid' => $this->field->id,
 178              'recordid' => $recordid,
 179              'content' => "<span class=\"nopreview\">$message</span>",
 180              'content1' => null,
 181              'content2' => null,
 182              'content3' => null,
 183              'content4' => null,
 184          ];
 185      }
 186  
 187      /**
 188       * Set the field to preview mode.
 189       *
 190       * @param bool $preview the new preview value
 191       */
 192      public function set_preview(bool $preview) {
 193          $this->preview = $preview;
 194      }
 195  
 196      /**
 197       * Get the field preview value.
 198       *
 199       * @return bool
 200       */
 201      public function get_preview(): bool {
 202          return $this->preview;
 203      }
 204  
 205  
 206      /**
 207       * This field just sets up a default field object
 208       *
 209       * @return bool
 210       */
 211      function define_default_field() {
 212          global $OUTPUT;
 213          if (empty($this->data->id)) {
 214              echo $OUTPUT->notification('Programmer error: dataid not defined in field class');
 215          }
 216          $this->field = new stdClass();
 217          $this->field->id = 0;
 218          $this->field->dataid = $this->data->id;
 219          $this->field->type   = $this->type;
 220          $this->field->param1 = '';
 221          $this->field->param2 = '';
 222          $this->field->param3 = '';
 223          $this->field->name = '';
 224          $this->field->description = '';
 225          $this->field->required = false;
 226  
 227          return true;
 228      }
 229  
 230      /**
 231       * Set up the field object according to data in an object.  Now is the time to clean it!
 232       *
 233       * @return bool
 234       */
 235      function define_field($data) {
 236          $this->field->type        = $this->type;
 237          $this->field->dataid      = $this->data->id;
 238  
 239          $this->field->name        = trim($data->name);
 240          $this->field->description = trim($data->description);
 241          $this->field->required    = !empty($data->required) ? 1 : 0;
 242  
 243          if (isset($data->param1)) {
 244              $this->field->param1 = trim($data->param1);
 245          }
 246          if (isset($data->param2)) {
 247              $this->field->param2 = trim($data->param2);
 248          }
 249          if (isset($data->param3)) {
 250              $this->field->param3 = trim($data->param3);
 251          }
 252          if (isset($data->param4)) {
 253              $this->field->param4 = trim($data->param4);
 254          }
 255          if (isset($data->param5)) {
 256              $this->field->param5 = trim($data->param5);
 257          }
 258  
 259          return true;
 260      }
 261  
 262      /**
 263       * Insert a new field in the database
 264       * We assume the field object is already defined as $this->field
 265       *
 266       * @global object
 267       * @return bool
 268       */
 269      function insert_field() {
 270          global $DB, $OUTPUT;
 271  
 272          if (empty($this->field)) {
 273              echo $OUTPUT->notification('Programmer error: Field has not been defined yet!  See define_field()');
 274              return false;
 275          }
 276  
 277          $this->field->id = $DB->insert_record('data_fields',$this->field);
 278  
 279          // Trigger an event for creating this field.
 280          $event = \mod_data\event\field_created::create(array(
 281              'objectid' => $this->field->id,
 282              'context' => $this->context,
 283              'other' => array(
 284                  'fieldname' => $this->field->name,
 285                  'dataid' => $this->data->id
 286              )
 287          ));
 288          $event->trigger();
 289  
 290          return true;
 291      }
 292  
 293  
 294      /**
 295       * Update a field in the database
 296       *
 297       * @global object
 298       * @return bool
 299       */
 300      function update_field() {
 301          global $DB;
 302  
 303          $DB->update_record('data_fields', $this->field);
 304  
 305          // Trigger an event for updating this field.
 306          $event = \mod_data\event\field_updated::create(array(
 307              'objectid' => $this->field->id,
 308              'context' => $this->context,
 309              'other' => array(
 310                  'fieldname' => $this->field->name,
 311                  'dataid' => $this->data->id
 312              )
 313          ));
 314          $event->trigger();
 315  
 316          return true;
 317      }
 318  
 319      /**
 320       * Delete a field completely
 321       *
 322       * @global object
 323       * @return bool
 324       */
 325      function delete_field() {
 326          global $DB;
 327  
 328          if (!empty($this->field->id)) {
 329              $manager = manager::create_from_instance($this->data);
 330  
 331              // Get the field before we delete it.
 332              $field = $DB->get_record('data_fields', array('id' => $this->field->id));
 333  
 334              $this->delete_content();
 335              $DB->delete_records('data_fields', array('id'=>$this->field->id));
 336  
 337              // Trigger an event for deleting this field.
 338              $event = \mod_data\event\field_deleted::create(array(
 339                  'objectid' => $this->field->id,
 340                  'context' => $this->context,
 341                  'other' => array(
 342                      'fieldname' => $this->field->name,
 343                      'dataid' => $this->data->id
 344                   )
 345              ));
 346  
 347              if (!$manager->has_fields() && $manager->has_records()) {
 348                  $DB->delete_records('data_records', ['dataid' => $this->data->id]);
 349              }
 350  
 351              $event->add_record_snapshot('data_fields', $field);
 352              $event->trigger();
 353          }
 354  
 355          return true;
 356      }
 357  
 358      /**
 359       * Print the relevant form element in the ADD template for this field
 360       *
 361       * @global object
 362       * @param int $recordid
 363       * @return string
 364       */
 365      function display_add_field($recordid=0, $formdata=null) {
 366          global $DB, $OUTPUT;
 367  
 368          if ($formdata) {
 369              $fieldname = 'field_' . $this->field->id;
 370              $content = $formdata->$fieldname;
 371          } else if ($recordid) {
 372              $content = $DB->get_field('data_content', 'content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid));
 373          } else {
 374              $content = '';
 375          }
 376  
 377          // beware get_field returns false for new, empty records MDL-18567
 378          if ($content===false) {
 379              $content='';
 380          }
 381  
 382          $str = '<div title="' . s($this->field->description) . '">';
 383          $str .= '<label for="field_'.$this->field->id.'"><span class="accesshide">'.$this->field->name.'</span>';
 384          if ($this->field->required) {
 385              $image = $OUTPUT->pix_icon('req', get_string('requiredelement', 'form'));
 386              $str .= html_writer::div($image, 'inline-req');
 387          }
 388          $str .= '</label><input class="basefieldinput form-control d-inline mod-data-input" ' .
 389                  'type="text" name="field_' . $this->field->id . '" ' .
 390                  'id="field_' . $this->field->id . '" value="' . s($content) . '" />';
 391          $str .= '</div>';
 392  
 393          return $str;
 394      }
 395  
 396      /**
 397       * Print the relevant form element to define the attributes for this field
 398       * viewable by teachers only.
 399       *
 400       * @global object
 401       * @global object
 402       * @return void Output is echo'd
 403       */
 404      function display_edit_field() {
 405          global $CFG, $DB, $OUTPUT;
 406  
 407          if (empty($this->field)) {   // No field has been defined yet, try and make one
 408              $this->define_default_field();
 409          }
 410  
 411          // Throw an exception if field type doen't exist. Anyway user should never access to edit a field with an unknown fieldtype.
 412          if ($this->type === 'unknown') {
 413              throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name]));
 414          }
 415  
 416          echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide');
 417  
 418          echo '<form id="editfield" action="'.$CFG->wwwroot.'/mod/data/field.php" method="post">'."\n";
 419          echo '<input type="hidden" name="d" value="'.$this->data->id.'" />'."\n";
 420          if (empty($this->field->id)) {
 421              echo '<input type="hidden" name="mode" value="add" />'."\n";
 422          } else {
 423              echo '<input type="hidden" name="fid" value="'.$this->field->id.'" />'."\n";
 424              echo '<input type="hidden" name="mode" value="update" />'."\n";
 425          }
 426          echo '<input type="hidden" name="type" value="'.$this->type.'" />'."\n";
 427          echo '<input name="sesskey" value="'.sesskey().'" type="hidden" />'."\n";
 428  
 429          echo $OUTPUT->heading($this->name(), 3);
 430  
 431          $filepath = $CFG->dirroot.'/mod/data/field/'.$this->type.'/mod.html';
 432  
 433          if (!file_exists($filepath)) {
 434              throw new \moodle_exception(get_string('missingfieldtype', 'data', (object)['name' => $this->field->name]));
 435          } else {
 436              require_once($filepath);
 437          }
 438  
 439          $actionbuttons = html_writer::start_div();
 440          $actionbuttons .= html_writer::tag('input', null, [
 441              'type' => 'submit',
 442              'name' => 'cancel',
 443              'value' => get_string('cancel'),
 444              'class' => 'btn btn-secondary mx-1'
 445          ]);
 446          $actionbuttons .= html_writer::tag('input', null, [
 447              'type' => 'submit',
 448              'value' => get_string('save'),
 449              'class' => 'btn btn-primary mx-1'
 450          ]);
 451          $actionbuttons .= html_writer::end_div();
 452  
 453          $stickyfooter = new core\output\sticky_footer($actionbuttons);
 454          echo $OUTPUT->render($stickyfooter);
 455  
 456          echo '</form>';
 457  
 458          echo $OUTPUT->box_end();
 459      }
 460  
 461      /**
 462       * Validates params of fieldinput data. Overwrite to validate fieldtype specific data.
 463       *
 464       * You are expected to return an array like ['paramname' => 'Error message for paramname param'] if there is an error,
 465       * return an empty array if everything is fine.
 466       *
 467       * @param stdClass $fieldinput The field input data to check
 468       * @return array $errors if empty validation was fine, otherwise contains one or more error messages
 469       */
 470      public function validate(stdClass $fieldinput): array {
 471          return [];
 472      }
 473  
 474      /**
 475       * Return the data_content of the field, or generate it if it is in preview mode.
 476       *
 477       * @param int $recordid the record id
 478       * @return stdClass|bool the record data or false if none
 479       */
 480      protected function get_data_content(int $recordid) {
 481          global $DB;
 482          if ($this->preview) {
 483              return $this->get_data_content_preview($recordid);
 484          }
 485          return $DB->get_record(
 486              'data_content',
 487              ['fieldid' => $this->field->id, 'recordid' => $recordid]
 488          );
 489      }
 490  
 491      /**
 492       * Display the content of the field in browse mode
 493       *
 494       * @global object
 495       * @param int $recordid
 496       * @param object $template
 497       * @return bool|string
 498       */
 499      function display_browse_field($recordid, $template) {
 500          global $DB;
 501          $content = $this->get_data_content($recordid);
 502          if (!$content || !isset($content->content)) {
 503              return '';
 504          }
 505          $options = new stdClass();
 506          if ($this->field->param1 == '1') {
 507              // We are autolinking this field, so disable linking within us.
 508              $options->filter = false;
 509          }
 510          $options->para = false;
 511          $str = format_text($content->content, $content->content1, $options);
 512          return $str;
 513      }
 514  
 515      /**
 516       * Update the content of one data field in the data_content table
 517       * @global object
 518       * @param int $recordid
 519       * @param mixed $value
 520       * @param string $name
 521       * @return bool
 522       */
 523      function update_content($recordid, $value, $name=''){
 524          global $DB;
 525  
 526          $content = new stdClass();
 527          $content->fieldid = $this->field->id;
 528          $content->recordid = $recordid;
 529          $content->content = clean_param($value, PARAM_NOTAGS);
 530  
 531          if ($oldcontent = $DB->get_record('data_content', array('fieldid'=>$this->field->id, 'recordid'=>$recordid))) {
 532              $content->id = $oldcontent->id;
 533              return $DB->update_record('data_content', $content);
 534          } else {
 535              return $DB->insert_record('data_content', $content);
 536          }
 537      }
 538  
 539      /**
 540       * Delete all content associated with the field
 541       *
 542       * @global object
 543       * @param int $recordid
 544       * @return bool
 545       */
 546      function delete_content($recordid=0) {
 547          global $DB;
 548  
 549          if ($recordid) {
 550              $conditions = array('fieldid'=>$this->field->id, 'recordid'=>$recordid);
 551          } else {
 552              $conditions = array('fieldid'=>$this->field->id);
 553          }
 554  
 555          $rs = $DB->get_recordset('data_content', $conditions);
 556          if ($rs->valid()) {
 557              $fs = get_file_storage();
 558              foreach ($rs as $content) {
 559                  $fs->delete_area_files($this->context->id, 'mod_data', 'content', $content->id);
 560              }
 561          }
 562          $rs->close();
 563  
 564          return $DB->delete_records('data_content', $conditions);
 565      }
 566  
 567      /**
 568       * Check if a field from an add form is empty
 569       *
 570       * @param mixed $value
 571       * @param mixed $name
 572       * @return bool
 573       */
 574      function notemptyfield($value, $name) {
 575          return !empty($value);
 576      }
 577  
 578      /**
 579       * Just in case a field needs to print something before the whole form
 580       */
 581      function print_before_form() {
 582      }
 583  
 584      /**
 585       * Just in case a field needs to print something after the whole form
 586       */
 587      function print_after_form() {
 588      }
 589  
 590  
 591      /**
 592       * Returns the sortable field for the content. By default, it's just content
 593       * but for some plugins, it could be content 1 - content4
 594       *
 595       * @return string
 596       */
 597      function get_sort_field() {
 598          return 'content';
 599      }
 600  
 601      /**
 602       * Returns the SQL needed to refer to the column.  Some fields may need to CAST() etc.
 603       *
 604       * @param string $fieldname
 605       * @return string $fieldname
 606       */
 607      function get_sort_sql($fieldname) {
 608          return $fieldname;
 609      }
 610  
 611      /**
 612       * Returns the name/type of the field
 613       *
 614       * @return string
 615       */
 616      function name() {
 617          return get_string('fieldtypelabel', "datafield_$this->type");
 618      }
 619  
 620      /**
 621       * Prints the respective type icon
 622       *
 623       * @global object
 624       * @return string
 625       */
 626      function image() {
 627          global $OUTPUT;
 628  
 629          return $OUTPUT->pix_icon('field/' . $this->type, $this->type, 'data');
 630      }
 631  
 632      /**
 633       * Per default, it is assumed that fields support text exporting.
 634       * Override this (return false) on fields not supporting text exporting.
 635       *
 636       * @return bool true
 637       */
 638      function text_export_supported() {
 639          return true;
 640      }
 641  
 642      /**
 643       * Per default, return the record's text value only from the "content" field.
 644       * Override this in fields class if necesarry.
 645       *
 646       * @param string $record
 647       * @return string
 648       */
 649      function export_text_value($record) {
 650          if ($this->text_export_supported()) {
 651              return $record->content;
 652          }
 653      }
 654  
 655      /**
 656       * @param string $relativepath
 657       * @return bool false
 658       */
 659      function file_ok($relativepath) {
 660          return false;
 661      }
 662  
 663      /**
 664       * Returns the priority for being indexed by globalsearch
 665       *
 666       * @return int
 667       */
 668      public static function get_priority() {
 669          return static::$priority;
 670      }
 671  
 672      /**
 673       * Returns the presentable string value for a field content.
 674       *
 675       * The returned string should be plain text.
 676       *
 677       * @param stdClass $content
 678       * @return string
 679       */
 680      public static function get_content_value($content) {
 681          return trim($content->content, "\r\n ");
 682      }
 683  
 684      /**
 685       * Return the plugin configs for external functions,
 686       * in some cases the configs will need formatting or be returned only if the current user has some capabilities enabled.
 687       *
 688       * @return array the list of config parameters
 689       * @since Moodle 3.3
 690       */
 691      public function get_config_for_external() {
 692          // Return all the field configs to null (maybe there is a private key for a service or something similar there).
 693          $configs = [];
 694          for ($i = 1; $i <= 10; $i++) {
 695              $configs["param$i"] = null;
 696          }
 697          return $configs;
 698      }
 699  }
 700  
 701  
 702  /**
 703   * Given a template and a dataid, generate a default case template
 704   *
 705   * @param stdClass $data the mod_data record.
 706   * @param string $template the template name
 707   * @param int $recordid the entry record
 708   * @param bool $form print a form instead of data
 709   * @param bool $update if the function update the $data object or not
 710   * @return string the template content or an empty string if no content is available (for instance, when database has no fields).
 711   */
 712  function data_generate_default_template(&$data, $template, $recordid = 0, $form = false, $update = true) {
 713      global $DB;
 714  
 715      if (!$data || !$template) {
 716          return '';
 717      }
 718  
 719      // These templates are empty by default (they have no content).
 720      $emptytemplates = [
 721          'csstemplate',
 722          'jstemplate',
 723          'listtemplateheader',
 724          'listtemplatefooter',
 725          'rsstitletemplate',
 726      ];
 727      if (in_array($template, $emptytemplates)) {
 728          return '';
 729      }
 730  
 731      $manager = manager::create_from_instance($data);
 732      if (empty($manager->get_fields())) {
 733          // No template will be returned if there are no fields.
 734          return '';
 735      }
 736  
 737      $templateclass = \mod_data\template::create_default_template($manager, $template, $form);
 738      $templatecontent = $templateclass->get_template_content();
 739  
 740      if ($update) {
 741          // Update the database instance.
 742          $newdata = new stdClass();
 743          $newdata->id = $data->id;
 744          $newdata->{$template} = $templatecontent;
 745          $DB->update_record('data', $newdata);
 746          $data->{$template} = $templatecontent;
 747      }
 748  
 749      return $templatecontent;
 750  }
 751  
 752  /**
 753   * Build the form elements to manage tags for a record.
 754   *
 755   * @param int|bool $recordid
 756   * @param string[] $selected raw tag names
 757   * @return string
 758   */
 759  function data_generate_tag_form($recordid = false, $selected = []) {
 760      global $CFG, $DB, $OUTPUT, $PAGE;
 761  
 762      $tagtypestoshow = \core_tag_area::get_showstandard('mod_data', 'data_records');
 763      $showstandard = ($tagtypestoshow != core_tag_tag::HIDE_STANDARD);
 764      $typenewtags = ($tagtypestoshow != core_tag_tag::STANDARD_ONLY);
 765  
 766      $str = html_writer::start_tag('div', array('class' => 'datatagcontrol'));
 767  
 768      $namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
 769  
 770      $tagcollid = \core_tag_area::get_collection('mod_data', 'data_records');
 771      $tags = [];
 772      $selectedtags = [];
 773  
 774      if ($showstandard) {
 775          $tags += $DB->get_records_menu('tag', array('isstandard' => 1, 'tagcollid' => $tagcollid),
 776              $namefield, 'id,' . $namefield . ' as fieldname');
 777      }
 778  
 779      if ($recordid) {
 780          $selectedtags += core_tag_tag::get_item_tags_array('mod_data', 'data_records', $recordid);
 781      }
 782  
 783      if (!empty($selected)) {
 784          list($sql, $params) = $DB->get_in_or_equal($selected, SQL_PARAMS_NAMED);
 785          $params['tagcollid'] = $tagcollid;
 786          $sql = "SELECT id, $namefield FROM {tag} WHERE tagcollid = :tagcollid AND rawname $sql";
 787          $selectedtags += $DB->get_records_sql_menu($sql, $params);
 788      }
 789  
 790      $tags += $selectedtags;
 791  
 792      $str .= '<select class="custom-select" name="tags[]" id="tags" multiple>';
 793      foreach ($tags as $tagid => $tag) {
 794          $selected = key_exists($tagid, $selectedtags) ? 'selected' : '';
 795          $str .= "<option value='$tag' $selected>$tag</option>";
 796      }
 797      $str .= '</select>';
 798  
 799      if (has_capability('moodle/tag:manage', context_system::instance()) && $showstandard) {
 800          $url = new moodle_url('/tag/manage.php', array('tc' => core_tag_area::get_collection('mod_data',
 801              'data_records')));
 802          $str .= ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag'));
 803      }
 804  
 805      $PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array(
 806              '#tags',
 807              $typenewtags,
 808              '',
 809              get_string('entertags', 'tag'),
 810              false,
 811              $showstandard,
 812              get_string('noselection', 'form')
 813          )
 814      );
 815  
 816      $str .= html_writer::end_tag('div');
 817  
 818      return $str;
 819  }
 820  
 821  
 822  /**
 823   * Search for a field name and replaces it with another one in all the
 824   * form templates. Set $newfieldname as '' if you want to delete the
 825   * field from the form.
 826   *
 827   * @global object
 828   * @param object $data
 829   * @param string $searchfieldname
 830   * @param string $newfieldname
 831   * @return bool
 832   */
 833  function data_replace_field_in_templates($data, $searchfieldname, $newfieldname) {
 834      global $DB;
 835  
 836      $newdata = (object)['id' => $data->id];
 837      $update = false;
 838      $templates = ['listtemplate', 'singletemplate', 'asearchtemplate', 'addtemplate', 'rsstemplate'];
 839      foreach ($templates as $templatename) {
 840          if (empty($data->$templatename)) {
 841              continue;
 842          }
 843          $search = [
 844              '[[' . $searchfieldname . ']]',
 845              '[[' . $searchfieldname . '#id]]',
 846              '[[' . $searchfieldname . '#name]]',
 847              '[[' . $searchfieldname . '#description]]',
 848          ];
 849          if (empty($newfieldname)) {
 850              $replace = ['', '', '', ''];
 851          } else {
 852              $replace = [
 853                  '[[' . $newfieldname . ']]',
 854                  '[[' . $newfieldname . '#id]]',
 855                  '[[' . $newfieldname . '#name]]',
 856                  '[[' . $newfieldname . '#description]]',
 857              ];
 858          }
 859          $newdata->{$templatename} = str_ireplace($search, $replace, $data->{$templatename} ?? '');
 860          $update = true;
 861      }
 862      if (!$update) {
 863          return true;
 864      }
 865      return $DB->update_record('data', $newdata);
 866  }
 867  
 868  
 869  /**
 870   * Appends a new field at the end of the form template.
 871   *
 872   * @global object
 873   * @param object $data
 874   * @param string $newfieldname
 875   * @return bool if the field has been added or not
 876   */
 877  function data_append_new_field_to_templates($data, $newfieldname): bool {
 878      global $DB, $OUTPUT;
 879  
 880      $newdata = (object)['id' => $data->id];
 881      $update = false;
 882      $templates = ['singletemplate', 'addtemplate', 'rsstemplate'];
 883      foreach ($templates as $templatename) {
 884          if (empty($data->$templatename)
 885              || strpos($data->$templatename, "[[$newfieldname]]") !== false
 886              || strpos($data->$templatename, "##otherfields##") !== false
 887          ) {
 888              continue;
 889          }
 890          $newdata->$templatename = $data->$templatename;
 891          $fields = [[
 892              'fieldname' => '[[' . $newfieldname . '#name]]',
 893              'fieldcontent' => '[[' . $newfieldname . ']]',
 894          ]];
 895          $newdata->$templatename .= $OUTPUT->render_from_template(
 896              'mod_data/fields_otherfields',
 897              ['fields' => $fields, 'classes' => 'added_field']
 898          );
 899          $update = true;
 900      }
 901      if (!$update) {
 902          return false;
 903      }
 904      return $DB->update_record('data', $newdata);
 905  }
 906  
 907  
 908  /**
 909   * given a field name
 910   * this function creates an instance of the particular subfield class
 911   *
 912   * @global object
 913   * @param string $name
 914   * @param object $data
 915   * @return object|bool
 916   */
 917  function data_get_field_from_name($name, $data){
 918      global $DB;
 919  
 920      $field = $DB->get_record('data_fields', array('name'=>$name, 'dataid'=>$data->id));
 921  
 922      if ($field) {
 923          return data_get_field($field, $data);
 924      } else {
 925          return false;
 926      }
 927  }
 928  
 929  /**
 930   * given a field id
 931   * this function creates an instance of the particular subfield class
 932   *
 933   * @global object
 934   * @param int $fieldid
 935   * @param object $data
 936   * @return bool|object
 937   */
 938  function data_get_field_from_id($fieldid, $data){
 939      global $DB;
 940  
 941      $field = $DB->get_record('data_fields', array('id'=>$fieldid, 'dataid'=>$data->id));
 942  
 943      if ($field) {
 944          return data_get_field($field, $data);
 945      } else {
 946          return false;
 947      }
 948  }
 949  
 950  /**
 951   * given a field id
 952   * this function creates an instance of the particular subfield class
 953   *
 954   * @global object
 955   * @param string $type
 956   * @param object $data
 957   * @return object
 958   */
 959  function data_get_field_new($type, $data) {
 960      global $CFG;
 961  
 962      $filepath = $CFG->dirroot.'/mod/data/field/'.$type.'/field.class.php';
 963      // It should never access this method if the subfield class doesn't exist.
 964      if (!file_exists($filepath)) {
 965          throw new \moodle_exception('invalidfieldtype', 'data');
 966      }
 967      require_once($filepath);
 968      $newfield = 'data_field_'.$type;
 969      $newfield = new $newfield(0, $data);
 970      return $newfield;
 971  }
 972  
 973  /**
 974   * returns a subclass field object given a record of the field, used to
 975   * invoke plugin methods
 976   * input: $param $field - record from db
 977   *
 978   * @global object
 979   * @param stdClass $field the field record
 980   * @param stdClass $data the data instance
 981   * @param stdClass|null $cm optional course module data
 982   * @return data_field_base the field object instance or data_field_base if unkown type
 983   */
 984  function data_get_field($field, $data, $cm=null) {
 985      global $CFG;
 986      if (!isset($field->type)) {
 987          return new data_field_base($field);
 988      }
 989      $filepath = $CFG->dirroot.'/mod/data/field/'.$field->type.'/field.class.php';
 990      if (!file_exists($filepath)) {
 991          return new data_field_base($field);
 992      }
 993      require_once($filepath);
 994      $newfield = 'data_field_'.$field->type;
 995      $newfield = new $newfield($field, $data, $cm);
 996      return $newfield;
 997  }
 998  
 999  
1000  /**
1001   * Given record object (or id), returns true if the record belongs to the current user
1002   *
1003   * @global object
1004   * @global object
1005   * @param mixed $record record object or id
1006   * @return bool
1007   */
1008  function data_isowner($record) {
1009      global $USER, $DB;
1010  
1011      if (!isloggedin()) { // perf shortcut
1012          return false;
1013      }
1014  
1015      if (!is_object($record)) {
1016          if (!$record = $DB->get_record('data_records', array('id'=>$record))) {
1017              return false;
1018          }
1019      }
1020  
1021      return ($record->userid == $USER->id);
1022  }
1023  
1024  /**
1025   * has a user reached the max number of entries?
1026   *
1027   * @param object $data
1028   * @return bool
1029   */
1030  function data_atmaxentries($data){
1031      if (!$data->maxentries){
1032          return false;
1033  
1034      } else {
1035          return (data_numentries($data) >= $data->maxentries);
1036      }
1037  }
1038  
1039  /**
1040   * returns the number of entries already made by this user
1041   *
1042   * @global object
1043   * @global object
1044   * @param object $data
1045   * @return int
1046   */
1047  function data_numentries($data, $userid=null) {
1048      global $USER, $DB;
1049      if ($userid === null) {
1050          $userid = $USER->id;
1051      }
1052      $sql = 'SELECT COUNT(*) FROM {data_records} WHERE dataid=? AND userid=?';
1053      return $DB->count_records_sql($sql, array($data->id, $userid));
1054  }
1055  
1056  /**
1057   * function that takes in a dataid and adds a record
1058   * this is used everytime an add template is submitted
1059   *
1060   * @global object
1061   * @global object
1062   * @param object $data
1063   * @param int $groupid
1064   * @param int $userid
1065   * @return bool
1066   */
1067  function data_add_record($data, $groupid = 0, $userid = null) {
1068      global $USER, $DB;
1069  
1070      $cm = get_coursemodule_from_instance('data', $data->id);
1071      $context = context_module::instance($cm->id);
1072  
1073      $record = new stdClass();
1074      $record->userid = $userid ?? $USER->id;
1075      $record->dataid = $data->id;
1076      $record->groupid = $groupid;
1077      $record->timecreated = $record->timemodified = time();
1078      if (has_capability('mod/data:approve', $context)) {
1079          $record->approved = 1;
1080      } else {
1081          $record->approved = 0;
1082      }
1083      $record->id = $DB->insert_record('data_records', $record);
1084  
1085      // Trigger an event for creating this record.
1086      $event = \mod_data\event\record_created::create(array(
1087          'objectid' => $record->id,
1088          'context' => $context,
1089          'other' => array(
1090              'dataid' => $data->id
1091          )
1092      ));
1093      $event->trigger();
1094  
1095      $course = get_course($cm->course);
1096      data_update_completion_state($data, $course, $cm);
1097  
1098      return $record->id;
1099  }
1100  
1101  /**
1102   * check the multple existence any tag in a template
1103   *
1104   * check to see if there are 2 or more of the same tag being used.
1105   *
1106   * @global object
1107   * @param int $dataid,
1108   * @param string $template
1109   * @return bool
1110   */
1111  function data_tags_check($dataid, $template) {
1112      global $DB, $OUTPUT;
1113  
1114      // first get all the possible tags
1115      $fields = $DB->get_records('data_fields', array('dataid'=>$dataid));
1116      // then we generate strings to replace
1117      $tagsok = true; // let's be optimistic
1118      foreach ($fields as $field){
1119          $pattern="/\[\[" . preg_quote($field->name, '/') . "\]\]/i";
1120          if (preg_match_all($pattern, $template, $dummy)>1){
1121              $tagsok = false;
1122              echo $OUTPUT->notification('[['.$field->name.']] - '.get_string('multipletags','data'));
1123          }
1124      }
1125      // else return true
1126      return $tagsok;
1127  }
1128  
1129  /**
1130   * Adds an instance of a data
1131   *
1132   * @param stdClass $data
1133   * @param mod_data_mod_form $mform
1134   * @return int intance id
1135   */
1136  function data_add_instance($data, $mform = null) {
1137      global $DB, $CFG;
1138      require_once($CFG->dirroot.'/mod/data/locallib.php');
1139  
1140      if (empty($data->assessed)) {
1141          $data->assessed = 0;
1142      }
1143  
1144      if (empty($data->ratingtime) || empty($data->assessed)) {
1145          $data->assesstimestart  = 0;
1146          $data->assesstimefinish = 0;
1147      }
1148  
1149      $data->timemodified = time();
1150  
1151      $data->id = $DB->insert_record('data', $data);
1152  
1153      // Add calendar events if necessary.
1154      data_set_events($data);
1155      if (!empty($data->completionexpected)) {
1156          \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $data->completionexpected);
1157      }
1158  
1159      data_grade_item_update($data);
1160  
1161      return $data->id;
1162  }
1163  
1164  /**
1165   * updates an instance of a data
1166   *
1167   * @global object
1168   * @param object $data
1169   * @return bool
1170   */
1171  function data_update_instance($data) {
1172      global $DB, $CFG;
1173      require_once($CFG->dirroot.'/mod/data/locallib.php');
1174  
1175      $data->timemodified = time();
1176      if (!empty($data->instance)) {
1177          $data->id = $data->instance;
1178      }
1179  
1180      if (empty($data->assessed)) {
1181          $data->assessed = 0;
1182      }
1183  
1184      if (empty($data->ratingtime) or empty($data->assessed)) {
1185          $data->assesstimestart  = 0;
1186          $data->assesstimefinish = 0;
1187      }
1188  
1189      if (empty($data->notification)) {
1190          $data->notification = 0;
1191      }
1192  
1193      $DB->update_record('data', $data);
1194  
1195      // Add calendar events if necessary.
1196      data_set_events($data);
1197      $completionexpected = (!empty($data->completionexpected)) ? $data->completionexpected : null;
1198      \core_completion\api::update_completion_date_event($data->coursemodule, 'data', $data->id, $completionexpected);
1199  
1200      data_grade_item_update($data);
1201  
1202      return true;
1203  
1204  }
1205  
1206  /**
1207   * deletes an instance of a data
1208   *
1209   * @global object
1210   * @param int $id
1211   * @return bool
1212   */
1213  function data_delete_instance($id) {    // takes the dataid
1214      global $DB, $CFG;
1215  
1216      if (!$data = $DB->get_record('data', array('id'=>$id))) {
1217          return false;
1218      }
1219  
1220      $cm = get_coursemodule_from_instance('data', $data->id);
1221      $context = context_module::instance($cm->id);
1222  
1223      // Delete all information related to fields.
1224      $fields = $DB->get_records('data_fields', ['dataid' => $id]);
1225      foreach ($fields as $field) {
1226          $todelete = data_get_field($field, $data, $cm);
1227          $todelete->delete_field();
1228      }
1229  
1230      // Remove old calendar events.
1231      $events = $DB->get_records('event', array('modulename' => 'data', 'instance' => $id));
1232      foreach ($events as $event) {
1233          $event = calendar_event::load($event);
1234          $event->delete();
1235      }
1236  
1237      // cleanup gradebook
1238      data_grade_item_delete($data);
1239  
1240      // Delete the instance itself
1241      // We must delete the module record after we delete the grade item.
1242      $result = $DB->delete_records('data', array('id'=>$id));
1243  
1244      return $result;
1245  }
1246  
1247  /**
1248   * returns a summary of data activity of this user
1249   *
1250   * @global object
1251   * @param object $course
1252   * @param object $user
1253   * @param object $mod
1254   * @param object $data
1255   * @return object|null
1256   */
1257  function data_user_outline($course, $user, $mod, $data) {
1258      global $DB, $CFG;
1259      require_once("$CFG->libdir/gradelib.php");
1260  
1261      $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1262      if (empty($grades->items[0]->grades)) {
1263          $grade = false;
1264      } else {
1265          $grade = reset($grades->items[0]->grades);
1266      }
1267  
1268  
1269      if ($countrecords = $DB->count_records('data_records', array('dataid'=>$data->id, 'userid'=>$user->id))) {
1270          $result = new stdClass();
1271          $result->info = get_string('numrecords', 'data', $countrecords);
1272          $lastrecord   = $DB->get_record_sql('SELECT id,timemodified FROM {data_records}
1273                                                WHERE dataid = ? AND userid = ?
1274                                             ORDER BY timemodified DESC', array($data->id, $user->id), true);
1275          $result->time = $lastrecord->timemodified;
1276          if ($grade) {
1277              if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1278                  $result->info .= ', ' . get_string('gradenoun') . ': ' . $grade->str_long_grade;
1279              } else {
1280                  $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1281              }
1282          }
1283          return $result;
1284      } else if ($grade) {
1285          $result = (object) [
1286              'time' => grade_get_date_for_user_grade($grade, $user),
1287          ];
1288          if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1289              $result->info = get_string('gradenoun') . ': ' . $grade->str_long_grade;
1290          } else {
1291              $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
1292          }
1293  
1294          return $result;
1295      }
1296      return NULL;
1297  }
1298  
1299  /**
1300   * Prints all the records uploaded by this user
1301   *
1302   * @global object
1303   * @param object $course
1304   * @param object $user
1305   * @param object $mod
1306   * @param object $data
1307   */
1308  function data_user_complete($course, $user, $mod, $data) {
1309      global $DB, $CFG, $OUTPUT;
1310      require_once("$CFG->libdir/gradelib.php");
1311  
1312      $grades = grade_get_grades($course->id, 'mod', 'data', $data->id, $user->id);
1313      if (!empty($grades->items[0]->grades)) {
1314          $grade = reset($grades->items[0]->grades);
1315          if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1316              echo $OUTPUT->container(get_string('gradenoun') . ': ' . $grade->str_long_grade);
1317              if ($grade->str_feedback) {
1318                  echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1319              }
1320          } else {
1321              echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades'));
1322          }
1323      }
1324      $records = $DB->get_records(
1325          'data_records',
1326          ['dataid' => $data->id, 'userid' => $user->id],
1327          'timemodified DESC'
1328      );
1329      if ($records) {
1330          $manager = manager::create_from_instance($data);
1331          $parser = $manager->get_template('singletemplate');
1332          echo $parser->parse_entries($records);
1333      }
1334  }
1335  
1336  /**
1337   * Return grade for given user or all users.
1338   *
1339   * @global object
1340   * @param object $data
1341   * @param int $userid optional user id, 0 means all users
1342   * @return array array of grades, false if none
1343   */
1344  function data_get_user_grades($data, $userid=0) {
1345      global $CFG;
1346  
1347      require_once($CFG->dirroot.'/rating/lib.php');
1348  
1349      $ratingoptions = new stdClass;
1350      $ratingoptions->component = 'mod_data';
1351      $ratingoptions->ratingarea = 'entry';
1352      $ratingoptions->modulename = 'data';
1353      $ratingoptions->moduleid   = $data->id;
1354  
1355      $ratingoptions->userid = $userid;
1356      $ratingoptions->aggregationmethod = $data->assessed;
1357      $ratingoptions->scaleid = $data->scale;
1358      $ratingoptions->itemtable = 'data_records';
1359      $ratingoptions->itemtableusercolumn = 'userid';
1360  
1361      $rm = new rating_manager();
1362      return $rm->get_user_grades($ratingoptions);
1363  }
1364  
1365  /**
1366   * Update activity grades
1367   *
1368   * @category grade
1369   * @param object $data
1370   * @param int $userid specific user only, 0 means all
1371   * @param bool $nullifnone
1372   */
1373  function data_update_grades($data, $userid=0, $nullifnone=true) {
1374      global $CFG, $DB;
1375      require_once($CFG->libdir.'/gradelib.php');
1376  
1377      if (!$data->assessed) {
1378          data_grade_item_update($data);
1379  
1380      } else if ($grades = data_get_user_grades($data, $userid)) {
1381          data_grade_item_update($data, $grades);
1382  
1383      } else if ($userid and $nullifnone) {
1384          $grade = new stdClass();
1385          $grade->userid   = $userid;
1386          $grade->rawgrade = NULL;
1387          data_grade_item_update($data, $grade);
1388  
1389      } else {
1390          data_grade_item_update($data);
1391      }
1392  }
1393  
1394  /**
1395   * Update/create grade item for given data
1396   *
1397   * @category grade
1398   * @param stdClass $data A database instance with extra cmidnumber property
1399   * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1400   * @return object grade_item
1401   */
1402  function data_grade_item_update($data, $grades=NULL) {
1403      global $CFG;
1404      require_once($CFG->libdir.'/gradelib.php');
1405  
1406      $params = array('itemname'=>$data->name, 'idnumber'=>$data->cmidnumber);
1407  
1408      if (!$data->assessed or $data->scale == 0) {
1409          $params['gradetype'] = GRADE_TYPE_NONE;
1410  
1411      } else if ($data->scale > 0) {
1412          $params['gradetype'] = GRADE_TYPE_VALUE;
1413          $params['grademax']  = $data->scale;
1414          $params['grademin']  = 0;
1415  
1416      } else if ($data->scale < 0) {
1417          $params['gradetype'] = GRADE_TYPE_SCALE;
1418          $params['scaleid']   = -$data->scale;
1419      }
1420  
1421      if ($grades  === 'reset') {
1422          $params['reset'] = true;
1423          $grades = NULL;
1424      }
1425  
1426      return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, $grades, $params);
1427  }
1428  
1429  /**
1430   * Delete grade item for given data
1431   *
1432   * @category grade
1433   * @param object $data object
1434   * @return object grade_item
1435   */
1436  function data_grade_item_delete($data) {
1437      global $CFG;
1438      require_once($CFG->libdir.'/gradelib.php');
1439  
1440      return grade_update('mod/data', $data->course, 'mod', 'data', $data->id, 0, NULL, array('deleted'=>1));
1441  }
1442  
1443  // junk functions
1444  /**
1445   * takes a list of records, the current data, a search string,
1446   * and mode to display prints the translated template
1447   *
1448   * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more.
1449   * @todo MDL-75189 Final deprecation in Moodle 4.5.
1450   * @param string $templatename the template name
1451   * @param array $records the entries records
1452   * @param stdClass $data the database instance object
1453   * @param string $search the current search term
1454   * @param int $page page number for pagination
1455   * @param bool $return if the result should be returned (true) or printed (false)
1456   * @param moodle_url|null $jumpurl a moodle_url by which to jump back to the record list (can be null)
1457   * @return mixed string with all parsed entries or nothing if $return is false
1458   */
1459  function data_print_template($templatename, $records, $data, $search='', $page=0, $return=false, moodle_url $jumpurl=null) {
1460      debugging(
1461          'data_print_template is deprecated. Use mod_data\\manager::get_template and mod_data\\template::parse_entries instead',
1462          DEBUG_DEVELOPER
1463      );
1464  
1465      $options = [
1466          'search' => $search,
1467          'page' => $page,
1468      ];
1469      if ($jumpurl) {
1470          $options['baseurl'] = $jumpurl;
1471      }
1472      $manager = manager::create_from_instance($data);
1473      $parser = $manager->get_template($templatename, $options);
1474      $content = $parser->parse_entries($records);
1475      if ($return) {
1476          return $content;
1477      }
1478      echo $content;
1479  }
1480  
1481  /**
1482   * Return rating related permissions
1483   *
1484   * @param string $contextid the context id
1485   * @param string $component the component to get rating permissions for
1486   * @param string $ratingarea the rating area to get permissions for
1487   * @return array an associative array of the user's rating permissions
1488   */
1489  function data_rating_permissions($contextid, $component, $ratingarea) {
1490      $context = context::instance_by_id($contextid, MUST_EXIST);
1491      if ($component != 'mod_data' || $ratingarea != 'entry') {
1492          return null;
1493      }
1494      return array(
1495          'view'    => has_capability('mod/data:viewrating',$context),
1496          'viewany' => has_capability('mod/data:viewanyrating',$context),
1497          'viewall' => has_capability('mod/data:viewallratings',$context),
1498          'rate'    => has_capability('mod/data:rate',$context)
1499      );
1500  }
1501  
1502  /**
1503   * Validates a submitted rating
1504   * @param array $params submitted data
1505   *            context => object the context in which the rated items exists [required]
1506   *            itemid => int the ID of the object being rated
1507   *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
1508   *            rating => int the submitted rating
1509   *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
1510   *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
1511   * @return boolean true if the rating is valid. Will throw rating_exception if not
1512   */
1513  function data_rating_validate($params) {
1514      global $DB, $USER;
1515  
1516      // Check the component is mod_data
1517      if ($params['component'] != 'mod_data') {
1518          throw new rating_exception('invalidcomponent');
1519      }
1520  
1521      // Check the ratingarea is entry (the only rating area in data module)
1522      if ($params['ratingarea'] != 'entry') {
1523          throw new rating_exception('invalidratingarea');
1524      }
1525  
1526      // Check the rateduserid is not the current user .. you can't rate your own entries
1527      if ($params['rateduserid'] == $USER->id) {
1528          throw new rating_exception('nopermissiontorate');
1529      }
1530  
1531      $datasql = "SELECT d.id as dataid, d.scale, d.course, r.userid as userid, d.approval, r.approved, r.timecreated, d.assesstimestart, d.assesstimefinish, r.groupid
1532                    FROM {data_records} r
1533                    JOIN {data} d ON r.dataid = d.id
1534                   WHERE r.id = :itemid";
1535      $dataparams = array('itemid'=>$params['itemid']);
1536      if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1537          //item doesn't exist
1538          throw new rating_exception('invaliditemid');
1539      }
1540  
1541      if ($info->scale != $params['scaleid']) {
1542          //the scale being submitted doesnt match the one in the database
1543          throw new rating_exception('invalidscaleid');
1544      }
1545  
1546      //check that the submitted rating is valid for the scale
1547  
1548      // lower limit
1549      if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
1550          throw new rating_exception('invalidnum');
1551      }
1552  
1553      // upper limit
1554      if ($info->scale < 0) {
1555          //its a custom scale
1556          $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
1557          if ($scalerecord) {
1558              $scalearray = explode(',', $scalerecord->scale);
1559              if ($params['rating'] > count($scalearray)) {
1560                  throw new rating_exception('invalidnum');
1561              }
1562          } else {
1563              throw new rating_exception('invalidscaleid');
1564          }
1565      } else if ($params['rating'] > $info->scale) {
1566          //if its numeric and submitted rating is above maximum
1567          throw new rating_exception('invalidnum');
1568      }
1569  
1570      if ($info->approval && !$info->approved) {
1571          //database requires approval but this item isnt approved
1572          throw new rating_exception('nopermissiontorate');
1573      }
1574  
1575      // check the item we're rating was created in the assessable time window
1576      if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
1577          if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
1578              throw new rating_exception('notavailable');
1579          }
1580      }
1581  
1582      $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST);
1583      $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1584      $context = context_module::instance($cm->id);
1585  
1586      // if the supplied context doesnt match the item's context
1587      if ($context->id != $params['context']->id) {
1588          throw new rating_exception('invalidcontext');
1589      }
1590  
1591      // Make sure groups allow this user to see the item they're rating
1592      $groupid = $info->groupid;
1593      if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
1594          if (!groups_group_exists($groupid)) { // Can't find group
1595              throw new rating_exception('cannotfindgroup');//something is wrong
1596          }
1597  
1598          if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
1599              // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
1600              throw new rating_exception('notmemberofgroup');
1601          }
1602      }
1603  
1604      return true;
1605  }
1606  
1607  /**
1608   * Can the current user see ratings for a given itemid?
1609   *
1610   * @param array $params submitted data
1611   *            contextid => int contextid [required]
1612   *            component => The component for this module - should always be mod_data [required]
1613   *            ratingarea => object the context in which the rated items exists [required]
1614   *            itemid => int the ID of the object being rated [required]
1615   *            scaleid => int scale id [optional]
1616   * @return bool
1617   * @throws coding_exception
1618   * @throws rating_exception
1619   */
1620  function mod_data_rating_can_see_item_ratings($params) {
1621      global $DB;
1622  
1623      // Check the component is mod_data.
1624      if (!isset($params['component']) || $params['component'] != 'mod_data') {
1625          throw new rating_exception('invalidcomponent');
1626      }
1627  
1628      // Check the ratingarea is entry (the only rating area in data).
1629      if (!isset($params['ratingarea']) || $params['ratingarea'] != 'entry') {
1630          throw new rating_exception('invalidratingarea');
1631      }
1632  
1633      if (!isset($params['itemid'])) {
1634          throw new rating_exception('invaliditemid');
1635      }
1636  
1637      $datasql = "SELECT d.id as dataid, d.course, r.groupid
1638                    FROM {data_records} r
1639                    JOIN {data} d ON r.dataid = d.id
1640                   WHERE r.id = :itemid";
1641      $dataparams = array('itemid' => $params['itemid']);
1642      if (!$info = $DB->get_record_sql($datasql, $dataparams)) {
1643          // Item doesn't exist.
1644          throw new rating_exception('invaliditemid');
1645      }
1646  
1647      // User can see ratings of all participants.
1648      if ($info->groupid == 0) {
1649          return true;
1650      }
1651  
1652      $course = $DB->get_record('course', array('id' => $info->course), '*', MUST_EXIST);
1653      $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
1654  
1655      // Make sure groups allow this user to see the item they're rating.
1656      return groups_group_visible($info->groupid, $course, $cm);
1657  }
1658  
1659  
1660  /**
1661   * function that takes in the current data, number of items per page,
1662   * a search string and prints a preference box in view.php
1663   *
1664   * This preference box prints a searchable advanced search template if
1665   *     a) A template is defined
1666   *  b) The advanced search checkbox is checked.
1667   *
1668   * @global object
1669   * @global object
1670   * @param object $data
1671   * @param int $perpage
1672   * @param string $search
1673   * @param string $sort
1674   * @param string $order
1675   * @param array $search_array
1676   * @param int $advanced
1677   * @param string $mode
1678   * @return void
1679   */
1680  function data_print_preference_form($data, $perpage, $search, $sort='', $order='ASC', $search_array = '', $advanced = 0, $mode= ''){
1681      global $DB, $PAGE, $OUTPUT;
1682  
1683      $cm = get_coursemodule_from_instance('data', $data->id);
1684      $context = context_module::instance($cm->id);
1685      echo '<div class="datapreferences my-5">';
1686      echo '<form id="options" action="view.php" method="get">';
1687      echo '<div class="d-flex">';
1688      echo '<div>';
1689      echo '<input type="hidden" name="d" value="'.$data->id.'" />';
1690      if ($mode =='asearch') {
1691          $advanced = 1;
1692          echo '<input type="hidden" name="mode" value="list" />';
1693      }
1694      echo '<label for="pref_perpage">'.get_string('pagesize','data').'</label> ';
1695      $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15,
1696                         20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000);
1697      echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id' => 'pref_perpage', 'class' => 'custom-select'));
1698  
1699      if ($advanced) {
1700          $regsearchclass = 'search_none';
1701          $advancedsearchclass = 'search_inline';
1702      } else {
1703          $regsearchclass = 'search_inline';
1704          $advancedsearchclass = 'search_none';
1705      }
1706      echo '<div id="reg_search" class="' . $regsearchclass . ' form-inline" >&nbsp;&nbsp;&nbsp;';
1707      echo '<label for="pref_search">' . get_string('search') . '</label> <input type="text" ' .
1708           'class="form-control" size="16" name="search" id= "pref_search" value="' . s($search) . '" /></div>';
1709      echo '&nbsp;&nbsp;&nbsp;<label for="pref_sortby">'.get_string('sortby').'</label> ';
1710      // foreach field, print the option
1711      echo '<select name="sort" id="pref_sortby" class="custom-select mr-1">';
1712      if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'name')) {
1713          echo '<optgroup label="'.get_string('fields', 'data').'">';
1714          foreach ($fields as $field) {
1715              if ($field->id == $sort) {
1716                  echo '<option value="'.$field->id.'" selected="selected">'.$field->name.'</option>';
1717              } else {
1718                  echo '<option value="'.$field->id.'">'.$field->name.'</option>';
1719              }
1720          }
1721          echo '</optgroup>';
1722      }
1723      $options = array();
1724      $options[DATA_TIMEADDED]    = get_string('timeadded', 'data');
1725      $options[DATA_TIMEMODIFIED] = get_string('timemodified', 'data');
1726      $options[DATA_FIRSTNAME]    = get_string('authorfirstname', 'data');
1727      $options[DATA_LASTNAME]     = get_string('authorlastname', 'data');
1728      if ($data->approval and has_capability('mod/data:approve', $context)) {
1729          $options[DATA_APPROVED] = get_string('approved', 'data');
1730      }
1731      echo '<optgroup label="'.get_string('other', 'data').'">';
1732      foreach ($options as $key => $name) {
1733          if ($key == $sort) {
1734              echo '<option value="'.$key.'" selected="selected">'.$name.'</option>';
1735          } else {
1736              echo '<option value="'.$key.'">'.$name.'</option>';
1737          }
1738      }
1739      echo '</optgroup>';
1740      echo '</select>';
1741      echo '<label for="pref_order" class="accesshide">'.get_string('order').'</label>';
1742      echo '<select id="pref_order" name="order" class="custom-select mr-1">';
1743      if ($order == 'ASC') {
1744          echo '<option value="ASC" selected="selected">'.get_string('ascending','data').'</option>';
1745      } else {
1746          echo '<option value="ASC">'.get_string('ascending','data').'</option>';
1747      }
1748      if ($order == 'DESC') {
1749          echo '<option value="DESC" selected="selected">'.get_string('descending','data').'</option>';
1750      } else {
1751          echo '<option value="DESC">'.get_string('descending','data').'</option>';
1752      }
1753      echo '</select>';
1754  
1755      if ($advanced) {
1756          $checked = ' checked="checked" ';
1757      }
1758      else {
1759          $checked = '';
1760      }
1761      $PAGE->requires->js('/mod/data/data.js');
1762      echo '&nbsp;<input type="hidden" name="advanced" value="0" />';
1763      echo '&nbsp;<input type="hidden" name="filter" value="1" />';
1764      echo '&nbsp;<input type="checkbox" id="advancedcheckbox" name="advanced" value="1" ' . $checked . ' ' .
1765           'onchange="showHideAdvSearch(this.checked);" class="mx-1" />' .
1766           '<label for="advancedcheckbox">' . get_string('advancedsearch', 'data') . '</label>';
1767      echo '</div>';
1768      echo '<div id="advsearch-save-sec" class="ml-auto '. $regsearchclass . '">';
1769      echo '&nbsp;<input type="submit" class="btn btn-secondary" value="' . get_string('savesettings', 'data') . '" />';
1770      echo '</div>';
1771      echo '</div>';
1772      echo '<div>';
1773  
1774      echo '<br />';
1775      echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">';
1776      echo '<table class="boxaligncenter">';
1777  
1778      // print ASC or DESC
1779      echo '<tr><td colspan="2">&nbsp;</td></tr>';
1780      $i = 0;
1781  
1782      // Determine if we are printing all fields for advanced search, or the template for advanced search
1783      // If a template is not defined, use the deafault template and display all fields.
1784      $asearchtemplate = $data->asearchtemplate;
1785      if (empty($asearchtemplate)) {
1786          $asearchtemplate = data_generate_default_template($data, 'asearchtemplate', 0, false, false);
1787      }
1788  
1789      static $fields = array();
1790      static $dataid = null;
1791  
1792      if (empty($dataid)) {
1793          $dataid = $data->id;
1794      } else if ($dataid != $data->id) {
1795          $fields = array();
1796      }
1797  
1798      if (empty($fields)) {
1799          $fieldrecords = $DB->get_records('data_fields', array('dataid'=>$data->id));
1800          foreach ($fieldrecords as $fieldrecord) {
1801              $fields[]= data_get_field($fieldrecord, $data);
1802          }
1803      }
1804  
1805      // Replacing tags
1806      $patterns = array();
1807      $replacement = array();
1808  
1809      // Then we generate strings to replace for normal tags
1810      $otherfields = [];
1811      foreach ($fields as $field) {
1812          $fieldname = $field->field->name;
1813          $fieldname = preg_quote($fieldname, '/');
1814          $searchfield = data_get_field_from_id($field->field->id, $data);
1815  
1816          if ($searchfield->type === 'unknown') {
1817              continue;
1818          }
1819          if (!empty($search_array[$field->field->id]->data)) {
1820              $searchinput = $searchfield->display_search_field($search_array[$field->field->id]->data);
1821          } else {
1822              $searchinput = $searchfield->display_search_field();
1823          }
1824          $patterns[] = "/\[\[$fieldname\]\]/i";
1825          $replacement[] = $searchinput;
1826          // Extra field information.
1827          $patterns[] = "/\[\[$fieldname#name\]\]/i";
1828          $replacement[] = $field->field->name;
1829          $patterns[] = "/\[\[$fieldname#description\]\]/i";
1830          $replacement[] = $field->field->description;
1831          // Other fields.
1832          if (strpos($asearchtemplate, "[[" . $field->field->name . "]]") === false) {
1833              $otherfields[] = [
1834                  'fieldname' => $searchfield->field->name,
1835                  'fieldcontent' => $searchinput,
1836              ];
1837          }
1838      }
1839      $patterns[] = "/##otherfields##/";
1840      if (!empty($otherfields)) {
1841          $replacement[] = $OUTPUT->render_from_template(
1842              'mod_data/fields_otherfields',
1843              ['fields' => $otherfields]
1844          );
1845      } else {
1846          $replacement[] = '';
1847      }
1848  
1849      $fn = !empty($search_array[DATA_FIRSTNAME]->data) ? $search_array[DATA_FIRSTNAME]->data : '';
1850      $ln = !empty($search_array[DATA_LASTNAME]->data) ? $search_array[DATA_LASTNAME]->data : '';
1851      $patterns[]    = '/##firstname##/';
1852      $replacement[] = '<label class="accesshide" for="u_fn">' . get_string('authorfirstname', 'data') . '</label>' .
1853                       '<input type="text" class="form-control" size="16" id="u_fn" name="u_fn" value="' . s($fn) . '" />';
1854      $patterns[]    = '/##lastname##/';
1855      $replacement[] = '<label class="accesshide" for="u_ln">' . get_string('authorlastname', 'data') . '</label>' .
1856                       '<input type="text" class="form-control" size="16" id="u_ln" name="u_ln" value="' . s($ln) . '" />';
1857  
1858      if (core_tag_tag::is_enabled('mod_data', 'data_records')) {
1859          $patterns[] = "/##tags##/";
1860          $selectedtags = isset($search_array[DATA_TAGS]->rawtagnames) ? $search_array[DATA_TAGS]->rawtagnames : [];
1861          $replacement[] = data_generate_tag_form(false, $selectedtags);
1862      }
1863  
1864      // actual replacement of the tags
1865  
1866      $options = new stdClass();
1867      $options->para=false;
1868      $options->noclean=true;
1869      echo '<tr><td>';
1870      echo preg_replace($patterns, $replacement, format_text($asearchtemplate, FORMAT_HTML, $options));
1871      echo '</td></tr>';
1872  
1873      echo '<tr><td colspan="4"><br/>' .
1874           '<input type="submit" class="btn btn-primary mr-1" value="' . get_string('savesettings', 'data') . '" />' .
1875           '<input type="submit" class="btn btn-secondary" name="resetadv" value="' . get_string('resetsettings', 'data') . '" />' .
1876           '</td></tr>';
1877      echo '</table>';
1878      echo '</div>';
1879      echo '</form>';
1880      echo '</div>';
1881      echo '<hr/>';
1882  }
1883  
1884  /**
1885   * @global object
1886   * @global object
1887   * @param object $data
1888   * @param object $record
1889   * @param bool $print if the result must be printed or returner.
1890   * @return void Output echo'd
1891   */
1892  function data_print_ratings($data, $record, bool $print = true) {
1893      global $OUTPUT;
1894      $result = '';
1895      if (!empty($record->rating)){
1896          $result = $OUTPUT->render($record->rating);
1897      }
1898      if (!$print) {
1899          return $result;
1900      }
1901      echo $result;
1902  }
1903  
1904  /**
1905   * List the actions that correspond to a view of this module.
1906   * This is used by the participation report.
1907   *
1908   * Note: This is not used by new logging system. Event with
1909   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1910   *       be considered as view action.
1911   *
1912   * @return array
1913   */
1914  function data_get_view_actions() {
1915      return array('view');
1916  }
1917  
1918  /**
1919   * List the actions that correspond to a post of this module.
1920   * This is used by the participation report.
1921   *
1922   * Note: This is not used by new logging system. Event with
1923   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1924   *       will be considered as post action.
1925   *
1926   * @return array
1927   */
1928  function data_get_post_actions() {
1929      return array('add','update','record delete');
1930  }
1931  
1932  /**
1933   * @param string $name
1934   * @param int $dataid
1935   * @param int $fieldid
1936   * @return bool
1937   */
1938  function data_fieldname_exists($name, $dataid, $fieldid = 0) {
1939      global $DB;
1940  
1941      if (!is_numeric($name)) {
1942          $like = $DB->sql_like('df.name', ':name', false);
1943      } else {
1944          $like = "df.name = :name";
1945      }
1946      $params = array('name'=>$name);
1947      if ($fieldid) {
1948          $params['dataid']   = $dataid;
1949          $params['fieldid1'] = $fieldid;
1950          $params['fieldid2'] = $fieldid;
1951          return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1952                                          WHERE $like AND df.dataid = :dataid
1953                                                AND ((df.id < :fieldid1) OR (df.id > :fieldid2))", $params);
1954      } else {
1955          $params['dataid']   = $dataid;
1956          return $DB->record_exists_sql("SELECT * FROM {data_fields} df
1957                                          WHERE $like AND df.dataid = :dataid", $params);
1958      }
1959  }
1960  
1961  /**
1962   * @param array $fieldinput
1963   */
1964  function data_convert_arrays_to_strings(&$fieldinput) {
1965      foreach ($fieldinput as $key => $val) {
1966          if (is_array($val)) {
1967              $str = '';
1968              foreach ($val as $inner) {
1969                  $str .= $inner . ',';
1970              }
1971              $str = substr($str, 0, -1);
1972  
1973              $fieldinput->$key = $str;
1974          }
1975      }
1976  }
1977  
1978  
1979  /**
1980   * Converts a database (module instance) to use the Roles System
1981   *
1982   * @global object
1983   * @global object
1984   * @uses CONTEXT_MODULE
1985   * @uses CAP_PREVENT
1986   * @uses CAP_ALLOW
1987   * @param object $data a data object with the same attributes as a record
1988   *                     from the data database table
1989   * @param int $datamodid the id of the data module, from the modules table
1990   * @param array $teacherroles array of roles that have archetype teacher
1991   * @param array $studentroles array of roles that have archetype student
1992   * @param array $guestroles array of roles that have archetype guest
1993   * @param int $cmid the course_module id for this data instance
1994   * @return boolean data module was converted or not
1995   */
1996  function data_convert_to_roles($data, $teacherroles=array(), $studentroles=array(), $cmid=NULL) {
1997      global $CFG, $DB, $OUTPUT;
1998  
1999      if (!isset($data->participants) && !isset($data->assesspublic)
2000              && !isset($data->groupmode)) {
2001          // We assume that this database has already been converted to use the
2002          // Roles System. above fields get dropped the data module has been
2003          // upgraded to use Roles.
2004          return false;
2005      }
2006  
2007      if (empty($cmid)) {
2008          // We were not given the course_module id. Try to find it.
2009          if (!$cm = get_coursemodule_from_instance('data', $data->id)) {
2010              echo $OUTPUT->notification('Could not get the course module for the data');
2011              return false;
2012          } else {
2013              $cmid = $cm->id;
2014          }
2015      }
2016      $context = context_module::instance($cmid);
2017  
2018  
2019      // $data->participants:
2020      // 1 - Only teachers can add entries
2021      // 3 - Teachers and students can add entries
2022      switch ($data->participants) {
2023          case 1:
2024              foreach ($studentroles as $studentrole) {
2025                  assign_capability('mod/data:writeentry', CAP_PREVENT, $studentrole->id, $context->id);
2026              }
2027              foreach ($teacherroles as $teacherrole) {
2028                  assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
2029              }
2030              break;
2031          case 3:
2032              foreach ($studentroles as $studentrole) {
2033                  assign_capability('mod/data:writeentry', CAP_ALLOW, $studentrole->id, $context->id);
2034              }
2035              foreach ($teacherroles as $teacherrole) {
2036                  assign_capability('mod/data:writeentry', CAP_ALLOW, $teacherrole->id, $context->id);
2037              }
2038              break;
2039      }
2040  
2041      // $data->assessed:
2042      // 2 - Only teachers can rate posts
2043      // 1 - Everyone can rate posts
2044      // 0 - No one can rate posts
2045      switch ($data->assessed) {
2046          case 0:
2047              foreach ($studentroles as $studentrole) {
2048                  assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2049              }
2050              foreach ($teacherroles as $teacherrole) {
2051                  assign_capability('mod/data:rate', CAP_PREVENT, $teacherrole->id, $context->id);
2052              }
2053              break;
2054          case 1:
2055              foreach ($studentroles as $studentrole) {
2056                  assign_capability('mod/data:rate', CAP_ALLOW, $studentrole->id, $context->id);
2057              }
2058              foreach ($teacherroles as $teacherrole) {
2059                  assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2060              }
2061              break;
2062          case 2:
2063              foreach ($studentroles as $studentrole) {
2064                  assign_capability('mod/data:rate', CAP_PREVENT, $studentrole->id, $context->id);
2065              }
2066              foreach ($teacherroles as $teacherrole) {
2067                  assign_capability('mod/data:rate', CAP_ALLOW, $teacherrole->id, $context->id);
2068              }
2069              break;
2070      }
2071  
2072      // $data->assesspublic:
2073      // 0 - Students can only see their own ratings
2074      // 1 - Students can see everyone's ratings
2075      switch ($data->assesspublic) {
2076          case 0:
2077              foreach ($studentroles as $studentrole) {
2078                  assign_capability('mod/data:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
2079              }
2080              foreach ($teacherroles as $teacherrole) {
2081                  assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2082              }
2083              break;
2084          case 1:
2085              foreach ($studentroles as $studentrole) {
2086                  assign_capability('mod/data:viewrating', CAP_ALLOW, $studentrole->id, $context->id);
2087              }
2088              foreach ($teacherroles as $teacherrole) {
2089                  assign_capability('mod/data:viewrating', CAP_ALLOW, $teacherrole->id, $context->id);
2090              }
2091              break;
2092      }
2093  
2094      if (empty($cm)) {
2095          $cm = $DB->get_record('course_modules', array('id'=>$cmid));
2096      }
2097  
2098      switch ($cm->groupmode) {
2099          case NOGROUPS:
2100              break;
2101          case SEPARATEGROUPS:
2102              foreach ($studentroles as $studentrole) {
2103                  assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
2104              }
2105              foreach ($teacherroles as $teacherrole) {
2106                  assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2107              }
2108              break;
2109          case VISIBLEGROUPS:
2110              foreach ($studentroles as $studentrole) {
2111                  assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
2112              }
2113              foreach ($teacherroles as $teacherrole) {
2114                  assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
2115              }
2116              break;
2117      }
2118      return true;
2119  }
2120  
2121  /**
2122   * Returns the best name to show for a preset
2123   *
2124   * @param string $shortname
2125   * @param  string $path
2126   * @return string
2127   * @deprecated since Moodle 4.1 MDL-75148 - please, use the preset::get_name_from_plugin() function instead.
2128   * @todo MDL-75189 This will be deleted in Moodle 4.5.
2129   * @see preset::get_name_from_plugin()
2130   */
2131  function data_preset_name($shortname, $path) {
2132      debugging('data_preset_name() is deprecated. Please use preset::get_name_from_plugin() instead.', DEBUG_DEVELOPER);
2133  
2134      return preset::get_name_from_plugin($shortname);
2135  }
2136  
2137  /**
2138   * Returns an array of all the available presets.
2139   *
2140   * @return array
2141   * @deprecated since Moodle 4.1 MDL-75148 - please, use the manager::get_available_presets() function instead.
2142   * @todo MDL-75189 This will be deleted in Moodle 4.5.
2143   * @see manager::get_available_presets()
2144   */
2145  function data_get_available_presets($context) {
2146      debugging('data_get_available_presets() is deprecated. Please use manager::get_available_presets() instead.', DEBUG_DEVELOPER);
2147  
2148      $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2149      $manager = manager::create_from_coursemodule($cm);
2150      return $manager->get_available_presets();
2151  }
2152  
2153  /**
2154   * Gets an array of all of the presets that users have saved to the site.
2155   *
2156   * @param stdClass $context The context that we are looking from.
2157   * @param array $presets
2158   * @return array An array of presets
2159   * @deprecated since Moodle 4.1 MDL-75148 - please, use the manager::get_available_saved_presets() function instead.
2160   * @todo MDL-75189 This will be deleted in Moodle 4.5.
2161   * @see manager::get_available_saved_presets()
2162   */
2163  function data_get_available_site_presets($context, array $presets=array()) {
2164      debugging(
2165          'data_get_available_site_presets() is deprecated. Please use manager::get_available_saved_presets() instead.',
2166          DEBUG_DEVELOPER
2167      );
2168  
2169      $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
2170      $manager = manager::create_from_coursemodule($cm);
2171      $savedpresets = $manager->get_available_saved_presets();
2172      return array_merge($presets, $savedpresets);
2173  }
2174  
2175  /**
2176   * Deletes a saved preset.
2177   *
2178   * @param string $name
2179   * @return bool
2180   * @deprecated since Moodle 4.1 MDL-75187 - please, use the preset::delete() function instead.
2181   * @todo MDL-75189 This will be deleted in Moodle 4.5.
2182   * @see preset::delete()
2183   */
2184  function data_delete_site_preset($name) {
2185      debugging('data_delete_site_preset() is deprecated. Please use preset::delete() instead.', DEBUG_DEVELOPER);
2186  
2187      $fs = get_file_storage();
2188  
2189      $files = $fs->get_directory_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/');
2190      if (!empty($files)) {
2191          foreach ($files as $file) {
2192              $file->delete();
2193          }
2194      }
2195  
2196      $dir = $fs->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, '/'.$name.'/', '.');
2197      if (!empty($dir)) {
2198          $dir->delete();
2199      }
2200      return true;
2201  }
2202  
2203  /**
2204   * Prints the heads for a page
2205   *
2206   * @param stdClass $course
2207   * @param stdClass $cm
2208   * @param stdClass $data
2209   * @param string $currenttab
2210   * @param string $actionbar
2211   */
2212  function data_print_header($course, $cm, $data, $currenttab='', string $actionbar = '') {
2213  
2214      global $CFG, $displaynoticegood, $displaynoticebad, $OUTPUT, $PAGE, $USER;
2215  
2216      echo $OUTPUT->header();
2217  
2218      echo $actionbar;
2219  
2220      // Print any notices
2221  
2222      if (!empty($displaynoticegood)) {
2223          echo $OUTPUT->notification($displaynoticegood, 'notifysuccess');    // good (usually green)
2224      } else if (!empty($displaynoticebad)) {
2225          echo $OUTPUT->notification($displaynoticebad);                     // bad (usuually red)
2226      }
2227  }
2228  
2229  /**
2230   * Can user add more entries?
2231   *
2232   * @param object $data
2233   * @param mixed $currentgroup
2234   * @param int $groupmode
2235   * @param stdClass $context
2236   * @return bool
2237   */
2238  function data_user_can_add_entry($data, $currentgroup, $groupmode, $context = null) {
2239      global $DB;
2240  
2241      // Don't let add entry to a database that has no fields.
2242      if (!$DB->record_exists('data_fields', ['dataid' => $data->id])) {
2243          return false;
2244      }
2245  
2246      if (empty($context)) {
2247          $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
2248          $context = context_module::instance($cm->id);
2249      }
2250  
2251      if (has_capability('mod/data:manageentries', $context)) {
2252          // no entry limits apply if user can manage
2253  
2254      } else if (!has_capability('mod/data:writeentry', $context)) {
2255          return false;
2256  
2257      } else if (data_atmaxentries($data)) {
2258          return false;
2259      } else if (data_in_readonly_period($data)) {
2260          // Check whether we're in a read-only period
2261          return false;
2262      }
2263  
2264      if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
2265          return true;
2266      }
2267  
2268      if ($currentgroup) {
2269          return groups_is_member($currentgroup);
2270      } else {
2271          //else it might be group 0 in visible mode
2272          if ($groupmode == VISIBLEGROUPS){
2273              return true;
2274          } else {
2275              return false;
2276          }
2277      }
2278  }
2279  
2280  /**
2281   * Check whether the current user is allowed to manage the given record considering manageentries capability,
2282   * data_in_readonly_period() result, ownership (determined by data_isowner()) and manageapproved setting.
2283   * @param mixed $record record object or id
2284   * @param object $data data object
2285   * @param object $context context object
2286   * @return bool returns true if the user is allowd to edit the entry, false otherwise
2287   */
2288  function data_user_can_manage_entry($record, $data, $context) {
2289      global $DB;
2290  
2291      if (has_capability('mod/data:manageentries', $context)) {
2292          return true;
2293      }
2294  
2295      // Check whether this activity is read-only at present.
2296      $readonly = data_in_readonly_period($data);
2297  
2298      if (!$readonly) {
2299          // Get record object from db if just id given like in data_isowner.
2300          // ...done before calling data_isowner() to avoid querying db twice.
2301          if (!is_object($record)) {
2302              if (!$record = $DB->get_record('data_records', array('id' => $record))) {
2303                  return false;
2304              }
2305          }
2306          if (data_isowner($record)) {
2307              if ($data->approval && $record->approved) {
2308                  return $data->manageapproved == 1;
2309              } else {
2310                  return true;
2311              }
2312          }
2313      }
2314  
2315      return false;
2316  }
2317  
2318  /**
2319   * Check whether the specified database activity is currently in a read-only period
2320   *
2321   * @param object $data
2322   * @return bool returns true if the time fields in $data indicate a read-only period; false otherwise
2323   */
2324  function data_in_readonly_period($data) {
2325      $now = time();
2326      if (!$data->timeviewfrom && !$data->timeviewto) {
2327          return false;
2328      } else if (($data->timeviewfrom && $now < $data->timeviewfrom) || ($data->timeviewto && $now > $data->timeviewto)) {
2329          return false;
2330      }
2331      return true;
2332  }
2333  
2334  /**
2335   * Check if the files in a directory are the expected for a preset.
2336   *
2337   * @return bool Wheter the defined $directory has or not all the expected preset files.
2338   *
2339   * @deprecated since Moodle 4.1 MDL-75148 - please, use the preset::is_directory_a_preset() function instead.
2340   * @todo MDL-75189 This will be deleted in Moodle 4.5.
2341   * @see manager::is_directory_a_preset()
2342   */
2343  function is_directory_a_preset($directory) {
2344      debugging('is_directory_a_preset() is deprecated. Please use preset::is_directory_a_preset() instead.', DEBUG_DEVELOPER);
2345  
2346      return preset::is_directory_a_preset($directory);
2347  }
2348  
2349  /**
2350   * Abstract class used for data preset importers
2351   *
2352   * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more.
2353   * @todo MDL-75189 Final deprecation in Moodle 4.5.
2354   */
2355  abstract class data_preset_importer {
2356  
2357      protected $course;
2358      protected $cm;
2359      protected $module;
2360      protected $directory;
2361  
2362      /**
2363       * Constructor
2364       *
2365       * @param stdClass $course
2366       * @param stdClass $cm
2367       * @param stdClass $module
2368       * @param string $directory
2369       */
2370      public function __construct($course, $cm, $module, $directory) {
2371          debugging(
2372              'data_preset_importer is deprecated. Please use mod\\data\\local\\importer\\preset_importer instead',
2373              DEBUG_DEVELOPER
2374          );
2375  
2376          $this->course = $course;
2377          $this->cm = $cm;
2378          $this->module = $module;
2379          $this->directory = $directory;
2380      }
2381  
2382      /**
2383       * Returns the name of the directory the preset is located in
2384       * @return string
2385       */
2386      public function get_directory() {
2387          return basename($this->directory);
2388      }
2389  
2390      /**
2391       * Retreive the contents of a file. That file may either be in a conventional directory of the Moodle file storage
2392       * @param file_storage $filestorage. should be null if using a conventional directory
2393       * @param stored_file $fileobj the directory to look in. null if using a conventional directory
2394       * @param string $dir the directory to look in. null if using the Moodle file storage
2395       * @param string $filename the name of the file we want
2396       * @return string the contents of the file or null if the file doesn't exist.
2397       */
2398      public function data_preset_get_file_contents(&$filestorage, &$fileobj, $dir, $filename) {
2399          if(empty($filestorage) || empty($fileobj)) {
2400              if (substr($dir, -1)!='/') {
2401                  $dir .= '/';
2402              }
2403              if (file_exists($dir.$filename)) {
2404                  return file_get_contents($dir.$filename);
2405              } else {
2406                  return null;
2407              }
2408          } else {
2409              if ($filestorage->file_exists(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename)) {
2410                  $file = $filestorage->get_file(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA, 0, $fileobj->get_filepath(), $filename);
2411                  return $file->get_content();
2412              } else {
2413                  return null;
2414              }
2415          }
2416  
2417      }
2418      /**
2419       * Gets the preset settings
2420       * @global moodle_database $DB
2421       * @return stdClass
2422       */
2423      public function get_preset_settings() {
2424          global $DB, $CFG;
2425          require_once($CFG->libdir.'/xmlize.php');
2426  
2427          $fs = $fileobj = null;
2428          if (!preset::is_directory_a_preset($this->directory)) {
2429              //maybe the user requested a preset stored in the Moodle file storage
2430  
2431              $fs = get_file_storage();
2432              $files = $fs->get_area_files(DATA_PRESET_CONTEXT, DATA_PRESET_COMPONENT, DATA_PRESET_FILEAREA);
2433  
2434              //preset name to find will be the final element of the directory
2435              $explodeddirectory = explode('/', $this->directory);
2436              $presettofind = end($explodeddirectory);
2437  
2438              //now go through the available files available and see if we can find it
2439              foreach ($files as $file) {
2440                  if (($file->is_directory() && $file->get_filepath()=='/') || !$file->is_directory()) {
2441                      continue;
2442                  }
2443                  $presetname = trim($file->get_filepath(), '/');
2444                  if ($presetname==$presettofind) {
2445                      $this->directory = $presetname;
2446                      $fileobj = $file;
2447                  }
2448              }
2449  
2450              if (empty($fileobj)) {
2451                  throw new \moodle_exception('invalidpreset', 'data', '', $this->directory);
2452              }
2453          }
2454  
2455          $allowed_settings = array(
2456              'intro',
2457              'comments',
2458              'requiredentries',
2459              'requiredentriestoview',
2460              'maxentries',
2461              'rssarticles',
2462              'approval',
2463              'defaultsortdir',
2464              'defaultsort');
2465  
2466          $result = new stdClass;
2467          $result->settings = new stdClass;
2468          $result->importfields = array();
2469          $result->currentfields = $DB->get_records('data_fields', array('dataid'=>$this->module->id));
2470          if (!$result->currentfields) {
2471              $result->currentfields = array();
2472          }
2473  
2474  
2475          /* Grab XML */
2476          $presetxml = $this->data_preset_get_file_contents($fs, $fileobj, $this->directory,'preset.xml');
2477          $parsedxml = xmlize($presetxml, 0);
2478  
2479          /* First, do settings. Put in user friendly array. */
2480          $settingsarray = $parsedxml['preset']['#']['settings'][0]['#'];
2481          $result->settings = new StdClass();
2482          foreach ($settingsarray as $setting => $value) {
2483              if (!is_array($value) || !in_array($setting, $allowed_settings)) {
2484                  // unsupported setting
2485                  continue;
2486              }
2487              $result->settings->$setting = $value[0]['#'];
2488          }
2489  
2490          /* Now work out fields to user friendly array */
2491          $fieldsarray = $parsedxml['preset']['#']['field'];
2492          foreach ($fieldsarray as $field) {
2493              if (!is_array($field)) {
2494                  continue;
2495              }
2496              $f = new StdClass();
2497              foreach ($field['#'] as $param => $value) {
2498                  if (!is_array($value)) {
2499                      continue;
2500                  }
2501                  $f->$param = $value[0]['#'];
2502              }
2503              $f->dataid = $this->module->id;
2504              $f->type = clean_param($f->type, PARAM_ALPHA);
2505              $result->importfields[] = $f;
2506          }
2507          /* Now add the HTML templates to the settings array so we can update d */
2508          $result->settings->singletemplate     = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"singletemplate.html");
2509          $result->settings->listtemplate       = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplate.html");
2510          $result->settings->listtemplateheader = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplateheader.html");
2511          $result->settings->listtemplatefooter = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"listtemplatefooter.html");
2512          $result->settings->addtemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"addtemplate.html");
2513          $result->settings->rsstemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstemplate.html");
2514          $result->settings->rsstitletemplate   = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"rsstitletemplate.html");
2515          $result->settings->csstemplate        = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"csstemplate.css");
2516          $result->settings->jstemplate         = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"jstemplate.js");
2517          $result->settings->asearchtemplate    = $this->data_preset_get_file_contents($fs, $fileobj,$this->directory,"asearchtemplate.html");
2518  
2519          $result->settings->instance = $this->module->id;
2520          return $result;
2521      }
2522  
2523      /**
2524       * Import the preset into the given database module
2525       * @return bool
2526       */
2527      function import($overwritesettings) {
2528          global $DB, $CFG, $OUTPUT;
2529  
2530          $params = $this->get_preset_settings();
2531          $settings = $params->settings;
2532          $newfields = $params->importfields;
2533          $currentfields = $params->currentfields;
2534          $preservedfields = array();
2535  
2536          /* Maps fields and makes new ones */
2537          if (!empty($newfields)) {
2538              /* We require an injective mapping, and need to know what to protect */
2539              foreach ($newfields as $nid => $newfield) {
2540                  $cid = optional_param("field_$nid", -1, PARAM_INT);
2541                  if ($cid == -1) {
2542                      continue;
2543                  }
2544                  if (array_key_exists($cid, $preservedfields)){
2545                      throw new \moodle_exception('notinjectivemap', 'data');
2546                  }
2547                  else $preservedfields[$cid] = true;
2548              }
2549              $missingfieldtypes = [];
2550              foreach ($newfields as $nid => $newfield) {
2551                  $cid = optional_param("field_$nid", -1, PARAM_INT);
2552  
2553                  /* A mapping. Just need to change field params. Data kept. */
2554                  if ($cid != -1 and isset($currentfields[$cid])) {
2555                      $fieldobject = data_get_field_from_id($currentfields[$cid]->id, $this->module);
2556                      foreach ($newfield as $param => $value) {
2557                          if ($param != "id") {
2558                              $fieldobject->field->$param = $value;
2559                          }
2560                      }
2561                      unset($fieldobject->field->similarfield);
2562                      $fieldobject->update_field();
2563                      unset($fieldobject);
2564                  } else {
2565                      /* Make a new field */
2566                      $filepath = "field/$newfield->type/field.class.php";
2567                      if (!file_exists($filepath)) {
2568                          $missingfieldtypes[] = $newfield->name;
2569                          continue;
2570                      }
2571                      include_once($filepath);
2572  
2573                      if (!isset($newfield->description)) {
2574                          $newfield->description = '';
2575                      }
2576                      $classname = 'data_field_'.$newfield->type;
2577                      $fieldclass = new $classname($newfield, $this->module);
2578                      $fieldclass->insert_field();
2579                      unset($fieldclass);
2580                  }
2581              }
2582              if (!empty($missingfieldtypes)) {
2583                  echo $OUTPUT->notification(get_string('missingfieldtypeimport', 'data') . html_writer::alist($missingfieldtypes));
2584              }
2585          }
2586  
2587          /* Get rid of all old unused data */
2588          foreach ($currentfields as $cid => $currentfield) {
2589              if (!array_key_exists($cid, $preservedfields)) {
2590                  /* Data not used anymore so wipe! */
2591                  echo "Deleting field $currentfield->name<br />";
2592  
2593                  // Delete all information related to fields.
2594                  $todelete = data_get_field_from_id($currentfield->id, $this->module);
2595                  $todelete->delete_field();
2596              }
2597          }
2598  
2599          // handle special settings here
2600          if (!empty($settings->defaultsort)) {
2601              if (is_numeric($settings->defaultsort)) {
2602                  // old broken value
2603                  $settings->defaultsort = 0;
2604              } else {
2605                  $settings->defaultsort = (int)$DB->get_field('data_fields', 'id', array('dataid'=>$this->module->id, 'name'=>$settings->defaultsort));
2606              }
2607          } else {
2608              $settings->defaultsort = 0;
2609          }
2610  
2611          // do we want to overwrite all current database settings?
2612          if ($overwritesettings) {
2613              // all supported settings
2614              $overwrite = array_keys((array)$settings);
2615          } else {
2616              // only templates and sorting
2617              $overwrite = array('singletemplate', 'listtemplate', 'listtemplateheader', 'listtemplatefooter',
2618                                 'addtemplate', 'rsstemplate', 'rsstitletemplate', 'csstemplate', 'jstemplate',
2619                                 'asearchtemplate', 'defaultsortdir', 'defaultsort');
2620          }
2621  
2622          // now overwrite current data settings
2623          foreach ($this->module as $prop=>$unused) {
2624              if (in_array($prop, $overwrite)) {
2625                  $this->module->$prop = $settings->$prop;
2626              }
2627          }
2628  
2629          data_update_instance($this->module);
2630  
2631          return $this->cleanup();
2632      }
2633  
2634      /**
2635       * Any clean up routines should go here
2636       * @return bool
2637       */
2638      public function cleanup() {
2639          return true;
2640      }
2641  }
2642  
2643  /**
2644   * Data preset importer for uploaded presets
2645   *
2646   * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more.
2647   * @todo MDL-75189 Final deprecation in Moodle 4.5.
2648   */
2649  class data_preset_upload_importer extends data_preset_importer {
2650      public function __construct($course, $cm, $module, $filepath) {
2651          global $USER;
2652  
2653          debugging(
2654              'data_preset_upload_importer is deprecated. Please use mod\\data\\local\\importer\\preset_upload_importer instead',
2655              DEBUG_DEVELOPER
2656          );
2657  
2658          if (is_file($filepath)) {
2659              $fp = get_file_packer();
2660              if ($fp->extract_to_pathname($filepath, $filepath.'_extracted')) {
2661                  fulldelete($filepath);
2662              }
2663              $filepath .= '_extracted';
2664          }
2665          parent::__construct($course, $cm, $module, $filepath);
2666      }
2667  
2668      public function cleanup() {
2669          return fulldelete($this->directory);
2670      }
2671  }
2672  
2673  /**
2674   * Data preset importer for existing presets
2675   *
2676   * @deprecated since Moodle 4.1 MDL-75140 - please do not use this class any more.
2677   * @todo MDL-75189 Final deprecation in Moodle 4.5.
2678   */
2679  class data_preset_existing_importer extends data_preset_importer {
2680      protected $userid;
2681      public function __construct($course, $cm, $module, $fullname) {
2682          global $USER;
2683  
2684          debugging(
2685              'data_preset_existing_importer is deprecated. Please use mod\\data\\local\\importer\\preset_existing_importer instead',
2686              DEBUG_DEVELOPER
2687          );
2688  
2689          list($userid, $shortname) = explode('/', $fullname, 2);
2690          $context = context_module::instance($cm->id);
2691          if ($userid && ($userid != $USER->id) && !has_capability('mod/data:manageuserpresets', $context) && !has_capability('mod/data:viewalluserpresets', $context)) {
2692             throw new coding_exception('Invalid preset provided');
2693          }
2694  
2695          $this->userid = $userid;
2696          $filepath = data_preset_path($course, $userid, $shortname);
2697          parent::__construct($course, $cm, $module, $filepath);
2698      }
2699      public function get_userid() {
2700          return $this->userid;
2701      }
2702  }
2703  
2704  /**
2705   * @global object
2706   * @global object
2707   * @param object $course
2708   * @param int $userid
2709   * @param string $shortname
2710   * @return string
2711   */
2712  function data_preset_path($course, $userid, $shortname) {
2713      global $USER, $CFG;
2714  
2715      $context = context_course::instance($course->id);
2716  
2717      $userid = (int)$userid;
2718  
2719      $path = null;
2720      if ($userid > 0 && ($userid == $USER->id || has_capability('mod/data:viewalluserpresets', $context))) {
2721          $path = $CFG->dataroot.'/data/preset/'.$userid.'/'.$shortname;
2722      } else if ($userid == 0) {
2723          $path = $CFG->dirroot.'/mod/data/preset/'.$shortname;
2724      } else if ($userid < 0) {
2725          $path = $CFG->tempdir.'/data/'.-$userid.'/'.$shortname;
2726      }
2727  
2728      return $path;
2729  }
2730  
2731  /**
2732   * Implementation of the function for printing the form elements that control
2733   * whether the course reset functionality affects the data.
2734   *
2735   * @param MoodleQuickForm $mform form passed by reference
2736   */
2737  function data_reset_course_form_definition(&$mform) {
2738      $mform->addElement('header', 'dataheader', get_string('modulenameplural', 'data'));
2739      $mform->addElement('checkbox', 'reset_data', get_string('deleteallentries','data'));
2740  
2741      $mform->addElement('checkbox', 'reset_data_notenrolled', get_string('deletenotenrolled', 'data'));
2742      $mform->disabledIf('reset_data_notenrolled', 'reset_data', 'checked');
2743  
2744      $mform->addElement('checkbox', 'reset_data_ratings', get_string('deleteallratings'));
2745      $mform->disabledIf('reset_data_ratings', 'reset_data', 'checked');
2746  
2747      $mform->addElement('checkbox', 'reset_data_comments', get_string('deleteallcomments'));
2748      $mform->disabledIf('reset_data_comments', 'reset_data', 'checked');
2749  
2750      $mform->addElement('checkbox', 'reset_data_tags', get_string('removealldatatags', 'data'));
2751      $mform->disabledIf('reset_data_tags', 'reset_data', 'checked');
2752  }
2753  
2754  /**
2755   * Course reset form defaults.
2756   * @return array
2757   */
2758  function data_reset_course_form_defaults($course) {
2759      return array('reset_data'=>0, 'reset_data_ratings'=>1, 'reset_data_comments'=>1, 'reset_data_notenrolled'=>0);
2760  }
2761  
2762  /**
2763   * Removes all grades from gradebook
2764   *
2765   * @global object
2766   * @global object
2767   * @param int $courseid
2768   * @param string $type optional type
2769   */
2770  function data_reset_gradebook($courseid, $type='') {
2771      global $CFG, $DB;
2772  
2773      $sql = "SELECT d.*, cm.idnumber as cmidnumber, d.course as courseid
2774                FROM {data} d, {course_modules} cm, {modules} m
2775               WHERE m.name='data' AND m.id=cm.module AND cm.instance=d.id AND d.course=?";
2776  
2777      if ($datas = $DB->get_records_sql($sql, array($courseid))) {
2778          foreach ($datas as $data) {
2779              data_grade_item_update($data, 'reset');
2780          }
2781      }
2782  }
2783  
2784  /**
2785   * Actual implementation of the reset course functionality, delete all the
2786   * data responses for course $data->courseid.
2787   *
2788   * @global object
2789   * @global object
2790   * @param object $data the data submitted from the reset course.
2791   * @return array status array
2792   */
2793  function data_reset_userdata($data) {
2794      global $CFG, $DB;
2795      require_once($CFG->libdir.'/filelib.php');
2796      require_once($CFG->dirroot.'/rating/lib.php');
2797  
2798      $componentstr = get_string('modulenameplural', 'data');
2799      $status = array();
2800  
2801      $allrecordssql = "SELECT r.id
2802                          FROM {data_records} r
2803                               INNER JOIN {data} d ON r.dataid = d.id
2804                         WHERE d.course = ?";
2805  
2806      $alldatassql = "SELECT d.id
2807                        FROM {data} d
2808                       WHERE d.course=?";
2809  
2810      $rm = new rating_manager();
2811      $ratingdeloptions = new stdClass;
2812      $ratingdeloptions->component = 'mod_data';
2813      $ratingdeloptions->ratingarea = 'entry';
2814  
2815      // Set the file storage - may need it to remove files later.
2816      $fs = get_file_storage();
2817  
2818      // delete entries if requested
2819      if (!empty($data->reset_data)) {
2820          $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2821          $DB->delete_records_select('data_content', "recordid IN ($allrecordssql)", array($data->courseid));
2822          $DB->delete_records_select('data_records', "dataid IN ($alldatassql)", array($data->courseid));
2823  
2824          if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2825              foreach ($datas as $dataid=>$unused) {
2826                  if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2827                      continue;
2828                  }
2829                  $datacontext = context_module::instance($cm->id);
2830  
2831                  // Delete any files that may exist.
2832                  $fs->delete_area_files($datacontext->id, 'mod_data', 'content');
2833  
2834                  $ratingdeloptions->contextid = $datacontext->id;
2835                  $rm->delete_ratings($ratingdeloptions);
2836  
2837                  core_tag_tag::delete_instances('mod_data', null, $datacontext->id);
2838              }
2839          }
2840  
2841          if (empty($data->reset_gradebook_grades)) {
2842              // remove all grades from gradebook
2843              data_reset_gradebook($data->courseid);
2844          }
2845          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallentries', 'data'), 'error'=>false);
2846      }
2847  
2848      // remove entries by users not enrolled into course
2849      if (!empty($data->reset_data_notenrolled)) {
2850          $recordssql = "SELECT r.id, r.userid, r.dataid, u.id AS userexists, u.deleted AS userdeleted
2851                           FROM {data_records} r
2852                                JOIN {data} d ON r.dataid = d.id
2853                                LEFT JOIN {user} u ON r.userid = u.id
2854                          WHERE d.course = ? AND r.userid > 0";
2855  
2856          $course_context = context_course::instance($data->courseid);
2857          $notenrolled = array();
2858          $fields = array();
2859          $rs = $DB->get_recordset_sql($recordssql, array($data->courseid));
2860          foreach ($rs as $record) {
2861              if (array_key_exists($record->userid, $notenrolled) or !$record->userexists or $record->userdeleted
2862                or !is_enrolled($course_context, $record->userid)) {
2863                  //delete ratings
2864                  if (!$cm = get_coursemodule_from_instance('data', $record->dataid)) {
2865                      continue;
2866                  }
2867                  $datacontext = context_module::instance($cm->id);
2868                  $ratingdeloptions->contextid = $datacontext->id;
2869                  $ratingdeloptions->itemid = $record->id;
2870                  $rm->delete_ratings($ratingdeloptions);
2871  
2872                  // Delete any files that may exist.
2873                  if ($contents = $DB->get_records('data_content', array('recordid' => $record->id), '', 'id')) {
2874                      foreach ($contents as $content) {
2875                          $fs->delete_area_files($datacontext->id, 'mod_data', 'content', $content->id);
2876                      }
2877                  }
2878                  $notenrolled[$record->userid] = true;
2879  
2880                  core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $record->id);
2881  
2882                  $DB->delete_records('comments', array('itemid' => $record->id, 'commentarea' => 'database_entry'));
2883                  $DB->delete_records('data_content', array('recordid' => $record->id));
2884                  $DB->delete_records('data_records', array('id' => $record->id));
2885              }
2886          }
2887          $rs->close();
2888          $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'data'), 'error'=>false);
2889      }
2890  
2891      // remove all ratings
2892      if (!empty($data->reset_data_ratings)) {
2893          if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2894              foreach ($datas as $dataid=>$unused) {
2895                  if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2896                      continue;
2897                  }
2898                  $datacontext = context_module::instance($cm->id);
2899  
2900                  $ratingdeloptions->contextid = $datacontext->id;
2901                  $rm->delete_ratings($ratingdeloptions);
2902              }
2903          }
2904  
2905          if (empty($data->reset_gradebook_grades)) {
2906              // remove all grades from gradebook
2907              data_reset_gradebook($data->courseid);
2908          }
2909  
2910          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false);
2911      }
2912  
2913      // remove all comments
2914      if (!empty($data->reset_data_comments)) {
2915          $DB->delete_records_select('comments', "itemid IN ($allrecordssql) AND commentarea='database_entry'", array($data->courseid));
2916          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false);
2917      }
2918  
2919      // Remove all the tags.
2920      if (!empty($data->reset_data_tags)) {
2921          if ($datas = $DB->get_records_sql($alldatassql, array($data->courseid))) {
2922              foreach ($datas as $dataid => $unused) {
2923                  if (!$cm = get_coursemodule_from_instance('data', $dataid)) {
2924                      continue;
2925                  }
2926  
2927                  $context = context_module::instance($cm->id);
2928                  core_tag_tag::delete_instances('mod_data', null, $context->id);
2929  
2930              }
2931          }
2932          $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'data'), 'error' => false);
2933      }
2934  
2935      // updating dates - shift may be negative too
2936      if ($data->timeshift) {
2937          // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
2938          // See MDL-9367.
2939          shift_course_mod_dates('data', array('timeavailablefrom', 'timeavailableto',
2940              'timeviewfrom', 'timeviewto', 'assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
2941          $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
2942      }
2943  
2944      return $status;
2945  }
2946  
2947  /**
2948   * Returns all other caps used in module
2949   *
2950   * @return array
2951   */
2952  function data_get_extra_capabilities() {
2953      return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate',
2954              'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete'];
2955  }
2956  
2957  /**
2958   * @param string $feature FEATURE_xx constant for requested feature
2959   * @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose.
2960   */
2961  function data_supports($feature) {
2962      switch($feature) {
2963          case FEATURE_GROUPS:                  return true;
2964          case FEATURE_GROUPINGS:               return true;
2965          case FEATURE_MOD_INTRO:               return true;
2966          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
2967          case FEATURE_COMPLETION_HAS_RULES:    return true;
2968          case FEATURE_GRADE_HAS_GRADE:         return true;
2969          case FEATURE_GRADE_OUTCOMES:          return true;
2970          case FEATURE_RATE:                    return true;
2971          case FEATURE_BACKUP_MOODLE2:          return true;
2972          case FEATURE_SHOW_DESCRIPTION:        return true;
2973          case FEATURE_COMMENT:                 return true;
2974          case FEATURE_MOD_PURPOSE:             return MOD_PURPOSE_COLLABORATION;
2975  
2976          default: return null;
2977      }
2978  }
2979  
2980  /**
2981   * Import records for a data instance from csv data.
2982   *
2983   * @param object $cm Course module of the data instance.
2984   * @param object $data The data instance.
2985   * @param string $csvdata The csv data to be imported.
2986   * @param string $encoding The encoding of csv data.
2987   * @param string $fielddelimiter The delimiter of the csv data.
2988   * @return int Number of records added.
2989   */
2990  function data_import_csv($cm, $data, &$csvdata, $encoding, $fielddelimiter) {
2991      global $CFG, $DB;
2992      // Large files are likely to take their time and memory. Let PHP know
2993      // that we'll take longer, and that the process should be recycled soon
2994      // to free up memory.
2995      core_php_time_limit::raise();
2996      raise_memory_limit(MEMORY_EXTRA);
2997  
2998      $iid = csv_import_reader::get_new_iid('moddata');
2999      $cir = new csv_import_reader($iid, 'moddata');
3000  
3001      $context = context_module::instance($cm->id);
3002  
3003      $readcount = $cir->load_csv_content($csvdata, $encoding, $fielddelimiter);
3004      $csvdata = null; // Free memory.
3005      if (empty($readcount)) {
3006          throw new \moodle_exception('csvfailed', 'data', "{$CFG->wwwroot}/mod/data/edit.php?d={$data->id}");
3007      } else {
3008          if (!$fieldnames = $cir->get_columns()) {
3009              throw new \moodle_exception('cannotreadtmpfile', 'error');
3010          }
3011  
3012          // Check the fieldnames are valid.
3013          $rawfields = $DB->get_records('data_fields', array('dataid' => $data->id), '', 'name, id, type');
3014          $fields = array();
3015          $errorfield = '';
3016          $usernamestring = get_string('username');
3017          $safetoskipfields = array(get_string('user'), get_string('email'),
3018              get_string('timeadded', 'data'), get_string('timemodified', 'data'),
3019              get_string('approved', 'data'), get_string('tags', 'data'));
3020          $userfieldid = null;
3021          foreach ($fieldnames as $id => $name) {
3022              if (!isset($rawfields[$name])) {
3023                  if ($name == $usernamestring) {
3024                      $userfieldid = $id;
3025                  } else if (!in_array($name, $safetoskipfields)) {
3026                      $errorfield .= "'$name' ";
3027                  }
3028              } else {
3029                  // If this is the second time, a field with this name comes up, it must be a field not provided by the user...
3030                  // like the username.
3031                  if (isset($fields[$name])) {
3032                      if ($name == $usernamestring) {
3033                          $userfieldid = $id;
3034                      }
3035                      unset($fieldnames[$id]); // To ensure the user provided content fields remain in the array once flipped.
3036                  } else {
3037                      $field = $rawfields[$name];
3038                      $filepath = "$CFG->dirroot/mod/data/field/$field->type/field.class.php";
3039                      if (!file_exists($filepath)) {
3040                          $errorfield .= "'$name' ";
3041                          continue;
3042                      }
3043                      require_once($filepath);
3044                      $classname = 'data_field_' . $field->type;
3045                      $fields[$name] = new $classname($field, $data, $cm);
3046                  }
3047              }
3048          }
3049  
3050          if (!empty($errorfield)) {
3051              throw new \moodle_exception('fieldnotmatched', 'data',
3052                  "{$CFG->wwwroot}/mod/data/edit.php?d={$data->id}", $errorfield);
3053          }
3054  
3055          $fieldnames = array_flip($fieldnames);
3056  
3057          $cir->init();
3058          $recordsadded = 0;
3059          while ($record = $cir->next()) {
3060              $authorid = null;
3061              if ($userfieldid) {
3062                  if (!($author = core_user::get_user_by_username($record[$userfieldid], 'id'))) {
3063                      $authorid = null;
3064                  } else {
3065                      $authorid = $author->id;
3066                  }
3067              }
3068              if ($recordid = data_add_record($data, 0, $authorid)) {  // Add instance to data_record.
3069                  foreach ($fields as $field) {
3070                      $fieldid = $fieldnames[$field->field->name];
3071                      if (isset($record[$fieldid])) {
3072                          $value = $record[$fieldid];
3073                      } else {
3074                          $value = '';
3075                      }
3076  
3077                      if (method_exists($field, 'update_content_import')) {
3078                          $field->update_content_import($recordid, $value, 'field_' . $field->field->id);
3079                      } else {
3080                          $content = new stdClass();
3081                          $content->fieldid = $field->field->id;
3082                          $content->content = $value;
3083                          $content->recordid = $recordid;
3084                          $DB->insert_record('data_content', $content);
3085                      }
3086                  }
3087  
3088                  if (core_tag_tag::is_enabled('mod_data', 'data_records') &&
3089                      isset($fieldnames[get_string('tags', 'data')])) {
3090                      $columnindex = $fieldnames[get_string('tags', 'data')];
3091                      $rawtags = $record[$columnindex];
3092                      $tags = explode(',', $rawtags);
3093                      foreach ($tags as $tag) {
3094                          $tag = trim($tag);
3095                          if (empty($tag)) {
3096                              continue;
3097                          }
3098                          core_tag_tag::add_item_tag('mod_data', 'data_records', $recordid, $context, $tag);
3099                      }
3100                  }
3101  
3102                  $recordsadded++;
3103                  print get_string('added', 'moodle', $recordsadded) . ". " . get_string('entry', 'data') . " (ID $recordid)<br />\n";
3104              }
3105          }
3106          $cir->close();
3107          $cir->cleanup(true);
3108          return $recordsadded;
3109      }
3110      return 0;
3111  }
3112  
3113  /**
3114   * @global object
3115   * @param array $export
3116   * @param string $delimiter_name
3117   * @param object $database
3118   * @param int $count
3119   * @param bool $return
3120   * @return string|void
3121   */
3122  function data_export_csv($export, $delimiter_name, $database, $count, $return=false) {
3123      global $CFG;
3124      require_once($CFG->libdir . '/csvlib.class.php');
3125  
3126      $filename = $database . '-' . $count . '-record';
3127      if ($count > 1) {
3128          $filename .= 's';
3129      }
3130      if ($return) {
3131          return csv_export_writer::print_array($export, $delimiter_name, '"', true);
3132      } else {
3133          csv_export_writer::download_array($filename, $export, $delimiter_name);
3134      }
3135  }
3136  
3137  /**
3138   * @global object
3139   * @param array $export
3140   * @param string $dataname
3141   * @param int $count
3142   * @return string
3143   */
3144  function data_export_xls($export, $dataname, $count) {
3145      global $CFG;
3146      require_once("$CFG->libdir/excellib.class.php");
3147      $filename = clean_filename("{$dataname}-{$count}_record");
3148      if ($count > 1) {
3149          $filename .= 's';
3150      }
3151      $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
3152      $filename .= '.xls';
3153  
3154      $filearg = '-';
3155      $workbook = new MoodleExcelWorkbook($filearg);
3156      $workbook->send($filename);
3157      $worksheet = array();
3158      $worksheet[0] = $workbook->add_worksheet('');
3159      $rowno = 0;
3160      foreach ($export as $row) {
3161          $colno = 0;
3162          foreach($row as $col) {
3163              $worksheet[0]->write($rowno, $colno, $col);
3164              $colno++;
3165          }
3166          $rowno++;
3167      }
3168      $workbook->close();
3169      return $filename;
3170  }
3171  
3172  /**
3173   * @global object
3174   * @param array $export
3175   * @param string $dataname
3176   * @param int $count
3177   * @param string
3178   */
3179  function data_export_ods($export, $dataname, $count) {
3180      global $CFG;
3181      require_once("$CFG->libdir/odslib.class.php");
3182      $filename = clean_filename("{$dataname}-{$count}_record");
3183      if ($count > 1) {
3184          $filename .= 's';
3185      }
3186      $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
3187      $filename .= '.ods';
3188      $filearg = '-';
3189      $workbook = new MoodleODSWorkbook($filearg);
3190      $workbook->send($filename);
3191      $worksheet = array();
3192      $worksheet[0] = $workbook->add_worksheet('');
3193      $rowno = 0;
3194      foreach ($export as $row) {
3195          $colno = 0;
3196          foreach($row as $col) {
3197              $worksheet[0]->write($rowno, $colno, $col);
3198              $colno++;
3199          }
3200          $rowno++;
3201      }
3202      $workbook->close();
3203      return $filename;
3204  }
3205  
3206  /**
3207   * @global object
3208   * @param int $dataid
3209   * @param array $fields
3210   * @param array $selectedfields
3211   * @param int $currentgroup group ID of the current group. This is used for
3212   * exporting data while maintaining group divisions.
3213   * @param object $context the context in which the operation is performed (for capability checks)
3214   * @param bool $userdetails whether to include the details of the record author
3215   * @param bool $time whether to include time created/modified
3216   * @param bool $approval whether to include approval status
3217   * @param bool $tags whether to include tags
3218   * @return array
3219   */
3220  function data_get_exportdata($dataid, $fields, $selectedfields, $currentgroup=0, $context=null,
3221                               $userdetails=false, $time=false, $approval=false, $tags = false) {
3222      global $DB;
3223  
3224      if (is_null($context)) {
3225          $context = context_system::instance();
3226      }
3227      // exporting user data needs special permission
3228      $userdetails = $userdetails && has_capability('mod/data:exportuserinfo', $context);
3229  
3230      $exportdata = array();
3231  
3232      // populate the header in first row of export
3233      foreach($fields as $key => $field) {
3234          if (!in_array($field->field->id, $selectedfields)) {
3235              // ignore values we aren't exporting
3236              unset($fields[$key]);
3237          } else {
3238              $exportdata[0][] = $field->field->name;
3239          }
3240      }
3241      if ($tags) {
3242          $exportdata[0][] = get_string('tags', 'data');
3243      }
3244      if ($userdetails) {
3245          $exportdata[0][] = get_string('user');
3246          $exportdata[0][] = get_string('username');
3247          $exportdata[0][] = get_string('email');
3248      }
3249      if ($time) {
3250          $exportdata[0][] = get_string('timeadded', 'data');
3251          $exportdata[0][] = get_string('timemodified', 'data');
3252      }
3253      if ($approval) {
3254          $exportdata[0][] = get_string('approved', 'data');
3255      }
3256  
3257      $datarecords = $DB->get_records('data_records', array('dataid'=>$dataid));
3258      ksort($datarecords);
3259      $line = 1;
3260      foreach($datarecords as $record) {
3261          // get content indexed by fieldid
3262          if ($currentgroup) {
3263              $select = 'SELECT c.fieldid, c.content, c.content1, c.content2, c.content3, c.content4 FROM {data_content} c, {data_records} r WHERE c.recordid = ? AND r.id = c.recordid AND r.groupid = ?';
3264              $where = array($record->id, $currentgroup);
3265          } else {
3266              $select = 'SELECT fieldid, content, content1, content2, content3, content4 FROM {data_content} WHERE recordid = ?';
3267              $where = array($record->id);
3268          }
3269  
3270          if( $content = $DB->get_records_sql($select, $where) ) {
3271              foreach($fields as $field) {
3272                  $contents = '';
3273                  if(isset($content[$field->field->id])) {
3274                      $contents = $field->export_text_value($content[$field->field->id]);
3275                  }
3276                  $exportdata[$line][] = $contents;
3277              }
3278              if ($tags) {
3279                  $itemtags = \core_tag_tag::get_item_tags_array('mod_data', 'data_records', $record->id);
3280                  $exportdata[$line][] = implode(', ', $itemtags);
3281              }
3282              if ($userdetails) { // Add user details to the export data
3283                  $userdata = get_complete_user_data('id', $record->userid);
3284                  $exportdata[$line][] = fullname($userdata);
3285                  $exportdata[$line][] = $userdata->username;
3286                  $exportdata[$line][] = $userdata->email;
3287              }
3288              if ($time) { // Add time added / modified
3289                  $exportdata[$line][] = userdate($record->timecreated);
3290                  $exportdata[$line][] = userdate($record->timemodified);
3291              }
3292              if ($approval) { // Add approval status
3293                  $exportdata[$line][] = (int) $record->approved;
3294              }
3295          }
3296          $line++;
3297      }
3298      $line--;
3299      return $exportdata;
3300  }
3301  
3302  ////////////////////////////////////////////////////////////////////////////////
3303  // File API                                                                   //
3304  ////////////////////////////////////////////////////////////////////////////////
3305  
3306  /**
3307   * Lists all browsable file areas
3308   *
3309   * @package  mod_data
3310   * @category files
3311   * @param stdClass $course course object
3312   * @param stdClass $cm course module object
3313   * @param stdClass $context context object
3314   * @return array
3315   */
3316  function data_get_file_areas($course, $cm, $context) {
3317      return array('content' => get_string('areacontent', 'mod_data'));
3318  }
3319  
3320  /**
3321   * File browsing support for data module.
3322   *
3323   * @param file_browser $browser
3324   * @param array $areas
3325   * @param stdClass $course
3326   * @param cm_info $cm
3327   * @param context $context
3328   * @param string $filearea
3329   * @param int $itemid
3330   * @param string $filepath
3331   * @param string $filename
3332   * @return file_info_stored file_info_stored instance or null if not found
3333   */
3334  function data_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
3335      global $CFG, $DB, $USER;
3336  
3337      if ($context->contextlevel != CONTEXT_MODULE) {
3338          return null;
3339      }
3340  
3341      if (!isset($areas[$filearea])) {
3342          return null;
3343      }
3344  
3345      if (is_null($itemid)) {
3346          require_once($CFG->dirroot.'/mod/data/locallib.php');
3347          return new data_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
3348      }
3349  
3350      if (!$content = $DB->get_record('data_content', array('id'=>$itemid))) {
3351          return null;
3352      }
3353  
3354      if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3355          return null;
3356      }
3357  
3358      if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3359          return null;
3360      }
3361  
3362      if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3363          return null;
3364      }
3365  
3366      //check if approved
3367      if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3368          return null;
3369      }
3370  
3371      // group access
3372      if ($record->groupid) {
3373          $groupmode = groups_get_activity_groupmode($cm, $course);
3374          if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3375              if (!groups_is_member($record->groupid)) {
3376                  return null;
3377              }
3378          }
3379      }
3380  
3381      $fieldobj = data_get_field($field, $data, $cm);
3382  
3383      $filepath = is_null($filepath) ? '/' : $filepath;
3384      $filename = is_null($filename) ? '.' : $filename;
3385      if (!$fieldobj->file_ok($filepath.$filename)) {
3386          return null;
3387      }
3388  
3389      $fs = get_file_storage();
3390      if (!($storedfile = $fs->get_file($context->id, 'mod_data', $filearea, $itemid, $filepath, $filename))) {
3391          return null;
3392      }
3393  
3394      // Checks to see if the user can manage files or is the owner.
3395      // TODO MDL-33805 - Do not use userid here and move the capability check above.
3396      if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
3397          return null;
3398      }
3399  
3400      $urlbase = $CFG->wwwroot.'/pluginfile.php';
3401  
3402      return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
3403  }
3404  
3405  /**
3406   * Serves the data attachments. Implements needed access control ;-)
3407   *
3408   * @package  mod_data
3409   * @category files
3410   * @param stdClass $course course object
3411   * @param stdClass $cm course module object
3412   * @param stdClass $context context object
3413   * @param string $filearea file area
3414   * @param array $args extra arguments
3415   * @param bool $forcedownload whether or not force download
3416   * @param array $options additional options affecting the file serving
3417   * @return bool false if file not found, does not return if found - justsend the file
3418   */
3419  function data_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
3420      global $CFG, $DB;
3421  
3422      if ($context->contextlevel != CONTEXT_MODULE) {
3423          return false;
3424      }
3425  
3426      require_course_login($course, true, $cm);
3427  
3428      if ($filearea === 'content') {
3429          $contentid = (int)array_shift($args);
3430  
3431          if (!$content = $DB->get_record('data_content', array('id'=>$contentid))) {
3432              return false;
3433          }
3434  
3435          if (!$field = $DB->get_record('data_fields', array('id'=>$content->fieldid))) {
3436              return false;
3437          }
3438  
3439          if (!$record = $DB->get_record('data_records', array('id'=>$content->recordid))) {
3440              return false;
3441          }
3442  
3443          if (!$data = $DB->get_record('data', array('id'=>$field->dataid))) {
3444              return false;
3445          }
3446  
3447          if ($data->id != $cm->instance) {
3448              // hacker attempt - context does not match the contentid
3449              return false;
3450          }
3451  
3452          //check if approved
3453          if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3454              return false;
3455          }
3456  
3457          // group access
3458          if ($record->groupid) {
3459              $groupmode = groups_get_activity_groupmode($cm, $course);
3460              if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3461                  if (!groups_is_member($record->groupid)) {
3462                      return false;
3463                  }
3464              }
3465          }
3466  
3467          $fieldobj = data_get_field($field, $data, $cm);
3468  
3469          $relativepath = implode('/', $args);
3470          $fullpath = "/$context->id/mod_data/content/$content->id/$relativepath";
3471  
3472          if (!$fieldobj->file_ok($relativepath)) {
3473              return false;
3474          }
3475  
3476          $fs = get_file_storage();
3477          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3478              return false;
3479          }
3480  
3481          // finally send the file
3482          send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
3483      }
3484  
3485      return false;
3486  }
3487  
3488  
3489  function data_extend_navigation($navigation, $course, $module, $cm) {
3490      global $CFG, $OUTPUT, $USER, $DB;
3491      require_once($CFG->dirroot . '/mod/data/locallib.php');
3492  
3493      $rid = optional_param('rid', 0, PARAM_INT);
3494  
3495      $data = $DB->get_record('data', array('id'=>$cm->instance));
3496      $currentgroup = groups_get_activity_group($cm);
3497      $groupmode = groups_get_activity_groupmode($cm);
3498  
3499       $numentries = data_numentries($data);
3500      $canmanageentries = has_capability('mod/data:manageentries', context_module::instance($cm->id));
3501  
3502      if ($data->entriesleft = data_get_entries_left_to_add($data, $numentries, $canmanageentries)) {
3503          $entriesnode = $navigation->add(get_string('entrieslefttoadd', 'data', $data));
3504          $entriesnode->add_class('note');
3505      }
3506  
3507      $navigation->add(get_string('list', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance)));
3508      if (!empty($rid)) {
3509          $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'rid'=>$rid)));
3510      } else {
3511          $navigation->add(get_string('single', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'single')));
3512      }
3513      $navigation->add(get_string('search', 'data'), new moodle_url('/mod/data/view.php', array('d'=>$cm->instance, 'mode'=>'asearch')));
3514  }
3515  
3516  /**
3517   * Adds module specific settings to the settings block
3518   *
3519   * @param settings_navigation $settings The settings navigation object
3520   * @param navigation_node $datanode The node to add module settings to
3521   */
3522  function data_extend_settings_navigation(settings_navigation $settings, navigation_node $datanode) {
3523      global $DB, $CFG, $USER;
3524  
3525      $data = $DB->get_record('data', array("id" => $settings->get_page()->cm->instance));
3526  
3527      $currentgroup = groups_get_activity_group($settings->get_page()->cm);
3528      $groupmode = groups_get_activity_groupmode($settings->get_page()->cm);
3529  
3530      // Took out participation list here!
3531      if (data_user_can_add_entry($data, $currentgroup, $groupmode, $settings->get_page()->cm->context)) {
3532          if (empty($editentry)) { //TODO: undefined
3533              $addstring = get_string('add', 'data');
3534          } else {
3535              $addstring = get_string('editentry', 'data');
3536          }
3537          $addentrynode = $datanode->add($addstring,
3538              new moodle_url('/mod/data/edit.php', array('d' => $settings->get_page()->cm->instance)));
3539          $addentrynode->set_show_in_secondary_navigation(false);
3540      }
3541  
3542      if (has_capability(DATA_CAP_EXPORT, $settings->get_page()->cm->context)) {
3543          // The capability required to Export database records is centrally defined in 'lib.php'
3544          // and should be weaker than those required to edit Templates, Fields and Presets.
3545          $exportentriesnode = $datanode->add(get_string('exportentries', 'data'),
3546              new moodle_url('/mod/data/export.php', array('d' => $data->id)));
3547          $exportentriesnode->set_show_in_secondary_navigation(false);
3548      }
3549      if (has_capability('mod/data:manageentries', $settings->get_page()->cm->context)) {
3550          $importentriesnode = $datanode->add(get_string('importentries', 'data'),
3551              new moodle_url('/mod/data/import.php', array('d' => $data->id)));
3552          $importentriesnode->set_show_in_secondary_navigation(false);
3553      }
3554  
3555      if (has_capability('mod/data:managetemplates', $settings->get_page()->cm->context)) {
3556          $currenttab = '';
3557          if ($currenttab == 'list') {
3558              $defaultemplate = 'listtemplate';
3559          } else if ($currenttab == 'add') {
3560              $defaultemplate = 'addtemplate';
3561          } else if ($currenttab == 'asearch') {
3562              $defaultemplate = 'asearchtemplate';
3563          } else {
3564              $defaultemplate = 'singletemplate';
3565          }
3566  
3567          $datanode->add(get_string('presets', 'data'), new moodle_url('/mod/data/preset.php', array('d' => $data->id)));
3568          $datanode->add(get_string('fields', 'data'),
3569              new moodle_url('/mod/data/field.php', array('d' => $data->id)));
3570          $datanode->add(get_string('templates', 'data'),
3571              new moodle_url('/mod/data/templates.php', array('d' => $data->id)));
3572      }
3573  
3574      if (!empty($CFG->enablerssfeeds) && !empty($CFG->data_enablerssfeeds) && $data->rssarticles > 0) {
3575          require_once("$CFG->libdir/rsslib.php");
3576  
3577          $string = get_string('rsstype', 'data');
3578  
3579          $url = new moodle_url(rss_get_url($settings->get_page()->cm->context->id, $USER->id, 'mod_data', $data->id));
3580          $datanode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3581      }
3582  }
3583  
3584  /**
3585   * Save the database configuration as a preset.
3586   *
3587   * @param stdClass $course The course the database module belongs to.
3588   * @param stdClass $cm The course module record
3589   * @param stdClass $data The database record
3590   * @param string $path
3591   * @return bool
3592   * @deprecated since Moodle 4.1 MDL-75142 - please, use the preset::save() function instead.
3593   * @todo MDL-75189 This will be deleted in Moodle 4.5.
3594   * @see preset::save()
3595   */
3596  function data_presets_save($course, $cm, $data, $path) {
3597      debugging('data_presets_save() is deprecated. Please use preset::save() instead.', DEBUG_DEVELOPER);
3598  
3599      $manager = manager::create_from_instance($data);
3600      $preset = preset::create_from_instance($manager, $path);
3601      return $preset->save();
3602  }
3603  
3604  /**
3605   * Generates the XML for the database module provided
3606   *
3607   * @global moodle_database $DB
3608   * @param stdClass $course The course the database module belongs to.
3609   * @param stdClass $cm The course module record
3610   * @param stdClass $data The database record
3611   * @return string The XML for the preset
3612   * @deprecated since Moodle 4.1 MDL-75142 - please, use the protected preset::generate_preset_xml() function instead.
3613   * @todo MDL-75189 This will be deleted in Moodle 4.5.
3614   * @see preset::generate_preset_xml()
3615   */
3616  function data_presets_generate_xml($course, $cm, $data) {
3617      debugging(
3618          'data_presets_generate_xml() is deprecated. Please use the protected preset::generate_preset_xml() instead.',
3619          DEBUG_DEVELOPER
3620      );
3621  
3622      $manager = manager::create_from_instance($data);
3623      $preset = preset::create_from_instance($manager, $data->name);
3624      $reflection = new \ReflectionClass(preset::class);
3625      $method = $reflection->getMethod('generate_preset_xml');
3626      $method->setAccessible(true);
3627      return $method->invokeArgs($preset, []);
3628  }
3629  
3630  /**
3631   * Export current fields and presets.
3632   *
3633   * @param stdClass $course The course the database module belongs to.
3634   * @param stdClass $cm The course module record
3635   * @param stdClass $data The database record
3636   * @param bool $tostorage
3637   * @return string the full path to the exported preset file.
3638   * @deprecated since Moodle 4.1 MDL-75142 - please, use the preset::export() function instead.
3639   * @todo MDL-75189 This will be deleted in Moodle 4.5.
3640   * @see preset::export()
3641   */
3642  function data_presets_export($course, $cm, $data, $tostorage=false) {
3643      debugging('data_presets_export() is deprecated. Please use preset::export() instead.', DEBUG_DEVELOPER);
3644  
3645      $manager = manager::create_from_instance($data);
3646      $preset = preset::create_from_instance($manager, $data->name);
3647      return $preset->export();
3648  }
3649  
3650  /**
3651   * Running addtional permission check on plugin, for example, plugins
3652   * may have switch to turn on/off comments option, this callback will
3653   * affect UI display, not like pluginname_comment_validate only throw
3654   * exceptions.
3655   * Capability check has been done in comment->check_permissions(), we
3656   * don't need to do it again here.
3657   *
3658   * @package  mod_data
3659   * @category comment
3660   *
3661   * @param stdClass $comment_param {
3662   *              context  => context the context object
3663   *              courseid => int course id
3664   *              cm       => stdClass course module object
3665   *              commentarea => string comment area
3666   *              itemid      => int itemid
3667   * }
3668   * @return array
3669   */
3670  function data_comment_permissions($comment_param) {
3671      global $CFG, $DB;
3672      if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3673          throw new comment_exception('invalidcommentitemid');
3674      }
3675      if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3676          throw new comment_exception('invalidid', 'data');
3677      }
3678      if ($data->comments) {
3679          return array('post'=>true, 'view'=>true);
3680      } else {
3681          return array('post'=>false, 'view'=>false);
3682      }
3683  }
3684  
3685  /**
3686   * Validate comment parameter before perform other comments actions
3687   *
3688   * @package  mod_data
3689   * @category comment
3690   *
3691   * @param stdClass $comment_param {
3692   *              context  => context the context object
3693   *              courseid => int course id
3694   *              cm       => stdClass course module object
3695   *              commentarea => string comment area
3696   *              itemid      => int itemid
3697   * }
3698   * @return boolean
3699   */
3700  function data_comment_validate($comment_param) {
3701      global $DB;
3702      // validate comment area
3703      if ($comment_param->commentarea != 'database_entry') {
3704          throw new comment_exception('invalidcommentarea');
3705      }
3706      // validate itemid
3707      if (!$record = $DB->get_record('data_records', array('id'=>$comment_param->itemid))) {
3708          throw new comment_exception('invalidcommentitemid');
3709      }
3710      if (!$data = $DB->get_record('data', array('id'=>$record->dataid))) {
3711          throw new comment_exception('invalidid', 'data');
3712      }
3713      if (!$course = $DB->get_record('course', array('id'=>$data->course))) {
3714          throw new comment_exception('coursemisconf');
3715      }
3716      if (!$cm = get_coursemodule_from_instance('data', $data->id, $course->id)) {
3717          throw new comment_exception('invalidcoursemodule');
3718      }
3719      if (!$data->comments) {
3720          throw new comment_exception('commentsoff', 'data');
3721      }
3722      $context = context_module::instance($cm->id);
3723  
3724      //check if approved
3725      if ($data->approval and !$record->approved and !data_isowner($record) and !has_capability('mod/data:approve', $context)) {
3726          throw new comment_exception('notapprovederror', 'data');
3727      }
3728  
3729      // group access
3730      if ($record->groupid) {
3731          $groupmode = groups_get_activity_groupmode($cm, $course);
3732          if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
3733              if (!groups_is_member($record->groupid)) {
3734                  throw new comment_exception('notmemberofgroup');
3735              }
3736          }
3737      }
3738      // validate context id
3739      if ($context->id != $comment_param->context->id) {
3740          throw new comment_exception('invalidcontext');
3741      }
3742      // validation for comment deletion
3743      if (!empty($comment_param->commentid)) {
3744          if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3745              if ($comment->commentarea != 'database_entry') {
3746                  throw new comment_exception('invalidcommentarea');
3747              }
3748              if ($comment->contextid != $comment_param->context->id) {
3749                  throw new comment_exception('invalidcontext');
3750              }
3751              if ($comment->itemid != $comment_param->itemid) {
3752                  throw new comment_exception('invalidcommentitemid');
3753              }
3754          } else {
3755              throw new comment_exception('invalidcommentid');
3756          }
3757      }
3758      return true;
3759  }
3760  
3761  /**
3762   * Return a list of page types
3763   * @param string $pagetype current page type
3764   * @param stdClass $parentcontext Block's parent context
3765   * @param stdClass $currentcontext Current context of block
3766   */
3767  function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
3768      $module_pagetype = array('mod-data-*'=>get_string('page-mod-data-x', 'data'));
3769      return $module_pagetype;
3770  }
3771  
3772  /**
3773   * Get all of the record ids from a database activity.
3774   *
3775   * @param int    $dataid      The dataid of the database module.
3776   * @param object $selectdata  Contains an additional sql statement for the
3777   *                            where clause for group and approval fields.
3778   * @param array  $params      Parameters that coincide with the sql statement.
3779   * @return array $idarray     An array of record ids
3780   */
3781  function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
3782      global $DB;
3783      $initsql = 'SELECT r.id
3784                    FROM {data_records} r
3785                   WHERE r.dataid = :dataid';
3786      if ($selectdata != '') {
3787          $initsql .= $selectdata;
3788          $params = array_merge(array('dataid' => $dataid), $params);
3789      } else {
3790          $params = array('dataid' => $dataid);
3791      }
3792      $initsql .= ' GROUP BY r.id';
3793      $initrecord = $DB->get_recordset_sql($initsql, $params);
3794      $idarray = array();
3795      foreach ($initrecord as $data) {
3796          $idarray[] = $data->id;
3797      }
3798      // Close the record set and free up resources.
3799      $initrecord->close();
3800      return $idarray;
3801  }
3802  
3803  /**
3804   * Get the ids of all the records that match that advanced search criteria
3805   * This goes and loops through each criterion one at a time until it either
3806   * runs out of records or returns a subset of records.
3807   *
3808   * @param array $recordids    An array of record ids.
3809   * @param array $searcharray  Contains information for the advanced search criteria
3810   * @param int $dataid         The data id of the database.
3811   * @return array $recordids   An array of record ids.
3812   */
3813  function data_get_advance_search_ids($recordids, $searcharray, $dataid) {
3814      // Check to see if we have any record IDs.
3815      if (empty($recordids)) {
3816          // Send back an empty search.
3817          return array();
3818      }
3819      $searchcriteria = array_keys($searcharray);
3820      // Loop through and reduce the IDs one search criteria at a time.
3821      foreach ($searchcriteria as $key) {
3822          $recordids = data_get_recordids($key, $searcharray, $dataid, $recordids);
3823          // If we don't have anymore IDs then stop.
3824          if (!$recordids) {
3825              break;
3826          }
3827      }
3828      return $recordids;
3829  }
3830  
3831  /**
3832   * Gets the record IDs given the search criteria
3833   *
3834   * @param string $alias       Record alias.
3835   * @param array $searcharray  Criteria for the search.
3836   * @param int $dataid         Data ID for the database
3837   * @param array $recordids    An array of record IDs.
3838   * @return array $nestarray   An arry of record IDs
3839   */
3840  function data_get_recordids($alias, $searcharray, $dataid, $recordids) {
3841      global $DB;
3842      $searchcriteria = $alias;   // Keep the criteria.
3843      $nestsearch = $searcharray[$alias];
3844      // searching for content outside of mdl_data_content
3845      if ($alias < 0) {
3846          $alias = '';
3847      }
3848      list($insql, $params) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED);
3849      $nestselect = 'SELECT c' . $alias . '.recordid
3850                       FROM {data_content} c' . $alias . '
3851                 INNER JOIN {data_fields} f
3852                         ON f.id = c' . $alias . '.fieldid
3853                 INNER JOIN {data_records} r
3854                         ON r.id = c' . $alias . '.recordid
3855                 INNER JOIN {user} u
3856                         ON u.id = r.userid ';
3857      $nestwhere = 'WHERE r.dataid = :dataid
3858                      AND c' . $alias .'.recordid ' . $insql . '
3859                      AND ';
3860  
3861      $params['dataid'] = $dataid;
3862      if (count($nestsearch->params) != 0) {
3863          $params = array_merge($params, $nestsearch->params);
3864          $nestsql = $nestselect . $nestwhere . $nestsearch->sql;
3865      } else if ($searchcriteria == DATA_TIMEMODIFIED) {
3866          $nestsql = $nestselect . $nestwhere . $nestsearch->field . ' >= :timemodified GROUP BY c' . $alias . '.recordid';
3867          $params['timemodified'] = $nestsearch->data;
3868      } else if ($searchcriteria == DATA_TAGS) {
3869          if (empty($nestsearch->rawtagnames)) {
3870              return [];
3871          }
3872          $i = 0;
3873          $tagwhere = [];
3874          $tagselect = '';
3875          foreach ($nestsearch->rawtagnames as $tagrawname) {
3876              $tagselect .= " INNER JOIN {tag_instance} ti_$i
3877                                      ON ti_$i.component = 'mod_data'
3878                                     AND ti_$i.itemtype = 'data_records'
3879                                     AND ti_$i.itemid = r.id
3880                              INNER JOIN {tag} t_$i
3881                                      ON ti_$i.tagid = t_$i.id ";
3882              $tagwhere[] = " t_$i.rawname = :trawname_$i ";
3883              $params["trawname_$i"] = $tagrawname;
3884              $i++;
3885          }
3886          $nestsql = $nestselect . $tagselect . $nestwhere . implode(' AND ', $tagwhere);
3887      } else {    // First name or last name.
3888          $thing = $DB->sql_like($nestsearch->field, ':search1', false);
3889          $nestsql = $nestselect . $nestwhere . $thing . ' GROUP BY c' . $alias . '.recordid';
3890          $params['search1'] = "%$nestsearch->data%";
3891      }
3892      $nestrecords = $DB->get_recordset_sql($nestsql, $params);
3893      $nestarray = array();
3894      foreach ($nestrecords as $data) {
3895          $nestarray[] = $data->recordid;
3896      }
3897      // Close the record set and free up resources.
3898      $nestrecords->close();
3899      return $nestarray;
3900  }
3901  
3902  /**
3903   * Returns an array with an sql string for advanced searches and the parameters that go with them.
3904   *
3905   * @param int $sort            DATA_*
3906   * @param stdClass $data       Data module object
3907   * @param array $recordids     An array of record IDs.
3908   * @param string $selectdata   Information for the where and select part of the sql statement.
3909   * @param string $sortorder    Additional sort parameters
3910   * @return array sqlselect     sqlselect['sql'] has the sql string, sqlselect['params'] contains an array of parameters.
3911   */
3912  function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $sortorder) {
3913      global $DB;
3914  
3915      $userfieldsapi = \core_user\fields::for_userpic()->excluding('id');
3916      $namefields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
3917  
3918      if ($sort == 0) {
3919          $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . '
3920                          FROM {data_content} c,
3921                               {data_records} r,
3922                               {user} u ';
3923          $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, u.firstname, u.lastname, ' . $namefields;
3924      } else {
3925          // Sorting through 'Other' criteria
3926          if ($sort <= 0) {
3927              switch ($sort) {
3928                  case DATA_LASTNAME:
3929                      $sortcontentfull = "u.lastname";
3930                      break;
3931                  case DATA_FIRSTNAME:
3932                      $sortcontentfull = "u.firstname";
3933                      break;
3934                  case DATA_APPROVED:
3935                      $sortcontentfull = "r.approved";
3936                      break;
3937                  case DATA_TIMEMODIFIED:
3938                      $sortcontentfull = "r.timemodified";
3939                      break;
3940                  case DATA_TIMEADDED:
3941                  default:
3942                      $sortcontentfull = "r.timecreated";
3943              }
3944          } else {
3945              $sortfield = data_get_field_from_id($sort, $data);
3946              $sortcontent = $DB->sql_compare_text('c.' . $sortfield->get_sort_field());
3947              $sortcontentfull = $sortfield->get_sort_sql($sortcontent);
3948          }
3949  
3950          $nestselectsql = 'SELECT r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ',
3951                                   ' . $sortcontentfull . '
3952                                AS sortorder
3953                              FROM {data_content} c,
3954                                   {data_records} r,
3955                                   {user} u ';
3956          $groupsql = ' GROUP BY r.id, r.approved, r.timecreated, r.timemodified, r.userid, ' . $namefields . ', ' .$sortcontentfull;
3957      }
3958  
3959      // Default to a standard Where statement if $selectdata is empty.
3960      if ($selectdata == '') {
3961          $selectdata = 'WHERE c.recordid = r.id
3962                           AND r.dataid = :dataid
3963                           AND r.userid = u.id ';
3964      }
3965  
3966      // Find the field we are sorting on
3967      if ($sort > 0 or data_get_field_from_id($sort, $data)) {
3968          $selectdata .= ' AND c.fieldid = :sort AND s.recordid = r.id';
3969          $nestselectsql .= ',{data_content} s ';
3970      }
3971  
3972      // If there are no record IDs then return an sql statment that will return no rows.
3973      if (count($recordids) != 0) {
3974          list($insql, $inparam) = $DB->get_in_or_equal($recordids, SQL_PARAMS_NAMED);
3975      } else {
3976          list($insql, $inparam) = $DB->get_in_or_equal(array('-1'), SQL_PARAMS_NAMED);
3977      }
3978      $nestfromsql = $selectdata . ' AND c.recordid ' . $insql . $groupsql;
3979      $sqlselect['sql'] = "$nestselectsql $nestfromsql $sortorder";
3980      $sqlselect['params'] = $inparam;
3981      return $sqlselect;
3982  }
3983  
3984  /**
3985   * Checks to see if the user has permission to delete the preset.
3986   * @param stdClass $context  Context object.
3987   * @param stdClass $preset  The preset object that we are checking for deletion.
3988   * @return bool  Returns true if the user can delete, otherwise false.
3989   * @deprecated since Moodle 4.1 MDL-75187 - please, use the preset::can_manage() function instead.
3990   * @todo MDL-75189 This will be deleted in Moodle 4.5.
3991   * @see preset::can_manage()
3992   */
3993  function data_user_can_delete_preset($context, $preset) {
3994      global $USER;
3995  
3996      debugging('data_user_can_delete_preset() is deprecated. Please use manager::can_manage() instead.', DEBUG_DEVELOPER);
3997  
3998      if ($context->contextlevel == CONTEXT_MODULE && isset($preset->name)) {
3999          $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
4000          $manager = manager::create_from_coursemodule($cm);
4001          $todelete = preset::create_from_instance($manager, $preset->name);
4002          return $todelete->can_manage();
4003      }
4004  
4005      if (has_capability('mod/data:manageuserpresets', $context)) {
4006          return true;
4007      } else {
4008          $candelete = false;
4009          $userid = $preset instanceof preset ? $preset->get_userid() : $preset->userid;
4010          if ($userid == $USER->id) {
4011              $candelete = true;
4012          }
4013          return $candelete;
4014      }
4015  }
4016  
4017  /**
4018   * Delete a record entry.
4019   *
4020   * @param int $recordid The ID for the record to be deleted.
4021   * @param object $data The data object for this activity.
4022   * @param int $courseid ID for the current course (for logging).
4023   * @param int $cmid The course module ID.
4024   * @return bool True if the record deleted, false if not.
4025   */
4026  function data_delete_record($recordid, $data, $courseid, $cmid) {
4027      global $DB, $CFG;
4028  
4029      if ($deleterecord = $DB->get_record('data_records', array('id' => $recordid))) {
4030          if ($deleterecord->dataid == $data->id) {
4031              if ($contents = $DB->get_records('data_content', array('recordid' => $deleterecord->id))) {
4032                  foreach ($contents as $content) {
4033                      if ($field = data_get_field_from_id($content->fieldid, $data)) {
4034                          $field->delete_content($content->recordid);
4035                      }
4036                  }
4037                  $DB->delete_records('data_content', array('recordid'=>$deleterecord->id));
4038                  $DB->delete_records('data_records', array('id'=>$deleterecord->id));
4039  
4040                  // Delete cached RSS feeds.
4041                  if (!empty($CFG->enablerssfeeds)) {
4042                      require_once($CFG->dirroot.'/mod/data/rsslib.php');
4043                      data_rss_delete_file($data);
4044                  }
4045  
4046                  core_tag_tag::remove_all_item_tags('mod_data', 'data_records', $recordid);
4047  
4048                  // Trigger an event for deleting this record.
4049                  $event = \mod_data\event\record_deleted::create(array(
4050                      'objectid' => $deleterecord->id,
4051                      'context' => context_module::instance($cmid),
4052                      'courseid' => $courseid,
4053                      'other' => array(
4054                          'dataid' => $deleterecord->dataid
4055                      )
4056                  ));
4057                  $event->add_record_snapshot('data_records', $deleterecord);
4058                  $event->trigger();
4059                  $course = get_course($courseid);
4060                  $cm = get_coursemodule_from_instance('data', $data->id, 0, false, MUST_EXIST);
4061                  data_update_completion_state($data, $course, $cm);
4062  
4063                  return true;
4064              }
4065          }
4066      }
4067  
4068      return false;
4069  }
4070  
4071  /**
4072   * Check for required fields, and build a list of fields to be updated in a
4073   * submission.
4074   *
4075   * @param $mod stdClass The current recordid - provided as an optimisation.
4076   * @param $fields array The field data
4077   * @param $datarecord stdClass The submitted data.
4078   * @return stdClass containing:
4079   * * string[] generalnotifications Notifications for the form as a whole.
4080   * * string[] fieldnotifications Notifications for a specific field.
4081   * * bool validated Whether the field was validated successfully.
4082   * * data_field_base[] fields The field objects to be update.
4083   */
4084  function data_process_submission(stdClass $mod, $fields, stdClass $datarecord) {
4085      $result = new stdClass();
4086  
4087      // Empty form checking - you can't submit an empty form.
4088      $emptyform = true;
4089      $requiredfieldsfilled = true;
4090      $fieldsvalidated = true;
4091  
4092      // Store the notifications.
4093      $result->generalnotifications = array();
4094      $result->fieldnotifications = array();
4095  
4096      // Store the instantiated classes as an optimisation when processing the result.
4097      // This prevents the fields being re-initialised when updating.
4098      $result->fields = array();
4099  
4100      $submitteddata = array();
4101      foreach ($datarecord as $fieldname => $fieldvalue) {
4102          if (strpos($fieldname, '_')) {
4103              $namearray = explode('_', $fieldname, 3);
4104              $fieldid = $namearray[1];
4105              if (!isset($submitteddata[$fieldid])) {
4106                  $submitteddata[$fieldid] = array();
4107              }
4108              if (count($namearray) === 2) {
4109                  $subfieldid = 0;
4110              } else {
4111                  $subfieldid = $namearray[2];
4112              }
4113  
4114              $fielddata = new stdClass();
4115              $fielddata->fieldname = $fieldname;
4116              $fielddata->value = $fieldvalue;
4117              $submitteddata[$fieldid][$subfieldid] = $fielddata;
4118          }
4119      }
4120  
4121      // Check all form fields which have the required are filled.
4122      foreach ($fields as $fieldrecord) {
4123          // Check whether the field has any data.
4124          $fieldhascontent = false;
4125  
4126          $field = data_get_field($fieldrecord, $mod);
4127          if (isset($submitteddata[$fieldrecord->id])) {
4128              // Field validation check.
4129              if (method_exists($field, 'field_validation')) {
4130                  $errormessage = $field->field_validation($submitteddata[$fieldrecord->id]);
4131                  if ($errormessage) {
4132                      $result->fieldnotifications[$field->field->name][] = $errormessage;
4133                      $fieldsvalidated = false;
4134                  }
4135              }
4136              foreach ($submitteddata[$fieldrecord->id] as $fieldname => $value) {
4137                  if ($field->notemptyfield($value->value, $value->fieldname)) {
4138                      // The field has content and the form is not empty.
4139                      $fieldhascontent = true;
4140                      $emptyform = false;
4141                  }
4142              }
4143          }
4144  
4145          // If the field is required, add a notification to that effect.
4146          if ($field->field->required && !$fieldhascontent) {
4147              if (!isset($result->fieldnotifications[$field->field->name])) {
4148                  $result->fieldnotifications[$field->field->name] = array();
4149              }
4150              $result->fieldnotifications[$field->field->name][] = get_string('errormustsupplyvalue', 'data');
4151              $requiredfieldsfilled = false;
4152          }
4153  
4154          // Update the field.
4155          if (isset($submitteddata[$fieldrecord->id])) {
4156              foreach ($submitteddata[$fieldrecord->id] as $value) {
4157                  $result->fields[$value->fieldname] = $field;
4158              }
4159          }
4160      }
4161  
4162      if ($emptyform) {
4163          // The form is empty.
4164          $result->generalnotifications[] = get_string('emptyaddform', 'data');
4165      }
4166  
4167      $result->validated = $requiredfieldsfilled && !$emptyform && $fieldsvalidated;
4168  
4169      return $result;
4170  }
4171  
4172  /**
4173   * This standard function will check all instances of this module
4174   * and make sure there are up-to-date events created for each of them.
4175   * If courseid = 0, then every data event in the site is checked, else
4176   * only data events belonging to the course specified are checked.
4177   * This function is used, in its new format, by restore_refresh_events()
4178   *
4179   * @param int $courseid
4180   * @param int|stdClass $instance Data module instance or ID.
4181   * @param int|stdClass $cm Course module object or ID (not used in this module).
4182   * @return bool
4183   */
4184  function data_refresh_events($courseid = 0, $instance = null, $cm = null) {
4185      global $DB, $CFG;
4186      require_once($CFG->dirroot.'/mod/data/locallib.php');
4187  
4188      // If we have instance information then we can just update the one event instead of updating all events.
4189      if (isset($instance)) {
4190          if (!is_object($instance)) {
4191              $instance = $DB->get_record('data', array('id' => $instance), '*', MUST_EXIST);
4192          }
4193          data_set_events($instance);
4194          return true;
4195      }
4196  
4197      if ($courseid) {
4198          if (! $data = $DB->get_records("data", array("course" => $courseid))) {
4199              return true;
4200          }
4201      } else {
4202          if (! $data = $DB->get_records("data")) {
4203              return true;
4204          }
4205      }
4206  
4207      foreach ($data as $datum) {
4208          data_set_events($datum);
4209      }
4210      return true;
4211  }
4212  
4213  /**
4214   * Fetch the configuration for this database activity.
4215   *
4216   * @param   stdClass    $database   The object returned from the database for this instance
4217   * @param   string      $key        The name of the key to retrieve. If none is supplied, then all configuration is returned
4218   * @param   mixed       $default    The default value to use if no value was found for the specified key
4219   * @return  mixed                   The returned value
4220   */
4221  function data_get_config($database, $key = null, $default = null) {
4222      if (!empty($database->config)) {
4223          $config = json_decode($database->config);
4224      } else {
4225          $config = new stdClass();
4226      }
4227  
4228      if ($key === null) {
4229          return $config;
4230      }
4231  
4232      if (property_exists($config, $key)) {
4233          return $config->$key;
4234      }
4235      return $default;
4236  }
4237  
4238  /**
4239   * Update the configuration for this database activity.
4240   *
4241   * @param   stdClass    $database   The object returned from the database for this instance
4242   * @param   string      $key        The name of the key to set
4243   * @param   mixed       $value      The value to set for the key
4244   */
4245  function data_set_config(&$database, $key, $value) {
4246      // Note: We must pass $database by reference because there may be subsequent calls to update_record and these should
4247      // not overwrite the configuration just set.
4248      global $DB;
4249  
4250      $config = data_get_config($database);
4251  
4252      if (!isset($config->$key) || $config->$key !== $value) {
4253          $config->$key = $value;
4254          $database->config = json_encode($config);
4255          $DB->set_field('data', 'config', $database->config, ['id' => $database->id]);
4256      }
4257  }
4258  /**
4259   * Sets the automatic completion state for this database item based on the
4260   * count of on its entries.
4261   * @since Moodle 3.3
4262   * @param object $data The data object for this activity
4263   * @param object $course Course
4264   * @param object $cm course-module
4265   */
4266  function data_update_completion_state($data, $course, $cm) {
4267      // If completion option is enabled, evaluate it and return true/false.
4268      $completion = new completion_info($course);
4269      if ($data->completionentries && $completion->is_enabled($cm)) {
4270          $numentries = data_numentries($data);
4271          // Check the number of entries required against the number of entries already made.
4272          if ($numentries >= $data->completionentries) {
4273              $completion->update_state($cm, COMPLETION_COMPLETE);
4274          } else {
4275              $completion->update_state($cm, COMPLETION_INCOMPLETE);
4276          }
4277      }
4278  }
4279  
4280  /**
4281   * Mark the activity completed (if required) and trigger the course_module_viewed event.
4282   *
4283   * @deprecated since Moodle 4.1 MDL-75146 - please do not use this function any more.
4284   * @todo MDL-75189 Final deprecation in Moodle 4.5.
4285   * @param  stdClass $data       data object
4286   * @param  stdClass $course     course object
4287   * @param  stdClass $cm         course module object
4288   * @param  stdClass $context    context object
4289   * @since Moodle 3.3
4290   */
4291  function data_view($data, $course, $cm, $context) {
4292      global $CFG;
4293      debugging('data_view is deprecated. Use mod_data\\manager::set_module_viewed instead', DEBUG_DEVELOPER);
4294      require_once($CFG->libdir . '/completionlib.php');
4295  
4296      // Trigger course_module_viewed event.
4297      $params = array(
4298          'context' => $context,
4299          'objectid' => $data->id
4300      );
4301  
4302      $event = \mod_data\event\course_module_viewed::create($params);
4303      $event->add_record_snapshot('course_modules', $cm);
4304      $event->add_record_snapshot('course', $course);
4305      $event->add_record_snapshot('data', $data);
4306      $event->trigger();
4307  
4308      // Completion.
4309      $completion = new completion_info($course);
4310      $completion->set_module_viewed($cm);
4311  }
4312  
4313  /**
4314   * Get icon mapping for font-awesome.
4315   */
4316  function mod_data_get_fontawesome_icon_map() {
4317      return [
4318          'mod_data:field/checkbox' => 'fa-check-square-o',
4319          'mod_data:field/date' => 'fa-calendar-o',
4320          'mod_data:field/file' => 'fa-file',
4321          'mod_data:field/latlong' => 'fa-globe',
4322          'mod_data:field/menu' => 'fa-bars',
4323          'mod_data:field/multimenu' => 'fa-bars',
4324          'mod_data:field/number' => 'fa-hashtag',
4325          'mod_data:field/picture' => 'fa-picture-o',
4326          'mod_data:field/radiobutton' => 'fa-circle-o',
4327          'mod_data:field/textarea' => 'fa-font',
4328          'mod_data:field/text' => 'fa-i-cursor',
4329          'mod_data:field/url' => 'fa-link',
4330      ];
4331  }
4332  
4333  /*
4334   * Check if the module has any update that affects the current user since a given time.
4335   *
4336   * @param  cm_info $cm course module data
4337   * @param  int $from the time to check updates from
4338   * @param  array $filter  if we need to check only specific updates
4339   * @return stdClass an object with the different type of areas indicating if they were updated or not
4340   * @since Moodle 3.2
4341   */
4342  function data_check_updates_since(cm_info $cm, $from, $filter = array()) {
4343      global $DB, $CFG;
4344      require_once($CFG->dirroot . '/mod/data/locallib.php');
4345  
4346      $updates = course_check_module_updates_since($cm, $from, array(), $filter);
4347  
4348      // Check for new entries.
4349      $updates->entries = (object) array('updated' => false);
4350  
4351      $data = $DB->get_record('data', array('id' => $cm->instance), '*', MUST_EXIST);
4352      $searcharray = [];
4353      $searcharray[DATA_TIMEMODIFIED] = new stdClass();
4354      $searcharray[DATA_TIMEMODIFIED]->sql     = '';
4355      $searcharray[DATA_TIMEMODIFIED]->params  = array();
4356      $searcharray[DATA_TIMEMODIFIED]->field   = 'r.timemodified';
4357      $searcharray[DATA_TIMEMODIFIED]->data    = $from;
4358  
4359      $currentgroup = groups_get_activity_group($cm);
4360      // Teachers should retrieve all entries when not in separate groups.
4361      if (has_capability('mod/data:manageentries', $cm->context) && groups_get_activity_groupmode($cm) != SEPARATEGROUPS) {
4362          $currentgroup = 0;
4363      }
4364      list($entries, $maxcount, $totalcount, $page, $nowperpage, $sort, $mode) =
4365          data_search_entries($data, $cm, $cm->context, 'list', $currentgroup, '', null, null, 0, 0, true, $searcharray);
4366  
4367      if (!empty($entries)) {
4368          $updates->entries->updated = true;
4369          $updates->entries->itemids = array_keys($entries);
4370      }
4371  
4372      return $updates;
4373  }
4374  
4375  /**
4376   * This function receives a calendar event and returns the action associated with it, or null if there is none.
4377   *
4378   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
4379   * is not displayed on the block.
4380   *
4381   * @param calendar_event $event
4382   * @param \core_calendar\action_factory $factory
4383   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
4384   * @return \core_calendar\local\event\entities\action_interface|null
4385   */
4386  function mod_data_core_calendar_provide_event_action(calendar_event $event,
4387                                                       \core_calendar\action_factory $factory,
4388                                                       int $userid = 0) {
4389      global $USER;
4390  
4391      if (!$userid) {
4392          $userid = $USER->id;
4393      }
4394  
4395      $cm = get_fast_modinfo($event->courseid, $userid)->instances['data'][$event->instance];
4396  
4397      if (!$cm->uservisible) {
4398          // The module is not visible to the user for any reason.
4399          return null;
4400      }
4401  
4402      $now = time();
4403  
4404      if (!empty($cm->customdata['timeavailableto']) && $cm->customdata['timeavailableto'] < $now) {
4405          // The module has closed so the user can no longer submit anything.
4406          return null;
4407      }
4408  
4409      // The module is actionable if we don't have a start time or the start time is
4410      // in the past.
4411      $actionable = (empty($cm->customdata['timeavailablefrom']) || $cm->customdata['timeavailablefrom'] <= $now);
4412  
4413      return $factory->create_instance(
4414          get_string('add', 'data'),
4415          new \moodle_url('/mod/data/view.php', array('id' => $cm->id)),
4416          1,
4417          $actionable
4418      );
4419  }
4420  
4421  /**
4422   * Add a get_coursemodule_info function in case any database type wants to add 'extra' information
4423   * for the course (see resource).
4424   *
4425   * Given a course_module object, this function returns any "extra" information that may be needed
4426   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
4427   *
4428   * @param stdClass $coursemodule The coursemodule object (record).
4429   * @return cached_cm_info An object on information that the courses
4430   *                        will know about (most noticeably, an icon).
4431   */
4432  function data_get_coursemodule_info($coursemodule) {
4433      global $DB;
4434  
4435      $dbparams = ['id' => $coursemodule->instance];
4436      $fields = 'id, name, intro, introformat, completionentries, timeavailablefrom, timeavailableto';
4437      if (!$data = $DB->get_record('data', $dbparams, $fields)) {
4438          return false;
4439      }
4440  
4441      $result = new cached_cm_info();
4442      $result->name = $data->name;
4443  
4444      if ($coursemodule->showdescription) {
4445          // Convert intro to html. Do not filter cached version, filters run at display time.
4446          $result->content = format_module_intro('data', $data, $coursemodule->id, false);
4447      }
4448  
4449      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
4450      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
4451          $result->customdata['customcompletionrules']['completionentries'] = $data->completionentries;
4452      }
4453      // Other properties that may be used in calendar or on dashboard.
4454      if ($data->timeavailablefrom) {
4455          $result->customdata['timeavailablefrom'] = $data->timeavailablefrom;
4456      }
4457      if ($data->timeavailableto) {
4458          $result->customdata['timeavailableto'] = $data->timeavailableto;
4459      }
4460  
4461      return $result;
4462  }
4463  
4464  /**
4465   * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
4466   *
4467   * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
4468   * @return array $descriptions the array of descriptions for the custom rules.
4469   */
4470  function mod_data_get_completion_active_rule_descriptions($cm) {
4471      // Values will be present in cm_info, and we assume these are up to date.
4472      if (empty($cm->customdata['customcompletionrules'])
4473          || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
4474          return [];
4475      }
4476  
4477      $descriptions = [];
4478      foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
4479          switch ($key) {
4480              case 'completionentries':
4481                  if (!empty($val)) {
4482                      $descriptions[] = get_string('completionentriesdesc', 'data', $val);
4483                  }
4484                  break;
4485              default:
4486                  break;
4487          }
4488      }
4489      return $descriptions;
4490  }
4491  
4492  /**
4493   * This function calculates the minimum and maximum cutoff values for the timestart of
4494   * the given event.
4495   *
4496   * It will return an array with two values, the first being the minimum cutoff value and
4497   * the second being the maximum cutoff value. Either or both values can be null, which
4498   * indicates there is no minimum or maximum, respectively.
4499   *
4500   * If a cutoff is required then the function must return an array containing the cutoff
4501   * timestamp and error string to display to the user if the cutoff value is violated.
4502   *
4503   * A minimum and maximum cutoff return value will look like:
4504   * [
4505   *     [1505704373, 'The due date must be after the sbumission start date'],
4506   *     [1506741172, 'The due date must be before the cutoff date']
4507   * ]
4508   *
4509   * @param calendar_event $event The calendar event to get the time range for
4510   * @param stdClass $instance The module instance to get the range from
4511   * @return array
4512   */
4513  function mod_data_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
4514      $mindate = null;
4515      $maxdate = null;
4516  
4517      if ($event->eventtype == DATA_EVENT_TYPE_OPEN) {
4518          // The start time of the open event can't be equal to or after the
4519          // close time of the database activity.
4520          if (!empty($instance->timeavailableto)) {
4521              $maxdate = [
4522                  $instance->timeavailableto,
4523                  get_string('openafterclose', 'data')
4524              ];
4525          }
4526      } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) {
4527          // The start time of the close event can't be equal to or earlier than the
4528          // open time of the database activity.
4529          if (!empty($instance->timeavailablefrom)) {
4530              $mindate = [
4531                  $instance->timeavailablefrom,
4532                  get_string('closebeforeopen', 'data')
4533              ];
4534          }
4535      }
4536  
4537      return [$mindate, $maxdate];
4538  }
4539  
4540  /**
4541   * This function will update the data module according to the
4542   * event that has been modified.
4543   *
4544   * It will set the timeopen or timeclose value of the data instance
4545   * according to the type of event provided.
4546   *
4547   * @throws \moodle_exception
4548   * @param \calendar_event $event
4549   * @param stdClass $data The module instance to get the range from
4550   */
4551  function mod_data_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $data) {
4552      global $DB;
4553  
4554      if (empty($event->instance) || $event->modulename != 'data') {
4555          return;
4556      }
4557  
4558      if ($event->instance != $data->id) {
4559          return;
4560      }
4561  
4562      if (!in_array($event->eventtype, [DATA_EVENT_TYPE_OPEN, DATA_EVENT_TYPE_CLOSE])) {
4563          return;
4564      }
4565  
4566      $courseid = $event->courseid;
4567      $modulename = $event->modulename;
4568      $instanceid = $event->instance;
4569      $modified = false;
4570  
4571      $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
4572      $context = context_module::instance($coursemodule->id);
4573  
4574      // The user does not have the capability to modify this activity.
4575      if (!has_capability('moodle/course:manageactivities', $context)) {
4576          return;
4577      }
4578  
4579      if ($event->eventtype == DATA_EVENT_TYPE_OPEN) {
4580          // If the event is for the data activity opening then we should
4581          // set the start time of the data activity to be the new start
4582          // time of the event.
4583          if ($data->timeavailablefrom != $event->timestart) {
4584              $data->timeavailablefrom = $event->timestart;
4585              $data->timemodified = time();
4586              $modified = true;
4587          }
4588      } else if ($event->eventtype == DATA_EVENT_TYPE_CLOSE) {
4589          // If the event is for the data activity closing then we should
4590          // set the end time of the data activity to be the new start
4591          // time of the event.
4592          if ($data->timeavailableto != $event->timestart) {
4593              $data->timeavailableto = $event->timestart;
4594              $modified = true;
4595          }
4596      }
4597  
4598      if ($modified) {
4599          $data->timemodified = time();
4600          $DB->update_record('data', $data);
4601          $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
4602          $event->trigger();
4603      }
4604  }
4605  
4606  /**
4607   * Callback to fetch the activity event type lang string.
4608   *
4609   * @param string $eventtype The event type.
4610   * @return lang_string The event type lang string.
4611   */
4612  function mod_data_core_calendar_get_event_action_string(string $eventtype): string {
4613      $modulename = get_string('modulename', 'data');
4614  
4615      switch ($eventtype) {
4616          case DATA_EVENT_TYPE_OPEN:
4617              $identifier = 'calendarstart';
4618              break;
4619          case DATA_EVENT_TYPE_CLOSE:
4620              $identifier = 'calendarend';
4621              break;
4622          default:
4623              return get_string('requiresaction', 'calendar', $modulename);
4624      }
4625  
4626      return get_string($identifier, 'data', $modulename);
4627  }