Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
/mod/data/ -> lib.php (source)

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

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