[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Library of internal classes and functions for module workshop 19 * 20 * All the workshop specific functions, needed to implement the module 21 * logic, should go to here. Instead of having bunch of function named 22 * workshop_something() taking the workshop instance as the first 23 * parameter, we use a class workshop that provides all methods. 24 * 25 * @package mod_workshop 26 * @copyright 2009 David Mudrak <david.mudrak@gmail.com> 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 require_once (__DIR__.'/lib.php'); // we extend this library here 33 require_once($CFG->libdir . '/gradelib.php'); // we use some rounding and comparing routines here 34 require_once($CFG->libdir . '/filelib.php'); 35 36 /** 37 * Full-featured workshop API 38 * 39 * This wraps the workshop database record with a set of methods that are called 40 * from the module itself. The class should be initialized right after you get 41 * $workshop, $cm and $course records at the begining of the script. 42 */ 43 class workshop { 44 45 /** error status of the {@link self::add_allocation()} */ 46 const ALLOCATION_EXISTS = -9999; 47 48 /** the internal code of the workshop phases as are stored in the database */ 49 const PHASE_SETUP = 10; 50 const PHASE_SUBMISSION = 20; 51 const PHASE_ASSESSMENT = 30; 52 const PHASE_EVALUATION = 40; 53 const PHASE_CLOSED = 50; 54 55 /** the internal code of the examples modes as are stored in the database */ 56 const EXAMPLES_VOLUNTARY = 0; 57 const EXAMPLES_BEFORE_SUBMISSION = 1; 58 const EXAMPLES_BEFORE_ASSESSMENT = 2; 59 60 /** @var cm_info course module record */ 61 public $cm; 62 63 /** @var stdclass course record */ 64 public $course; 65 66 /** @var stdclass context object */ 67 public $context; 68 69 /** @var int workshop instance identifier */ 70 public $id; 71 72 /** @var string workshop activity name */ 73 public $name; 74 75 /** @var string introduction or description of the activity */ 76 public $intro; 77 78 /** @var int format of the {@link $intro} */ 79 public $introformat; 80 81 /** @var string instructions for the submission phase */ 82 public $instructauthors; 83 84 /** @var int format of the {@link $instructauthors} */ 85 public $instructauthorsformat; 86 87 /** @var string instructions for the assessment phase */ 88 public $instructreviewers; 89 90 /** @var int format of the {@link $instructreviewers} */ 91 public $instructreviewersformat; 92 93 /** @var int timestamp of when the module was modified */ 94 public $timemodified; 95 96 /** @var int current phase of workshop, for example {@link workshop::PHASE_SETUP} */ 97 public $phase; 98 99 /** @var bool optional feature: students practise evaluating on example submissions from teacher */ 100 public $useexamples; 101 102 /** @var bool optional feature: students perform peer assessment of others' work (deprecated, consider always enabled) */ 103 public $usepeerassessment; 104 105 /** @var bool optional feature: students perform self assessment of their own work */ 106 public $useselfassessment; 107 108 /** @var float number (10, 5) unsigned, the maximum grade for submission */ 109 public $grade; 110 111 /** @var float number (10, 5) unsigned, the maximum grade for assessment */ 112 public $gradinggrade; 113 114 /** @var string type of the current grading strategy used in this workshop, for example 'accumulative' */ 115 public $strategy; 116 117 /** @var string the name of the evaluation plugin to use for grading grades calculation */ 118 public $evaluation; 119 120 /** @var int number of digits that should be shown after the decimal point when displaying grades */ 121 public $gradedecimals; 122 123 /** @var int number of allowed submission attachments and the files embedded into submission */ 124 public $nattachments; 125 126 /** @var string list of allowed file types that are allowed to be embedded into submission */ 127 public $submissionfiletypes = null; 128 129 /** @var bool allow submitting the work after the deadline */ 130 public $latesubmissions; 131 132 /** @var int maximum size of the one attached file in bytes */ 133 public $maxbytes; 134 135 /** @var int mode of example submissions support, for example {@link workshop::EXAMPLES_VOLUNTARY} */ 136 public $examplesmode; 137 138 /** @var int if greater than 0 then the submission is not allowed before this timestamp */ 139 public $submissionstart; 140 141 /** @var int if greater than 0 then the submission is not allowed after this timestamp */ 142 public $submissionend; 143 144 /** @var int if greater than 0 then the peer assessment is not allowed before this timestamp */ 145 public $assessmentstart; 146 147 /** @var int if greater than 0 then the peer assessment is not allowed after this timestamp */ 148 public $assessmentend; 149 150 /** @var bool automatically switch to the assessment phase after the submissions deadline */ 151 public $phaseswitchassessment; 152 153 /** @var string conclusion text to be displayed at the end of the activity */ 154 public $conclusion; 155 156 /** @var int format of the conclusion text */ 157 public $conclusionformat; 158 159 /** @var int the mode of the overall feedback */ 160 public $overallfeedbackmode; 161 162 /** @var int maximum number of overall feedback attachments */ 163 public $overallfeedbackfiles; 164 165 /** @var string list of allowed file types that can be attached to the overall feedback */ 166 public $overallfeedbackfiletypes = null; 167 168 /** @var int maximum size of one file attached to the overall feedback */ 169 public $overallfeedbackmaxbytes; 170 171 /** 172 * @var workshop_strategy grading strategy instance 173 * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()} 174 */ 175 protected $strategyinstance = null; 176 177 /** 178 * @var workshop_evaluation grading evaluation instance 179 * Do not use directly, get the instance using {@link workshop::grading_evaluation_instance()} 180 */ 181 protected $evaluationinstance = null; 182 183 /** 184 * Initializes the workshop API instance using the data from DB 185 * 186 * Makes deep copy of all passed records properties. 187 * 188 * For unit testing only, $cm and $course may be set to null. This is so that 189 * you can test without having any real database objects if you like. Not all 190 * functions will work in this situation. 191 * 192 * @param stdClass $dbrecord Workshop instance data from {workshop} table 193 * @param stdClass|cm_info $cm Course module record 194 * @param stdClass $course Course record from {course} table 195 * @param stdClass $context The context of the workshop instance 196 */ 197 public function __construct(stdclass $dbrecord, $cm, $course, stdclass $context=null) { 198 foreach ($dbrecord as $field => $value) { 199 if (property_exists('workshop', $field)) { 200 $this->{$field} = $value; 201 } 202 } 203 if (is_null($cm) || is_null($course)) { 204 throw new coding_exception('Must specify $cm and $course'); 205 } 206 $this->course = $course; 207 if ($cm instanceof cm_info) { 208 $this->cm = $cm; 209 } else { 210 $modinfo = get_fast_modinfo($course); 211 $this->cm = $modinfo->get_cm($cm->id); 212 } 213 if (is_null($context)) { 214 $this->context = context_module::instance($this->cm->id); 215 } else { 216 $this->context = $context; 217 } 218 } 219 220 //////////////////////////////////////////////////////////////////////////////// 221 // Static methods // 222 //////////////////////////////////////////////////////////////////////////////// 223 224 /** 225 * Return list of available allocation methods 226 * 227 * @return array Array ['string' => 'string'] of localized allocation method names 228 */ 229 public static function installed_allocators() { 230 $installed = core_component::get_plugin_list('workshopallocation'); 231 $forms = array(); 232 foreach ($installed as $allocation => $allocationpath) { 233 if (file_exists($allocationpath . '/lib.php')) { 234 $forms[$allocation] = get_string('pluginname', 'workshopallocation_' . $allocation); 235 } 236 } 237 // usability - make sure that manual allocation appears the first 238 if (isset($forms['manual'])) { 239 $m = array('manual' => $forms['manual']); 240 unset($forms['manual']); 241 $forms = array_merge($m, $forms); 242 } 243 return $forms; 244 } 245 246 /** 247 * Returns an array of options for the editors that are used for submitting and assessing instructions 248 * 249 * @param stdClass $context 250 * @uses EDITOR_UNLIMITED_FILES hard-coded value for the 'maxfiles' option 251 * @return array 252 */ 253 public static function instruction_editors_options(stdclass $context) { 254 return array('subdirs' => 1, 'maxbytes' => 0, 'maxfiles' => -1, 255 'changeformat' => 1, 'context' => $context, 'noclean' => 1, 'trusttext' => 0); 256 } 257 258 /** 259 * Given the percent and the total, returns the number 260 * 261 * @param float $percent from 0 to 100 262 * @param float $total the 100% value 263 * @return float 264 */ 265 public static function percent_to_value($percent, $total) { 266 if ($percent < 0 or $percent > 100) { 267 throw new coding_exception('The percent can not be less than 0 or higher than 100'); 268 } 269 270 return $total * $percent / 100; 271 } 272 273 /** 274 * Returns an array of numeric values that can be used as maximum grades 275 * 276 * @return array Array of integers 277 */ 278 public static function available_maxgrades_list() { 279 $grades = array(); 280 for ($i=100; $i>=0; $i--) { 281 $grades[$i] = $i; 282 } 283 return $grades; 284 } 285 286 /** 287 * Returns the localized list of supported examples modes 288 * 289 * @return array 290 */ 291 public static function available_example_modes_list() { 292 $options = array(); 293 $options[self::EXAMPLES_VOLUNTARY] = get_string('examplesvoluntary', 'workshop'); 294 $options[self::EXAMPLES_BEFORE_SUBMISSION] = get_string('examplesbeforesubmission', 'workshop'); 295 $options[self::EXAMPLES_BEFORE_ASSESSMENT] = get_string('examplesbeforeassessment', 'workshop'); 296 return $options; 297 } 298 299 /** 300 * Returns the list of available grading strategy methods 301 * 302 * @return array ['string' => 'string'] 303 */ 304 public static function available_strategies_list() { 305 $installed = core_component::get_plugin_list('workshopform'); 306 $forms = array(); 307 foreach ($installed as $strategy => $strategypath) { 308 if (file_exists($strategypath . '/lib.php')) { 309 $forms[$strategy] = get_string('pluginname', 'workshopform_' . $strategy); 310 } 311 } 312 return $forms; 313 } 314 315 /** 316 * Returns the list of available grading evaluation methods 317 * 318 * @return array of (string)name => (string)localized title 319 */ 320 public static function available_evaluators_list() { 321 $evals = array(); 322 foreach (core_component::get_plugin_list_with_file('workshopeval', 'lib.php', false) as $eval => $evalpath) { 323 $evals[$eval] = get_string('pluginname', 'workshopeval_' . $eval); 324 } 325 return $evals; 326 } 327 328 /** 329 * Return an array of possible values of assessment dimension weight 330 * 331 * @return array of integers 0, 1, 2, ..., 16 332 */ 333 public static function available_dimension_weights_list() { 334 $weights = array(); 335 for ($i=16; $i>=0; $i--) { 336 $weights[$i] = $i; 337 } 338 return $weights; 339 } 340 341 /** 342 * Return an array of possible values of assessment weight 343 * 344 * Note there is no real reason why the maximum value here is 16. It used to be 10 in 345 * workshop 1.x and I just decided to use the same number as in the maximum weight of 346 * a single assessment dimension. 347 * The value looks reasonable, though. Teachers who would want to assign themselves 348 * higher weight probably do not want peer assessment really... 349 * 350 * @return array of integers 0, 1, 2, ..., 16 351 */ 352 public static function available_assessment_weights_list() { 353 $weights = array(); 354 for ($i=16; $i>=0; $i--) { 355 $weights[$i] = $i; 356 } 357 return $weights; 358 } 359 360 /** 361 * Helper function returning the greatest common divisor 362 * 363 * @param int $a 364 * @param int $b 365 * @return int 366 */ 367 public static function gcd($a, $b) { 368 return ($b == 0) ? ($a):(self::gcd($b, $a % $b)); 369 } 370 371 /** 372 * Helper function returning the least common multiple 373 * 374 * @param int $a 375 * @param int $b 376 * @return int 377 */ 378 public static function lcm($a, $b) { 379 return ($a / self::gcd($a,$b)) * $b; 380 } 381 382 /** 383 * Returns an object suitable for strings containing dates/times 384 * 385 * The returned object contains properties date, datefullshort, datetime, ... containing the given 386 * timestamp formatted using strftimedate, strftimedatefullshort, strftimedatetime, ... from the 387 * current lang's langconfig.php 388 * This allows translators and administrators customize the date/time format. 389 * 390 * @param int $timestamp the timestamp in UTC 391 * @return stdclass 392 */ 393 public static function timestamp_formats($timestamp) { 394 $formats = array('date', 'datefullshort', 'dateshort', 'datetime', 395 'datetimeshort', 'daydate', 'daydatetime', 'dayshort', 'daytime', 396 'monthyear', 'recent', 'recentfull', 'time'); 397 $a = new stdclass(); 398 foreach ($formats as $format) { 399 $a->{$format} = userdate($timestamp, get_string('strftime'.$format, 'langconfig')); 400 } 401 $day = userdate($timestamp, '%Y%m%d', 99, false); 402 $today = userdate(time(), '%Y%m%d', 99, false); 403 $tomorrow = userdate(time() + DAYSECS, '%Y%m%d', 99, false); 404 $yesterday = userdate(time() - DAYSECS, '%Y%m%d', 99, false); 405 $distance = (int)round(abs(time() - $timestamp) / DAYSECS); 406 if ($day == $today) { 407 $a->distanceday = get_string('daystoday', 'workshop'); 408 } elseif ($day == $yesterday) { 409 $a->distanceday = get_string('daysyesterday', 'workshop'); 410 } elseif ($day < $today) { 411 $a->distanceday = get_string('daysago', 'workshop', $distance); 412 } elseif ($day == $tomorrow) { 413 $a->distanceday = get_string('daystomorrow', 'workshop'); 414 } elseif ($day > $today) { 415 $a->distanceday = get_string('daysleft', 'workshop', $distance); 416 } 417 return $a; 418 } 419 420 /** 421 * Converts the argument into an array (list) of file extensions. 422 * 423 * The list can be separated by whitespace, end of lines, commas colons and semicolons. 424 * Empty values are not returned. Values are converted to lowercase. 425 * Duplicates are removed. Glob evaluation is not supported. 426 * 427 * @param string|array $extensions list of file extensions 428 * @return array of strings 429 */ 430 public static function normalize_file_extensions($extensions) { 431 432 if ($extensions === '') { 433 return array(); 434 } 435 436 if (!is_array($extensions)) { 437 $extensions = preg_split('/[\s,;:"\']+/', $extensions, null, PREG_SPLIT_NO_EMPTY); 438 } 439 440 foreach ($extensions as $i => $extension) { 441 $extension = str_replace('*.', '', $extension); 442 $extension = strtolower($extension); 443 $extension = ltrim($extension, '.'); 444 $extension = trim($extension); 445 $extensions[$i] = $extension; 446 } 447 448 foreach ($extensions as $i => $extension) { 449 if (strpos($extension, '*') !== false or strpos($extension, '?') !== false) { 450 unset($extensions[$i]); 451 } 452 } 453 454 $extensions = array_filter($extensions, 'strlen'); 455 $extensions = array_keys(array_flip($extensions)); 456 457 foreach ($extensions as $i => $extension) { 458 $extensions[$i] = '.'.$extension; 459 } 460 461 return $extensions; 462 } 463 464 /** 465 * Cleans the user provided list of file extensions. 466 * 467 * @param string $extensions 468 * @return string 469 */ 470 public static function clean_file_extensions($extensions) { 471 472 $extensions = self::normalize_file_extensions($extensions); 473 474 foreach ($extensions as $i => $extension) { 475 $extensions[$i] = ltrim($extension, '.'); 476 } 477 478 return implode(', ', $extensions); 479 } 480 481 /** 482 * Check given file types and return invalid/unknown ones. 483 * 484 * Empty whitelist is interpretted as "any extension is valid". 485 * 486 * @param string|array $extensions list of file extensions 487 * @param string|array $whitelist list of valid extensions 488 * @return array list of invalid extensions not found in the whitelist 489 */ 490 public static function invalid_file_extensions($extensions, $whitelist) { 491 492 $extensions = self::normalize_file_extensions($extensions); 493 $whitelist = self::normalize_file_extensions($whitelist); 494 495 if (empty($extensions) or empty($whitelist)) { 496 return array(); 497 } 498 499 // Return those items from $extensions that are not present in $whitelist. 500 return array_keys(array_diff_key(array_flip($extensions), array_flip($whitelist))); 501 } 502 503 /** 504 * Is the file have allowed to be uploaded to the workshop? 505 * 506 * Empty whitelist is interpretted as "any file type is allowed" rather 507 * than "no file can be uploaded". 508 * 509 * @param string $filename the file name 510 * @param string|array $whitelist list of allowed file extensions 511 * @return false 512 */ 513 public static function is_allowed_file_type($filename, $whitelist) { 514 515 $whitelist = self::normalize_file_extensions($whitelist); 516 517 if (empty($whitelist)) { 518 return true; 519 } 520 521 $haystack = strrev(trim(strtolower($filename))); 522 523 foreach ($whitelist as $extension) { 524 if (strpos($haystack, strrev($extension)) === 0) { 525 // The file name ends with the extension. 526 return true; 527 } 528 } 529 530 return false; 531 } 532 533 //////////////////////////////////////////////////////////////////////////////// 534 // Workshop API // 535 //////////////////////////////////////////////////////////////////////////////// 536 537 /** 538 * Fetches all enrolled users with the capability mod/workshop:submit in the current workshop 539 * 540 * The returned objects contain properties required by user_picture and are ordered by lastname, firstname. 541 * Only users with the active enrolment are returned. 542 * 543 * @param bool $musthavesubmission if true, return only users who have already submitted 544 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 545 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set) 546 * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set) 547 * @return array array[userid] => stdClass 548 */ 549 public function get_potential_authors($musthavesubmission=true, $groupid=0, $limitfrom=0, $limitnum=0) { 550 global $DB; 551 552 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid); 553 554 if (empty($sql)) { 555 return array(); 556 } 557 558 list($sort, $sortparams) = users_order_by_sql('tmp'); 559 $sql = "SELECT * 560 FROM ($sql) tmp 561 ORDER BY $sort"; 562 563 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum); 564 } 565 566 /** 567 * Returns the total number of users that would be fetched by {@link self::get_potential_authors()} 568 * 569 * @param bool $musthavesubmission if true, count only users who have already submitted 570 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 571 * @return int 572 */ 573 public function count_potential_authors($musthavesubmission=true, $groupid=0) { 574 global $DB; 575 576 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid); 577 578 if (empty($sql)) { 579 return 0; 580 } 581 582 $sql = "SELECT COUNT(*) 583 FROM ($sql) tmp"; 584 585 return $DB->count_records_sql($sql, $params); 586 } 587 588 /** 589 * Fetches all enrolled users with the capability mod/workshop:peerassess in the current workshop 590 * 591 * The returned objects contain properties required by user_picture and are ordered by lastname, firstname. 592 * Only users with the active enrolment are returned. 593 * 594 * @param bool $musthavesubmission if true, return only users who have already submitted 595 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 596 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set) 597 * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set) 598 * @return array array[userid] => stdClass 599 */ 600 public function get_potential_reviewers($musthavesubmission=false, $groupid=0, $limitfrom=0, $limitnum=0) { 601 global $DB; 602 603 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid); 604 605 if (empty($sql)) { 606 return array(); 607 } 608 609 list($sort, $sortparams) = users_order_by_sql('tmp'); 610 $sql = "SELECT * 611 FROM ($sql) tmp 612 ORDER BY $sort"; 613 614 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum); 615 } 616 617 /** 618 * Returns the total number of users that would be fetched by {@link self::get_potential_reviewers()} 619 * 620 * @param bool $musthavesubmission if true, count only users who have already submitted 621 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 622 * @return int 623 */ 624 public function count_potential_reviewers($musthavesubmission=false, $groupid=0) { 625 global $DB; 626 627 list($sql, $params) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid); 628 629 if (empty($sql)) { 630 return 0; 631 } 632 633 $sql = "SELECT COUNT(*) 634 FROM ($sql) tmp"; 635 636 return $DB->count_records_sql($sql, $params); 637 } 638 639 /** 640 * Fetches all enrolled users that are authors or reviewers (or both) in the current workshop 641 * 642 * The returned objects contain properties required by user_picture and are ordered by lastname, firstname. 643 * Only users with the active enrolment are returned. 644 * 645 * @see self::get_potential_authors() 646 * @see self::get_potential_reviewers() 647 * @param bool $musthavesubmission if true, return only users who have already submitted 648 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 649 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set) 650 * @param int $limitnum return a subset containing this number of records (optional, required if $limitfrom is set) 651 * @return array array[userid] => stdClass 652 */ 653 public function get_participants($musthavesubmission=false, $groupid=0, $limitfrom=0, $limitnum=0) { 654 global $DB; 655 656 list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid); 657 658 if (empty($sql)) { 659 return array(); 660 } 661 662 list($sort, $sortparams) = users_order_by_sql('tmp'); 663 $sql = "SELECT * 664 FROM ($sql) tmp 665 ORDER BY $sort"; 666 667 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum); 668 } 669 670 /** 671 * Returns the total number of records that would be returned by {@link self::get_participants()} 672 * 673 * @param bool $musthavesubmission if true, return only users who have already submitted 674 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 675 * @return int 676 */ 677 public function count_participants($musthavesubmission=false, $groupid=0) { 678 global $DB; 679 680 list($sql, $params) = $this->get_participants_sql($musthavesubmission, $groupid); 681 682 if (empty($sql)) { 683 return 0; 684 } 685 686 $sql = "SELECT COUNT(*) 687 FROM ($sql) tmp"; 688 689 return $DB->count_records_sql($sql, $params); 690 } 691 692 /** 693 * Checks if the given user is an actively enrolled participant in the workshop 694 * 695 * @param int $userid, defaults to the current $USER 696 * @return boolean 697 */ 698 public function is_participant($userid=null) { 699 global $USER, $DB; 700 701 if (is_null($userid)) { 702 $userid = $USER->id; 703 } 704 705 list($sql, $params) = $this->get_participants_sql(); 706 707 if (empty($sql)) { 708 return false; 709 } 710 711 $sql = "SELECT COUNT(*) 712 FROM {user} uxx 713 JOIN ({$sql}) pxx ON uxx.id = pxx.id 714 WHERE uxx.id = :uxxid"; 715 $params['uxxid'] = $userid; 716 717 if ($DB->count_records_sql($sql, $params)) { 718 return true; 719 } 720 721 return false; 722 } 723 724 /** 725 * Groups the given users by the group membership 726 * 727 * This takes the module grouping settings into account. If a grouping is 728 * set, returns only groups withing the course module grouping. Always 729 * returns group [0] with all the given users. 730 * 731 * @param array $users array[userid] => stdclass{->id ->lastname ->firstname} 732 * @return array array[groupid][userid] => stdclass{->id ->lastname ->firstname} 733 */ 734 public function get_grouped($users) { 735 global $DB; 736 global $CFG; 737 738 $grouped = array(); // grouped users to be returned 739 if (empty($users)) { 740 return $grouped; 741 } 742 if ($this->cm->groupingid) { 743 // Group workshop set to specified grouping - only consider groups 744 // within this grouping, and leave out users who aren't members of 745 // this grouping. 746 $groupingid = $this->cm->groupingid; 747 // All users that are members of at least one group will be 748 // added into a virtual group id 0 749 $grouped[0] = array(); 750 } else { 751 $groupingid = 0; 752 // there is no need to be member of a group so $grouped[0] will contain 753 // all users 754 $grouped[0] = $users; 755 } 756 $gmemberships = groups_get_all_groups($this->cm->course, array_keys($users), $groupingid, 757 'gm.id,gm.groupid,gm.userid'); 758 foreach ($gmemberships as $gmembership) { 759 if (!isset($grouped[$gmembership->groupid])) { 760 $grouped[$gmembership->groupid] = array(); 761 } 762 $grouped[$gmembership->groupid][$gmembership->userid] = $users[$gmembership->userid]; 763 $grouped[0][$gmembership->userid] = $users[$gmembership->userid]; 764 } 765 return $grouped; 766 } 767 768 /** 769 * Returns the list of all allocations (i.e. assigned assessments) in the workshop 770 * 771 * Assessments of example submissions are ignored 772 * 773 * @return array 774 */ 775 public function get_allocations() { 776 global $DB; 777 778 $sql = 'SELECT a.id, a.submissionid, a.reviewerid, s.authorid 779 FROM {workshop_assessments} a 780 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) 781 WHERE s.example = 0 AND s.workshopid = :workshopid'; 782 $params = array('workshopid' => $this->id); 783 784 return $DB->get_records_sql($sql, $params); 785 } 786 787 /** 788 * Returns the total number of records that would be returned by {@link self::get_submissions()} 789 * 790 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only 791 * @param int $groupid If non-zero, return only submissions by authors in the specified group 792 * @return int number of records 793 */ 794 public function count_submissions($authorid='all', $groupid=0) { 795 global $DB; 796 797 $params = array('workshopid' => $this->id); 798 $sql = "SELECT COUNT(s.id) 799 FROM {workshop_submissions} s 800 JOIN {user} u ON (s.authorid = u.id)"; 801 if ($groupid) { 802 $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)"; 803 $params['groupid'] = $groupid; 804 } 805 $sql .= " WHERE s.example = 0 AND s.workshopid = :workshopid"; 806 807 if ('all' === $authorid) { 808 // no additional conditions 809 } elseif (!empty($authorid)) { 810 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED); 811 $sql .= " AND authorid $usql"; 812 $params = array_merge($params, $uparams); 813 } else { 814 // $authorid is empty 815 return 0; 816 } 817 818 return $DB->count_records_sql($sql, $params); 819 } 820 821 822 /** 823 * Returns submissions from this workshop 824 * 825 * Fetches data from {workshop_submissions} and adds some useful information from other 826 * tables. Does not return textual fields to prevent possible memory lack issues. 827 * 828 * @see self::count_submissions() 829 * @param mixed $authorid int|array|'all' If set to [array of] integer, return submission[s] of the given user[s] only 830 * @param int $groupid If non-zero, return only submissions by authors in the specified group 831 * @param int $limitfrom Return a subset of records, starting at this point (optional) 832 * @param int $limitnum Return a subset containing this many records in total (optional, required if $limitfrom is set) 833 * @return array of records or an empty array 834 */ 835 public function get_submissions($authorid='all', $groupid=0, $limitfrom=0, $limitnum=0) { 836 global $DB; 837 838 $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); 839 $gradeoverbyfields = user_picture::fields('t', null, 'gradeoverbyx', 'over'); 840 $params = array('workshopid' => $this->id); 841 $sql = "SELECT s.id, s.workshopid, s.example, s.authorid, s.timecreated, s.timemodified, 842 s.title, s.grade, s.gradeover, s.gradeoverby, s.published, 843 $authorfields, $gradeoverbyfields 844 FROM {workshop_submissions} s 845 JOIN {user} u ON (s.authorid = u.id)"; 846 if ($groupid) { 847 $sql .= " JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)"; 848 $params['groupid'] = $groupid; 849 } 850 $sql .= " LEFT JOIN {user} t ON (s.gradeoverby = t.id) 851 WHERE s.example = 0 AND s.workshopid = :workshopid"; 852 853 if ('all' === $authorid) { 854 // no additional conditions 855 } elseif (!empty($authorid)) { 856 list($usql, $uparams) = $DB->get_in_or_equal($authorid, SQL_PARAMS_NAMED); 857 $sql .= " AND authorid $usql"; 858 $params = array_merge($params, $uparams); 859 } else { 860 // $authorid is empty 861 return array(); 862 } 863 list($sort, $sortparams) = users_order_by_sql('u'); 864 $sql .= " ORDER BY $sort"; 865 866 return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum); 867 } 868 869 /** 870 * Returns a submission record with the author's data 871 * 872 * @param int $id submission id 873 * @return stdclass 874 */ 875 public function get_submission_by_id($id) { 876 global $DB; 877 878 // we intentionally check the workshopid here, too, so the workshop can't touch submissions 879 // from other instances 880 $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); 881 $gradeoverbyfields = user_picture::fields('g', null, 'gradeoverbyx', 'gradeoverby'); 882 $sql = "SELECT s.*, $authorfields, $gradeoverbyfields 883 FROM {workshop_submissions} s 884 INNER JOIN {user} u ON (s.authorid = u.id) 885 LEFT JOIN {user} g ON (s.gradeoverby = g.id) 886 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.id = :id"; 887 $params = array('workshopid' => $this->id, 'id' => $id); 888 return $DB->get_record_sql($sql, $params, MUST_EXIST); 889 } 890 891 /** 892 * Returns a submission submitted by the given author 893 * 894 * @param int $id author id 895 * @return stdclass|false 896 */ 897 public function get_submission_by_author($authorid) { 898 global $DB; 899 900 if (empty($authorid)) { 901 return false; 902 } 903 $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); 904 $gradeoverbyfields = user_picture::fields('g', null, 'gradeoverbyx', 'gradeoverby'); 905 $sql = "SELECT s.*, $authorfields, $gradeoverbyfields 906 FROM {workshop_submissions} s 907 INNER JOIN {user} u ON (s.authorid = u.id) 908 LEFT JOIN {user} g ON (s.gradeoverby = g.id) 909 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.authorid = :authorid"; 910 $params = array('workshopid' => $this->id, 'authorid' => $authorid); 911 return $DB->get_record_sql($sql, $params); 912 } 913 914 /** 915 * Returns published submissions with their authors data 916 * 917 * @return array of stdclass 918 */ 919 public function get_published_submissions($orderby='finalgrade DESC') { 920 global $DB; 921 922 $authorfields = user_picture::fields('u', null, 'authoridx', 'author'); 923 $sql = "SELECT s.id, s.authorid, s.timecreated, s.timemodified, 924 s.title, s.grade, s.gradeover, COALESCE(s.gradeover,s.grade) AS finalgrade, 925 $authorfields 926 FROM {workshop_submissions} s 927 INNER JOIN {user} u ON (s.authorid = u.id) 928 WHERE s.example = 0 AND s.workshopid = :workshopid AND s.published = 1 929 ORDER BY $orderby"; 930 $params = array('workshopid' => $this->id); 931 return $DB->get_records_sql($sql, $params); 932 } 933 934 /** 935 * Returns full record of the given example submission 936 * 937 * @param int $id example submission od 938 * @return object 939 */ 940 public function get_example_by_id($id) { 941 global $DB; 942 return $DB->get_record('workshop_submissions', 943 array('id' => $id, 'workshopid' => $this->id, 'example' => 1), '*', MUST_EXIST); 944 } 945 946 /** 947 * Returns the list of example submissions in this workshop with reference assessments attached 948 * 949 * @return array of objects or an empty array 950 * @see workshop::prepare_example_summary() 951 */ 952 public function get_examples_for_manager() { 953 global $DB; 954 955 $sql = 'SELECT s.id, s.title, 956 a.id AS assessmentid, a.grade, a.gradinggrade 957 FROM {workshop_submissions} s 958 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.weight = 1) 959 WHERE s.example = 1 AND s.workshopid = :workshopid 960 ORDER BY s.title'; 961 return $DB->get_records_sql($sql, array('workshopid' => $this->id)); 962 } 963 964 /** 965 * Returns the list of all example submissions in this workshop with the information of assessments done by the given user 966 * 967 * @param int $reviewerid user id 968 * @return array of objects, indexed by example submission id 969 * @see workshop::prepare_example_summary() 970 */ 971 public function get_examples_for_reviewer($reviewerid) { 972 global $DB; 973 974 if (empty($reviewerid)) { 975 return false; 976 } 977 $sql = 'SELECT s.id, s.title, 978 a.id AS assessmentid, a.grade, a.gradinggrade 979 FROM {workshop_submissions} s 980 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id AND a.reviewerid = :reviewerid AND a.weight = 0) 981 WHERE s.example = 1 AND s.workshopid = :workshopid 982 ORDER BY s.title'; 983 return $DB->get_records_sql($sql, array('workshopid' => $this->id, 'reviewerid' => $reviewerid)); 984 } 985 986 /** 987 * Prepares renderable submission component 988 * 989 * @param stdClass $record required by {@see workshop_submission} 990 * @param bool $showauthor show the author-related information 991 * @return workshop_submission 992 */ 993 public function prepare_submission(stdClass $record, $showauthor = false) { 994 995 $submission = new workshop_submission($this, $record, $showauthor); 996 $submission->url = $this->submission_url($record->id); 997 998 return $submission; 999 } 1000 1001 /** 1002 * Prepares renderable submission summary component 1003 * 1004 * @param stdClass $record required by {@see workshop_submission_summary} 1005 * @param bool $showauthor show the author-related information 1006 * @return workshop_submission_summary 1007 */ 1008 public function prepare_submission_summary(stdClass $record, $showauthor = false) { 1009 1010 $summary = new workshop_submission_summary($this, $record, $showauthor); 1011 $summary->url = $this->submission_url($record->id); 1012 1013 return $summary; 1014 } 1015 1016 /** 1017 * Prepares renderable example submission component 1018 * 1019 * @param stdClass $record required by {@see workshop_example_submission} 1020 * @return workshop_example_submission 1021 */ 1022 public function prepare_example_submission(stdClass $record) { 1023 1024 $example = new workshop_example_submission($this, $record); 1025 1026 return $example; 1027 } 1028 1029 /** 1030 * Prepares renderable example submission summary component 1031 * 1032 * If the example is editable, the caller must set the 'editable' flag explicitly. 1033 * 1034 * @param stdClass $example as returned by {@link workshop::get_examples_for_manager()} or {@link workshop::get_examples_for_reviewer()} 1035 * @return workshop_example_submission_summary to be rendered 1036 */ 1037 public function prepare_example_summary(stdClass $example) { 1038 1039 $summary = new workshop_example_submission_summary($this, $example); 1040 1041 if (is_null($example->grade)) { 1042 $summary->status = 'notgraded'; 1043 $summary->assesslabel = get_string('assess', 'workshop'); 1044 } else { 1045 $summary->status = 'graded'; 1046 $summary->assesslabel = get_string('reassess', 'workshop'); 1047 } 1048 1049 $summary->gradeinfo = new stdclass(); 1050 $summary->gradeinfo->received = $this->real_grade($example->grade); 1051 $summary->gradeinfo->max = $this->real_grade(100); 1052 1053 $summary->url = new moodle_url($this->exsubmission_url($example->id)); 1054 $summary->editurl = new moodle_url($this->exsubmission_url($example->id), array('edit' => 'on')); 1055 $summary->assessurl = new moodle_url($this->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey())); 1056 1057 return $summary; 1058 } 1059 1060 /** 1061 * Prepares renderable assessment component 1062 * 1063 * The $options array supports the following keys: 1064 * showauthor - should the author user info be available for the renderer 1065 * showreviewer - should the reviewer user info be available for the renderer 1066 * showform - show the assessment form if it is available 1067 * showweight - should the assessment weight be available for the renderer 1068 * 1069 * @param stdClass $record as returned by eg {@link self::get_assessment_by_id()} 1070 * @param workshop_assessment_form|null $form as returned by {@link workshop_strategy::get_assessment_form()} 1071 * @param array $options 1072 * @return workshop_assessment 1073 */ 1074 public function prepare_assessment(stdClass $record, $form, array $options = array()) { 1075 1076 $assessment = new workshop_assessment($this, $record, $options); 1077 $assessment->url = $this->assess_url($record->id); 1078 $assessment->maxgrade = $this->real_grade(100); 1079 1080 if (!empty($options['showform']) and !($form instanceof workshop_assessment_form)) { 1081 debugging('Not a valid instance of workshop_assessment_form supplied', DEBUG_DEVELOPER); 1082 } 1083 1084 if (!empty($options['showform']) and ($form instanceof workshop_assessment_form)) { 1085 $assessment->form = $form; 1086 } 1087 1088 if (empty($options['showweight'])) { 1089 $assessment->weight = null; 1090 } 1091 1092 if (!is_null($record->grade)) { 1093 $assessment->realgrade = $this->real_grade($record->grade); 1094 } 1095 1096 return $assessment; 1097 } 1098 1099 /** 1100 * Prepares renderable example submission's assessment component 1101 * 1102 * The $options array supports the following keys: 1103 * showauthor - should the author user info be available for the renderer 1104 * showreviewer - should the reviewer user info be available for the renderer 1105 * showform - show the assessment form if it is available 1106 * 1107 * @param stdClass $record as returned by eg {@link self::get_assessment_by_id()} 1108 * @param workshop_assessment_form|null $form as returned by {@link workshop_strategy::get_assessment_form()} 1109 * @param array $options 1110 * @return workshop_example_assessment 1111 */ 1112 public function prepare_example_assessment(stdClass $record, $form = null, array $options = array()) { 1113 1114 $assessment = new workshop_example_assessment($this, $record, $options); 1115 $assessment->url = $this->exassess_url($record->id); 1116 $assessment->maxgrade = $this->real_grade(100); 1117 1118 if (!empty($options['showform']) and !($form instanceof workshop_assessment_form)) { 1119 debugging('Not a valid instance of workshop_assessment_form supplied', DEBUG_DEVELOPER); 1120 } 1121 1122 if (!empty($options['showform']) and ($form instanceof workshop_assessment_form)) { 1123 $assessment->form = $form; 1124 } 1125 1126 if (!is_null($record->grade)) { 1127 $assessment->realgrade = $this->real_grade($record->grade); 1128 } 1129 1130 $assessment->weight = null; 1131 1132 return $assessment; 1133 } 1134 1135 /** 1136 * Prepares renderable example submission's reference assessment component 1137 * 1138 * The $options array supports the following keys: 1139 * showauthor - should the author user info be available for the renderer 1140 * showreviewer - should the reviewer user info be available for the renderer 1141 * showform - show the assessment form if it is available 1142 * 1143 * @param stdClass $record as returned by eg {@link self::get_assessment_by_id()} 1144 * @param workshop_assessment_form|null $form as returned by {@link workshop_strategy::get_assessment_form()} 1145 * @param array $options 1146 * @return workshop_example_reference_assessment 1147 */ 1148 public function prepare_example_reference_assessment(stdClass $record, $form = null, array $options = array()) { 1149 1150 $assessment = new workshop_example_reference_assessment($this, $record, $options); 1151 $assessment->maxgrade = $this->real_grade(100); 1152 1153 if (!empty($options['showform']) and !($form instanceof workshop_assessment_form)) { 1154 debugging('Not a valid instance of workshop_assessment_form supplied', DEBUG_DEVELOPER); 1155 } 1156 1157 if (!empty($options['showform']) and ($form instanceof workshop_assessment_form)) { 1158 $assessment->form = $form; 1159 } 1160 1161 if (!is_null($record->grade)) { 1162 $assessment->realgrade = $this->real_grade($record->grade); 1163 } 1164 1165 $assessment->weight = null; 1166 1167 return $assessment; 1168 } 1169 1170 /** 1171 * Removes the submission and all relevant data 1172 * 1173 * @param stdClass $submission record to delete 1174 * @return void 1175 */ 1176 public function delete_submission(stdclass $submission) { 1177 global $DB; 1178 1179 $assessments = $DB->get_records('workshop_assessments', array('submissionid' => $submission->id), '', 'id'); 1180 $this->delete_assessment(array_keys($assessments)); 1181 1182 $fs = get_file_storage(); 1183 $fs->delete_area_files($this->context->id, 'mod_workshop', 'submission_content', $submission->id); 1184 $fs->delete_area_files($this->context->id, 'mod_workshop', 'submission_attachment', $submission->id); 1185 1186 $DB->delete_records('workshop_submissions', array('id' => $submission->id)); 1187 } 1188 1189 /** 1190 * Returns the list of all assessments in the workshop with some data added 1191 * 1192 * Fetches data from {workshop_assessments} and adds some useful information from other 1193 * tables. The returned object does not contain textual fields (i.e. comments) to prevent memory 1194 * lack issues. 1195 * 1196 * @return array [assessmentid] => assessment stdclass 1197 */ 1198 public function get_all_assessments() { 1199 global $DB; 1200 1201 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); 1202 $authorfields = user_picture::fields('author', null, 'authorid', 'author'); 1203 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); 1204 list($sort, $params) = users_order_by_sql('reviewer'); 1205 $sql = "SELECT a.id, a.submissionid, a.reviewerid, a.timecreated, a.timemodified, 1206 a.grade, a.gradinggrade, a.gradinggradeover, a.gradinggradeoverby, 1207 $reviewerfields, $authorfields, $overbyfields, 1208 s.title 1209 FROM {workshop_assessments} a 1210 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) 1211 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) 1212 INNER JOIN {user} author ON (s.authorid = author.id) 1213 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id) 1214 WHERE s.workshopid = :workshopid AND s.example = 0 1215 ORDER BY $sort"; 1216 $params['workshopid'] = $this->id; 1217 1218 return $DB->get_records_sql($sql, $params); 1219 } 1220 1221 /** 1222 * Get the complete information about the given assessment 1223 * 1224 * @param int $id Assessment ID 1225 * @return stdclass 1226 */ 1227 public function get_assessment_by_id($id) { 1228 global $DB; 1229 1230 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); 1231 $authorfields = user_picture::fields('author', null, 'authorid', 'author'); 1232 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); 1233 $sql = "SELECT a.*, s.title, $reviewerfields, $authorfields, $overbyfields 1234 FROM {workshop_assessments} a 1235 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) 1236 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) 1237 INNER JOIN {user} author ON (s.authorid = author.id) 1238 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id) 1239 WHERE a.id = :id AND s.workshopid = :workshopid"; 1240 $params = array('id' => $id, 'workshopid' => $this->id); 1241 1242 return $DB->get_record_sql($sql, $params, MUST_EXIST); 1243 } 1244 1245 /** 1246 * Get the complete information about the user's assessment of the given submission 1247 * 1248 * @param int $sid submission ID 1249 * @param int $uid user ID of the reviewer 1250 * @return false|stdclass false if not found, stdclass otherwise 1251 */ 1252 public function get_assessment_of_submission_by_user($submissionid, $reviewerid) { 1253 global $DB; 1254 1255 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); 1256 $authorfields = user_picture::fields('author', null, 'authorid', 'author'); 1257 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); 1258 $sql = "SELECT a.*, s.title, $reviewerfields, $authorfields, $overbyfields 1259 FROM {workshop_assessments} a 1260 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) 1261 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0) 1262 INNER JOIN {user} author ON (s.authorid = author.id) 1263 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id) 1264 WHERE s.id = :sid AND reviewer.id = :rid AND s.workshopid = :workshopid"; 1265 $params = array('sid' => $submissionid, 'rid' => $reviewerid, 'workshopid' => $this->id); 1266 1267 return $DB->get_record_sql($sql, $params, IGNORE_MISSING); 1268 } 1269 1270 /** 1271 * Get the complete information about all assessments of the given submission 1272 * 1273 * @param int $submissionid 1274 * @return array 1275 */ 1276 public function get_assessments_of_submission($submissionid) { 1277 global $DB; 1278 1279 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); 1280 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); 1281 list($sort, $params) = users_order_by_sql('reviewer'); 1282 $sql = "SELECT a.*, s.title, $reviewerfields, $overbyfields 1283 FROM {workshop_assessments} a 1284 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) 1285 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) 1286 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id) 1287 WHERE s.example = 0 AND s.id = :submissionid AND s.workshopid = :workshopid 1288 ORDER BY $sort"; 1289 $params['submissionid'] = $submissionid; 1290 $params['workshopid'] = $this->id; 1291 1292 return $DB->get_records_sql($sql, $params); 1293 } 1294 1295 /** 1296 * Get the complete information about all assessments allocated to the given reviewer 1297 * 1298 * @param int $reviewerid 1299 * @return array 1300 */ 1301 public function get_assessments_by_reviewer($reviewerid) { 1302 global $DB; 1303 1304 $reviewerfields = user_picture::fields('reviewer', null, 'revieweridx', 'reviewer'); 1305 $authorfields = user_picture::fields('author', null, 'authorid', 'author'); 1306 $overbyfields = user_picture::fields('overby', null, 'gradinggradeoverbyx', 'overby'); 1307 $sql = "SELECT a.*, $reviewerfields, $authorfields, $overbyfields, 1308 s.id AS submissionid, s.title AS submissiontitle, s.timecreated AS submissioncreated, 1309 s.timemodified AS submissionmodified 1310 FROM {workshop_assessments} a 1311 INNER JOIN {user} reviewer ON (a.reviewerid = reviewer.id) 1312 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) 1313 INNER JOIN {user} author ON (s.authorid = author.id) 1314 LEFT JOIN {user} overby ON (a.gradinggradeoverby = overby.id) 1315 WHERE s.example = 0 AND reviewer.id = :reviewerid AND s.workshopid = :workshopid"; 1316 $params = array('reviewerid' => $reviewerid, 'workshopid' => $this->id); 1317 1318 return $DB->get_records_sql($sql, $params); 1319 } 1320 1321 /** 1322 * Get allocated assessments not graded yet by the given reviewer 1323 * 1324 * @see self::get_assessments_by_reviewer() 1325 * @param int $reviewerid the reviewer id 1326 * @param null|int|array $exclude optional assessment id (or list of them) to be excluded 1327 * @return array 1328 */ 1329 public function get_pending_assessments_by_reviewer($reviewerid, $exclude = null) { 1330 1331 $assessments = $this->get_assessments_by_reviewer($reviewerid); 1332 1333 foreach ($assessments as $id => $assessment) { 1334 if (!is_null($assessment->grade)) { 1335 unset($assessments[$id]); 1336 continue; 1337 } 1338 if (!empty($exclude)) { 1339 if (is_array($exclude) and in_array($id, $exclude)) { 1340 unset($assessments[$id]); 1341 continue; 1342 } else if ($id == $exclude) { 1343 unset($assessments[$id]); 1344 continue; 1345 } 1346 } 1347 } 1348 1349 return $assessments; 1350 } 1351 1352 /** 1353 * Allocate a submission to a user for review 1354 * 1355 * @param stdClass $submission Submission object with at least id property 1356 * @param int $reviewerid User ID 1357 * @param int $weight of the new assessment, from 0 to 16 1358 * @param bool $bulk repeated inserts into DB expected 1359 * @return int ID of the new assessment or an error code {@link self::ALLOCATION_EXISTS} if the allocation already exists 1360 */ 1361 public function add_allocation(stdclass $submission, $reviewerid, $weight=1, $bulk=false) { 1362 global $DB; 1363 1364 if ($DB->record_exists('workshop_assessments', array('submissionid' => $submission->id, 'reviewerid' => $reviewerid))) { 1365 return self::ALLOCATION_EXISTS; 1366 } 1367 1368 $weight = (int)$weight; 1369 if ($weight < 0) { 1370 $weight = 0; 1371 } 1372 if ($weight > 16) { 1373 $weight = 16; 1374 } 1375 1376 $now = time(); 1377 $assessment = new stdclass(); 1378 $assessment->submissionid = $submission->id; 1379 $assessment->reviewerid = $reviewerid; 1380 $assessment->timecreated = $now; // do not set timemodified here 1381 $assessment->weight = $weight; 1382 $assessment->feedbackauthorformat = editors_get_preferred_format(); 1383 $assessment->feedbackreviewerformat = editors_get_preferred_format(); 1384 1385 return $DB->insert_record('workshop_assessments', $assessment, true, $bulk); 1386 } 1387 1388 /** 1389 * Delete assessment record or records. 1390 * 1391 * Removes associated records from the workshop_grades table, too. 1392 * 1393 * @param int|array $id assessment id or array of assessments ids 1394 * @todo Give grading strategy plugins a chance to clean up their data, too. 1395 * @return bool true 1396 */ 1397 public function delete_assessment($id) { 1398 global $DB; 1399 1400 if (empty($id)) { 1401 return true; 1402 } 1403 1404 $fs = get_file_storage(); 1405 1406 if (is_array($id)) { 1407 $DB->delete_records_list('workshop_grades', 'assessmentid', $id); 1408 foreach ($id as $itemid) { 1409 $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_content', $itemid); 1410 $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_attachment', $itemid); 1411 } 1412 $DB->delete_records_list('workshop_assessments', 'id', $id); 1413 1414 } else { 1415 $DB->delete_records('workshop_grades', array('assessmentid' => $id)); 1416 $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_content', $id); 1417 $fs->delete_area_files($this->context->id, 'mod_workshop', 'overallfeedback_attachment', $id); 1418 $DB->delete_records('workshop_assessments', array('id' => $id)); 1419 } 1420 1421 return true; 1422 } 1423 1424 /** 1425 * Returns instance of grading strategy class 1426 * 1427 * @return stdclass Instance of a grading strategy 1428 */ 1429 public function grading_strategy_instance() { 1430 global $CFG; // because we require other libs here 1431 1432 if (is_null($this->strategyinstance)) { 1433 $strategylib = __DIR__ . '/form/' . $this->strategy . '/lib.php'; 1434 if (is_readable($strategylib)) { 1435 require_once($strategylib); 1436 } else { 1437 throw new coding_exception('the grading forms subplugin must contain library ' . $strategylib); 1438 } 1439 $classname = 'workshop_' . $this->strategy . '_strategy'; 1440 $this->strategyinstance = new $classname($this); 1441 if (!in_array('workshop_strategy', class_implements($this->strategyinstance))) { 1442 throw new coding_exception($classname . ' does not implement workshop_strategy interface'); 1443 } 1444 } 1445 return $this->strategyinstance; 1446 } 1447 1448 /** 1449 * Sets the current evaluation method to the given plugin. 1450 * 1451 * @param string $method the name of the workshopeval subplugin 1452 * @return bool true if successfully set 1453 * @throws coding_exception if attempting to set a non-installed evaluation method 1454 */ 1455 public function set_grading_evaluation_method($method) { 1456 global $DB; 1457 1458 $evaluationlib = __DIR__ . '/eval/' . $method . '/lib.php'; 1459 1460 if (is_readable($evaluationlib)) { 1461 $this->evaluationinstance = null; 1462 $this->evaluation = $method; 1463 $DB->set_field('workshop', 'evaluation', $method, array('id' => $this->id)); 1464 return true; 1465 } 1466 1467 throw new coding_exception('Attempt to set a non-existing evaluation method.'); 1468 } 1469 1470 /** 1471 * Returns instance of grading evaluation class 1472 * 1473 * @return stdclass Instance of a grading evaluation 1474 */ 1475 public function grading_evaluation_instance() { 1476 global $CFG; // because we require other libs here 1477 1478 if (is_null($this->evaluationinstance)) { 1479 if (empty($this->evaluation)) { 1480 $this->evaluation = 'best'; 1481 } 1482 $evaluationlib = __DIR__ . '/eval/' . $this->evaluation . '/lib.php'; 1483 if (is_readable($evaluationlib)) { 1484 require_once($evaluationlib); 1485 } else { 1486 // Fall back in case the subplugin is not available. 1487 $this->evaluation = 'best'; 1488 $evaluationlib = __DIR__ . '/eval/' . $this->evaluation . '/lib.php'; 1489 if (is_readable($evaluationlib)) { 1490 require_once($evaluationlib); 1491 } else { 1492 // Fall back in case the subplugin is not available any more. 1493 throw new coding_exception('Missing default grading evaluation library ' . $evaluationlib); 1494 } 1495 } 1496 $classname = 'workshop_' . $this->evaluation . '_evaluation'; 1497 $this->evaluationinstance = new $classname($this); 1498 if (!in_array('workshop_evaluation', class_parents($this->evaluationinstance))) { 1499 throw new coding_exception($classname . ' does not extend workshop_evaluation class'); 1500 } 1501 } 1502 return $this->evaluationinstance; 1503 } 1504 1505 /** 1506 * Returns instance of submissions allocator 1507 * 1508 * @param string $method The name of the allocation method, must be PARAM_ALPHA 1509 * @return stdclass Instance of submissions allocator 1510 */ 1511 public function allocator_instance($method) { 1512 global $CFG; // because we require other libs here 1513 1514 $allocationlib = __DIR__ . '/allocation/' . $method . '/lib.php'; 1515 if (is_readable($allocationlib)) { 1516 require_once($allocationlib); 1517 } else { 1518 throw new coding_exception('Unable to find the allocation library ' . $allocationlib); 1519 } 1520 $classname = 'workshop_' . $method . '_allocator'; 1521 return new $classname($this); 1522 } 1523 1524 /** 1525 * @return moodle_url of this workshop's view page 1526 */ 1527 public function view_url() { 1528 global $CFG; 1529 return new moodle_url('/mod/workshop/view.php', array('id' => $this->cm->id)); 1530 } 1531 1532 /** 1533 * @return moodle_url of the page for editing this workshop's grading form 1534 */ 1535 public function editform_url() { 1536 global $CFG; 1537 return new moodle_url('/mod/workshop/editform.php', array('cmid' => $this->cm->id)); 1538 } 1539 1540 /** 1541 * @return moodle_url of the page for previewing this workshop's grading form 1542 */ 1543 public function previewform_url() { 1544 global $CFG; 1545 return new moodle_url('/mod/workshop/editformpreview.php', array('cmid' => $this->cm->id)); 1546 } 1547 1548 /** 1549 * @param int $assessmentid The ID of assessment record 1550 * @return moodle_url of the assessment page 1551 */ 1552 public function assess_url($assessmentid) { 1553 global $CFG; 1554 $assessmentid = clean_param($assessmentid, PARAM_INT); 1555 return new moodle_url('/mod/workshop/assessment.php', array('asid' => $assessmentid)); 1556 } 1557 1558 /** 1559 * @param int $assessmentid The ID of assessment record 1560 * @return moodle_url of the example assessment page 1561 */ 1562 public function exassess_url($assessmentid) { 1563 global $CFG; 1564 $assessmentid = clean_param($assessmentid, PARAM_INT); 1565 return new moodle_url('/mod/workshop/exassessment.php', array('asid' => $assessmentid)); 1566 } 1567 1568 /** 1569 * @return moodle_url of the page to view a submission, defaults to the own one 1570 */ 1571 public function submission_url($id=null) { 1572 global $CFG; 1573 return new moodle_url('/mod/workshop/submission.php', array('cmid' => $this->cm->id, 'id' => $id)); 1574 } 1575 1576 /** 1577 * @param int $id example submission id 1578 * @return moodle_url of the page to view an example submission 1579 */ 1580 public function exsubmission_url($id) { 1581 global $CFG; 1582 return new moodle_url('/mod/workshop/exsubmission.php', array('cmid' => $this->cm->id, 'id' => $id)); 1583 } 1584 1585 /** 1586 * @param int $sid submission id 1587 * @param array $aid of int assessment ids 1588 * @return moodle_url of the page to compare assessments of the given submission 1589 */ 1590 public function compare_url($sid, array $aids) { 1591 global $CFG; 1592 1593 $url = new moodle_url('/mod/workshop/compare.php', array('cmid' => $this->cm->id, 'sid' => $sid)); 1594 $i = 0; 1595 foreach ($aids as $aid) { 1596 $url->param("aid{$i}", $aid); 1597 $i++; 1598 } 1599 return $url; 1600 } 1601 1602 /** 1603 * @param int $sid submission id 1604 * @param int $aid assessment id 1605 * @return moodle_url of the page to compare the reference assessments of the given example submission 1606 */ 1607 public function excompare_url($sid, $aid) { 1608 global $CFG; 1609 return new moodle_url('/mod/workshop/excompare.php', array('cmid' => $this->cm->id, 'sid' => $sid, 'aid' => $aid)); 1610 } 1611 1612 /** 1613 * @return moodle_url of the mod_edit form 1614 */ 1615 public function updatemod_url() { 1616 global $CFG; 1617 return new moodle_url('/course/modedit.php', array('update' => $this->cm->id, 'return' => 1)); 1618 } 1619 1620 /** 1621 * @param string $method allocation method 1622 * @return moodle_url to the allocation page 1623 */ 1624 public function allocation_url($method=null) { 1625 global $CFG; 1626 $params = array('cmid' => $this->cm->id); 1627 if (!empty($method)) { 1628 $params['method'] = $method; 1629 } 1630 return new moodle_url('/mod/workshop/allocation.php', $params); 1631 } 1632 1633 /** 1634 * @param int $phasecode The internal phase code 1635 * @return moodle_url of the script to change the current phase to $phasecode 1636 */ 1637 public function switchphase_url($phasecode) { 1638 global $CFG; 1639 $phasecode = clean_param($phasecode, PARAM_INT); 1640 return new moodle_url('/mod/workshop/switchphase.php', array('cmid' => $this->cm->id, 'phase' => $phasecode)); 1641 } 1642 1643 /** 1644 * @return moodle_url to the aggregation page 1645 */ 1646 public function aggregate_url() { 1647 global $CFG; 1648 return new moodle_url('/mod/workshop/aggregate.php', array('cmid' => $this->cm->id)); 1649 } 1650 1651 /** 1652 * @return moodle_url of this workshop's toolbox page 1653 */ 1654 public function toolbox_url($tool) { 1655 global $CFG; 1656 return new moodle_url('/mod/workshop/toolbox.php', array('id' => $this->cm->id, 'tool' => $tool)); 1657 } 1658 1659 /** 1660 * Workshop wrapper around {@see add_to_log()} 1661 * @deprecated since 2.7 Please use the provided event classes for logging actions. 1662 * 1663 * @param string $action to be logged 1664 * @param moodle_url $url absolute url as returned by {@see workshop::submission_url()} and friends 1665 * @param mixed $info additional info, usually id in a table 1666 * @param bool $return true to return the arguments for add_to_log. 1667 * @return void|array array of arguments for add_to_log if $return is true 1668 */ 1669 public function log($action, moodle_url $url = null, $info = null, $return = false) { 1670 debugging('The log method is now deprecated, please use event classes instead', DEBUG_DEVELOPER); 1671 1672 if (is_null($url)) { 1673 $url = $this->view_url(); 1674 } 1675 1676 if (is_null($info)) { 1677 $info = $this->id; 1678 } 1679 1680 $logurl = $this->log_convert_url($url); 1681 $args = array($this->course->id, 'workshop', $action, $logurl, $info, $this->cm->id); 1682 if ($return) { 1683 return $args; 1684 } 1685 call_user_func_array('add_to_log', $args); 1686 } 1687 1688 /** 1689 * Is the given user allowed to create their submission? 1690 * 1691 * @param int $userid 1692 * @return bool 1693 */ 1694 public function creating_submission_allowed($userid) { 1695 1696 $now = time(); 1697 $ignoredeadlines = has_capability('mod/workshop:ignoredeadlines', $this->context, $userid); 1698 1699 if ($this->latesubmissions) { 1700 if ($this->phase != self::PHASE_SUBMISSION and $this->phase != self::PHASE_ASSESSMENT) { 1701 // late submissions are allowed in the submission and assessment phase only 1702 return false; 1703 } 1704 if (!$ignoredeadlines and !empty($this->submissionstart) and $this->submissionstart > $now) { 1705 // late submissions are not allowed before the submission start 1706 return false; 1707 } 1708 return true; 1709 1710 } else { 1711 if ($this->phase != self::PHASE_SUBMISSION) { 1712 // submissions are allowed during the submission phase only 1713 return false; 1714 } 1715 if (!$ignoredeadlines and !empty($this->submissionstart) and $this->submissionstart > $now) { 1716 // if enabled, submitting is not allowed before the date/time defined in the mod_form 1717 return false; 1718 } 1719 if (!$ignoredeadlines and !empty($this->submissionend) and $now > $this->submissionend ) { 1720 // if enabled, submitting is not allowed after the date/time defined in the mod_form unless late submission is allowed 1721 return false; 1722 } 1723 return true; 1724 } 1725 } 1726 1727 /** 1728 * Is the given user allowed to modify their existing submission? 1729 * 1730 * @param int $userid 1731 * @return bool 1732 */ 1733 public function modifying_submission_allowed($userid) { 1734 1735 $now = time(); 1736 $ignoredeadlines = has_capability('mod/workshop:ignoredeadlines', $this->context, $userid); 1737 1738 if ($this->phase != self::PHASE_SUBMISSION) { 1739 // submissions can be edited during the submission phase only 1740 return false; 1741 } 1742 if (!$ignoredeadlines and !empty($this->submissionstart) and $this->submissionstart > $now) { 1743 // if enabled, re-submitting is not allowed before the date/time defined in the mod_form 1744 return false; 1745 } 1746 if (!$ignoredeadlines and !empty($this->submissionend) and $now > $this->submissionend) { 1747 // if enabled, re-submitting is not allowed after the date/time defined in the mod_form even if late submission is allowed 1748 return false; 1749 } 1750 return true; 1751 } 1752 1753 /** 1754 * Is the given reviewer allowed to create/edit their assessments? 1755 * 1756 * @param int $userid 1757 * @return bool 1758 */ 1759 public function assessing_allowed($userid) { 1760 1761 if ($this->phase != self::PHASE_ASSESSMENT) { 1762 // assessing is allowed in the assessment phase only, unless the user is a teacher 1763 // providing additional assessment during the evaluation phase 1764 if ($this->phase != self::PHASE_EVALUATION or !has_capability('mod/workshop:overridegrades', $this->context, $userid)) { 1765 return false; 1766 } 1767 } 1768 1769 $now = time(); 1770 $ignoredeadlines = has_capability('mod/workshop:ignoredeadlines', $this->context, $userid); 1771 1772 if (!$ignoredeadlines and !empty($this->assessmentstart) and $this->assessmentstart > $now) { 1773 // if enabled, assessing is not allowed before the date/time defined in the mod_form 1774 return false; 1775 } 1776 if (!$ignoredeadlines and !empty($this->assessmentend) and $now > $this->assessmentend) { 1777 // if enabled, assessing is not allowed after the date/time defined in the mod_form 1778 return false; 1779 } 1780 // here we go, assessing is allowed 1781 return true; 1782 } 1783 1784 /** 1785 * Are reviewers allowed to create/edit their assessments of the example submissions? 1786 * 1787 * Returns null if example submissions are not enabled in this workshop. Otherwise returns 1788 * true or false. Note this does not check other conditions like the number of already 1789 * assessed examples, examples mode etc. 1790 * 1791 * @return null|bool 1792 */ 1793 public function assessing_examples_allowed() { 1794 if (empty($this->useexamples)) { 1795 return null; 1796 } 1797 if (self::EXAMPLES_VOLUNTARY == $this->examplesmode) { 1798 return true; 1799 } 1800 if (self::EXAMPLES_BEFORE_SUBMISSION == $this->examplesmode and self::PHASE_SUBMISSION == $this->phase) { 1801 return true; 1802 } 1803 if (self::EXAMPLES_BEFORE_ASSESSMENT == $this->examplesmode and self::PHASE_ASSESSMENT == $this->phase) { 1804 return true; 1805 } 1806 return false; 1807 } 1808 1809 /** 1810 * Are the peer-reviews available to the authors? 1811 * 1812 * @return bool 1813 */ 1814 public function assessments_available() { 1815 return $this->phase == self::PHASE_CLOSED; 1816 } 1817 1818 /** 1819 * Switch to a new workshop phase 1820 * 1821 * Modifies the underlying database record. You should terminate the script shortly after calling this. 1822 * 1823 * @param int $newphase new phase code 1824 * @return bool true if success, false otherwise 1825 */ 1826 public function switch_phase($newphase) { 1827 global $DB; 1828 1829 $known = $this->available_phases_list(); 1830 if (!isset($known[$newphase])) { 1831 return false; 1832 } 1833 1834 if (self::PHASE_CLOSED == $newphase) { 1835 // push the grades into the gradebook 1836 $workshop = new stdclass(); 1837 foreach ($this as $property => $value) { 1838 $workshop->{$property} = $value; 1839 } 1840 $workshop->course = $this->course->id; 1841 $workshop->cmidnumber = $this->cm->id; 1842 $workshop->modname = 'workshop'; 1843 workshop_update_grades($workshop); 1844 } 1845 1846 $DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id)); 1847 $this->phase = $newphase; 1848 $eventdata = array( 1849 'objectid' => $this->id, 1850 'context' => $this->context, 1851 'other' => array( 1852 'workshopphase' => $this->phase 1853 ) 1854 ); 1855 $event = \mod_workshop\event\phase_switched::create($eventdata); 1856 $event->trigger(); 1857 return true; 1858 } 1859 1860 /** 1861 * Saves a raw grade for submission as calculated from the assessment form fields 1862 * 1863 * @param array $assessmentid assessment record id, must exists 1864 * @param mixed $grade raw percentual grade from 0.00000 to 100.00000 1865 * @return false|float the saved grade 1866 */ 1867 public function set_peer_grade($assessmentid, $grade) { 1868 global $DB; 1869 1870 if (is_null($grade)) { 1871 return false; 1872 } 1873 $data = new stdclass(); 1874 $data->id = $assessmentid; 1875 $data->grade = $grade; 1876 $data->timemodified = time(); 1877 $DB->update_record('workshop_assessments', $data); 1878 return $grade; 1879 } 1880 1881 /** 1882 * Prepares data object with all workshop grades to be rendered 1883 * 1884 * @param int $userid the user we are preparing the report for 1885 * @param int $groupid if non-zero, prepare the report for the given group only 1886 * @param int $page the current page (for the pagination) 1887 * @param int $perpage participants per page (for the pagination) 1888 * @param string $sortby lastname|firstname|submissiontitle|submissiongrade|gradinggrade 1889 * @param string $sorthow ASC|DESC 1890 * @return stdclass data for the renderer 1891 */ 1892 public function prepare_grading_report_data($userid, $groupid, $page, $perpage, $sortby, $sorthow) { 1893 global $DB; 1894 1895 $canviewall = has_capability('mod/workshop:viewallassessments', $this->context, $userid); 1896 $isparticipant = $this->is_participant($userid); 1897 1898 if (!$canviewall and !$isparticipant) { 1899 // who the hell is this? 1900 return array(); 1901 } 1902 1903 if (!in_array($sortby, array('lastname', 'firstname', 'submissiontitle', 'submissionmodified', 1904 'submissiongrade', 'gradinggrade'))) { 1905 $sortby = 'lastname'; 1906 } 1907 1908 if (!($sorthow === 'ASC' or $sorthow === 'DESC')) { 1909 $sorthow = 'ASC'; 1910 } 1911 1912 // get the list of user ids to be displayed 1913 if ($canviewall) { 1914 $participants = $this->get_participants(false, $groupid); 1915 } else { 1916 // this is an ordinary workshop participant (aka student) - display the report just for him/her 1917 $participants = array($userid => (object)array('id' => $userid)); 1918 } 1919 1920 // we will need to know the number of all records later for the pagination purposes 1921 $numofparticipants = count($participants); 1922 1923 if ($numofparticipants > 0) { 1924 // load all fields which can be used for sorting and paginate the records 1925 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED); 1926 $params['workshopid1'] = $this->id; 1927 $params['workshopid2'] = $this->id; 1928 $sqlsort = array(); 1929 $sqlsortfields = array($sortby => $sorthow) + array('lastname' => 'ASC', 'firstname' => 'ASC', 'u.id' => 'ASC'); 1930 foreach ($sqlsortfields as $sqlsortfieldname => $sqlsortfieldhow) { 1931 $sqlsort[] = $sqlsortfieldname . ' ' . $sqlsortfieldhow; 1932 } 1933 $sqlsort = implode(',', $sqlsort); 1934 $picturefields = user_picture::fields('u', array(), 'userid'); 1935 $sql = "SELECT $picturefields, s.title AS submissiontitle, s.timemodified AS submissionmodified, 1936 s.grade AS submissiongrade, ag.gradinggrade 1937 FROM {user} u 1938 LEFT JOIN {workshop_submissions} s ON (s.authorid = u.id AND s.workshopid = :workshopid1 AND s.example = 0) 1939 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = u.id AND ag.workshopid = :workshopid2) 1940 WHERE u.id $participantids 1941 ORDER BY $sqlsort"; 1942 $participants = $DB->get_records_sql($sql, $params, $page * $perpage, $perpage); 1943 } else { 1944 $participants = array(); 1945 } 1946 1947 // this will hold the information needed to display user names and pictures 1948 $userinfo = array(); 1949 1950 // get the user details for all participants to display 1951 $additionalnames = get_all_user_name_fields(); 1952 foreach ($participants as $participant) { 1953 if (!isset($userinfo[$participant->userid])) { 1954 $userinfo[$participant->userid] = new stdclass(); 1955 $userinfo[$participant->userid]->id = $participant->userid; 1956 $userinfo[$participant->userid]->picture = $participant->picture; 1957 $userinfo[$participant->userid]->imagealt = $participant->imagealt; 1958 $userinfo[$participant->userid]->email = $participant->email; 1959 foreach ($additionalnames as $addname) { 1960 $userinfo[$participant->userid]->$addname = $participant->$addname; 1961 } 1962 } 1963 } 1964 1965 // load the submissions details 1966 $submissions = $this->get_submissions(array_keys($participants)); 1967 1968 // get the user details for all moderators (teachers) that have overridden a submission grade 1969 foreach ($submissions as $submission) { 1970 if (!isset($userinfo[$submission->gradeoverby])) { 1971 $userinfo[$submission->gradeoverby] = new stdclass(); 1972 $userinfo[$submission->gradeoverby]->id = $submission->gradeoverby; 1973 $userinfo[$submission->gradeoverby]->picture = $submission->overpicture; 1974 $userinfo[$submission->gradeoverby]->imagealt = $submission->overimagealt; 1975 $userinfo[$submission->gradeoverby]->email = $submission->overemail; 1976 foreach ($additionalnames as $addname) { 1977 $temp = 'over' . $addname; 1978 $userinfo[$submission->gradeoverby]->$addname = $submission->$temp; 1979 } 1980 } 1981 } 1982 1983 // get the user details for all reviewers of the displayed participants 1984 $reviewers = array(); 1985 1986 if ($submissions) { 1987 list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED); 1988 list($sort, $sortparams) = users_order_by_sql('r'); 1989 $picturefields = user_picture::fields('r', array(), 'reviewerid'); 1990 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.weight, 1991 $picturefields, s.id AS submissionid, s.authorid 1992 FROM {workshop_assessments} a 1993 JOIN {user} r ON (a.reviewerid = r.id) 1994 JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0) 1995 WHERE a.submissionid $submissionids 1996 ORDER BY a.weight DESC, $sort"; 1997 $reviewers = $DB->get_records_sql($sql, array_merge($params, $sortparams)); 1998 foreach ($reviewers as $reviewer) { 1999 if (!isset($userinfo[$reviewer->reviewerid])) { 2000 $userinfo[$reviewer->reviewerid] = new stdclass(); 2001 $userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid; 2002 $userinfo[$reviewer->reviewerid]->picture = $reviewer->picture; 2003 $userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt; 2004 $userinfo[$reviewer->reviewerid]->email = $reviewer->email; 2005 foreach ($additionalnames as $addname) { 2006 $userinfo[$reviewer->reviewerid]->$addname = $reviewer->$addname; 2007 } 2008 } 2009 } 2010 } 2011 2012 // get the user details for all reviewees of the displayed participants 2013 $reviewees = array(); 2014 if ($participants) { 2015 list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED); 2016 list($sort, $sortparams) = users_order_by_sql('e'); 2017 $params['workshopid'] = $this->id; 2018 $picturefields = user_picture::fields('e', array(), 'authorid'); 2019 $sql = "SELECT a.id AS assessmentid, a.submissionid, a.grade, a.gradinggrade, a.gradinggradeover, a.reviewerid, a.weight, 2020 s.id AS submissionid, $picturefields 2021 FROM {user} u 2022 JOIN {workshop_assessments} a ON (a.reviewerid = u.id) 2023 JOIN {workshop_submissions} s ON (a.submissionid = s.id AND s.example = 0) 2024 JOIN {user} e ON (s.authorid = e.id) 2025 WHERE u.id $participantids AND s.workshopid = :workshopid 2026 ORDER BY a.weight DESC, $sort"; 2027 $reviewees = $DB->get_records_sql($sql, array_merge($params, $sortparams)); 2028 foreach ($reviewees as $reviewee) { 2029 if (!isset($userinfo[$reviewee->authorid])) { 2030 $userinfo[$reviewee->authorid] = new stdclass(); 2031 $userinfo[$reviewee->authorid]->id = $reviewee->authorid; 2032 $userinfo[$reviewee->authorid]->picture = $reviewee->picture; 2033 $userinfo[$reviewee->authorid]->imagealt = $reviewee->imagealt; 2034 $userinfo[$reviewee->authorid]->email = $reviewee->email; 2035 foreach ($additionalnames as $addname) { 2036 $userinfo[$reviewee->authorid]->$addname = $reviewee->$addname; 2037 } 2038 } 2039 } 2040 } 2041 2042 // finally populate the object to be rendered 2043 $grades = $participants; 2044 2045 foreach ($participants as $participant) { 2046 // set up default (null) values 2047 $grades[$participant->userid]->submissionid = null; 2048 $grades[$participant->userid]->submissiontitle = null; 2049 $grades[$participant->userid]->submissiongrade = null; 2050 $grades[$participant->userid]->submissiongradeover = null; 2051 $grades[$participant->userid]->submissiongradeoverby = null; 2052 $grades[$participant->userid]->submissionpublished = null; 2053 $grades[$participant->userid]->reviewedby = array(); 2054 $grades[$participant->userid]->reviewerof = array(); 2055 } 2056 unset($participants); 2057 unset($participant); 2058 2059 foreach ($submissions as $submission) { 2060 $grades[$submission->authorid]->submissionid = $submission->id; 2061 $grades[$submission->authorid]->submissiontitle = $submission->title; 2062 $grades[$submission->authorid]->submissiongrade = $this->real_grade($submission->grade); 2063 $grades[$submission->authorid]->submissiongradeover = $this->real_grade($submission->gradeover); 2064 $grades[$submission->authorid]->submissiongradeoverby = $submission->gradeoverby; 2065 $grades[$submission->authorid]->submissionpublished = $submission->published; 2066 } 2067 unset($submissions); 2068 unset($submission); 2069 2070 foreach($reviewers as $reviewer) { 2071 $info = new stdclass(); 2072 $info->userid = $reviewer->reviewerid; 2073 $info->assessmentid = $reviewer->assessmentid; 2074 $info->submissionid = $reviewer->submissionid; 2075 $info->grade = $this->real_grade($reviewer->grade); 2076 $info->gradinggrade = $this->real_grading_grade($reviewer->gradinggrade); 2077 $info->gradinggradeover = $this->real_grading_grade($reviewer->gradinggradeover); 2078 $info->weight = $reviewer->weight; 2079 $grades[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $info; 2080 } 2081 unset($reviewers); 2082 unset($reviewer); 2083 2084 foreach($reviewees as $reviewee) { 2085 $info = new stdclass(); 2086 $info->userid = $reviewee->authorid; 2087 $info->assessmentid = $reviewee->assessmentid; 2088 $info->submissionid = $reviewee->submissionid; 2089 $info->grade = $this->real_grade($reviewee->grade); 2090 $info->gradinggrade = $this->real_grading_grade($reviewee->gradinggrade); 2091 $info->gradinggradeover = $this->real_grading_grade($reviewee->gradinggradeover); 2092 $info->weight = $reviewee->weight; 2093 $grades[$reviewee->reviewerid]->reviewerof[$reviewee->authorid] = $info; 2094 } 2095 unset($reviewees); 2096 unset($reviewee); 2097 2098 foreach ($grades as $grade) { 2099 $grade->gradinggrade = $this->real_grading_grade($grade->gradinggrade); 2100 } 2101 2102 $data = new stdclass(); 2103 $data->grades = $grades; 2104 $data->userinfo = $userinfo; 2105 $data->totalcount = $numofparticipants; 2106 $data->maxgrade = $this->real_grade(100); 2107 $data->maxgradinggrade = $this->real_grading_grade(100); 2108 return $data; 2109 } 2110 2111 /** 2112 * Calculates the real value of a grade 2113 * 2114 * @param float $value percentual value from 0 to 100 2115 * @param float $max the maximal grade 2116 * @return string 2117 */ 2118 public function real_grade_value($value, $max) { 2119 $localized = true; 2120 if (is_null($value) or $value === '') { 2121 return null; 2122 } elseif ($max == 0) { 2123 return 0; 2124 } else { 2125 return format_float($max * $value / 100, $this->gradedecimals, $localized); 2126 } 2127 } 2128 2129 /** 2130 * Calculates the raw (percentual) value from a real grade 2131 * 2132 * This is used in cases when a user wants to give a grade such as 12 of 20 and we need to save 2133 * this value in a raw percentual form into DB 2134 * @param float $value given grade 2135 * @param float $max the maximal grade 2136 * @return float suitable to be stored as numeric(10,5) 2137 */ 2138 public function raw_grade_value($value, $max) { 2139 if (is_null($value) or $value === '') { 2140 return null; 2141 } 2142 if ($max == 0 or $value < 0) { 2143 return 0; 2144 } 2145 $p = $value / $max * 100; 2146 if ($p > 100) { 2147 return $max; 2148 } 2149 return grade_floatval($p); 2150 } 2151 2152 /** 2153 * Calculates the real value of grade for submission 2154 * 2155 * @param float $value percentual value from 0 to 100 2156 * @return string 2157 */ 2158 public function real_grade($value) { 2159 return $this->real_grade_value($value, $this->grade); 2160 } 2161 2162 /** 2163 * Calculates the real value of grade for assessment 2164 * 2165 * @param float $value percentual value from 0 to 100 2166 * @return string 2167 */ 2168 public function real_grading_grade($value) { 2169 return $this->real_grade_value($value, $this->gradinggrade); 2170 } 2171 2172 /** 2173 * Sets the given grades and received grading grades to null 2174 * 2175 * This does not clear the information about how the peers filled the assessment forms, but 2176 * clears the calculated grades in workshop_assessments. Therefore reviewers have to re-assess 2177 * the allocated submissions. 2178 * 2179 * @return void 2180 */ 2181 public function clear_assessments() { 2182 global $DB; 2183 2184 $submissions = $this->get_submissions(); 2185 if (empty($submissions)) { 2186 // no money, no love 2187 return; 2188 } 2189 $submissions = array_keys($submissions); 2190 list($sql, $params) = $DB->get_in_or_equal($submissions, SQL_PARAMS_NAMED); 2191 $sql = "submissionid $sql"; 2192 $DB->set_field_select('workshop_assessments', 'grade', null, $sql, $params); 2193 $DB->set_field_select('workshop_assessments', 'gradinggrade', null, $sql, $params); 2194 } 2195 2196 /** 2197 * Sets the grades for submission to null 2198 * 2199 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s) 2200 * @return void 2201 */ 2202 public function clear_submission_grades($restrict=null) { 2203 global $DB; 2204 2205 $sql = "workshopid = :workshopid AND example = 0"; 2206 $params = array('workshopid' => $this->id); 2207 2208 if (is_null($restrict)) { 2209 // update all users - no more conditions 2210 } elseif (!empty($restrict)) { 2211 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); 2212 $sql .= " AND authorid $usql"; 2213 $params = array_merge($params, $uparams); 2214 } else { 2215 throw new coding_exception('Empty value is not a valid parameter here'); 2216 } 2217 2218 $DB->set_field_select('workshop_submissions', 'grade', null, $sql, $params); 2219 } 2220 2221 /** 2222 * Calculates grades for submission for the given participant(s) and updates it in the database 2223 * 2224 * @param null|int|array $restrict If null, update all authors, otherwise update just grades for the given author(s) 2225 * @return void 2226 */ 2227 public function aggregate_submission_grades($restrict=null) { 2228 global $DB; 2229 2230 // fetch a recordset with all assessments to process 2231 $sql = 'SELECT s.id AS submissionid, s.grade AS submissiongrade, 2232 a.weight, a.grade 2233 FROM {workshop_submissions} s 2234 LEFT JOIN {workshop_assessments} a ON (a.submissionid = s.id) 2235 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont. 2236 $params = array('workshopid' => $this->id); 2237 2238 if (is_null($restrict)) { 2239 // update all users - no more conditions 2240 } elseif (!empty($restrict)) { 2241 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); 2242 $sql .= " AND s.authorid $usql"; 2243 $params = array_merge($params, $uparams); 2244 } else { 2245 throw new coding_exception('Empty value is not a valid parameter here'); 2246 } 2247 2248 $sql .= ' ORDER BY s.id'; // this is important for bulk processing 2249 2250 $rs = $DB->get_recordset_sql($sql, $params); 2251 $batch = array(); // will contain a set of all assessments of a single submission 2252 $previous = null; // a previous record in the recordset 2253 2254 foreach ($rs as $current) { 2255 if (is_null($previous)) { 2256 // we are processing the very first record in the recordset 2257 $previous = $current; 2258 } 2259 if ($current->submissionid == $previous->submissionid) { 2260 // we are still processing the current submission 2261 $batch[] = $current; 2262 } else { 2263 // process all the assessments of a sigle submission 2264 $this->aggregate_submission_grades_process($batch); 2265 // and then start to process another submission 2266 $batch = array($current); 2267 $previous = $current; 2268 } 2269 } 2270 // do not forget to process the last batch! 2271 $this->aggregate_submission_grades_process($batch); 2272 $rs->close(); 2273 } 2274 2275 /** 2276 * Sets the aggregated grades for assessment to null 2277 * 2278 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s) 2279 * @return void 2280 */ 2281 public function clear_grading_grades($restrict=null) { 2282 global $DB; 2283 2284 $sql = "workshopid = :workshopid"; 2285 $params = array('workshopid' => $this->id); 2286 2287 if (is_null($restrict)) { 2288 // update all users - no more conditions 2289 } elseif (!empty($restrict)) { 2290 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); 2291 $sql .= " AND userid $usql"; 2292 $params = array_merge($params, $uparams); 2293 } else { 2294 throw new coding_exception('Empty value is not a valid parameter here'); 2295 } 2296 2297 $DB->set_field_select('workshop_aggregations', 'gradinggrade', null, $sql, $params); 2298 } 2299 2300 /** 2301 * Calculates grades for assessment for the given participant(s) 2302 * 2303 * Grade for assessment is calculated as a simple mean of all grading grades calculated by the grading evaluator. 2304 * The assessment weight is not taken into account here. 2305 * 2306 * @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewer(s) 2307 * @return void 2308 */ 2309 public function aggregate_grading_grades($restrict=null) { 2310 global $DB; 2311 2312 // fetch a recordset with all assessments to process 2313 $sql = 'SELECT a.reviewerid, a.gradinggrade, a.gradinggradeover, 2314 ag.id AS aggregationid, ag.gradinggrade AS aggregatedgrade 2315 FROM {workshop_assessments} a 2316 INNER JOIN {workshop_submissions} s ON (a.submissionid = s.id) 2317 LEFT JOIN {workshop_aggregations} ag ON (ag.userid = a.reviewerid AND ag.workshopid = s.workshopid) 2318 WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont. 2319 $params = array('workshopid' => $this->id); 2320 2321 if (is_null($restrict)) { 2322 // update all users - no more conditions 2323 } elseif (!empty($restrict)) { 2324 list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); 2325 $sql .= " AND a.reviewerid $usql"; 2326 $params = array_merge($params, $uparams); 2327 } else { 2328 throw new coding_exception('Empty value is not a valid parameter here'); 2329 } 2330 2331 $sql .= ' ORDER BY a.reviewerid'; // this is important for bulk processing 2332 2333 $rs = $DB->get_recordset_sql($sql, $params); 2334 $batch = array(); // will contain a set of all assessments of a single submission 2335 $previous = null; // a previous record in the recordset 2336 2337 foreach ($rs as $current) { 2338 if (is_null($previous)) { 2339 // we are processing the very first record in the recordset 2340 $previous = $current; 2341 } 2342 if ($current->reviewerid == $previous->reviewerid) { 2343 // we are still processing the current reviewer 2344 $batch[] = $current; 2345 } else { 2346 // process all the assessments of a sigle submission 2347 $this->aggregate_grading_grades_process($batch); 2348 // and then start to process another reviewer 2349 $batch = array($current); 2350 $previous = $current; 2351 } 2352 } 2353 // do not forget to process the last batch! 2354 $this->aggregate_grading_grades_process($batch); 2355 $rs->close(); 2356 } 2357 2358 /** 2359 * Returns the mform the teachers use to put a feedback for the reviewer 2360 * 2361 * @param moodle_url $actionurl 2362 * @param stdClass $assessment 2363 * @param array $options editable, editableweight, overridablegradinggrade 2364 * @return workshop_feedbackreviewer_form 2365 */ 2366 public function get_feedbackreviewer_form(moodle_url $actionurl, stdclass $assessment, $options=array()) { 2367 global $CFG; 2368 require_once (__DIR__ . '/feedbackreviewer_form.php'); 2369 2370 $current = new stdclass(); 2371 $current->asid = $assessment->id; 2372 $current->weight = $assessment->weight; 2373 $current->gradinggrade = $this->real_grading_grade($assessment->gradinggrade); 2374 $current->gradinggradeover = $this->real_grading_grade($assessment->gradinggradeover); 2375 $current->feedbackreviewer = $assessment->feedbackreviewer; 2376 $current->feedbackreviewerformat = $assessment->feedbackreviewerformat; 2377 if (is_null($current->gradinggrade)) { 2378 $current->gradinggrade = get_string('nullgrade', 'workshop'); 2379 } 2380 if (!isset($options['editable'])) { 2381 $editable = true; // by default 2382 } else { 2383 $editable = (bool)$options['editable']; 2384 } 2385 2386 // prepare wysiwyg editor 2387 $current = file_prepare_standard_editor($current, 'feedbackreviewer', array()); 2388 2389 return new workshop_feedbackreviewer_form($actionurl, 2390 array('workshop' => $this, 'current' => $current, 'editoropts' => array(), 'options' => $options), 2391 'post', '', null, $editable); 2392 } 2393 2394 /** 2395 * Returns the mform the teachers use to put a feedback for the author on their submission 2396 * 2397 * @param moodle_url $actionurl 2398 * @param stdClass $submission 2399 * @param array $options editable 2400 * @return workshop_feedbackauthor_form 2401 */ 2402 public function get_feedbackauthor_form(moodle_url $actionurl, stdclass $submission, $options=array()) { 2403 global $CFG; 2404 require_once (__DIR__ . '/feedbackauthor_form.php'); 2405 2406 $current = new stdclass(); 2407 $current->submissionid = $submission->id; 2408 $current->published = $submission->published; 2409 $current->grade = $this->real_grade($submission->grade); 2410 $current->gradeover = $this->real_grade($submission->gradeover); 2411 $current->feedbackauthor = $submission->feedbackauthor; 2412 $current->feedbackauthorformat = $submission->feedbackauthorformat; 2413 if (is_null($current->grade)) { 2414 $current->grade = get_string('nullgrade', 'workshop'); 2415 } 2416 if (!isset($options['editable'])) { 2417 $editable = true; // by default 2418 } else { 2419 $editable = (bool)$options['editable']; 2420 } 2421 2422 // prepare wysiwyg editor 2423 $current = file_prepare_standard_editor($current, 'feedbackauthor', array()); 2424 2425 return new workshop_feedbackauthor_form($actionurl, 2426 array('workshop' => $this, 'current' => $current, 'editoropts' => array(), 'options' => $options), 2427 'post', '', null, $editable); 2428 } 2429 2430 /** 2431 * Returns the information about the user's grades as they are stored in the gradebook 2432 * 2433 * The submission grade is returned for users with the capability mod/workshop:submit and the 2434 * assessment grade is returned for users with the capability mod/workshop:peerassess. Unless the 2435 * user has the capability to view hidden grades, grades must be visible to be returned. Null 2436 * grades are not returned. If none grade is to be returned, this method returns false. 2437 * 2438 * @param int $userid the user's id 2439 * @return workshop_final_grades|false 2440 */ 2441 public function get_gradebook_grades($userid) { 2442 global $CFG; 2443 require_once($CFG->libdir.'/gradelib.php'); 2444 2445 if (empty($userid)) { 2446 throw new coding_exception('User id expected, empty value given.'); 2447 } 2448 2449 // Read data via the Gradebook API 2450 $gradebook = grade_get_grades($this->course->id, 'mod', 'workshop', $this->id, $userid); 2451 2452 $grades = new workshop_final_grades(); 2453 2454 if (has_capability('mod/workshop:submit', $this->context, $userid)) { 2455 if (!empty($gradebook->items[0]->grades)) { 2456 $submissiongrade = reset($gradebook->items[0]->grades); 2457 if (!is_null($submissiongrade->grade)) { 2458 if (!$submissiongrade->hidden or has_capability('moodle/grade:viewhidden', $this->context, $userid)) { 2459 $grades->submissiongrade = $submissiongrade; 2460 } 2461 } 2462 } 2463 } 2464 2465 if (has_capability('mod/workshop:peerassess', $this->context, $userid)) { 2466 if (!empty($gradebook->items[1]->grades)) { 2467 $assessmentgrade = reset($gradebook->items[1]->grades); 2468 if (!is_null($assessmentgrade->grade)) { 2469 if (!$assessmentgrade->hidden or has_capability('moodle/grade:viewhidden', $this->context, $userid)) { 2470 $grades->assessmentgrade = $assessmentgrade; 2471 } 2472 } 2473 } 2474 } 2475 2476 if (!is_null($grades->submissiongrade) or !is_null($grades->assessmentgrade)) { 2477 return $grades; 2478 } 2479 2480 return false; 2481 } 2482 2483 /** 2484 * Return the editor options for the submission content field. 2485 * 2486 * @return array 2487 */ 2488 public function submission_content_options() { 2489 global $CFG; 2490 require_once($CFG->dirroot.'/repository/lib.php'); 2491 2492 return array( 2493 'trusttext' => true, 2494 'subdirs' => false, 2495 'maxfiles' => $this->nattachments, 2496 'maxbytes' => $this->maxbytes, 2497 'context' => $this->context, 2498 'return_types' => FILE_INTERNAL | FILE_EXTERNAL, 2499 ); 2500 } 2501 2502 /** 2503 * Return the filemanager options for the submission attachments field. 2504 * 2505 * @return array 2506 */ 2507 public function submission_attachment_options() { 2508 global $CFG; 2509 require_once($CFG->dirroot.'/repository/lib.php'); 2510 2511 $options = array( 2512 'subdirs' => true, 2513 'maxfiles' => $this->nattachments, 2514 'maxbytes' => $this->maxbytes, 2515 'return_types' => FILE_INTERNAL, 2516 ); 2517 2518 if ($acceptedtypes = self::normalize_file_extensions($this->submissionfiletypes)) { 2519 $options['accepted_types'] = $acceptedtypes; 2520 } 2521 2522 return $options; 2523 } 2524 2525 /** 2526 * Return the editor options for the overall feedback for the author. 2527 * 2528 * @return array 2529 */ 2530 public function overall_feedback_content_options() { 2531 global $CFG; 2532 require_once($CFG->dirroot.'/repository/lib.php'); 2533 2534 return array( 2535 'subdirs' => 0, 2536 'maxbytes' => $this->overallfeedbackmaxbytes, 2537 'maxfiles' => $this->overallfeedbackfiles, 2538 'changeformat' => 1, 2539 'context' => $this->context, 2540 'return_types' => FILE_INTERNAL, 2541 ); 2542 } 2543 2544 /** 2545 * Return the filemanager options for the overall feedback for the author. 2546 * 2547 * @return array 2548 */ 2549 public function overall_feedback_attachment_options() { 2550 global $CFG; 2551 require_once($CFG->dirroot.'/repository/lib.php'); 2552 2553 $options = array( 2554 'subdirs' => 1, 2555 'maxbytes' => $this->overallfeedbackmaxbytes, 2556 'maxfiles' => $this->overallfeedbackfiles, 2557 'return_types' => FILE_INTERNAL, 2558 ); 2559 2560 if ($acceptedtypes = self::normalize_file_extensions($this->overallfeedbackfiletypes)) { 2561 $options['accepted_types'] = $acceptedtypes; 2562 } 2563 2564 return $options; 2565 } 2566 2567 /** 2568 * Performs the reset of this workshop instance. 2569 * 2570 * @param stdClass $data The actual course reset settings. 2571 * @return array List of results, each being array[(string)component, (string)item, (string)error] 2572 */ 2573 public function reset_userdata(stdClass $data) { 2574 2575 $componentstr = get_string('pluginname', 'workshop').': '.format_string($this->name); 2576 $status = array(); 2577 2578 if (!empty($data->reset_workshop_assessments) or !empty($data->reset_workshop_submissions)) { 2579 // Reset all data related to assessments, including assessments of 2580 // example submissions. 2581 $result = $this->reset_userdata_assessments($data); 2582 if ($result === true) { 2583 $status[] = array( 2584 'component' => $componentstr, 2585 'item' => get_string('resetassessments', 'mod_workshop'), 2586 'error' => false, 2587 ); 2588 } else { 2589 $status[] = array( 2590 'component' => $componentstr, 2591 'item' => get_string('resetassessments', 'mod_workshop'), 2592 'error' => $result, 2593 ); 2594 } 2595 } 2596 2597 if (!empty($data->reset_workshop_submissions)) { 2598 // Reset all remaining data related to submissions. 2599 $result = $this->reset_userdata_submissions($data); 2600 if ($result === true) { 2601 $status[] = array( 2602 'component' => $componentstr, 2603 'item' => get_string('resetsubmissions', 'mod_workshop'), 2604 'error' => false, 2605 ); 2606 } else { 2607 $status[] = array( 2608 'component' => $componentstr, 2609 'item' => get_string('resetsubmissions', 'mod_workshop'), 2610 'error' => $result, 2611 ); 2612 } 2613 } 2614 2615 if (!empty($data->reset_workshop_phase)) { 2616 // Do not use the {@link workshop::switch_phase()} here, we do not 2617 // want to trigger events. 2618 $this->reset_phase(); 2619 $status[] = array( 2620 'component' => $componentstr, 2621 'item' => get_string('resetsubmissions', 'mod_workshop'), 2622 'error' => false, 2623 ); 2624 } 2625 2626 return $status; 2627 } 2628 2629 2630 //////////////////////////////////////////////////////////////////////////////// 2631 // Internal methods (implementation details) // 2632 //////////////////////////////////////////////////////////////////////////////// 2633 2634 /** 2635 * Given an array of all assessments of a single submission, calculates the final grade for this submission 2636 * 2637 * This calculates the weighted mean of the passed assessment grades. If, however, the submission grade 2638 * was overridden by a teacher, the gradeover value is returned and the rest of grades are ignored. 2639 * 2640 * @param array $assessments of stdclass(->submissionid ->submissiongrade ->gradeover ->weight ->grade) 2641 * @return void 2642 */ 2643 protected function aggregate_submission_grades_process(array $assessments) { 2644 global $DB; 2645 2646 $submissionid = null; // the id of the submission being processed 2647 $current = null; // the grade currently saved in database 2648 $finalgrade = null; // the new grade to be calculated 2649 $sumgrades = 0; 2650 $sumweights = 0; 2651 2652 foreach ($assessments as $assessment) { 2653 if (is_null($submissionid)) { 2654 // the id is the same in all records, fetch it during the first loop cycle 2655 $submissionid = $assessment->submissionid; 2656 } 2657 if (is_null($current)) { 2658 // the currently saved grade is the same in all records, fetch it during the first loop cycle 2659 $current = $assessment->submissiongrade; 2660 } 2661 if (is_null($assessment->grade)) { 2662 // this was not assessed yet 2663 continue; 2664 } 2665 if ($assessment->weight == 0) { 2666 // this does not influence the calculation 2667 continue; 2668 } 2669 $sumgrades += $assessment->grade * $assessment->weight; 2670 $sumweights += $assessment->weight; 2671 } 2672 if ($sumweights > 0 and is_null($finalgrade)) { 2673 $finalgrade = grade_floatval($sumgrades / $sumweights); 2674 } 2675 // check if the new final grade differs from the one stored in the database 2676 if (grade_floats_different($finalgrade, $current)) { 2677 // we need to save new calculation into the database 2678 $record = new stdclass(); 2679 $record->id = $submissionid; 2680 $record->grade = $finalgrade; 2681 $record->timegraded = time(); 2682 $DB->update_record('workshop_submissions', $record); 2683 } 2684 } 2685 2686 /** 2687 * Given an array of all assessments done by a single reviewer, calculates the final grading grade 2688 * 2689 * This calculates the simple mean of the passed grading grades. If, however, the grading grade 2690 * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored. 2691 * 2692 * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade) 2693 * @param null|int $timegraded explicit timestamp of the aggregation, defaults to the current time 2694 * @return void 2695 */ 2696 protected function aggregate_grading_grades_process(array $assessments, $timegraded = null) { 2697 global $DB; 2698 2699 $reviewerid = null; // the id of the reviewer being processed 2700 $current = null; // the gradinggrade currently saved in database 2701 $finalgrade = null; // the new grade to be calculated 2702 $agid = null; // aggregation id 2703 $sumgrades = 0; 2704 $count = 0; 2705 2706 if (is_null($timegraded)) { 2707 $timegraded = time(); 2708 } 2709 2710 foreach ($assessments as $assessment) { 2711 if (is_null($reviewerid)) { 2712 // the id is the same in all records, fetch it during the first loop cycle 2713 $reviewerid = $assessment->reviewerid; 2714 } 2715 if (is_null($agid)) { 2716 // the id is the same in all records, fetch it during the first loop cycle 2717 $agid = $assessment->aggregationid; 2718 } 2719 if (is_null($current)) { 2720 // the currently saved grade is the same in all records, fetch it during the first loop cycle 2721 $current = $assessment->aggregatedgrade; 2722 } 2723 if (!is_null($assessment->gradinggradeover)) { 2724 // the grading grade for this assessment is overridden by a teacher 2725 $sumgrades += $assessment->gradinggradeover; 2726 $count++; 2727 } else { 2728 if (!is_null($assessment->gradinggrade)) { 2729 $sumgrades += $assessment->gradinggrade; 2730 $count++; 2731 } 2732 } 2733 } 2734 if ($count > 0) { 2735 $finalgrade = grade_floatval($sumgrades / $count); 2736 } 2737 2738 // Event information. 2739 $params = array( 2740 'context' => $this->context, 2741 'courseid' => $this->course->id, 2742 'relateduserid' => $reviewerid 2743 ); 2744 2745 // check if the new final grade differs from the one stored in the database 2746 if (grade_floats_different($finalgrade, $current)) { 2747 $params['other'] = array( 2748 'currentgrade' => $current, 2749 'finalgrade' => $finalgrade 2750 ); 2751 2752 // we need to save new calculation into the database 2753 if (is_null($agid)) { 2754 // no aggregation record yet 2755 $record = new stdclass(); 2756 $record->workshopid = $this->id; 2757 $record->userid = $reviewerid; 2758 $record->gradinggrade = $finalgrade; 2759 $record->timegraded = $timegraded; 2760 $record->id = $DB->insert_record('workshop_aggregations', $record); 2761 $params['objectid'] = $record->id; 2762 $event = \mod_workshop\event\assessment_evaluated::create($params); 2763 $event->trigger(); 2764 } else { 2765 $record = new stdclass(); 2766 $record->id = $agid; 2767 $record->gradinggrade = $finalgrade; 2768 $record->timegraded = $timegraded; 2769 $DB->update_record('workshop_aggregations', $record); 2770 $params['objectid'] = $agid; 2771 $event = \mod_workshop\event\assessment_reevaluated::create($params); 2772 $event->trigger(); 2773 } 2774 } 2775 } 2776 2777 /** 2778 * Returns SQL to fetch all enrolled users with the given capability in the current workshop 2779 * 2780 * The returned array consists of string $sql and the $params array. Note that the $sql can be 2781 * empty if a grouping is selected and it has no groups. 2782 * 2783 * The list is automatically restricted according to any availability restrictions 2784 * that apply to user lists (e.g. group, grouping restrictions). 2785 * 2786 * @param string $capability the name of the capability 2787 * @param bool $musthavesubmission ff true, return only users who have already submitted 2788 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 2789 * @return array of (string)sql, (array)params 2790 */ 2791 protected function get_users_with_capability_sql($capability, $musthavesubmission, $groupid) { 2792 global $CFG; 2793 /** @var int static counter used to generate unique parameter holders */ 2794 static $inc = 0; 2795 $inc++; 2796 2797 // If the caller requests all groups and we are using a selected grouping, 2798 // recursively call this function for each group in the grouping (this is 2799 // needed because get_enrolled_sql only supports a single group). 2800 if (empty($groupid) and $this->cm->groupingid) { 2801 $groupingid = $this->cm->groupingid; 2802 $groupinggroupids = array_keys(groups_get_all_groups($this->cm->course, 0, $this->cm->groupingid, 'g.id')); 2803 $sql = array(); 2804 $params = array(); 2805 foreach ($groupinggroupids as $groupinggroupid) { 2806 if ($groupinggroupid > 0) { // just in case in order not to fall into the endless loop 2807 list($gsql, $gparams) = $this->get_users_with_capability_sql($capability, $musthavesubmission, $groupinggroupid); 2808 $sql[] = $gsql; 2809 $params = array_merge($params, $gparams); 2810 } 2811 } 2812 $sql = implode(PHP_EOL." UNION ".PHP_EOL, $sql); 2813 return array($sql, $params); 2814 } 2815 2816 list($esql, $params) = get_enrolled_sql($this->context, $capability, $groupid, true); 2817 2818 $userfields = user_picture::fields('u'); 2819 2820 $sql = "SELECT $userfields 2821 FROM {user} u 2822 JOIN ($esql) je ON (je.id = u.id AND u.deleted = 0) "; 2823 2824 if ($musthavesubmission) { 2825 $sql .= " JOIN {workshop_submissions} ws ON (ws.authorid = u.id AND ws.example = 0 AND ws.workshopid = :workshopid{$inc}) "; 2826 $params['workshopid'.$inc] = $this->id; 2827 } 2828 2829 // If the activity is restricted so that only certain users should appear 2830 // in user lists, integrate this into the same SQL. 2831 $info = new \core_availability\info_module($this->cm); 2832 list ($listsql, $listparams) = $info->get_user_list_sql(false); 2833 if ($listsql) { 2834 $sql .= " JOIN ($listsql) restricted ON restricted.id = u.id "; 2835 $params = array_merge($params, $listparams); 2836 } 2837 2838 return array($sql, $params); 2839 } 2840 2841 /** 2842 * Returns SQL statement that can be used to fetch all actively enrolled participants in the workshop 2843 * 2844 * @param bool $musthavesubmission if true, return only users who have already submitted 2845 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 2846 * @return array of (string)sql, (array)params 2847 */ 2848 protected function get_participants_sql($musthavesubmission=false, $groupid=0) { 2849 2850 list($sql1, $params1) = $this->get_users_with_capability_sql('mod/workshop:submit', $musthavesubmission, $groupid); 2851 list($sql2, $params2) = $this->get_users_with_capability_sql('mod/workshop:peerassess', $musthavesubmission, $groupid); 2852 2853 if (empty($sql1) or empty($sql2)) { 2854 if (empty($sql1) and empty($sql2)) { 2855 return array('', array()); 2856 } else if (empty($sql1)) { 2857 $sql = $sql2; 2858 $params = $params2; 2859 } else { 2860 $sql = $sql1; 2861 $params = $params1; 2862 } 2863 } else { 2864 $sql = $sql1.PHP_EOL." UNION ".PHP_EOL.$sql2; 2865 $params = array_merge($params1, $params2); 2866 } 2867 2868 return array($sql, $params); 2869 } 2870 2871 /** 2872 * @return array of available workshop phases 2873 */ 2874 protected function available_phases_list() { 2875 return array( 2876 self::PHASE_SETUP => true, 2877 self::PHASE_SUBMISSION => true, 2878 self::PHASE_ASSESSMENT => true, 2879 self::PHASE_EVALUATION => true, 2880 self::PHASE_CLOSED => true, 2881 ); 2882 } 2883 2884 /** 2885 * Converts absolute URL to relative URL needed by {@see add_to_log()} 2886 * 2887 * @param moodle_url $url absolute URL 2888 * @return string 2889 */ 2890 protected function log_convert_url(moodle_url $fullurl) { 2891 static $baseurl; 2892 2893 if (!isset($baseurl)) { 2894 $baseurl = new moodle_url('/mod/workshop/'); 2895 $baseurl = $baseurl->out(); 2896 } 2897 2898 return substr($fullurl->out(), strlen($baseurl)); 2899 } 2900 2901 /** 2902 * Removes all user data related to assessments (including allocations). 2903 * 2904 * This includes assessments of example submissions as long as they are not 2905 * referential assessments. 2906 * 2907 * @param stdClass $data The actual course reset settings. 2908 * @return bool|string True on success, error message otherwise. 2909 */ 2910 protected function reset_userdata_assessments(stdClass $data) { 2911 global $DB; 2912 2913 $sql = "SELECT a.id 2914 FROM {workshop_assessments} a 2915 JOIN {workshop_submissions} s ON (a.submissionid = s.id) 2916 WHERE s.workshopid = :workshopid 2917 AND (s.example = 0 OR (s.example = 1 AND a.weight = 0))"; 2918 2919 $assessments = $DB->get_records_sql($sql, array('workshopid' => $this->id)); 2920 $this->delete_assessment(array_keys($assessments)); 2921 2922 $DB->delete_records('workshop_aggregations', array('workshopid' => $this->id)); 2923 2924 return true; 2925 } 2926 2927 /** 2928 * Removes all user data related to participants' submissions. 2929 * 2930 * @param stdClass $data The actual course reset settings. 2931 * @return bool|string True on success, error message otherwise. 2932 */ 2933 protected function reset_userdata_submissions(stdClass $data) { 2934 global $DB; 2935 2936 $submissions = $this->get_submissions(); 2937 foreach ($submissions as $submission) { 2938 $this->delete_submission($submission); 2939 } 2940 2941 return true; 2942 } 2943 2944 /** 2945 * Hard set the workshop phase to the setup one. 2946 */ 2947 protected function reset_phase() { 2948 global $DB; 2949 2950 $DB->set_field('workshop', 'phase', self::PHASE_SETUP, array('id' => $this->id)); 2951 $this->phase = self::PHASE_SETUP; 2952 } 2953 } 2954 2955 //////////////////////////////////////////////////////////////////////////////// 2956 // Renderable components 2957 //////////////////////////////////////////////////////////////////////////////// 2958 2959 /** 2960 * Represents the user planner tool 2961 * 2962 * Planner contains list of phases. Each phase contains list of tasks. Task is a simple object with 2963 * title, link and completed (true/false/null logic). 2964 */ 2965 class workshop_user_plan implements renderable { 2966 2967 /** @var int id of the user this plan is for */ 2968 public $userid; 2969 /** @var workshop */ 2970 public $workshop; 2971 /** @var array of (stdclass)tasks */ 2972 public $phases = array(); 2973 /** @var null|array of example submissions to be assessed by the planner owner */ 2974 protected $examples = null; 2975 2976 /** 2977 * Prepare an individual workshop plan for the given user. 2978 * 2979 * @param workshop $workshop instance 2980 * @param int $userid whom the plan is prepared for 2981 */ 2982 public function __construct(workshop $workshop, $userid) { 2983 global $DB; 2984 2985 $this->workshop = $workshop; 2986 $this->userid = $userid; 2987 2988 //--------------------------------------------------------- 2989 // * SETUP | submission | assessment | evaluation | closed 2990 //--------------------------------------------------------- 2991 $phase = new stdclass(); 2992 $phase->title = get_string('phasesetup', 'workshop'); 2993 $phase->tasks = array(); 2994 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) { 2995 $task = new stdclass(); 2996 $task->title = get_string('taskintro', 'workshop'); 2997 $task->link = $workshop->updatemod_url(); 2998 $task->completed = !(trim($workshop->intro) == ''); 2999 $phase->tasks['intro'] = $task; 3000 } 3001 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) { 3002 $task = new stdclass(); 3003 $task->title = get_string('taskinstructauthors', 'workshop'); 3004 $task->link = $workshop->updatemod_url(); 3005 $task->completed = !(trim($workshop->instructauthors) == ''); 3006 $phase->tasks['instructauthors'] = $task; 3007 } 3008 if (has_capability('mod/workshop:editdimensions', $workshop->context, $userid)) { 3009 $task = new stdclass(); 3010 $task->title = get_string('editassessmentform', 'workshop'); 3011 $task->link = $workshop->editform_url(); 3012 if ($workshop->grading_strategy_instance()->form_ready()) { 3013 $task->completed = true; 3014 } elseif ($workshop->phase > workshop::PHASE_SETUP) { 3015 $task->completed = false; 3016 } 3017 $phase->tasks['editform'] = $task; 3018 } 3019 if ($workshop->useexamples and has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) { 3020 $task = new stdclass(); 3021 $task->title = get_string('prepareexamples', 'workshop'); 3022 if ($DB->count_records('workshop_submissions', array('example' => 1, 'workshopid' => $workshop->id)) > 0) { 3023 $task->completed = true; 3024 } elseif ($workshop->phase > workshop::PHASE_SETUP) { 3025 $task->completed = false; 3026 } 3027 $phase->tasks['prepareexamples'] = $task; 3028 } 3029 if (empty($phase->tasks) and $workshop->phase == workshop::PHASE_SETUP) { 3030 // if we are in the setup phase and there is no task (typical for students), let us 3031 // display some explanation what is going on 3032 $task = new stdclass(); 3033 $task->title = get_string('undersetup', 'workshop'); 3034 $task->completed = 'info'; 3035 $phase->tasks['setupinfo'] = $task; 3036 } 3037 $this->phases[workshop::PHASE_SETUP] = $phase; 3038 3039 //--------------------------------------------------------- 3040 // setup | * SUBMISSION | assessment | evaluation | closed 3041 //--------------------------------------------------------- 3042 $phase = new stdclass(); 3043 $phase->title = get_string('phasesubmission', 'workshop'); 3044 $phase->tasks = array(); 3045 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) { 3046 $task = new stdclass(); 3047 $task->title = get_string('taskinstructreviewers', 'workshop'); 3048 $task->link = $workshop->updatemod_url(); 3049 if (trim($workshop->instructreviewers)) { 3050 $task->completed = true; 3051 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) { 3052 $task->completed = false; 3053 } 3054 $phase->tasks['instructreviewers'] = $task; 3055 } 3056 if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_SUBMISSION 3057 and has_capability('mod/workshop:submit', $workshop->context, $userid, false) 3058 and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) { 3059 $task = new stdclass(); 3060 $task->title = get_string('exampleassesstask', 'workshop'); 3061 $examples = $this->get_examples(); 3062 $a = new stdclass(); 3063 $a->expected = count($examples); 3064 $a->assessed = 0; 3065 foreach ($examples as $exampleid => $example) { 3066 if (!is_null($example->grade)) { 3067 $a->assessed++; 3068 } 3069 } 3070 $task->details = get_string('exampleassesstaskdetails', 'workshop', $a); 3071 if ($a->assessed == $a->expected) { 3072 $task->completed = true; 3073 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) { 3074 $task->completed = false; 3075 } 3076 $phase->tasks['examples'] = $task; 3077 } 3078 if (has_capability('mod/workshop:submit', $workshop->context, $userid, false)) { 3079 $task = new stdclass(); 3080 $task->title = get_string('tasksubmit', 'workshop'); 3081 $task->link = $workshop->submission_url(); 3082 if ($DB->record_exists('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0, 'authorid'=>$userid))) { 3083 $task->completed = true; 3084 } elseif ($workshop->phase >= workshop::PHASE_ASSESSMENT) { 3085 $task->completed = false; 3086 } else { 3087 $task->completed = null; // still has a chance to submit 3088 } 3089 $phase->tasks['submit'] = $task; 3090 } 3091 if (has_capability('mod/workshop:allocate', $workshop->context, $userid)) { 3092 if ($workshop->phaseswitchassessment) { 3093 $task = new stdClass(); 3094 $allocator = $DB->get_record('workshopallocation_scheduled', array('workshopid' => $workshop->id)); 3095 if (empty($allocator)) { 3096 $task->completed = false; 3097 } else if ($allocator->enabled and is_null($allocator->resultstatus)) { 3098 $task->completed = true; 3099 } else if ($workshop->submissionend > time()) { 3100 $task->completed = null; 3101 } else { 3102 $task->completed = false; 3103 } 3104 $task->title = get_string('setup', 'workshopallocation_scheduled'); 3105 $task->link = $workshop->allocation_url('scheduled'); 3106 $phase->tasks['allocatescheduled'] = $task; 3107 } 3108 $task = new stdclass(); 3109 $task->title = get_string('allocate', 'workshop'); 3110 $task->link = $workshop->allocation_url(); 3111 $numofauthors = $workshop->count_potential_authors(false); 3112 $numofsubmissions = $DB->count_records('workshop_submissions', array('workshopid'=>$workshop->id, 'example'=>0)); 3113 $sql = 'SELECT COUNT(s.id) AS nonallocated 3114 FROM {workshop_submissions} s 3115 LEFT JOIN {workshop_assessments} a ON (a.submissionid=s.id) 3116 WHERE s.workshopid = :workshopid AND s.example=0 AND a.submissionid IS NULL'; 3117 $params['workshopid'] = $workshop->id; 3118 $numnonallocated = $DB->count_records_sql($sql, $params); 3119 if ($numofsubmissions == 0) { 3120 $task->completed = null; 3121 } elseif ($numnonallocated == 0) { 3122 $task->completed = true; 3123 } elseif ($workshop->phase > workshop::PHASE_SUBMISSION) { 3124 $task->completed = false; 3125 } else { 3126 $task->completed = null; // still has a chance to allocate 3127 } 3128 $a = new stdclass(); 3129 $a->expected = $numofauthors; 3130 $a->submitted = $numofsubmissions; 3131 $a->allocate = $numnonallocated; 3132 $task->details = get_string('allocatedetails', 'workshop', $a); 3133 unset($a); 3134 $phase->tasks['allocate'] = $task; 3135 3136 if ($numofsubmissions < $numofauthors and $workshop->phase >= workshop::PHASE_SUBMISSION) { 3137 $task = new stdclass(); 3138 $task->title = get_string('someuserswosubmission', 'workshop'); 3139 $task->completed = 'info'; 3140 $phase->tasks['allocateinfo'] = $task; 3141 } 3142 3143 } 3144 if ($workshop->submissionstart) { 3145 $task = new stdclass(); 3146 $task->title = get_string('submissionstartdatetime', 'workshop', workshop::timestamp_formats($workshop->submissionstart)); 3147 $task->completed = 'info'; 3148 $phase->tasks['submissionstartdatetime'] = $task; 3149 } 3150 if ($workshop->submissionend) { 3151 $task = new stdclass(); 3152 $task->title = get_string('submissionenddatetime', 'workshop', workshop::timestamp_formats($workshop->submissionend)); 3153 $task->completed = 'info'; 3154 $phase->tasks['submissionenddatetime'] = $task; 3155 } 3156 if (($workshop->submissionstart < time()) and $workshop->latesubmissions) { 3157 $task = new stdclass(); 3158 $task->title = get_string('latesubmissionsallowed', 'workshop'); 3159 $task->completed = 'info'; 3160 $phase->tasks['latesubmissionsallowed'] = $task; 3161 } 3162 if (isset($phase->tasks['submissionstartdatetime']) or isset($phase->tasks['submissionenddatetime'])) { 3163 if (has_capability('mod/workshop:ignoredeadlines', $workshop->context, $userid)) { 3164 $task = new stdclass(); 3165 $task->title = get_string('deadlinesignored', 'workshop'); 3166 $task->completed = 'info'; 3167 $phase->tasks['deadlinesignored'] = $task; 3168 } 3169 } 3170 $this->phases[workshop::PHASE_SUBMISSION] = $phase; 3171 3172 //--------------------------------------------------------- 3173 // setup | submission | * ASSESSMENT | evaluation | closed 3174 //--------------------------------------------------------- 3175 $phase = new stdclass(); 3176 $phase->title = get_string('phaseassessment', 'workshop'); 3177 $phase->tasks = array(); 3178 $phase->isreviewer = has_capability('mod/workshop:peerassess', $workshop->context, $userid); 3179 if ($workshop->phase == workshop::PHASE_SUBMISSION and $workshop->phaseswitchassessment 3180 and has_capability('mod/workshop:switchphase', $workshop->context, $userid)) { 3181 $task = new stdClass(); 3182 $task->title = get_string('switchphase30auto', 'mod_workshop', workshop::timestamp_formats($workshop->submissionend)); 3183 $task->completed = 'info'; 3184 $phase->tasks['autoswitchinfo'] = $task; 3185 } 3186 if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_ASSESSMENT 3187 and $phase->isreviewer and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) { 3188 $task = new stdclass(); 3189 $task->title = get_string('exampleassesstask', 'workshop'); 3190 $examples = $workshop->get_examples_for_reviewer($userid); 3191 $a = new stdclass(); 3192 $a->expected = count($examples); 3193 $a->assessed = 0; 3194 foreach ($examples as $exampleid => $example) { 3195 if (!is_null($example->grade)) { 3196 $a->assessed++; 3197 } 3198 } 3199 $task->details = get_string('exampleassesstaskdetails', 'workshop', $a); 3200 if ($a->assessed == $a->expected) { 3201 $task->completed = true; 3202 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) { 3203 $task->completed = false; 3204 } 3205 $phase->tasks['examples'] = $task; 3206 } 3207 if (empty($phase->tasks['examples']) or !empty($phase->tasks['examples']->completed)) { 3208 $phase->assessments = $workshop->get_assessments_by_reviewer($userid); 3209 $numofpeers = 0; // number of allocated peer-assessments 3210 $numofpeerstodo = 0; // number of peer-assessments to do 3211 $numofself = 0; // number of allocated self-assessments - should be 0 or 1 3212 $numofselftodo = 0; // number of self-assessments to do - should be 0 or 1 3213 foreach ($phase->assessments as $a) { 3214 if ($a->authorid == $userid) { 3215 $numofself++; 3216 if (is_null($a->grade)) { 3217 $numofselftodo++; 3218 } 3219 } else { 3220 $numofpeers++; 3221 if (is_null($a->grade)) { 3222 $numofpeerstodo++; 3223 } 3224 } 3225 } 3226 unset($a); 3227 if ($numofpeers) { 3228 $task = new stdclass(); 3229 if ($numofpeerstodo == 0) { 3230 $task->completed = true; 3231 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) { 3232 $task->completed = false; 3233 } 3234 $a = new stdclass(); 3235 $a->total = $numofpeers; 3236 $a->todo = $numofpeerstodo; 3237 $task->title = get_string('taskassesspeers', 'workshop'); 3238 $task->details = get_string('taskassesspeersdetails', 'workshop', $a); 3239 unset($a); 3240 $phase->tasks['assesspeers'] = $task; 3241 } 3242 if ($workshop->useselfassessment and $numofself) { 3243 $task = new stdclass(); 3244 if ($numofselftodo == 0) { 3245 $task->completed = true; 3246 } elseif ($workshop->phase > workshop::PHASE_ASSESSMENT) { 3247 $task->completed = false; 3248 } 3249 $task->title = get_string('taskassessself', 'workshop'); 3250 $phase->tasks['assessself'] = $task; 3251 } 3252 } 3253 if ($workshop->assessmentstart) { 3254 $task = new stdclass(); 3255 $task->title = get_string('assessmentstartdatetime', 'workshop', workshop::timestamp_formats($workshop->assessmentstart)); 3256 $task->completed = 'info'; 3257 $phase->tasks['assessmentstartdatetime'] = $task; 3258 } 3259 if ($workshop->assessmentend) { 3260 $task = new stdclass(); 3261 $task->title = get_string('assessmentenddatetime', 'workshop', workshop::timestamp_formats($workshop->assessmentend)); 3262 $task->completed = 'info'; 3263 $phase->tasks['assessmentenddatetime'] = $task; 3264 } 3265 if (isset($phase->tasks['assessmentstartdatetime']) or isset($phase->tasks['assessmentenddatetime'])) { 3266 if (has_capability('mod/workshop:ignoredeadlines', $workshop->context, $userid)) { 3267 $task = new stdclass(); 3268 $task->title = get_string('deadlinesignored', 'workshop'); 3269 $task->completed = 'info'; 3270 $phase->tasks['deadlinesignored'] = $task; 3271 } 3272 } 3273 $this->phases[workshop::PHASE_ASSESSMENT] = $phase; 3274 3275 //--------------------------------------------------------- 3276 // setup | submission | assessment | * EVALUATION | closed 3277 //--------------------------------------------------------- 3278 $phase = new stdclass(); 3279 $phase->title = get_string('phaseevaluation', 'workshop'); 3280 $phase->tasks = array(); 3281 if (has_capability('mod/workshop:overridegrades', $workshop->context)) { 3282 $expected = $workshop->count_potential_authors(false); 3283 $calculated = $DB->count_records_select('workshop_submissions', 3284 'workshopid = ? AND (grade IS NOT NULL OR gradeover IS NOT NULL)', array($workshop->id)); 3285 $task = new stdclass(); 3286 $task->title = get_string('calculatesubmissiongrades', 'workshop'); 3287 $a = new stdclass(); 3288 $a->expected = $expected; 3289 $a->calculated = $calculated; 3290 $task->details = get_string('calculatesubmissiongradesdetails', 'workshop', $a); 3291 if ($calculated >= $expected) { 3292 $task->completed = true; 3293 } elseif ($workshop->phase > workshop::PHASE_EVALUATION) { 3294 $task->completed = false; 3295 } 3296 $phase->tasks['calculatesubmissiongrade'] = $task; 3297 3298 $expected = $workshop->count_potential_reviewers(false); 3299 $calculated = $DB->count_records_select('workshop_aggregations', 3300 'workshopid = ? AND gradinggrade IS NOT NULL', array($workshop->id)); 3301 $task = new stdclass(); 3302 $task->title = get_string('calculategradinggrades', 'workshop'); 3303 $a = new stdclass(); 3304 $a->expected = $expected; 3305 $a->calculated = $calculated; 3306 $task->details = get_string('calculategradinggradesdetails', 'workshop', $a); 3307 if ($calculated >= $expected) { 3308 $task->completed = true; 3309 } elseif ($workshop->phase > workshop::PHASE_EVALUATION) { 3310 $task->completed = false; 3311 } 3312 $phase->tasks['calculategradinggrade'] = $task; 3313 3314 } elseif ($workshop->phase == workshop::PHASE_EVALUATION) { 3315 $task = new stdclass(); 3316 $task->title = get_string('evaluategradeswait', 'workshop'); 3317 $task->completed = 'info'; 3318 $phase->tasks['evaluateinfo'] = $task; 3319 } 3320 3321 if (has_capability('moodle/course:manageactivities', $workshop->context, $userid)) { 3322 $task = new stdclass(); 3323 $task->title = get_string('taskconclusion', 'workshop'); 3324 $task->link = $workshop->updatemod_url(); 3325 if (trim($workshop->conclusion)) { 3326 $task->completed = true; 3327 } elseif ($workshop->phase >= workshop::PHASE_EVALUATION) { 3328 $task->completed = false; 3329 } 3330 $phase->tasks['conclusion'] = $task; 3331 } 3332 3333 $this->phases[workshop::PHASE_EVALUATION] = $phase; 3334 3335 //--------------------------------------------------------- 3336 // setup | submission | assessment | evaluation | * CLOSED 3337 //--------------------------------------------------------- 3338 $phase = new stdclass(); 3339 $phase->title = get_string('phaseclosed', 'workshop'); 3340 $phase->tasks = array(); 3341 $this->phases[workshop::PHASE_CLOSED] = $phase; 3342 3343 // Polish data, set default values if not done explicitly 3344 foreach ($this->phases as $phasecode => $phase) { 3345 $phase->title = isset($phase->title) ? $phase->title : ''; 3346 $phase->tasks = isset($phase->tasks) ? $phase->tasks : array(); 3347 if ($phasecode == $workshop->phase) { 3348 $phase->active = true; 3349 } else { 3350 $phase->active = false; 3351 } 3352 if (!isset($phase->actions)) { 3353 $phase->actions = array(); 3354 } 3355 3356 foreach ($phase->tasks as $taskcode => $task) { 3357 $task->title = isset($task->title) ? $task->title : ''; 3358 $task->link = isset($task->link) ? $task->link : null; 3359 $task->details = isset($task->details) ? $task->details : ''; 3360 $task->completed = isset($task->completed) ? $task->completed : null; 3361 } 3362 } 3363 3364 // Add phase switching actions 3365 if (has_capability('mod/workshop:switchphase', $workshop->context, $userid)) { 3366 foreach ($this->phases as $phasecode => $phase) { 3367 if (! $phase->active) { 3368 $action = new stdclass(); 3369 $action->type = 'switchphase'; 3370 $action->url = $workshop->switchphase_url($phasecode); 3371 $phase->actions[] = $action; 3372 } 3373 } 3374 } 3375 } 3376 3377 /** 3378 * Returns example submissions to be assessed by the owner of the planner 3379 * 3380 * This is here to cache the DB query because the same list is needed later in view.php 3381 * 3382 * @see workshop::get_examples_for_reviewer() for the format of returned value 3383 * @return array 3384 */ 3385 public function get_examples() { 3386 if (is_null($this->examples)) { 3387 $this->examples = $this->workshop->get_examples_for_reviewer($this->userid); 3388 } 3389 return $this->examples; 3390 } 3391 } 3392 3393 /** 3394 * Common base class for submissions and example submissions rendering 3395 * 3396 * Subclasses of this class convert raw submission record from 3397 * workshop_submissions table (as returned by {@see workshop::get_submission_by_id()} 3398 * for example) into renderable objects. 3399 */ 3400 abstract class workshop_submission_base { 3401 3402 /** @var bool is the submission anonymous (i.e. contains author information) */ 3403 protected $anonymous; 3404 3405 /* @var array of columns from workshop_submissions that are assigned as properties */ 3406 protected $fields = array(); 3407 3408 /** @var workshop */ 3409 protected $workshop; 3410 3411 /** 3412 * Copies the properties of the given database record into properties of $this instance 3413 * 3414 * @param workshop $workshop 3415 * @param stdClass $submission full record 3416 * @param bool $showauthor show the author-related information 3417 * @param array $options additional properties 3418 */ 3419 public function __construct(workshop $workshop, stdClass $submission, $showauthor = false) { 3420 3421 $this->workshop = $workshop; 3422 3423 foreach ($this->fields as $field) { 3424 if (!property_exists($submission, $field)) { 3425 throw new coding_exception('Submission record must provide public property ' . $field); 3426 } 3427 if (!property_exists($this, $field)) { 3428 throw new coding_exception('Renderable component must accept public property ' . $field); 3429 } 3430 $this->{$field} = $submission->{$field}; 3431 } 3432 3433 if ($showauthor) { 3434 $this->anonymous = false; 3435 } else { 3436 $this->anonymize(); 3437 } 3438 } 3439 3440 /** 3441 * Unsets all author-related properties so that the renderer does not have access to them 3442 * 3443 * Usually this is called by the contructor but can be called explicitely, too. 3444 */ 3445 public function anonymize() { 3446 $authorfields = explode(',', user_picture::fields()); 3447 foreach ($authorfields as $field) { 3448 $prefixedusernamefield = 'author' . $field; 3449 unset($this->{$prefixedusernamefield}); 3450 } 3451 $this->anonymous = true; 3452 } 3453 3454 /** 3455 * Does the submission object contain author-related information? 3456 * 3457 * @return null|boolean 3458 */ 3459 public function is_anonymous() { 3460 return $this->anonymous; 3461 } 3462 } 3463 3464 /** 3465 * Renderable object containing a basic set of information needed to display the submission summary 3466 * 3467 * @see workshop_renderer::render_workshop_submission_summary 3468 */ 3469 class workshop_submission_summary extends workshop_submission_base implements renderable { 3470 3471 /** @var int */ 3472 public $id; 3473 /** @var string */ 3474 public $title; 3475 /** @var string graded|notgraded */ 3476 public $status; 3477 /** @var int */ 3478 public $timecreated; 3479 /** @var int */ 3480 public $timemodified; 3481 /** @var int */ 3482 public $authorid; 3483 /** @var string */ 3484 public $authorfirstname; 3485 /** @var string */ 3486 public $authorlastname; 3487 /** @var string */ 3488 public $authorfirstnamephonetic; 3489 /** @var string */ 3490 public $authorlastnamephonetic; 3491 /** @var string */ 3492 public $authormiddlename; 3493 /** @var string */ 3494 public $authoralternatename; 3495 /** @var int */ 3496 public $authorpicture; 3497 /** @var string */ 3498 public $authorimagealt; 3499 /** @var string */ 3500 public $authoremail; 3501 /** @var moodle_url to display submission */ 3502 public $url; 3503 3504 /** 3505 * @var array of columns from workshop_submissions that are assigned as properties 3506 * of instances of this class 3507 */ 3508 protected $fields = array( 3509 'id', 'title', 'timecreated', 'timemodified', 3510 'authorid', 'authorfirstname', 'authorlastname', 'authorfirstnamephonetic', 'authorlastnamephonetic', 3511 'authormiddlename', 'authoralternatename', 'authorpicture', 3512 'authorimagealt', 'authoremail'); 3513 } 3514 3515 /** 3516 * Renderable object containing all the information needed to display the submission 3517 * 3518 * @see workshop_renderer::render_workshop_submission() 3519 */ 3520 class workshop_submission extends workshop_submission_summary implements renderable { 3521 3522 /** @var string */ 3523 public $content; 3524 /** @var int */ 3525 public $contentformat; 3526 /** @var bool */ 3527 public $contenttrust; 3528 /** @var array */ 3529 public $attachment; 3530 3531 /** 3532 * @var array of columns from workshop_submissions that are assigned as properties 3533 * of instances of this class 3534 */ 3535 protected $fields = array( 3536 'id', 'title', 'timecreated', 'timemodified', 'content', 'contentformat', 'contenttrust', 3537 'attachment', 'authorid', 'authorfirstname', 'authorlastname', 'authorfirstnamephonetic', 'authorlastnamephonetic', 3538 'authormiddlename', 'authoralternatename', 'authorpicture', 'authorimagealt', 'authoremail'); 3539 } 3540 3541 /** 3542 * Renderable object containing a basic set of information needed to display the example submission summary 3543 * 3544 * @see workshop::prepare_example_summary() 3545 * @see workshop_renderer::render_workshop_example_submission_summary() 3546 */ 3547 class workshop_example_submission_summary extends workshop_submission_base implements renderable { 3548 3549 /** @var int */ 3550 public $id; 3551 /** @var string */ 3552 public $title; 3553 /** @var string graded|notgraded */ 3554 public $status; 3555 /** @var stdClass */ 3556 public $gradeinfo; 3557 /** @var moodle_url */ 3558 public $url; 3559 /** @var moodle_url */ 3560 public $editurl; 3561 /** @var string */ 3562 public $assesslabel; 3563 /** @var moodle_url */ 3564 public $assessurl; 3565 /** @var bool must be set explicitly by the caller */ 3566 public $editable = false; 3567 3568 /** 3569 * @var array of columns from workshop_submissions that are assigned as properties 3570 * of instances of this class 3571 */ 3572 protected $fields = array('id', 'title'); 3573 3574 /** 3575 * Example submissions are always anonymous 3576 * 3577 * @return true 3578 */ 3579 public function is_anonymous() { 3580 return true; 3581 } 3582 } 3583 3584 /** 3585 * Renderable object containing all the information needed to display the example submission 3586 * 3587 * @see workshop_renderer::render_workshop_example_submission() 3588 */ 3589 class workshop_example_submission extends workshop_example_submission_summary implements renderable { 3590 3591 /** @var string */ 3592 public $content; 3593 /** @var int */ 3594 public $contentformat; 3595 /** @var bool */ 3596 public $contenttrust; 3597 /** @var array */ 3598 public $attachment; 3599 3600 /** 3601 * @var array of columns from workshop_submissions that are assigned as properties 3602 * of instances of this class 3603 */ 3604 protected $fields = array('id', 'title', 'content', 'contentformat', 'contenttrust', 'attachment'); 3605 } 3606 3607 3608 /** 3609 * Common base class for assessments rendering 3610 * 3611 * Subclasses of this class convert raw assessment record from 3612 * workshop_assessments table (as returned by {@see workshop::get_assessment_by_id()} 3613 * for example) into renderable objects. 3614 */ 3615 abstract class workshop_assessment_base { 3616 3617 /** @var string the optional title of the assessment */ 3618 public $title = ''; 3619 3620 /** @var workshop_assessment_form $form as returned by {@link workshop_strategy::get_assessment_form()} */ 3621 public $form; 3622 3623 /** @var moodle_url */ 3624 public $url; 3625 3626 /** @var float|null the real received grade */ 3627 public $realgrade = null; 3628 3629 /** @var float the real maximum grade */ 3630 public $maxgrade; 3631 3632 /** @var stdClass|null reviewer user info */ 3633 public $reviewer = null; 3634 3635 /** @var stdClass|null assessed submission's author user info */ 3636 public $author = null; 3637 3638 /** @var array of actions */ 3639 public $actions = array(); 3640 3641 /* @var array of columns that are assigned as properties */ 3642 protected $fields = array(); 3643 3644 /** @var workshop */ 3645 protected $workshop; 3646 3647 /** 3648 * Copies the properties of the given database record into properties of $this instance 3649 * 3650 * The $options keys are: showreviewer, showauthor 3651 * @param workshop $workshop 3652 * @param stdClass $assessment full record 3653 * @param array $options additional properties 3654 */ 3655 public function __construct(workshop $workshop, stdClass $record, array $options = array()) { 3656 3657 $this->workshop = $workshop; 3658 $this->validate_raw_record($record); 3659 3660 foreach ($this->fields as $field) { 3661 if (!property_exists($record, $field)) { 3662 throw new coding_exception('Assessment record must provide public property ' . $field); 3663 } 3664 if (!property_exists($this, $field)) { 3665 throw new coding_exception('Renderable component must accept public property ' . $field); 3666 } 3667 $this->{$field} = $record->{$field}; 3668 } 3669 3670 if (!empty($options['showreviewer'])) { 3671 $this->reviewer = user_picture::unalias($record, null, 'revieweridx', 'reviewer'); 3672 } 3673 3674 if (!empty($options['showauthor'])) { 3675 $this->author = user_picture::unalias($record, null, 'authorid', 'author'); 3676 } 3677 } 3678 3679 /** 3680 * Adds a new action 3681 * 3682 * @param moodle_url $url action URL 3683 * @param string $label action label 3684 * @param string $method get|post 3685 */ 3686 public function add_action(moodle_url $url, $label, $method = 'get') { 3687 3688 $action = new stdClass(); 3689 $action->url = $url; 3690 $action->label = $label; 3691 $action->method = $method; 3692 3693 $this->actions[] = $action; 3694 } 3695 3696 /** 3697 * Makes sure that we can cook the renderable component from the passed raw database record 3698 * 3699 * @param stdClass $assessment full assessment record 3700 * @throws coding_exception if the caller passed unexpected data 3701 */ 3702 protected function validate_raw_record(stdClass $record) { 3703 // nothing to do here 3704 } 3705 } 3706 3707 3708 /** 3709 * Represents a rendarable full assessment 3710 */ 3711 class workshop_assessment extends workshop_assessment_base implements renderable { 3712 3713 /** @var int */ 3714 public $id; 3715 3716 /** @var int */ 3717 public $submissionid; 3718 3719 /** @var int */ 3720 public $weight; 3721 3722 /** @var int */ 3723 public $timecreated; 3724 3725 /** @var int */ 3726 public $timemodified; 3727 3728 /** @var float */ 3729 public $grade; 3730 3731 /** @var float */ 3732 public $gradinggrade; 3733 3734 /** @var float */ 3735 public $gradinggradeover; 3736 3737 /** @var string */ 3738 public $feedbackauthor; 3739 3740 /** @var int */ 3741 public $feedbackauthorformat; 3742 3743 /** @var int */ 3744 public $feedbackauthorattachment; 3745 3746 /** @var array */ 3747 protected $fields = array('id', 'submissionid', 'weight', 'timecreated', 3748 'timemodified', 'grade', 'gradinggrade', 'gradinggradeover', 'feedbackauthor', 3749 'feedbackauthorformat', 'feedbackauthorattachment'); 3750 3751 /** 3752 * Format the overall feedback text content 3753 * 3754 * False is returned if the overall feedback feature is disabled. Null is returned 3755 * if the overall feedback content has not been found. Otherwise, string with 3756 * formatted feedback text is returned. 3757 * 3758 * @return string|bool|null 3759 */ 3760 public function get_overall_feedback_content() { 3761 3762 if ($this->workshop->overallfeedbackmode == 0) { 3763 return false; 3764 } 3765 3766 if (trim($this->feedbackauthor) === '') { 3767 return null; 3768 } 3769 3770 $content = file_rewrite_pluginfile_urls($this->feedbackauthor, 'pluginfile.php', $this->workshop->context->id, 3771 'mod_workshop', 'overallfeedback_content', $this->id); 3772 $content = format_text($content, $this->feedbackauthorformat, 3773 array('overflowdiv' => true, 'context' => $this->workshop->context)); 3774 3775 return $content; 3776 } 3777 3778 /** 3779 * Prepares the list of overall feedback attachments 3780 * 3781 * Returns false if overall feedback attachments are not allowed. Otherwise returns 3782 * list of attachments (may be empty). 3783 * 3784 * @return bool|array of stdClass 3785 */ 3786 public function get_overall_feedback_attachments() { 3787 3788 if ($this->workshop->overallfeedbackmode == 0) { 3789 return false; 3790 } 3791 3792 if ($this->workshop->overallfeedbackfiles == 0) { 3793 return false; 3794 } 3795 3796 if (empty($this->feedbackauthorattachment)) { 3797 return array(); 3798 } 3799 3800 $attachments = array(); 3801 $fs = get_file_storage(); 3802 $files = $fs->get_area_files($this->workshop->context->id, 'mod_workshop', 'overallfeedback_attachment', $this->id); 3803 foreach ($files as $file) { 3804 if ($file->is_directory()) { 3805 continue; 3806 } 3807 $filepath = $file->get_filepath(); 3808 $filename = $file->get_filename(); 3809 $fileurl = moodle_url::make_pluginfile_url($this->workshop->context->id, 'mod_workshop', 3810 'overallfeedback_attachment', $this->id, $filepath, $filename, true); 3811 $previewurl = new moodle_url(moodle_url::make_pluginfile_url($this->workshop->context->id, 'mod_workshop', 3812 'overallfeedback_attachment', $this->id, $filepath, $filename, false), array('preview' => 'bigthumb')); 3813 $attachments[] = (object)array( 3814 'filepath' => $filepath, 3815 'filename' => $filename, 3816 'fileurl' => $fileurl, 3817 'previewurl' => $previewurl, 3818 'mimetype' => $file->get_mimetype(), 3819 3820 ); 3821 } 3822 3823 return $attachments; 3824 } 3825 } 3826 3827 3828 /** 3829 * Represents a renderable training assessment of an example submission 3830 */ 3831 class workshop_example_assessment extends workshop_assessment implements renderable { 3832 3833 /** 3834 * @see parent::validate_raw_record() 3835 */ 3836 protected function validate_raw_record(stdClass $record) { 3837 if ($record->weight != 0) { 3838 throw new coding_exception('Invalid weight of example submission assessment'); 3839 } 3840 parent::validate_raw_record($record); 3841 } 3842 } 3843 3844 3845 /** 3846 * Represents a renderable reference assessment of an example submission 3847 */ 3848 class workshop_example_reference_assessment extends workshop_assessment implements renderable { 3849 3850 /** 3851 * @see parent::validate_raw_record() 3852 */ 3853 protected function validate_raw_record(stdClass $record) { 3854 if ($record->weight != 1) { 3855 throw new coding_exception('Invalid weight of the reference example submission assessment'); 3856 } 3857 parent::validate_raw_record($record); 3858 } 3859 } 3860 3861 3862 /** 3863 * Renderable message to be displayed to the user 3864 * 3865 * Message can contain an optional action link with a label that is supposed to be rendered 3866 * as a button or a link. 3867 * 3868 * @see workshop::renderer::render_workshop_message() 3869 */ 3870 class workshop_message implements renderable { 3871 3872 const TYPE_INFO = 10; 3873 const TYPE_OK = 20; 3874 const TYPE_ERROR = 30; 3875 3876 /** @var string */ 3877 protected $text = ''; 3878 /** @var int */ 3879 protected $type = self::TYPE_INFO; 3880 /** @var moodle_url */ 3881 protected $actionurl = null; 3882 /** @var string */ 3883 protected $actionlabel = ''; 3884 3885 /** 3886 * @param string $text short text to be displayed 3887 * @param string $type optional message type info|ok|error 3888 */ 3889 public function __construct($text = null, $type = self::TYPE_INFO) { 3890 $this->set_text($text); 3891 $this->set_type($type); 3892 } 3893 3894 /** 3895 * Sets the message text 3896 * 3897 * @param string $text short text to be displayed 3898 */ 3899 public function set_text($text) { 3900 $this->text = $text; 3901 } 3902 3903 /** 3904 * Sets the message type 3905 * 3906 * @param int $type 3907 */ 3908 public function set_type($type = self::TYPE_INFO) { 3909 if (in_array($type, array(self::TYPE_OK, self::TYPE_ERROR, self::TYPE_INFO))) { 3910 $this->type = $type; 3911 } else { 3912 throw new coding_exception('Unknown message type.'); 3913 } 3914 } 3915 3916 /** 3917 * Sets the optional message action 3918 * 3919 * @param moodle_url $url to follow on action 3920 * @param string $label action label 3921 */ 3922 public function set_action(moodle_url $url, $label) { 3923 $this->actionurl = $url; 3924 $this->actionlabel = $label; 3925 } 3926 3927 /** 3928 * Returns message text with HTML tags quoted 3929 * 3930 * @return string 3931 */ 3932 public function get_message() { 3933 return s($this->text); 3934 } 3935 3936 /** 3937 * Returns message type 3938 * 3939 * @return int 3940 */ 3941 public function get_type() { 3942 return $this->type; 3943 } 3944 3945 /** 3946 * Returns action URL 3947 * 3948 * @return moodle_url|null 3949 */ 3950 public function get_action_url() { 3951 return $this->actionurl; 3952 } 3953 3954 /** 3955 * Returns action label 3956 * 3957 * @return string 3958 */ 3959 public function get_action_label() { 3960 return $this->actionlabel; 3961 } 3962 } 3963 3964 3965 /** 3966 * Renderable component containing all the data needed to display the grading report 3967 */ 3968 class workshop_grading_report implements renderable { 3969 3970 /** @var stdClass returned by {@see workshop::prepare_grading_report_data()} */ 3971 protected $data; 3972 /** @var stdClass rendering options */ 3973 protected $options; 3974 3975 /** 3976 * Grades in $data must be already rounded to the set number of decimals or must be null 3977 * (in which later case, the [mod_workshop,nullgrade] string shall be displayed) 3978 * 3979 * @param stdClass $data prepared by {@link workshop::prepare_grading_report_data()} 3980 * @param stdClass $options display options (showauthornames, showreviewernames, sortby, sorthow, showsubmissiongrade, showgradinggrade) 3981 */ 3982 public function __construct(stdClass $data, stdClass $options) { 3983 $this->data = $data; 3984 $this->options = $options; 3985 } 3986 3987 /** 3988 * @return stdClass grading report data 3989 */ 3990 public function get_data() { 3991 return $this->data; 3992 } 3993 3994 /** 3995 * @return stdClass rendering options 3996 */ 3997 public function get_options() { 3998 return $this->options; 3999 } 4000 } 4001 4002 4003 /** 4004 * Base class for renderable feedback for author and feedback for reviewer 4005 */ 4006 abstract class workshop_feedback { 4007 4008 /** @var stdClass the user info */ 4009 protected $provider = null; 4010 4011 /** @var string the feedback text */ 4012 protected $content = null; 4013 4014 /** @var int format of the feedback text */ 4015 protected $format = null; 4016 4017 /** 4018 * @return stdClass the user info 4019 */ 4020 public function get_provider() { 4021 4022 if (is_null($this->provider)) { 4023 throw new coding_exception('Feedback provider not set'); 4024 } 4025 4026 return $this->provider; 4027 } 4028 4029 /** 4030 * @return string the feedback text 4031 */ 4032 public function get_content() { 4033 4034 if (is_null($this->content)) { 4035 throw new coding_exception('Feedback content not set'); 4036 } 4037 4038 return $this->content; 4039 } 4040 4041 /** 4042 * @return int format of the feedback text 4043 */ 4044 public function get_format() { 4045 4046 if (is_null($this->format)) { 4047 throw new coding_exception('Feedback text format not set'); 4048 } 4049 4050 return $this->format; 4051 } 4052 } 4053 4054 4055 /** 4056 * Renderable feedback for the author of submission 4057 */ 4058 class workshop_feedback_author extends workshop_feedback implements renderable { 4059 4060 /** 4061 * Extracts feedback from the given submission record 4062 * 4063 * @param stdClass $submission record as returned by {@see self::get_submission_by_id()} 4064 */ 4065 public function __construct(stdClass $submission) { 4066 4067 $this->provider = user_picture::unalias($submission, null, 'gradeoverbyx', 'gradeoverby'); 4068 $this->content = $submission->feedbackauthor; 4069 $this->format = $submission->feedbackauthorformat; 4070 } 4071 } 4072 4073 4074 /** 4075 * Renderable feedback for the reviewer 4076 */ 4077 class workshop_feedback_reviewer extends workshop_feedback implements renderable { 4078 4079 /** 4080 * Extracts feedback from the given assessment record 4081 * 4082 * @param stdClass $assessment record as returned by eg {@see self::get_assessment_by_id()} 4083 */ 4084 public function __construct(stdClass $assessment) { 4085 4086 $this->provider = user_picture::unalias($assessment, null, 'gradinggradeoverbyx', 'overby'); 4087 $this->content = $assessment->feedbackreviewer; 4088 $this->format = $assessment->feedbackreviewerformat; 4089 } 4090 } 4091 4092 4093 /** 4094 * Holds the final grades for the activity as are stored in the gradebook 4095 */ 4096 class workshop_final_grades implements renderable { 4097 4098 /** @var object the info from the gradebook about the grade for submission */ 4099 public $submissiongrade = null; 4100 4101 /** @var object the infor from the gradebook about the grade for assessment */ 4102 public $assessmentgrade = null; 4103 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |