Search moodle.org's
Developer Documentation

See Release Notes

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

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

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