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