Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Library of workshop module functions needed by Moodle core and other subsystems
  20   *
  21   * All the functions neeeded by Moodle core, gradebook, file subsystem etc
  22   * are placed here.
  23   *
  24   * @package    mod_workshop
  25   * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  require_once($CFG->dirroot . '/calendar/lib.php');
  32  
  33  define('WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN',   'opensubmission');
  34  define('WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE',  'closesubmission');
  35  define('WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN',   'openassessment');
  36  define('WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE',  'closeassessment');
  37  define('WORKSHOP_SUBMISSION_TYPE_DISABLED', 0);
  38  define('WORKSHOP_SUBMISSION_TYPE_AVAILABLE', 1);
  39  define('WORKSHOP_SUBMISSION_TYPE_REQUIRED', 2);
  40  
  41  ////////////////////////////////////////////////////////////////////////////////
  42  // Moodle core API                                                            //
  43  ////////////////////////////////////////////////////////////////////////////////
  44  
  45  /**
  46   * Returns the information if the module supports a feature
  47   *
  48   * @see plugin_supports() in lib/moodlelib.php
  49   * @param string $feature FEATURE_xx constant for requested feature
  50   * @return mixed True if module supports feature, false if not, null if doesn't know or string for the module purpose.
  51   */
  52  function workshop_supports($feature) {
  53      switch($feature) {
  54          case FEATURE_GRADE_HAS_GRADE:   return true;
  55          case FEATURE_GROUPS:            return true;
  56          case FEATURE_GROUPINGS:         return true;
  57          case FEATURE_MOD_INTRO:         return true;
  58          case FEATURE_BACKUP_MOODLE2:    return true;
  59          case FEATURE_COMPLETION_TRACKS_VIEWS:
  60              return true;
  61          case FEATURE_SHOW_DESCRIPTION:  return true;
  62          case FEATURE_PLAGIARISM:        return true;
  63          case FEATURE_MOD_PURPOSE:       return MOD_PURPOSE_ASSESSMENT;
  64          default:                        return null;
  65      }
  66  }
  67  
  68  /**
  69   * Saves a new instance of the workshop into the database
  70   *
  71   * Given an object containing all the necessary data,
  72   * (defined by the form in mod_form.php) this function
  73   * will save a new instance and return the id number
  74   * of the new instance.
  75   *
  76   * @param stdClass $workshop An object from the form in mod_form.php
  77   * @return int The id of the newly inserted workshop record
  78   */
  79  function workshop_add_instance(stdclass $workshop) {
  80      global $CFG, $DB;
  81      require_once (__DIR__ . '/locallib.php');
  82  
  83      $workshop->phase                 = workshop::PHASE_SETUP;
  84      $workshop->timecreated           = time();
  85      $workshop->timemodified          = $workshop->timecreated;
  86      $workshop->useexamples           = (int)!empty($workshop->useexamples);
  87      $workshop->usepeerassessment     = 1;
  88      $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
  89      $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
  90      $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
  91      $workshop->evaluation            = 'best';
  92  
  93      if (isset($workshop->gradinggradepass)) {
  94          $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
  95      }
  96  
  97      if (isset($workshop->submissiongradepass)) {
  98          $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
  99      }
 100  
 101      if (isset($workshop->submissionfiletypes)) {
 102          $filetypesutil = new \core_form\filetypes_util();
 103          $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
 104          $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
 105      }
 106  
 107      if (isset($workshop->overallfeedbackfiletypes)) {
 108          $filetypesutil = new \core_form\filetypes_util();
 109          $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
 110          $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
 111      }
 112  
 113      // insert the new record so we get the id
 114      $workshop->id = $DB->insert_record('workshop', $workshop);
 115  
 116      // we need to use context now, so we need to make sure all needed info is already in db
 117      $cmid = $workshop->coursemodule;
 118      $DB->set_field('course_modules', 'instance', $workshop->id, array('id' => $cmid));
 119      $context = context_module::instance($cmid);
 120  
 121      // process the custom wysiwyg editors
 122      if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
 123          $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
 124                  0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
 125          $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
 126      }
 127  
 128      if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
 129          $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
 130                  0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
 131          $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
 132      }
 133  
 134      if ($draftitemid = $workshop->conclusioneditor['itemid']) {
 135          $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
 136                  0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
 137          $workshop->conclusionformat = $workshop->conclusioneditor['format'];
 138      }
 139  
 140      // re-save the record with the replaced URLs in editor fields
 141      $DB->update_record('workshop', $workshop);
 142  
 143      // create gradebook items
 144      workshop_grade_item_update($workshop);
 145      workshop_grade_item_category_update($workshop);
 146  
 147      // create calendar events
 148      workshop_calendar_update($workshop, $workshop->coursemodule);
 149      if (!empty($workshop->completionexpected)) {
 150          \core_completion\api::update_completion_date_event($cmid, 'workshop', $workshop->id, $workshop->completionexpected);
 151      }
 152  
 153      return $workshop->id;
 154  }
 155  
 156  /**
 157   * Given an object containing all the necessary data,
 158   * (defined by the form in mod_form.php) this function
 159   * will update an existing instance with new data.
 160   *
 161   * @param stdClass $workshop An object from the form in mod_form.php
 162   * @return bool success
 163   */
 164  function workshop_update_instance(stdclass $workshop) {
 165      global $CFG, $DB;
 166      require_once (__DIR__ . '/locallib.php');
 167  
 168      $workshop->timemodified          = time();
 169      $workshop->id                    = $workshop->instance;
 170      $workshop->useexamples           = (int)!empty($workshop->useexamples);
 171      $workshop->usepeerassessment     = 1;
 172      $workshop->useselfassessment     = (int)!empty($workshop->useselfassessment);
 173      $workshop->latesubmissions       = (int)!empty($workshop->latesubmissions);
 174      $workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
 175  
 176      if (isset($workshop->gradinggradepass)) {
 177          $workshop->gradinggradepass = (float)unformat_float($workshop->gradinggradepass);
 178      }
 179  
 180      if (isset($workshop->submissiongradepass)) {
 181          $workshop->submissiongradepass = (float)unformat_float($workshop->submissiongradepass);
 182      }
 183  
 184      if (isset($workshop->submissionfiletypes)) {
 185          $filetypesutil = new \core_form\filetypes_util();
 186          $submissionfiletypes = $filetypesutil->normalize_file_types($workshop->submissionfiletypes);
 187          $workshop->submissionfiletypes = implode(' ', $submissionfiletypes);
 188      }
 189  
 190      if (isset($workshop->overallfeedbackfiletypes)) {
 191          $filetypesutil = new \core_form\filetypes_util();
 192          $overallfeedbackfiletypes = $filetypesutil->normalize_file_types($workshop->overallfeedbackfiletypes);
 193          $workshop->overallfeedbackfiletypes = implode(' ', $overallfeedbackfiletypes);
 194      }
 195  
 196      // todo - if the grading strategy is being changed, we may want to replace all aggregated peer grades with nulls
 197  
 198      $DB->update_record('workshop', $workshop);
 199      $context = context_module::instance($workshop->coursemodule);
 200  
 201      // process the custom wysiwyg editors
 202      if ($draftitemid = $workshop->instructauthorseditor['itemid']) {
 203          $workshop->instructauthors = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructauthors',
 204                  0, workshop::instruction_editors_options($context), $workshop->instructauthorseditor['text']);
 205          $workshop->instructauthorsformat = $workshop->instructauthorseditor['format'];
 206      }
 207  
 208      if ($draftitemid = $workshop->instructreviewerseditor['itemid']) {
 209          $workshop->instructreviewers = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'instructreviewers',
 210                  0, workshop::instruction_editors_options($context), $workshop->instructreviewerseditor['text']);
 211          $workshop->instructreviewersformat = $workshop->instructreviewerseditor['format'];
 212      }
 213  
 214      if ($draftitemid = $workshop->conclusioneditor['itemid']) {
 215          $workshop->conclusion = file_save_draft_area_files($draftitemid, $context->id, 'mod_workshop', 'conclusion',
 216                  0, workshop::instruction_editors_options($context), $workshop->conclusioneditor['text']);
 217          $workshop->conclusionformat = $workshop->conclusioneditor['format'];
 218      }
 219  
 220      // re-save the record with the replaced URLs in editor fields
 221      $DB->update_record('workshop', $workshop);
 222  
 223      // update gradebook items
 224      workshop_grade_item_update($workshop);
 225      workshop_grade_item_category_update($workshop);
 226  
 227      // update calendar events
 228      workshop_calendar_update($workshop, $workshop->coursemodule);
 229      $completionexpected = (!empty($workshop->completionexpected)) ? $workshop->completionexpected : null;
 230      \core_completion\api::update_completion_date_event($workshop->coursemodule, 'workshop', $workshop->id, $completionexpected);
 231  
 232      return true;
 233  }
 234  
 235  /**
 236   * Given an ID of an instance of this module,
 237   * this function will permanently delete the instance
 238   * and any data that depends on it.
 239   *
 240   * @param int $id Id of the module instance
 241   * @return boolean Success/Failure
 242   */
 243  function workshop_delete_instance($id) {
 244      global $CFG, $DB;
 245      require_once($CFG->libdir.'/gradelib.php');
 246  
 247      if (! $workshop = $DB->get_record('workshop', array('id' => $id))) {
 248          return false;
 249      }
 250  
 251      // delete all associated aggregations
 252      $DB->delete_records('workshop_aggregations', array('workshopid' => $workshop->id));
 253  
 254      // get the list of ids of all submissions
 255      $submissions = $DB->get_records('workshop_submissions', array('workshopid' => $workshop->id), '', 'id');
 256  
 257      // get the list of all allocated assessments
 258      $assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
 259  
 260      // delete the associated records from the workshop core tables
 261      $DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
 262      $DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
 263      $DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
 264  
 265      // call the static clean-up methods of all available subplugins
 266      $strategies = core_component::get_plugin_list('workshopform');
 267      foreach ($strategies as $strategy => $path) {
 268          require_once ($path.'/lib.php');
 269          $classname = 'workshop_'.$strategy.'_strategy';
 270          call_user_func($classname.'::delete_instance', $workshop->id);
 271      }
 272  
 273      $allocators = core_component::get_plugin_list('workshopallocation');
 274      foreach ($allocators as $allocator => $path) {
 275          require_once ($path.'/lib.php');
 276          $classname = 'workshop_'.$allocator.'_allocator';
 277          call_user_func($classname.'::delete_instance', $workshop->id);
 278      }
 279  
 280      $evaluators = core_component::get_plugin_list('workshopeval');
 281      foreach ($evaluators as $evaluator => $path) {
 282          require_once ($path.'/lib.php');
 283          $classname = 'workshop_'.$evaluator.'_evaluation';
 284          call_user_func($classname.'::delete_instance', $workshop->id);
 285      }
 286  
 287      // delete the calendar events
 288      $events = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
 289      foreach ($events as $event) {
 290          $event = calendar_event::load($event);
 291          $event->delete();
 292      }
 293  
 294      // gradebook cleanup
 295      grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, array('deleted' => true));
 296      grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, array('deleted' => true));
 297  
 298      // finally remove the workshop record itself
 299      // We must delete the module record after we delete the grade item.
 300      $DB->delete_records('workshop', array('id' => $workshop->id));
 301  
 302      return true;
 303  }
 304  
 305  /**
 306   * This standard function will check all instances of this module
 307   * and make sure there are up-to-date events created for each of them.
 308   * If courseid = 0, then every workshop event in the site is checked, else
 309   * only workshop events belonging to the course specified are checked.
 310   *
 311   * @param  integer $courseid The Course ID.
 312   * @param int|stdClass $instance workshop module instance or ID.
 313   * @param int|stdClass $cm Course module object or ID.
 314   * @return bool Returns true if the calendar events were successfully updated.
 315   */
 316  function workshop_refresh_events($courseid = 0, $instance = null, $cm = null) {
 317      global $DB;
 318  
 319      // If we have instance information then we can just update the one event instead of updating all events.
 320      if (isset($instance)) {
 321          if (!is_object($instance)) {
 322              $instance = $DB->get_record('workshop', array('id' => $instance), '*', MUST_EXIST);
 323          }
 324          if (isset($cm)) {
 325              if (!is_object($cm)) {
 326                  $cm = (object)array('id' => $cm);
 327              }
 328          } else {
 329              $cm = get_coursemodule_from_instance('workshop', $instance->id);
 330          }
 331          workshop_calendar_update($instance, $cm->id);
 332          return true;
 333      }
 334  
 335      if ($courseid) {
 336          // Make sure that the course id is numeric.
 337          if (!is_numeric($courseid)) {
 338              return false;
 339          }
 340          if (!$workshops = $DB->get_records('workshop', array('course' => $courseid))) {
 341              return false;
 342          }
 343      } else {
 344          if (!$workshops = $DB->get_records('workshop')) {
 345              return false;
 346          }
 347      }
 348      foreach ($workshops as $workshop) {
 349          if (!$cm = get_coursemodule_from_instance('workshop', $workshop->id, $courseid, false)) {
 350              continue;
 351          }
 352          workshop_calendar_update($workshop, $cm->id);
 353      }
 354      return true;
 355  }
 356  
 357  /**
 358   * List the actions that correspond to a view of this module.
 359   * This is used by the participation report.
 360   *
 361   * Note: This is not used by new logging system. Event with
 362   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
 363   *       be considered as view action.
 364   *
 365   * @return array
 366   */
 367  function workshop_get_view_actions() {
 368      return array('view', 'view all', 'view submission', 'view example');
 369  }
 370  
 371  /**
 372   * List the actions that correspond to a post of this module.
 373   * This is used by the participation report.
 374   *
 375   * Note: This is not used by new logging system. Event with
 376   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
 377   *       will be considered as post action.
 378   *
 379   * @return array
 380   */
 381  function workshop_get_post_actions() {
 382      return array('add', 'add assessment', 'add example', 'add submission',
 383                   'update', 'update assessment', 'update example', 'update submission');
 384  }
 385  
 386  /**
 387   * Return a small object with summary information about what a
 388   * user has done with a given particular instance of this module
 389   * Used for user activity reports.
 390   * $return->time = the time they did it
 391   * $return->info = a short text description
 392   *
 393   * @param stdClass $course The course record.
 394   * @param stdClass $user The user record.
 395   * @param cm_info|stdClass $mod The course module info object or record.
 396   * @param stdClass $workshop The workshop instance record.
 397   * @return stdclass|null
 398   */
 399  function workshop_user_outline($course, $user, $mod, $workshop) {
 400      global $CFG, $DB;
 401      require_once($CFG->libdir.'/gradelib.php');
 402  
 403      $grades = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
 404  
 405      $submissiongrade = null;
 406      $assessmentgrade = null;
 407  
 408      $info = '';
 409      $time = 0;
 410  
 411      if (!empty($grades->items[0]->grades)) {
 412          $submissiongrade = reset($grades->items[0]->grades);
 413          $time = max($time, $submissiongrade->dategraded);
 414          if (!$submissiongrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 415              $info .= get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade
 416                  . html_writer::empty_tag('br');
 417          } else {
 418              $info .= get_string('submissiongrade', 'workshop') . ': ' . get_string('hidden', 'grades')
 419                  . html_writer::empty_tag('br');
 420          }
 421      }
 422      if (!empty($grades->items[1]->grades)) {
 423          $assessmentgrade = reset($grades->items[1]->grades);
 424          $time = max($time, $assessmentgrade->dategraded);
 425          if (!$assessmentgrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 426              $info .= get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
 427          } else {
 428              $info .= get_string('gradinggrade', 'workshop') . ': ' . get_string('hidden', 'grades');
 429          }
 430      }
 431  
 432      if (!empty($info) and !empty($time)) {
 433          $return = new stdclass();
 434          $return->time = $time;
 435          $return->info = $info;
 436          return $return;
 437      }
 438  
 439      return null;
 440  }
 441  
 442  /**
 443   * Print a detailed representation of what a user has done with
 444   * a given particular instance of this module, for user activity reports.
 445   *
 446   * @param stdClass $course The course record.
 447   * @param stdClass $user The user record.
 448   * @param cm_info|stdClass $mod The course module info object or record.
 449   * @param stdClass $workshop The workshop instance record.
 450   * @return string HTML
 451   */
 452  function workshop_user_complete($course, $user, $mod, $workshop) {
 453      global $CFG, $DB, $OUTPUT;
 454      require_once (__DIR__.'/locallib.php');
 455      require_once($CFG->libdir.'/gradelib.php');
 456  
 457      $workshop   = new workshop($workshop, $mod, $course);
 458      $grades     = grade_get_grades($course->id, 'mod', 'workshop', $workshop->id, $user->id);
 459  
 460      if (!empty($grades->items[0]->grades)) {
 461          $submissiongrade = reset($grades->items[0]->grades);
 462          if (!$submissiongrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 463              $info = get_string('submissiongrade', 'workshop') . ': ' . $submissiongrade->str_long_grade;
 464          } else {
 465              $info = get_string('submissiongrade', 'workshop') . ': ' . get_string('hidden', 'grades');
 466          }
 467          echo html_writer::tag('li', $info, array('class'=>'submissiongrade'));
 468      }
 469      if (!empty($grades->items[1]->grades)) {
 470          $assessmentgrade = reset($grades->items[1]->grades);
 471          if (!$assessmentgrade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 472              $info = get_string('gradinggrade', 'workshop') . ': ' . $assessmentgrade->str_long_grade;
 473          } else {
 474              $info = get_string('gradinggrade', 'workshop') . ': ' . get_string('hidden', 'grades');
 475          }
 476          echo html_writer::tag('li', $info, array('class'=>'gradinggrade'));
 477      }
 478  
 479      if (has_capability('mod/workshop:viewallsubmissions', $workshop->context)) {
 480          $canviewsubmission = true;
 481          if (groups_get_activity_groupmode($workshop->cm) == SEPARATEGROUPS) {
 482              // user must have accessallgroups or share at least one group with the submission author
 483              if (!has_capability('moodle/site:accessallgroups', $workshop->context)) {
 484                  $usersgroups = groups_get_activity_allowed_groups($workshop->cm);
 485                  $authorsgroups = groups_get_all_groups($workshop->course->id, $user->id, $workshop->cm->groupingid, 'g.id');
 486                  $sharedgroups = array_intersect_key($usersgroups, $authorsgroups);
 487                  if (empty($sharedgroups)) {
 488                      $canviewsubmission = false;
 489                  }
 490              }
 491          }
 492          if ($canviewsubmission and $submission = $workshop->get_submission_by_author($user->id)) {
 493              $title      = format_string($submission->title);
 494              $url        = $workshop->submission_url($submission->id);
 495              $link       = html_writer::link($url, $title);
 496              $info       = get_string('submission', 'workshop').': '.$link;
 497              echo html_writer::tag('li', $info, array('class'=>'submission'));
 498          }
 499      }
 500  
 501      if (has_capability('mod/workshop:viewallassessments', $workshop->context)) {
 502          if ($assessments = $workshop->get_assessments_by_reviewer($user->id)) {
 503              foreach ($assessments as $assessment) {
 504                  $a = new stdclass();
 505                  $a->submissionurl = $workshop->submission_url($assessment->submissionid)->out();
 506                  $a->assessmenturl = $workshop->assess_url($assessment->id)->out();
 507                  $a->submissiontitle = s($assessment->submissiontitle);
 508                  echo html_writer::tag('li', get_string('assessmentofsubmission', 'workshop', $a));
 509              }
 510          }
 511      }
 512  }
 513  
 514  /**
 515   * Given a course and a time, this module should find recent activity
 516   * that has occurred in workshop activities and print it out.
 517   * Return true if there was output, or false is there was none.
 518   *
 519   * @param stdClass $course
 520   * @param bool $viewfullnames
 521   * @param int $timestart
 522   * @return boolean
 523   */
 524  function workshop_print_recent_activity($course, $viewfullnames, $timestart) {
 525      global $CFG, $USER, $DB, $OUTPUT;
 526  
 527      $userfieldsapi = \core_user\fields::for_name();
 528      $authoramefields = $userfieldsapi->get_sql('author', false, 'author', '', false)->selects;
 529      $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', '', false)->selects;
 530  
 531      $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
 532                     author.id AS authorid, $authoramefields, a.id AS assessmentid, a.timemodified AS assessmentmodified,
 533                     reviewer.id AS reviewerid, $reviewerfields, cm.id AS cmid
 534                FROM {workshop} w
 535          INNER JOIN {course_modules} cm ON cm.instance = w.id
 536          INNER JOIN {modules} md ON md.id = cm.module
 537          INNER JOIN {workshop_submissions} s ON s.workshopid = w.id
 538          INNER JOIN {user} author ON s.authorid = author.id
 539           LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
 540           LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
 541               WHERE cm.course = ?
 542                     AND md.name = 'workshop'
 543                     AND s.example = 0
 544                     AND (s.timemodified > ? OR a.timemodified > ?)
 545            ORDER BY s.timemodified";
 546  
 547      $rs = $DB->get_recordset_sql($sql, array($course->id, $timestart, $timestart));
 548  
 549      $modinfo = get_fast_modinfo($course); // reference needed because we might load the groups
 550  
 551      $submissions = array(); // recent submissions indexed by submission id
 552      $assessments = array(); // recent assessments indexed by assessment id
 553      $users       = array();
 554  
 555      foreach ($rs as $activity) {
 556          if (!array_key_exists($activity->cmid, $modinfo->cms)) {
 557              // this should not happen but just in case
 558              continue;
 559          }
 560  
 561          $cm = $modinfo->cms[$activity->cmid];
 562          if (!$cm->uservisible) {
 563              continue;
 564          }
 565  
 566          // remember all user names we can use later
 567          if (empty($users[$activity->authorid])) {
 568              $u = new stdclass();
 569              $users[$activity->authorid] = username_load_fields_from_object($u, $activity, 'author');
 570          }
 571          if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
 572              $u = new stdclass();
 573              $users[$activity->reviewerid] = username_load_fields_from_object($u, $activity, 'reviewer');
 574          }
 575  
 576          $context = context_module::instance($cm->id);
 577          $groupmode = groups_get_activity_groupmode($cm, $course);
 578  
 579          if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
 580              $s = new stdclass();
 581              $s->title = $activity->submissiontitle;
 582              $s->authorid = $activity->authorid;
 583              $s->timemodified = $activity->submissionmodified;
 584              $s->cmid = $activity->cmid;
 585              if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
 586                  $s->authornamevisible = true;
 587              } else {
 588                  $s->authornamevisible = false;
 589              }
 590  
 591              // the following do-while wrapper allows to break from deeply nested if-statements
 592              do {
 593                  if ($s->authorid === $USER->id) {
 594                      // own submissions always visible
 595                      $submissions[$activity->submissionid] = $s;
 596                      break;
 597                  }
 598  
 599                  if (has_capability('mod/workshop:viewallsubmissions', $context)) {
 600                      if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
 601                          if (isguestuser()) {
 602                              // shortcut - guest user does not belong into any group
 603                              break;
 604                          }
 605  
 606                          // this might be slow - show only submissions by users who share group with me in this cm
 607                          if (!$modinfo->get_groups($cm->groupingid)) {
 608                              break;
 609                          }
 610                          $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
 611                          if (is_array($authorsgroups)) {
 612                              $authorsgroups = array_keys($authorsgroups);
 613                              $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
 614                              if (empty($intersect)) {
 615                                  break;
 616                              } else {
 617                                  // can see all submissions and shares a group with the author
 618                                  $submissions[$activity->submissionid] = $s;
 619                                  break;
 620                              }
 621                          }
 622  
 623                      } else {
 624                          // can see all submissions from all groups
 625                          $submissions[$activity->submissionid] = $s;
 626                      }
 627                  }
 628              } while (0);
 629          }
 630  
 631          if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
 632              $a = new stdclass();
 633              $a->submissionid = $activity->submissionid;
 634              $a->submissiontitle = $activity->submissiontitle;
 635              $a->reviewerid = $activity->reviewerid;
 636              $a->timemodified = $activity->assessmentmodified;
 637              $a->cmid = $activity->cmid;
 638              if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
 639                  $a->reviewernamevisible = true;
 640              } else {
 641                  $a->reviewernamevisible = false;
 642              }
 643  
 644              // the following do-while wrapper allows to break from deeply nested if-statements
 645              do {
 646                  if ($a->reviewerid === $USER->id) {
 647                      // own assessments always visible
 648                      $assessments[$activity->assessmentid] = $a;
 649                      break;
 650                  }
 651  
 652                  if (has_capability('mod/workshop:viewallassessments', $context)) {
 653                      if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
 654                          if (isguestuser()) {
 655                              // shortcut - guest user does not belong into any group
 656                              break;
 657                          }
 658  
 659                          // this might be slow - show only submissions by users who share group with me in this cm
 660                          if (!$modinfo->get_groups($cm->groupingid)) {
 661                              break;
 662                          }
 663                          $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
 664                          if (is_array($reviewersgroups)) {
 665                              $reviewersgroups = array_keys($reviewersgroups);
 666                              $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
 667                              if (empty($intersect)) {
 668                                  break;
 669                              } else {
 670                                  // can see all assessments and shares a group with the reviewer
 671                                  $assessments[$activity->assessmentid] = $a;
 672                                  break;
 673                              }
 674                          }
 675  
 676                      } else {
 677                          // can see all assessments from all groups
 678                          $assessments[$activity->assessmentid] = $a;
 679                      }
 680                  }
 681              } while (0);
 682          }
 683      }
 684      $rs->close();
 685  
 686      $shown = false;
 687  
 688      if (!empty($submissions)) {
 689          $shown = true;
 690          echo $OUTPUT->heading(get_string('recentsubmissions', 'workshop') . ':', 6);
 691          foreach ($submissions as $id => $submission) {
 692              $link = new moodle_url('/mod/workshop/submission.php', array('id'=>$id, 'cmid'=>$submission->cmid));
 693              if ($submission->authornamevisible) {
 694                  $author = $users[$submission->authorid];
 695              } else {
 696                  $author = null;
 697              }
 698              print_recent_activity_note($submission->timemodified, $author, $submission->title, $link->out(), false, $viewfullnames);
 699          }
 700      }
 701  
 702      if (!empty($assessments)) {
 703          $shown = true;
 704          echo $OUTPUT->heading(get_string('recentassessments', 'workshop') . ':', 6);
 705          core_collator::asort_objects_by_property($assessments, 'timemodified');
 706          foreach ($assessments as $id => $assessment) {
 707              $link = new moodle_url('/mod/workshop/assessment.php', array('asid' => $id));
 708              if ($assessment->reviewernamevisible) {
 709                  $reviewer = $users[$assessment->reviewerid];
 710              } else {
 711                  $reviewer = null;
 712              }
 713              print_recent_activity_note($assessment->timemodified, $reviewer, $assessment->submissiontitle, $link->out(), false, $viewfullnames);
 714          }
 715      }
 716  
 717      if ($shown) {
 718          return true;
 719      }
 720  
 721      return false;
 722  }
 723  
 724  /**
 725   * Returns all activity in course workshops since a given time
 726   *
 727   * @param array $activities sequentially indexed array of objects
 728   * @param int $index
 729   * @param int $timestart
 730   * @param int $courseid
 731   * @param int $cmid
 732   * @param int $userid defaults to 0
 733   * @param int $groupid defaults to 0
 734   * @return void adds items into $activities and increases $index
 735   */
 736  function workshop_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
 737      global $CFG, $COURSE, $USER, $DB;
 738  
 739      if ($COURSE->id == $courseid) {
 740          $course = $COURSE;
 741      } else {
 742          $course = $DB->get_record('course', array('id'=>$courseid));
 743      }
 744  
 745      $modinfo = get_fast_modinfo($course);
 746  
 747      $cm = $modinfo->cms[$cmid];
 748  
 749      $params = array();
 750      if ($userid) {
 751          $userselect = "AND (author.id = :authorid OR reviewer.id = :reviewerid)";
 752          $params['authorid'] = $userid;
 753          $params['reviewerid'] = $userid;
 754      } else {
 755          $userselect = "";
 756      }
 757  
 758      if ($groupid) {
 759          $groupselect = "AND (authorgroupmembership.groupid = :authorgroupid OR reviewergroupmembership.groupid = :reviewergroupid)";
 760          $groupjoin   = "LEFT JOIN {groups_members} authorgroupmembership ON authorgroupmembership.userid = author.id
 761                          LEFT JOIN {groups_members} reviewergroupmembership ON reviewergroupmembership.userid = reviewer.id";
 762          $params['authorgroupid'] = $groupid;
 763          $params['reviewergroupid'] = $groupid;
 764      } else {
 765          $groupselect = "";
 766          $groupjoin   = "";
 767      }
 768  
 769      $params['cminstance'] = $cm->instance;
 770      $params['submissionmodified'] = $timestart;
 771      $params['assessmentmodified'] = $timestart;
 772  
 773      $userfieldsapi = \core_user\fields::for_name();
 774      $authornamefields = $userfieldsapi->get_sql('author', false, 'author', '', false)->selects;
 775      $reviewerfields = $userfieldsapi->get_sql('reviewer', false, 'reviewer', '', false)->selects;
 776  
 777      $sql = "SELECT s.id AS submissionid, s.title AS submissiontitle, s.timemodified AS submissionmodified,
 778                     author.id AS authorid, $authornamefields, author.picture AS authorpicture, author.imagealt AS authorimagealt,
 779                     author.email AS authoremail, a.id AS assessmentid, a.timemodified AS assessmentmodified,
 780                     reviewer.id AS reviewerid, $reviewerfields, reviewer.picture AS reviewerpicture,
 781                     reviewer.imagealt AS reviewerimagealt, reviewer.email AS revieweremail
 782                FROM {workshop_submissions} s
 783          INNER JOIN {workshop} w ON s.workshopid = w.id
 784          INNER JOIN {user} author ON s.authorid = author.id
 785           LEFT JOIN {workshop_assessments} a ON a.submissionid = s.id
 786           LEFT JOIN {user} reviewer ON a.reviewerid = reviewer.id
 787          $groupjoin
 788               WHERE w.id = :cminstance
 789                     AND s.example = 0
 790                     $userselect $groupselect
 791                     AND (s.timemodified > :submissionmodified OR a.timemodified > :assessmentmodified)
 792            ORDER BY s.timemodified ASC, a.timemodified ASC";
 793  
 794      $rs = $DB->get_recordset_sql($sql, $params);
 795  
 796      $groupmode       = groups_get_activity_groupmode($cm, $course);
 797      $context         = context_module::instance($cm->id);
 798      $grader          = has_capability('moodle/grade:viewall', $context);
 799      $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
 800      $viewauthors     = has_capability('mod/workshop:viewauthornames', $context);
 801      $viewreviewers   = has_capability('mod/workshop:viewreviewernames', $context);
 802  
 803      $submissions = array(); // recent submissions indexed by submission id
 804      $assessments = array(); // recent assessments indexed by assessment id
 805      $users       = array();
 806  
 807      foreach ($rs as $activity) {
 808  
 809          // remember all user names we can use later
 810          if (empty($users[$activity->authorid])) {
 811              $u = new stdclass();
 812              $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
 813              $u = username_load_fields_from_object($u, $activity, 'author', $additionalfields);
 814              $users[$activity->authorid] = $u;
 815          }
 816          if ($activity->reviewerid and empty($users[$activity->reviewerid])) {
 817              $u = new stdclass();
 818              $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
 819              $u = username_load_fields_from_object($u, $activity, 'reviewer', $additionalfields);
 820              $users[$activity->reviewerid] = $u;
 821          }
 822  
 823          if ($activity->submissionmodified > $timestart and empty($submissions[$activity->submissionid])) {
 824              $s = new stdclass();
 825              $s->id = $activity->submissionid;
 826              $s->title = $activity->submissiontitle;
 827              $s->authorid = $activity->authorid;
 828              $s->timemodified = $activity->submissionmodified;
 829              if ($activity->authorid == $USER->id || has_capability('mod/workshop:viewauthornames', $context)) {
 830                  $s->authornamevisible = true;
 831              } else {
 832                  $s->authornamevisible = false;
 833              }
 834  
 835              // the following do-while wrapper allows to break from deeply nested if-statements
 836              do {
 837                  if ($s->authorid === $USER->id) {
 838                      // own submissions always visible
 839                      $submissions[$activity->submissionid] = $s;
 840                      break;
 841                  }
 842  
 843                  if (has_capability('mod/workshop:viewallsubmissions', $context)) {
 844                      if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
 845                          if (isguestuser()) {
 846                              // shortcut - guest user does not belong into any group
 847                              break;
 848                          }
 849  
 850                          // this might be slow - show only submissions by users who share group with me in this cm
 851                          if (!$modinfo->get_groups($cm->groupingid)) {
 852                              break;
 853                          }
 854                          $authorsgroups = groups_get_all_groups($course->id, $s->authorid, $cm->groupingid);
 855                          if (is_array($authorsgroups)) {
 856                              $authorsgroups = array_keys($authorsgroups);
 857                              $intersect = array_intersect($authorsgroups, $modinfo->get_groups($cm->groupingid));
 858                              if (empty($intersect)) {
 859                                  break;
 860                              } else {
 861                                  // can see all submissions and shares a group with the author
 862                                  $submissions[$activity->submissionid] = $s;
 863                                  break;
 864                              }
 865                          }
 866  
 867                      } else {
 868                          // can see all submissions from all groups
 869                          $submissions[$activity->submissionid] = $s;
 870                      }
 871                  }
 872              } while (0);
 873          }
 874  
 875          if ($activity->assessmentmodified > $timestart and empty($assessments[$activity->assessmentid])) {
 876              $a = new stdclass();
 877              $a->id = $activity->assessmentid;
 878              $a->submissionid = $activity->submissionid;
 879              $a->submissiontitle = $activity->submissiontitle;
 880              $a->reviewerid = $activity->reviewerid;
 881              $a->timemodified = $activity->assessmentmodified;
 882              if ($activity->reviewerid == $USER->id || has_capability('mod/workshop:viewreviewernames', $context)) {
 883                  $a->reviewernamevisible = true;
 884              } else {
 885                  $a->reviewernamevisible = false;
 886              }
 887  
 888              // the following do-while wrapper allows to break from deeply nested if-statements
 889              do {
 890                  if ($a->reviewerid === $USER->id) {
 891                      // own assessments always visible
 892                      $assessments[$activity->assessmentid] = $a;
 893                      break;
 894                  }
 895  
 896                  if (has_capability('mod/workshop:viewallassessments', $context)) {
 897                      if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
 898                          if (isguestuser()) {
 899                              // shortcut - guest user does not belong into any group
 900                              break;
 901                          }
 902  
 903                          // this might be slow - show only submissions by users who share group with me in this cm
 904                          if (!$modinfo->get_groups($cm->groupingid)) {
 905                              break;
 906                          }
 907                          $reviewersgroups = groups_get_all_groups($course->id, $a->reviewerid, $cm->groupingid);
 908                          if (is_array($reviewersgroups)) {
 909                              $reviewersgroups = array_keys($reviewersgroups);
 910                              $intersect = array_intersect($reviewersgroups, $modinfo->get_groups($cm->groupingid));
 911                              if (empty($intersect)) {
 912                                  break;
 913                              } else {
 914                                  // can see all assessments and shares a group with the reviewer
 915                                  $assessments[$activity->assessmentid] = $a;
 916                                  break;
 917                              }
 918                          }
 919  
 920                      } else {
 921                          // can see all assessments from all groups
 922                          $assessments[$activity->assessmentid] = $a;
 923                      }
 924                  }
 925              } while (0);
 926          }
 927      }
 928      $rs->close();
 929  
 930      $workshopname = format_string($cm->name, true);
 931  
 932      if ($grader) {
 933          require_once($CFG->libdir.'/gradelib.php');
 934          $grades = grade_get_grades($courseid, 'mod', 'workshop', $cm->instance, array_keys($users));
 935      }
 936  
 937      foreach ($submissions as $submission) {
 938          $tmpactivity                = new stdclass();
 939          $tmpactivity->type          = 'workshop';
 940          $tmpactivity->cmid          = $cm->id;
 941          $tmpactivity->name          = $workshopname;
 942          $tmpactivity->sectionnum    = $cm->sectionnum;
 943          $tmpactivity->timestamp     = $submission->timemodified;
 944          $tmpactivity->subtype       = 'submission';
 945          $tmpactivity->content       = $submission;
 946          if ($grader) {
 947              $tmpactivity->grade     = $grades->items[0]->grades[$submission->authorid]->str_long_grade;
 948          }
 949          if ($submission->authornamevisible and !empty($users[$submission->authorid])) {
 950              $tmpactivity->user      = $users[$submission->authorid];
 951          }
 952          $activities[$index++]       = $tmpactivity;
 953      }
 954  
 955      foreach ($assessments as $assessment) {
 956          $tmpactivity                = new stdclass();
 957          $tmpactivity->type          = 'workshop';
 958          $tmpactivity->cmid          = $cm->id;
 959          $tmpactivity->name          = $workshopname;
 960          $tmpactivity->sectionnum    = $cm->sectionnum;
 961          $tmpactivity->timestamp     = $assessment->timemodified;
 962          $tmpactivity->subtype       = 'assessment';
 963          $tmpactivity->content       = $assessment;
 964          if ($grader) {
 965              $tmpactivity->grade     = $grades->items[1]->grades[$assessment->reviewerid]->str_long_grade;
 966          }
 967          if ($assessment->reviewernamevisible and !empty($users[$assessment->reviewerid])) {
 968              $tmpactivity->user      = $users[$assessment->reviewerid];
 969          }
 970          $activities[$index++]       = $tmpactivity;
 971      }
 972  }
 973  
 974  /**
 975   * Print single activity item prepared by {@see workshop_get_recent_mod_activity()}
 976   */
 977  function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
 978      global $CFG, $OUTPUT;
 979  
 980      if (!empty($activity->user)) {
 981          echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
 982                  array('style' => 'float: left; padding: 7px;'));
 983      }
 984  
 985      if ($activity->subtype == 'submission') {
 986          echo html_writer::start_tag('div', array('class'=>'submission', 'style'=>'padding: 7px; float:left;'));
 987  
 988          if ($detail) {
 989              echo html_writer::start_tag('h4', array('class'=>'workshop'));
 990              $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
 991              $name = s($activity->name);
 992              echo $OUTPUT->image_icon('monologo', $name, $activity->type);
 993              echo ' ' . $modnames[$activity->type];
 994              echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
 995              echo html_writer::end_tag('h4');
 996          }
 997  
 998          echo html_writer::start_tag('div', array('class'=>'title'));
 999          $url = new moodle_url('/mod/workshop/submission.php', array('cmid'=>$activity->cmid, 'id'=>$activity->content->id));
1000          $name = s($activity->content->title);
1001          echo html_writer::tag('strong', html_writer::link($url, $name));
1002          echo html_writer::end_tag('div');
1003  
1004          if (!empty($activity->user)) {
1005              echo html_writer::start_tag('div', array('class'=>'user'));
1006              $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1007              $name = fullname($activity->user);
1008              $link = html_writer::link($url, $name);
1009              echo get_string('submissionby', 'workshop', $link);
1010              echo ' - '.userdate($activity->timestamp);
1011              echo html_writer::end_tag('div');
1012          } else {
1013              echo html_writer::start_tag('div', array('class'=>'anonymous'));
1014              echo get_string('submission', 'workshop');
1015              echo ' - '.userdate($activity->timestamp);
1016              echo html_writer::end_tag('div');
1017          }
1018  
1019          echo html_writer::end_tag('div');
1020      }
1021  
1022      if ($activity->subtype == 'assessment') {
1023          echo html_writer::start_tag('div', array('class'=>'assessment', 'style'=>'padding: 7px; float:left;'));
1024  
1025          if ($detail) {
1026              echo html_writer::start_tag('h4', array('class'=>'workshop'));
1027              $url = new moodle_url('/mod/workshop/view.php', array('id'=>$activity->cmid));
1028              $name = s($activity->name);
1029              echo $OUTPUT->image_icon('monologo', $name, $activity->type);
1030              echo ' ' . $modnames[$activity->type];
1031              echo html_writer::link($url, $name, array('class'=>'name', 'style'=>'margin-left: 5px'));
1032              echo html_writer::end_tag('h4');
1033          }
1034  
1035          echo html_writer::start_tag('div', array('class'=>'title'));
1036          $url = new moodle_url('/mod/workshop/assessment.php', array('asid'=>$activity->content->id));
1037          $name = s($activity->content->submissiontitle);
1038          echo html_writer::tag('em', html_writer::link($url, $name));
1039          echo html_writer::end_tag('div');
1040  
1041          if (!empty($activity->user)) {
1042              echo html_writer::start_tag('div', array('class'=>'user'));
1043              $url = new moodle_url('/user/view.php', array('id'=>$activity->user->id, 'course'=>$courseid));
1044              $name = fullname($activity->user);
1045              $link = html_writer::link($url, $name);
1046              echo get_string('assessmentbyfullname', 'workshop', $link);
1047              echo ' - '.userdate($activity->timestamp);
1048              echo html_writer::end_tag('div');
1049          } else {
1050              echo html_writer::start_tag('div', array('class'=>'anonymous'));
1051              echo get_string('assessment', 'workshop');
1052              echo ' - '.userdate($activity->timestamp);
1053              echo html_writer::end_tag('div');
1054          }
1055  
1056          echo html_writer::end_tag('div');
1057      }
1058  
1059      echo html_writer::empty_tag('br', array('style'=>'clear:both'));
1060  }
1061  
1062  /**
1063   * @deprecated since Moodle 3.8
1064   */
1065  function workshop_scale_used() {
1066      throw new coding_exception('workshop_scale_used() can not be used anymore. Plugins can implement ' .
1067          '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
1068  }
1069  
1070  /**
1071   * Is a given scale used by any instance of workshop?
1072   *
1073   * The function asks all installed grading strategy subplugins. The workshop
1074   * core itself does not use scales. Both grade for submission and grade for
1075   * assessments do not use scales.
1076   *
1077   * @param int $scaleid id of the scale to check
1078   * @return bool
1079   */
1080  function workshop_scale_used_anywhere($scaleid) {
1081      global $CFG; // other files included from here
1082  
1083      $strategies = core_component::get_plugin_list('workshopform');
1084      foreach ($strategies as $strategy => $strategypath) {
1085          $strategylib = $strategypath . '/lib.php';
1086          if (is_readable($strategylib)) {
1087              require_once($strategylib);
1088          } else {
1089              throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib);
1090          }
1091          $classname = 'workshop_' . $strategy . '_strategy';
1092          if (method_exists($classname, 'scale_used')) {
1093              if (call_user_func(array($classname, 'scale_used'), $scaleid)) {
1094                  // no need to include any other files - scale is used
1095                  return true;
1096              }
1097          }
1098      }
1099  
1100      return false;
1101  }
1102  
1103  ////////////////////////////////////////////////////////////////////////////////
1104  // Gradebook API                                                              //
1105  ////////////////////////////////////////////////////////////////////////////////
1106  
1107  /**
1108   * Creates or updates grade items for the give workshop instance
1109   *
1110   * Needed by grade_update_mod_grades() in lib/gradelib.php. Also used by
1111   * {@link workshop_update_grades()}.
1112   *
1113   * @param stdClass $workshop instance object with extra cmidnumber property
1114   * @param stdClass $submissiongrades data for the first grade item
1115   * @param stdClass $assessmentgrades data for the second grade item
1116   * @return void
1117   */
1118  function workshop_grade_item_update(stdclass $workshop, $submissiongrades=null, $assessmentgrades=null) {
1119      global $CFG;
1120      require_once($CFG->libdir.'/gradelib.php');
1121  
1122      $a = new stdclass();
1123      $a->workshopname = clean_param($workshop->name, PARAM_NOTAGS);
1124  
1125      $item = array();
1126      $item['itemname'] = get_string('gradeitemsubmission', 'workshop', $a);
1127      $item['gradetype'] = GRADE_TYPE_VALUE;
1128      $item['grademax']  = $workshop->grade;
1129      $item['grademin']  = 0;
1130      grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, $submissiongrades , $item);
1131  
1132      $item = array();
1133      $item['itemname'] = get_string('gradeitemassessment', 'workshop', $a);
1134      $item['gradetype'] = GRADE_TYPE_VALUE;
1135      $item['grademax']  = $workshop->gradinggrade;
1136      $item['grademin']  = 0;
1137      grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, $assessmentgrades, $item);
1138  }
1139  
1140  /**
1141   * Update workshop grades in the gradebook
1142   *
1143   * Needed by grade_update_mod_grades() in lib/gradelib.php
1144   *
1145   * @category grade
1146   * @param stdClass $workshop instance object with extra cmidnumber and modname property
1147   * @param int $userid        update grade of specific user only, 0 means all participants
1148   * @return void
1149   */
1150  function workshop_update_grades(stdclass $workshop, $userid=0) {
1151      global $CFG, $DB;
1152      require_once($CFG->libdir.'/gradelib.php');
1153  
1154      $whereuser = $userid ? ' AND authorid = :userid' : '';
1155      $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1156      $sql = 'SELECT authorid, grade, gradeover, gradeoverby, feedbackauthor, feedbackauthorformat, timemodified, timegraded
1157                FROM {workshop_submissions}
1158               WHERE workshopid = :workshopid AND example=0' . $whereuser;
1159      $records = $DB->get_records_sql($sql, $params);
1160      $submissiongrades = array();
1161      foreach ($records as $record) {
1162          $grade = new stdclass();
1163          $grade->userid = $record->authorid;
1164          if (!is_null($record->gradeover)) {
1165              $grade->rawgrade = grade_floatval($workshop->grade * $record->gradeover / 100);
1166              $grade->usermodified = $record->gradeoverby;
1167          } else {
1168              $grade->rawgrade = grade_floatval($workshop->grade * $record->grade / 100);
1169          }
1170          $grade->feedback = $record->feedbackauthor;
1171          $grade->feedbackformat = $record->feedbackauthorformat;
1172          $grade->datesubmitted = $record->timemodified;
1173          $grade->dategraded = $record->timegraded;
1174          $submissiongrades[$record->authorid] = $grade;
1175      }
1176  
1177      $whereuser = $userid ? ' AND userid = :userid' : '';
1178      $params = array('workshopid' => $workshop->id, 'userid' => $userid);
1179      $sql = 'SELECT userid, gradinggrade, timegraded
1180                FROM {workshop_aggregations}
1181               WHERE workshopid = :workshopid' . $whereuser;
1182      $records = $DB->get_records_sql($sql, $params);
1183      $assessmentgrades = array();
1184      foreach ($records as $record) {
1185          $grade = new stdclass();
1186          $grade->userid = $record->userid;
1187          $grade->rawgrade = grade_floatval($workshop->gradinggrade * $record->gradinggrade / 100);
1188          $grade->dategraded = $record->timegraded;
1189          $assessmentgrades[$record->userid] = $grade;
1190      }
1191  
1192      workshop_grade_item_update($workshop, $submissiongrades, $assessmentgrades);
1193  }
1194  
1195  /**
1196   * Update the grade items categories if they are changed via mod_form.php
1197   *
1198   * We must do it manually here in the workshop module because modedit supports only
1199   * single grade item while we use two.
1200   *
1201   * @param stdClass $workshop An object from the form in mod_form.php
1202   */
1203  function workshop_grade_item_category_update($workshop) {
1204  
1205      $gradeitems = grade_item::fetch_all(array(
1206          'itemtype'      => 'mod',
1207          'itemmodule'    => 'workshop',
1208          'iteminstance'  => $workshop->id,
1209          'courseid'      => $workshop->course));
1210  
1211      if (!empty($gradeitems)) {
1212          foreach ($gradeitems as $gradeitem) {
1213              if ($gradeitem->itemnumber == 0) {
1214                  if (isset($workshop->submissiongradepass) &&
1215                          $gradeitem->gradepass != $workshop->submissiongradepass) {
1216                      $gradeitem->gradepass = $workshop->submissiongradepass;
1217                      $gradeitem->update();
1218                  }
1219                  if ($gradeitem->categoryid != $workshop->gradecategory) {
1220                      $gradeitem->set_parent($workshop->gradecategory);
1221                  }
1222              } else if ($gradeitem->itemnumber == 1) {
1223                  if (isset($workshop->gradinggradepass) &&
1224                          $gradeitem->gradepass != $workshop->gradinggradepass) {
1225                      $gradeitem->gradepass = $workshop->gradinggradepass;
1226                      $gradeitem->update();
1227                  }
1228                  if ($gradeitem->categoryid != $workshop->gradinggradecategory) {
1229                      $gradeitem->set_parent($workshop->gradinggradecategory);
1230                  }
1231              }
1232          }
1233      }
1234  }
1235  
1236  ////////////////////////////////////////////////////////////////////////////////
1237  // File API                                                                   //
1238  ////////////////////////////////////////////////////////////////////////////////
1239  
1240  /**
1241   * Returns the lists of all browsable file areas within the given module context
1242   *
1243   * The file area workshop_intro for the activity introduction field is added automatically
1244   * by {@link file_browser::get_file_info_context_module()}
1245   *
1246   * @package  mod_workshop
1247   * @category files
1248   *
1249   * @param stdClass $course
1250   * @param stdClass $cm
1251   * @param stdClass $context
1252   * @return array of [(string)filearea] => (string)description
1253   */
1254  function workshop_get_file_areas($course, $cm, $context) {
1255      $areas = array();
1256      $areas['instructauthors']          = get_string('areainstructauthors', 'workshop');
1257      $areas['instructreviewers']        = get_string('areainstructreviewers', 'workshop');
1258      $areas['submission_content']       = get_string('areasubmissioncontent', 'workshop');
1259      $areas['submission_attachment']    = get_string('areasubmissionattachment', 'workshop');
1260      $areas['conclusion']               = get_string('areaconclusion', 'workshop');
1261      $areas['overallfeedback_content']  = get_string('areaoverallfeedbackcontent', 'workshop');
1262      $areas['overallfeedback_attachment'] = get_string('areaoverallfeedbackattachment', 'workshop');
1263  
1264      return $areas;
1265  }
1266  
1267  /**
1268   * Serves the files from the workshop file areas
1269   *
1270   * Apart from module intro (handled by pluginfile.php automatically), workshop files may be
1271   * media inserted into submission content (like images) and submission attachments. For these two,
1272   * the fileareas submission_content and submission_attachment are used.
1273   * Besides that, areas instructauthors, instructreviewers and conclusion contain the media
1274   * embedded using the mod_form.php.
1275   *
1276   * @package  mod_workshop
1277   * @category files
1278   *
1279   * @param stdClass $course the course object
1280   * @param stdClass $cm the course module object
1281   * @param stdClass $context the workshop's context
1282   * @param string $filearea the name of the file area
1283   * @param array $args extra arguments (itemid, path)
1284   * @param bool $forcedownload whether or not force download
1285   * @param array $options additional options affecting the file serving
1286   * @return bool false if the file not found, just send the file otherwise and do not return anything
1287   */
1288  function workshop_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) {
1289      global $DB, $CFG, $USER;
1290  
1291      if ($context->contextlevel != CONTEXT_MODULE) {
1292          return false;
1293      }
1294  
1295      require_login($course, true, $cm);
1296  
1297      if ($filearea === 'instructauthors' or $filearea === 'instructreviewers' or $filearea === 'conclusion') {
1298          // The $args are supposed to contain just the path, not the item id.
1299          $relativepath = implode('/', $args);
1300          $fullpath = "/$context->id/mod_workshop/$filearea/0/$relativepath";
1301  
1302          $fs = get_file_storage();
1303          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1304              send_file_not_found();
1305          }
1306          send_stored_file($file, null, 0, $forcedownload, $options);
1307  
1308      } else if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1309          $itemid = (int)array_shift($args);
1310          if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1311              return false;
1312          }
1313          if (!$submission = $DB->get_record('workshop_submissions', array('id' => $itemid, 'workshopid' => $workshop->id))) {
1314              return false;
1315          }
1316  
1317          // make sure the user is allowed to see the file
1318          if (empty($submission->example)) {
1319              if ($USER->id != $submission->authorid) {
1320                  if ($submission->published == 1 and $workshop->phase == 50
1321                          and has_capability('mod/workshop:viewpublishedsubmissions', $context)) {
1322                      // Published submission, we can go (workshop does not take the group mode
1323                      // into account in this case yet).
1324                  } else if (!$DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $USER->id))) {
1325                      if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1326                          send_file_not_found();
1327                      } else {
1328                          $gmode = groups_get_activity_groupmode($cm, $course);
1329                          if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1330                              // check there is at least one common group with both the $USER
1331                              // and the submission author
1332                              $sql = "SELECT 'x'
1333                                        FROM {workshop_submissions} s
1334                                        JOIN {user} a ON (a.id = s.authorid)
1335                                        JOIN {groups_members} agm ON (a.id = agm.userid)
1336                                        JOIN {user} u ON (u.id = ?)
1337                                        JOIN {groups_members} ugm ON (u.id = ugm.userid)
1338                                       WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1339                              $params = array($USER->id, $workshop->id, $submission->id);
1340                              if (!$DB->record_exists_sql($sql, $params)) {
1341                                  send_file_not_found();
1342                              }
1343                          }
1344                      }
1345                  }
1346              }
1347          }
1348  
1349          $fs = get_file_storage();
1350          $relativepath = implode('/', $args);
1351          $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1352          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1353              return false;
1354          }
1355          // finally send the file
1356          // these files are uploaded by students - forcing download for security reasons
1357          send_stored_file($file, 0, 0, true, $options);
1358  
1359      } else if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1360          $itemid = (int)array_shift($args);
1361          if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
1362              return false;
1363          }
1364          if (!$assessment = $DB->get_record('workshop_assessments', array('id' => $itemid))) {
1365              return false;
1366          }
1367          if (!$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'workshopid' => $workshop->id))) {
1368              return false;
1369          }
1370  
1371          if ($USER->id == $assessment->reviewerid) {
1372              // Reviewers can always see their own files.
1373          } else if ($USER->id == $submission->authorid and $workshop->phase == 50) {
1374              // Authors can see the feedback once the workshop is closed.
1375          } else if (!empty($submission->example) and $assessment->weight == 1) {
1376              // Reference assessments of example submissions can be displayed.
1377          } else if (!has_capability('mod/workshop:viewallassessments', $context)) {
1378              send_file_not_found();
1379          } else {
1380              $gmode = groups_get_activity_groupmode($cm, $course);
1381              if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1382                  // Check there is at least one common group with both the $USER
1383                  // and the submission author.
1384                  $sql = "SELECT 'x'
1385                            FROM {workshop_submissions} s
1386                            JOIN {user} a ON (a.id = s.authorid)
1387                            JOIN {groups_members} agm ON (a.id = agm.userid)
1388                            JOIN {user} u ON (u.id = ?)
1389                            JOIN {groups_members} ugm ON (u.id = ugm.userid)
1390                           WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1391                  $params = array($USER->id, $workshop->id, $submission->id);
1392                  if (!$DB->record_exists_sql($sql, $params)) {
1393                      send_file_not_found();
1394                  }
1395              }
1396          }
1397  
1398          $fs = get_file_storage();
1399          $relativepath = implode('/', $args);
1400          $fullpath = "/$context->id/mod_workshop/$filearea/$itemid/$relativepath";
1401          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1402              return false;
1403          }
1404          // finally send the file
1405          // these files are uploaded by students - forcing download for security reasons
1406          send_stored_file($file, 0, 0, true, $options);
1407      }
1408  
1409      return false;
1410  }
1411  
1412  /**
1413   * File browsing support for workshop file areas
1414   *
1415   * @package  mod_workshop
1416   * @category files
1417   *
1418   * @param file_browser $browser
1419   * @param array $areas
1420   * @param stdClass $course
1421   * @param stdClass $cm
1422   * @param stdClass $context
1423   * @param string $filearea
1424   * @param int $itemid
1425   * @param string $filepath
1426   * @param string $filename
1427   * @return file_info instance or null if not found
1428   */
1429  function workshop_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1430      global $CFG, $DB, $USER;
1431  
1432      /** @var array internal cache for author names */
1433      static $submissionauthors = array();
1434  
1435      $fs = get_file_storage();
1436  
1437      if ($filearea === 'submission_content' or $filearea === 'submission_attachment') {
1438  
1439          if (!has_capability('mod/workshop:viewallsubmissions', $context)) {
1440              return null;
1441          }
1442  
1443          if (is_null($itemid)) {
1444              // no itemid (submissionid) passed, display the list of all submissions
1445              require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1446              return new workshop_file_info_submissions_container($browser, $course, $cm, $context, $areas, $filearea);
1447          }
1448  
1449          // make sure the user can see the particular submission in separate groups mode
1450          $gmode = groups_get_activity_groupmode($cm, $course);
1451  
1452          if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1453              // check there is at least one common group with both the $USER
1454              // and the submission author (this is not expected to be a frequent
1455              // usecase so we can live with pretty ineffective one query per submission here...)
1456              $sql = "SELECT 'x'
1457                        FROM {workshop_submissions} s
1458                        JOIN {user} a ON (a.id = s.authorid)
1459                        JOIN {groups_members} agm ON (a.id = agm.userid)
1460                        JOIN {user} u ON (u.id = ?)
1461                        JOIN {groups_members} ugm ON (u.id = ugm.userid)
1462                       WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1463              $params = array($USER->id, $cm->instance, $itemid);
1464              if (!$DB->record_exists_sql($sql, $params)) {
1465                  return null;
1466              }
1467          }
1468  
1469          // we are inside some particular submission container
1470  
1471          $filepath = is_null($filepath) ? '/' : $filepath;
1472          $filename = is_null($filename) ? '.' : $filename;
1473  
1474          if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1475              if ($filepath === '/' and $filename === '.') {
1476                  $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1477              } else {
1478                  // not found
1479                  return null;
1480              }
1481          }
1482  
1483          // Checks to see if the user can manage files or is the owner.
1484          // TODO MDL-33805 - Do not use userid here and move the capability check above.
1485          if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1486              return null;
1487          }
1488  
1489          // let us display the author's name instead of itemid (submission id)
1490  
1491          if (isset($submissionauthors[$itemid])) {
1492              $topvisiblename = $submissionauthors[$itemid];
1493  
1494          } else {
1495  
1496              $userfieldsapi = \core_user\fields::for_name();
1497              $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
1498              $sql = "SELECT s.id, $userfields
1499                        FROM {workshop_submissions} s
1500                        JOIN {user} u ON (s.authorid = u.id)
1501                       WHERE s.example = 0 AND s.workshopid = ?";
1502              $params = array($cm->instance);
1503              $rs = $DB->get_recordset_sql($sql, $params);
1504  
1505              foreach ($rs as $submissionauthor) {
1506                  $title = s(fullname($submissionauthor)); // this is generally not unique...
1507                  $submissionauthors[$submissionauthor->id] = $title;
1508              }
1509              $rs->close();
1510  
1511              if (!isset($submissionauthors[$itemid])) {
1512                  // should not happen
1513                  return null;
1514              } else {
1515                  $topvisiblename = $submissionauthors[$itemid];
1516              }
1517          }
1518  
1519          $urlbase = $CFG->wwwroot . '/pluginfile.php';
1520          // do not allow manual modification of any files!
1521          return new file_info_stored($browser, $context, $storedfile, $urlbase, $topvisiblename, true, true, false, false);
1522      }
1523  
1524      if ($filearea === 'overallfeedback_content' or $filearea === 'overallfeedback_attachment') {
1525  
1526          if (!has_capability('mod/workshop:viewallassessments', $context)) {
1527              return null;
1528          }
1529  
1530          if (is_null($itemid)) {
1531              // No itemid (assessmentid) passed, display the list of all assessments.
1532              require_once($CFG->dirroot . '/mod/workshop/fileinfolib.php');
1533              return new workshop_file_info_overallfeedback_container($browser, $course, $cm, $context, $areas, $filearea);
1534          }
1535  
1536          // Make sure the user can see the particular assessment in separate groups mode.
1537          $gmode = groups_get_activity_groupmode($cm, $course);
1538          if ($gmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1539              // Check there is at least one common group with both the $USER
1540              // and the submission author.
1541              $sql = "SELECT 'x'
1542                        FROM {workshop_submissions} s
1543                        JOIN {user} a ON (a.id = s.authorid)
1544                        JOIN {groups_members} agm ON (a.id = agm.userid)
1545                        JOIN {user} u ON (u.id = ?)
1546                        JOIN {groups_members} ugm ON (u.id = ugm.userid)
1547                       WHERE s.example = 0 AND s.workshopid = ? AND s.id = ? AND agm.groupid = ugm.groupid";
1548              $params = array($USER->id, $cm->instance, $itemid);
1549              if (!$DB->record_exists_sql($sql, $params)) {
1550                  return null;
1551              }
1552          }
1553  
1554          // We are inside a particular assessment container.
1555          $filepath = is_null($filepath) ? '/' : $filepath;
1556          $filename = is_null($filename) ? '.' : $filename;
1557  
1558          if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, $itemid, $filepath, $filename)) {
1559              if ($filepath === '/' and $filename === '.') {
1560                  $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, $itemid);
1561              } else {
1562                  // Not found
1563                  return null;
1564              }
1565          }
1566  
1567          // Check to see if the user can manage files or is the owner.
1568          if (!has_capability('moodle/course:managefiles', $context) and $storedfile->get_userid() != $USER->id) {
1569              return null;
1570          }
1571  
1572          $urlbase = $CFG->wwwroot . '/pluginfile.php';
1573  
1574          // Do not allow manual modification of any files.
1575          return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
1576      }
1577  
1578      if ($filearea == 'instructauthors' or $filearea == 'instructreviewers' or $filearea == 'conclusion') {
1579          // always only itemid 0
1580  
1581          $filepath = is_null($filepath) ? '/' : $filepath;
1582          $filename = is_null($filename) ? '.' : $filename;
1583  
1584          $urlbase = $CFG->wwwroot.'/pluginfile.php';
1585          if (!$storedfile = $fs->get_file($context->id, 'mod_workshop', $filearea, 0, $filepath, $filename)) {
1586              if ($filepath === '/' and $filename === '.') {
1587                  $storedfile = new virtual_root_file($context->id, 'mod_workshop', $filearea, 0);
1588              } else {
1589                  // not found
1590                  return null;
1591              }
1592          }
1593          return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, true, false);
1594      }
1595  }
1596  
1597  ////////////////////////////////////////////////////////////////////////////////
1598  // Navigation API                                                             //
1599  ////////////////////////////////////////////////////////////////////////////////
1600  
1601  /**
1602   * Extends the global navigation tree by adding workshop nodes if there is a relevant content
1603   *
1604   * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly.
1605   *
1606   * @param navigation_node $navref An object representing the navigation tree node of the workshop module instance
1607   * @param stdClass $course
1608   * @param stdClass $module
1609   * @param cm_info $cm
1610   */
1611  function workshop_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) {
1612      global $CFG;
1613  
1614      if (has_capability('mod/workshop:submit', context_module::instance($cm->id))) {
1615          $url = new moodle_url('/mod/workshop/submission.php', array('cmid' => $cm->id));
1616          $mysubmission = $navref->add(get_string('mysubmission', 'workshop'), $url);
1617          $mysubmission->mainnavonly = true;
1618      }
1619  }
1620  
1621  /**
1622   * Extends the settings navigation with the Workshop settings
1623  
1624   * This function is called when the context for the page is a workshop module. This is not called by AJAX
1625   * so it is safe to rely on the $PAGE.
1626   *
1627   * @param settings_navigation $settingsnav {@link settings_navigation}
1628   * @param navigation_node $workshopnode {@link navigation_node}
1629   */
1630  function workshop_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $workshopnode=null) {
1631      if (has_capability('mod/workshop:editdimensions', $settingsnav->get_page()->cm->context)) {
1632          $url = new moodle_url('/mod/workshop/editform.php', array('cmid' => $settingsnav->get_page()->cm->id));
1633          $workshopnode->add(get_string('assessmentform', 'workshop'), $url,
1634          settings_navigation::TYPE_SETTING, null, 'workshopassessement');
1635      }
1636      if (has_capability('mod/workshop:allocate', $settingsnav->get_page()->cm->context)) {
1637          $url = new moodle_url('/mod/workshop/allocation.php', array('cmid' => $settingsnav->get_page()->cm->id));
1638          $workshopnode->add(get_string('submissionsallocation', 'workshop'), $url, settings_navigation::TYPE_SETTING);
1639      }
1640  }
1641  
1642  /**
1643   * Return a list of page types
1644   * @param string $pagetype current page type
1645   * @param stdClass $parentcontext Block's parent context
1646   * @param stdClass $currentcontext Current context of block
1647   */
1648  function workshop_page_type_list($pagetype, $parentcontext, $currentcontext) {
1649      $module_pagetype = array('mod-workshop-*'=>get_string('page-mod-workshop-x', 'workshop'));
1650      return $module_pagetype;
1651  }
1652  
1653  ////////////////////////////////////////////////////////////////////////////////
1654  // Calendar API                                                               //
1655  ////////////////////////////////////////////////////////////////////////////////
1656  
1657  /**
1658   * Updates the calendar events associated to the given workshop
1659   *
1660   * @param stdClass $workshop the workshop instance record
1661   * @param int $cmid course module id
1662   */
1663  function workshop_calendar_update(stdClass $workshop, $cmid) {
1664      global $DB;
1665  
1666      // get the currently registered events so that we can re-use their ids
1667      $currentevents = $DB->get_records('event', array('modulename' => 'workshop', 'instance' => $workshop->id));
1668  
1669      // the common properties for all events
1670      $base = new stdClass();
1671      $base->description  = format_module_intro('workshop', $workshop, $cmid, false);
1672      $base->format       = FORMAT_HTML;
1673      $base->courseid     = $workshop->course;
1674      $base->groupid      = 0;
1675      $base->userid       = 0;
1676      $base->modulename   = 'workshop';
1677      $base->instance     = $workshop->id;
1678      $base->visible      = instance_is_visible('workshop', $workshop);
1679      $base->timeduration = 0;
1680  
1681      if ($workshop->submissionstart) {
1682          $event = clone($base);
1683          $event->name = get_string('submissionstartevent', 'mod_workshop', $workshop->name);
1684          $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN;
1685          $event->type = empty($workshop->submissionend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1686          $event->timestart = $workshop->submissionstart;
1687          $event->timesort  = $workshop->submissionstart;
1688          if ($reusedevent = array_shift($currentevents)) {
1689              $event->id = $reusedevent->id;
1690          } else {
1691              // should not be set but just in case
1692              unset($event->id);
1693          }
1694          // update() will reuse a db record if the id field is set
1695          $eventobj = new calendar_event($event);
1696          $eventobj->update($event, false);
1697      }
1698  
1699      if ($workshop->submissionend) {
1700          $event = clone($base);
1701          $event->name = get_string('submissionendevent', 'mod_workshop', $workshop->name);
1702          $event->eventtype = WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE;
1703          $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1704          $event->timestart = $workshop->submissionend;
1705          $event->timesort  = $workshop->submissionend;
1706          if ($reusedevent = array_shift($currentevents)) {
1707              $event->id = $reusedevent->id;
1708          } else {
1709              // should not be set but just in case
1710              unset($event->id);
1711          }
1712          // update() will reuse a db record if the id field is set
1713          $eventobj = new calendar_event($event);
1714          $eventobj->update($event, false);
1715      }
1716  
1717      if ($workshop->assessmentstart) {
1718          $event = clone($base);
1719          $event->name = get_string('assessmentstartevent', 'mod_workshop', $workshop->name);
1720          $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN;
1721          $event->type      = empty($workshop->assessmentend) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
1722          $event->timestart = $workshop->assessmentstart;
1723          $event->timesort  = $workshop->assessmentstart;
1724          if ($reusedevent = array_shift($currentevents)) {
1725              $event->id = $reusedevent->id;
1726          } else {
1727              // should not be set but just in case
1728              unset($event->id);
1729          }
1730          // update() will reuse a db record if the id field is set
1731          $eventobj = new calendar_event($event);
1732          $eventobj->update($event, false);
1733      }
1734  
1735      if ($workshop->assessmentend) {
1736          $event = clone($base);
1737          $event->name = get_string('assessmentendevent', 'mod_workshop', $workshop->name);
1738          $event->eventtype = WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
1739          $event->type      = CALENDAR_EVENT_TYPE_ACTION;
1740          $event->timestart = $workshop->assessmentend;
1741          $event->timesort  = $workshop->assessmentend;
1742          if ($reusedevent = array_shift($currentevents)) {
1743              $event->id = $reusedevent->id;
1744          } else {
1745              // should not be set but just in case
1746              unset($event->id);
1747          }
1748          // update() will reuse a db record if the id field is set
1749          $eventobj = new calendar_event($event);
1750          $eventobj->update($event, false);
1751      }
1752  
1753      // delete any leftover events
1754      foreach ($currentevents as $oldevent) {
1755          $oldevent = calendar_event::load($oldevent);
1756          $oldevent->delete();
1757      }
1758  }
1759  
1760  /**
1761   * This function receives a calendar event and returns the action associated with it, or null if there is none.
1762   *
1763   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1764   * is not displayed on the block.
1765   *
1766   * @param calendar_event $event
1767   * @param \core_calendar\action_factory $factory
1768   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1769   * @return \core_calendar\local\event\entities\action_interface|null
1770   */
1771  function mod_workshop_core_calendar_provide_event_action(calendar_event $event,
1772          \core_calendar\action_factory $factory, int $userid = 0) {
1773      global $USER;
1774  
1775      if (!$userid) {
1776          $userid = $USER->id;
1777      }
1778  
1779      $cm = get_fast_modinfo($event->courseid, $userid)->instances['workshop'][$event->instance];
1780  
1781      if (!$cm->uservisible) {
1782          // The module is not visible to the user for any reason.
1783          return null;
1784      }
1785  
1786      $completion = new \completion_info($cm->get_course());
1787  
1788      $completiondata = $completion->get_data($cm, false, $userid);
1789  
1790      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
1791          return null;
1792      }
1793  
1794      return $factory->create_instance(
1795          get_string('viewworkshopsummary', 'workshop'),
1796          new \moodle_url('/mod/workshop/view.php', array('id' => $cm->id)),
1797          1,
1798          true
1799      );
1800  }
1801  
1802  /**
1803   * This function calculates the minimum and maximum cutoff values for the timestart of
1804   * the given event.
1805   *
1806   * It will return an array with two values, the first being the minimum cutoff value and
1807   * the second being the maximum cutoff value. Either or both values can be null, which
1808   * indicates there is no minimum or maximum, respectively.
1809   *
1810   * If a cutoff is required then the function must return an array containing the cutoff
1811   * timestamp and error string to display to the user if the cutoff value is violated.
1812   *
1813   * A minimum and maximum cutoff return value will look like:
1814   * [
1815   *     [1505704373, 'The date must be after this date'],
1816   *     [1506741172, 'The date must be before this date']
1817   * ]
1818   *
1819   * @param calendar_event $event The calendar event to get the time range for
1820   * @param stdClass $workshop The module instance to get the range from
1821   * @return array Returns an array with min and max date.
1822   */
1823  function mod_workshop_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $workshop) : array {
1824      $mindate = null;
1825      $maxdate = null;
1826  
1827      $phasesubmissionend = max($workshop->submissionstart, $workshop->submissionend);
1828      $phaseassessmentstart = min($workshop->assessmentstart, $workshop->assessmentend);
1829      if ($phaseassessmentstart == 0) {
1830          $phaseassessmentstart = max($workshop->assessmentstart, $workshop->assessmentend);
1831      }
1832  
1833      switch ($event->eventtype) {
1834          case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
1835              if (!empty($workshop->submissionend)) {
1836                  $maxdate = [
1837                      $workshop->submissionend - 1,   // The submissionstart and submissionend cannot be exactly the same.
1838                      get_string('submissionendbeforestart', 'mod_workshop')
1839                  ];
1840              } else if ($phaseassessmentstart) {
1841                  $maxdate = [
1842                      $phaseassessmentstart,
1843                      get_string('phasesoverlap', 'mod_workshop')
1844                  ];
1845              }
1846              break;
1847          case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
1848              if (!empty($workshop->submissionstart)) {
1849                  $mindate = [
1850                      $workshop->submissionstart + 1, // The submissionstart and submissionend cannot be exactly the same.
1851                      get_string('submissionendbeforestart', 'mod_workshop')
1852                  ];
1853              }
1854              if ($phaseassessmentstart) {
1855                  $maxdate = [
1856                      $phaseassessmentstart,
1857                      get_string('phasesoverlap', 'mod_workshop')
1858                  ];
1859              }
1860              break;
1861          case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
1862              if ($phasesubmissionend) {
1863                  $mindate = [
1864                      $phasesubmissionend,
1865                      get_string('phasesoverlap', 'mod_workshop')
1866                  ];
1867              }
1868              if (!empty($workshop->assessmentend)) {
1869                  $maxdate = [
1870                      $workshop->assessmentend - 1,   // The assessmentstart and assessmentend cannot be exactly the same.
1871                      get_string('assessmentendbeforestart', 'mod_workshop')
1872                  ];
1873              }
1874              break;
1875          case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE:
1876              if (!empty($workshop->assessmentstart)) {
1877                  $mindate = [
1878                      $workshop->assessmentstart + 1, // The assessmentstart and assessmentend cannot be exactly the same.
1879                      get_string('assessmentendbeforestart', 'mod_workshop')
1880                  ];
1881              } else if ($phasesubmissionend) {
1882                  $mindate = [
1883                      $phasesubmissionend,
1884                      get_string('phasesoverlap', 'mod_workshop')
1885                  ];
1886              }
1887              break;
1888      }
1889  
1890      return [$mindate, $maxdate];
1891  }
1892  
1893  /**
1894   * This function will update the workshop module according to the
1895   * event that has been modified.
1896   *
1897   * @param \calendar_event $event
1898   * @param stdClass $workshop The module instance to get the range from
1899   */
1900  function mod_workshop_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $workshop) : void {
1901      global $DB;
1902  
1903      $courseid = $event->courseid;
1904      $modulename = $event->modulename;
1905      $instanceid = $event->instance;
1906  
1907      // Something weird going on. The event is for a different module so
1908      // we should ignore it.
1909      if ($modulename != 'workshop') {
1910          return;
1911      }
1912  
1913      if ($workshop->id != $instanceid) {
1914          return;
1915      }
1916  
1917      if (!in_array(
1918              $event->eventtype,
1919              [
1920                  WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN,
1921                  WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE,
1922                  WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN,
1923                  WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE
1924              ]
1925      )) {
1926          return;
1927      }
1928  
1929      $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1930      $context = context_module::instance($coursemodule->id);
1931  
1932      // The user does not have the capability to modify this activity.
1933      if (!has_capability('moodle/course:manageactivities', $context)) {
1934          return;
1935      }
1936  
1937      $modified = false;
1938  
1939      switch ($event->eventtype) {
1940          case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
1941              if ($event->timestart != $workshop->submissionstart) {
1942                  $workshop->submissionstart = $event->timestart;
1943                  $modified = true;
1944              }
1945              break;
1946          case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
1947              if ($event->timestart != $workshop->submissionend) {
1948                  $workshop->submissionend = $event->timestart;
1949                  $modified = true;
1950              }
1951              break;
1952          case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
1953              if ($event->timestart != $workshop->assessmentstart) {
1954                  $workshop->assessmentstart = $event->timestart;
1955                  $modified = true;
1956              }
1957              break;
1958          case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE:
1959              if ($event->timestart != $workshop->assessmentend) {
1960                  $workshop->assessmentend = $event->timestart;
1961                  $modified = true;
1962              }
1963              break;
1964      }
1965  
1966      if ($modified) {
1967          $workshop->timemodified = time();
1968          // Persist the assign instance changes.
1969          $DB->update_record('workshop', $workshop);
1970          $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
1971          $event->trigger();
1972      }
1973  }
1974  
1975  ////////////////////////////////////////////////////////////////////////////////
1976  // Course reset API                                                           //
1977  ////////////////////////////////////////////////////////////////////////////////
1978  
1979  /**
1980   * Extends the course reset form with workshop specific settings.
1981   *
1982   * @param MoodleQuickForm $mform
1983   */
1984  function workshop_reset_course_form_definition($mform) {
1985  
1986      $mform->addElement('header', 'workshopheader', get_string('modulenameplural', 'mod_workshop'));
1987  
1988      $mform->addElement('advcheckbox', 'reset_workshop_submissions', get_string('resetsubmissions', 'mod_workshop'));
1989      $mform->addHelpButton('reset_workshop_submissions', 'resetsubmissions', 'mod_workshop');
1990  
1991      $mform->addElement('advcheckbox', 'reset_workshop_assessments', get_string('resetassessments', 'mod_workshop'));
1992      $mform->addHelpButton('reset_workshop_assessments', 'resetassessments', 'mod_workshop');
1993      $mform->disabledIf('reset_workshop_assessments', 'reset_workshop_submissions', 'checked');
1994  
1995      $mform->addElement('advcheckbox', 'reset_workshop_phase', get_string('resetphase', 'mod_workshop'));
1996      $mform->addHelpButton('reset_workshop_phase', 'resetphase', 'mod_workshop');
1997  }
1998  
1999  /**
2000   * Provides default values for the workshop settings in the course reset form.
2001   *
2002   * @param stdClass $course The course to be reset.
2003   */
2004  function workshop_reset_course_form_defaults(stdClass $course) {
2005  
2006      $defaults = array(
2007          'reset_workshop_submissions'    => 1,
2008          'reset_workshop_assessments'    => 1,
2009          'reset_workshop_phase'          => 1,
2010      );
2011  
2012      return $defaults;
2013  }
2014  
2015  /**
2016   * Performs the reset of all workshop instances in the course.
2017   *
2018   * @param stdClass $data The actual course reset settings.
2019   * @return array List of results, each being array[(string)component, (string)item, (string)error]
2020   */
2021  function workshop_reset_userdata(stdClass $data) {
2022      global $CFG, $DB;
2023  
2024      // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
2025      // See MDL-9367.
2026      shift_course_mod_dates('workshop', array('submissionstart', 'submissionend', 'assessmentstart', 'assessmentend'),
2027          $data->timeshift, $data->courseid);
2028      $status = array();
2029      $status[] = array('component' => get_string('modulenameplural', 'workshop'), 'item' => get_string('datechanged'),
2030          'error' => false);
2031  
2032      if (empty($data->reset_workshop_submissions)
2033              and empty($data->reset_workshop_assessments)
2034              and empty($data->reset_workshop_phase) ) {
2035          // Nothing to do here.
2036          return $status;
2037      }
2038  
2039      $workshoprecords = $DB->get_records('workshop', array('course' => $data->courseid));
2040  
2041      if (empty($workshoprecords)) {
2042          // What a boring course - no workshops here!
2043          return $status;
2044      }
2045  
2046      require_once($CFG->dirroot . '/mod/workshop/locallib.php');
2047  
2048      $course = $DB->get_record('course', array('id' => $data->courseid), '*', MUST_EXIST);
2049  
2050      foreach ($workshoprecords as $workshoprecord) {
2051          $cm = get_coursemodule_from_instance('workshop', $workshoprecord->id, $course->id, false, MUST_EXIST);
2052          $workshop = new workshop($workshoprecord, $cm, $course);
2053          $status = array_merge($status, $workshop->reset_userdata($data));
2054      }
2055  
2056      return $status;
2057  }
2058  
2059  /**
2060   * Get icon mapping for font-awesome.
2061   */
2062  function mod_workshop_get_fontawesome_icon_map() {
2063      return [
2064          'mod_workshop:userplan/task-info' => 'fa-info text-info',
2065          'mod_workshop:userplan/task-todo' => 'fa-square-o',
2066          'mod_workshop:userplan/task-done' => 'fa-check text-success',
2067          'mod_workshop:userplan/task-fail' => 'fa-remove text-danger',
2068      ];
2069  }
2070  
2071  /**
2072   * Check if the module has any update that affects the current user since a given time.
2073   *
2074   * @param  cm_info $cm course module data
2075   * @param  int $from the time to check updates from
2076   * @param  array $filter  if we need to check only specific updates
2077   * @return stdClass an object with the different type of areas indicating if they were updated or not
2078   * @since Moodle 3.4
2079   */
2080  function workshop_check_updates_since(cm_info $cm, $from, $filter = array()) {
2081      global $DB, $USER;
2082  
2083      $updates = course_check_module_updates_since($cm, $from, array('instructauthors', 'instructreviewers', 'conclusion'), $filter);
2084  
2085      // Check if there are new submissions, assessments or assessments grades in the workshop.
2086      $updates->submissions = (object) array('updated' => false);
2087      $updates->assessments = (object) array('updated' => false);
2088      $updates->assessmentgrades = (object) array('updated' => false);
2089  
2090      $select = 'workshopid = ? AND authorid = ? AND (timecreated > ? OR timegraded > ? OR timemodified > ?)';
2091      $params = array($cm->instance, $USER->id, $from, $from, $from);
2092      $submissions = $DB->get_records_select('workshop_submissions', $select, $params, '', 'id');
2093      if (!empty($submissions)) {
2094          $updates->submissions->updated = true;
2095          $updates->submissions->itemids = array_keys($submissions);
2096      }
2097  
2098      // Get assessments updates (both submissions reviewed by me or reviews by others).
2099      $select = "SELECT a.id
2100                   FROM {workshop_assessments} a
2101                   JOIN {workshop_submissions} s ON a.submissionid = s.id
2102                   WHERE s.workshopid = ? AND (a.timecreated > ? OR a.timemodified > ?) AND (s.authorid = ? OR a.reviewerid = ?)";
2103      $params = array($cm->instance, $from, $from, $USER->id, $USER->id);
2104      $assessments = $DB->get_records_sql($select, $params);
2105      if (!empty($assessments)) {
2106          $updates->assessments->updated = true;
2107          $updates->assessments->itemids = array_keys($assessments);
2108      }
2109      // Finally assessment aggregated grades.
2110      $select = 'workshopid = ? AND userid = ? AND timegraded > ?';
2111      $params = array($cm->instance, $USER->id, $from);
2112      $assessmentgrades = $DB->get_records_select('workshop_aggregations', $select, $params, '', 'id');
2113      if (!empty($assessmentgrades)) {
2114          $updates->assessmentgrades->updated = true;
2115          $updates->assessmentgrades->itemids = array_keys($assessmentgrades);
2116      }
2117  
2118      // Now, teachers should see other students updates.
2119      $canviewallsubmissions = has_capability('mod/workshop:viewallsubmissions', $cm->context);
2120      $canviewallassessments = has_capability('mod/workshop:viewallassessments', $cm->context);
2121      if ($canviewallsubmissions || $canviewallassessments) {
2122  
2123          $insql = '';
2124          $inparams = array();
2125          // To filter by users in my groups when separated groups are forced.
2126          if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
2127              $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
2128              if (empty($groupusers)) {
2129                  return $updates;
2130              }
2131              list($insql, $inparams) = $DB->get_in_or_equal($groupusers);
2132          }
2133  
2134          if ($canviewallsubmissions) {
2135              $updates->usersubmissions = (object) array('updated' => false);
2136              $select = 'workshopid = ? AND (timecreated > ? OR timegraded > ? OR timemodified > ?)';
2137              $params = array($cm->instance, $from, $from, $from);
2138              if (!empty($insql)) {
2139                  $select .= " AND authorid $insql";
2140                  $params = array_merge($params, $inparams);
2141              }
2142              $usersubmissions = $DB->get_records_select('workshop_submissions', $select, $params, '', 'id');
2143              if (!empty($usersubmissions)) {
2144                  $updates->usersubmissions->updated = true;
2145                  $updates->usersubmissions->itemids = array_keys($usersubmissions);
2146              }
2147          }
2148  
2149          if ($canviewallassessments) {
2150              $updates->userassessments = (object) array('updated' => false);
2151              $select = "SELECT a.id
2152                           FROM {workshop_assessments} a
2153                           JOIN {workshop_submissions} s ON a.submissionid = s.id
2154                          WHERE s.workshopid = ? AND (a.timecreated > ? OR a.timemodified > ?)";
2155              $params = array($cm->instance, $from, $from);
2156              if (!empty($insql)) {
2157                  $select .= " AND s.reviewerid $insql";
2158                  $params = array_merge($params, $inparams);
2159              }
2160              $userassessments = $DB->get_records_sql($select, $params);
2161              if (!empty($userassessments)) {
2162                  $updates->userassessments->updated = true;
2163                  $updates->userassessments->itemids = array_keys($userassessments);
2164              }
2165  
2166              $updates->userassessmentgrades = (object) array('updated' => false);
2167              $select = 'workshopid = ? AND timegraded > ?';
2168              $params = array($cm->instance, $USER->id);
2169              if (!empty($insql)) {
2170                  $select .= " AND userid $insql";
2171                  $params = array_merge($params, $inparams);
2172              }
2173              $userassessmentgrades = $DB->get_records_select('workshop_aggregations', $select, $params, '', 'id');
2174              if (!empty($userassessmentgrades)) {
2175                  $updates->userassessmentgrades->updated = true;
2176                  $updates->userassessmentgrades->itemids = array_keys($userassessmentgrades);
2177              }
2178          }
2179      }
2180      return $updates;
2181  }
2182  
2183  /**
2184   * Given an array with a file path, it returns the itemid and the filepath for the defined filearea.
2185   *
2186   * @param  string $filearea The filearea.
2187   * @param  array  $args The path (the part after the filearea and before the filename).
2188   * @return array|null The itemid and the filepath inside the $args path, for the defined filearea.
2189   */
2190  function mod_workshop_get_path_from_pluginfile(string $filearea, array $args) : ?array {
2191      if ($filearea !== 'instructauthors' && $filearea !== 'instructreviewers' && $filearea !== 'conclusion') {
2192          return null;
2193      }
2194  
2195      // Workshop only has empty itemid for some of the fileareas.
2196      array_shift($args);
2197  
2198      // Get the filepath.
2199      if (empty($args)) {
2200          $filepath = '/';
2201      } else {
2202          $filepath = '/' . implode('/', $args) . '/';
2203      }
2204  
2205      return [
2206          'itemid' => 0,
2207          'filepath' => $filepath,
2208      ];
2209  }
2210  
2211  /**
2212   * Add a get_coursemodule_info function in case any feedback type wants to add 'extra' information
2213   * for the course (see resource).
2214   *
2215   * Given a course_module object, this function returns any "extra" information that may be needed
2216   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
2217   *
2218   * @param stdClass $coursemodule The coursemodule object (record).
2219   * @return cached_cm_info|false An object on information that the courses will know about (most noticeably, an icon).
2220   */
2221  function workshop_get_coursemodule_info($coursemodule) {
2222      global $DB;
2223  
2224      $dbparams = ['id' => $coursemodule->instance];
2225      $fields = 'id, name, intro, introformat, submissionstart, submissionend, assessmentstart, assessmentend';
2226      if (!$workshop = $DB->get_record('workshop', $dbparams, $fields)) {
2227          return false;
2228      }
2229  
2230      $result = new cached_cm_info();
2231      $result->name = $workshop->name;
2232  
2233      if ($coursemodule->showdescription) {
2234          // Convert intro to html. Do not filter cached version, filters run at display time.
2235          $result->content = format_module_intro('workshop', $workshop, $coursemodule->id, false);
2236      }
2237  
2238      // Populate some other values that can be used in calendar or on dashboard.
2239      if ($workshop->submissionstart) {
2240          $result->customdata['submissionstart'] = $workshop->submissionstart;
2241      }
2242      if ($workshop->submissionend) {
2243          $result->customdata['submissionend'] = $workshop->submissionend;
2244      }
2245      if ($workshop->assessmentstart) {
2246          $result->customdata['assessmentstart'] = $workshop->assessmentstart;
2247      }
2248      if ($workshop->assessmentend) {
2249          $result->customdata['assessmentend'] = $workshop->assessmentend;
2250      }
2251  
2252      return $result;
2253  }
2254  
2255  /**
2256   * Callback to fetch the activity event type lang string.
2257   *
2258   * @param string $eventtype The event type.
2259   * @return lang_string The event type lang string.
2260   */
2261  function mod_workshop_core_calendar_get_event_action_string($eventtype): string {
2262      $modulename = get_string('modulename', 'workshop');
2263  
2264      switch ($eventtype) {
2265          case WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN:
2266              $identifier = 'submissionstartevent';
2267              break;
2268          case WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE:
2269              $identifier = 'submissionendevent';
2270              break;
2271          case WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN:
2272              $identifier = 'assessmentstartevent';
2273              break;
2274          case WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE;
2275              $identifier = 'assessmentendevent';
2276              break;
2277          default:
2278              return get_string('requiresaction', 'calendar', $modulename);
2279      }
2280  
2281      return get_string($identifier, 'workshop', $modulename);
2282  }