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