PageRenderTime 66ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/workshop/lib.php

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