Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
/mod/data/ -> lib.php (source)

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

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