first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Aggregates the grades for submission and grades for assessments
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$cmid = required_param('cmid', PARAM_INT); // course module
$confirm = optional_param('confirm', false, PARAM_BOOL); // confirmation
// the params to be re-passed to view.php
$page = optional_param('page', 0, PARAM_INT);
$sortby = optional_param('sortby', 'lastname', PARAM_ALPHA);
$sorthow = optional_param('sorthow', 'ASC', PARAM_ALPHA);
$cm = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$workshop = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
$PAGE->set_url($workshop->aggregate_url(), compact('confirm', 'page', 'sortby', 'sorthow'));
require_login($course, false, $cm);
require_capability('mod/workshop:overridegrades', $PAGE->context);
// load and init the grading evaluator
$evaluator = $workshop->grading_evaluation_instance();
$settingsform = $evaluator->get_settings_form($PAGE->url);
if ($settingsdata = $settingsform->get_data()) {
$workshop->aggregate_submission_grades(); // updates 'grade' in {workshop_submissions}
$evaluator->update_grading_grades($settingsdata); // updates 'gradinggrade' in {workshop_assessments}
$workshop->aggregate_grading_grades(); // updates 'gradinggrade' in {workshop_aggregations}
}
redirect(new moodle_url($workshop->view_url(), compact('page', 'sortby', 'sorthow')));
+78
View File
@@ -0,0 +1,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* At this page, teachers allocate submissions to students for a review
*
* The allocation logic itself is delegated to allocators - subplugins in ./allocation
* folder.
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
require_once(__DIR__.'/allocation/lib.php');
$cmid = required_param('cmid', PARAM_INT); // course module
$method = optional_param('method', 'manual', PARAM_ALPHA); // method to use
$cm = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$workshop = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
$url = $workshop->allocation_url($method);
$PAGE->set_url($url);
require_login($course, false, $cm);
$context = $PAGE->context;
require_capability('mod/workshop:allocate', $context);
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add(get_string('allocation', 'workshop'), $workshop->allocation_url($method));
$PAGE->activityheader->set_attrs([
'hidecompletion' => true,
'description' => ''
]);
$allocator = $workshop->allocator_instance($method);
$initresult = $allocator->init();
//
// Output starts here
//
$actionbar = new \mod_workshop\output\actionbar($url, $workshop);
$output = $PAGE->get_renderer('mod_workshop');
echo $output->header();
echo $output->render_allocation_menu($actionbar);
if (is_null($initresult->get_status()) or $initresult->get_status() == workshop_allocation_result::STATUS_VOID) {
echo $output->container_start('allocator-ui');
echo $allocator->ui();
echo $output->container_end();
} else {
echo $output->container_start('allocator-init-results');
echo $output->render($initresult);
echo $output->continue_button($workshop->allocation_url($method));
echo $output->container_end();
}
echo $output->footer();
+182
View File
@@ -0,0 +1,182 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Code for the submissions allocation support is defined here
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Allocators are responsible for assigning submissions to reviewers for assessments
*
* The task of the allocator is to assign the correct number of submissions to reviewers
* for assessment. Several allocation methods are expected and they can be combined. For
* example, teacher can allocate several submissions manually (by 'manual' allocator) and
* then let the other submissions being allocated randomly (by 'random' allocator).
* Allocation is actually done by creating an initial assessment record in the
* workshop_assessments table.
*/
interface workshop_allocator {
/**
* Initialize the allocator and eventually process submitted data
*
* This method is called soon after the allocator is constructed and before any output
* is generated. Therefore it may process any data submitted and do other tasks.
* It must not produce any output.
*
* @throws moodle_exception
* @return workshop_allocation_result
*/
public function init();
/**
* Print HTML to be displayed as the user interface
*
* If a form is part of the UI, the caller should have called $PAGE->set_url(...)
*
* @param stdClass $wsoutput workshop module renderer can be used
* @return string HTML code to be echoed
*/
public function ui();
/**
* Delete all data related to a given workshop module instance
*
* This is called from {@link workshop_delete_instance()}.
*
* @param int $workshopid id of the workshop module instance being deleted
* @return void
*/
public static function delete_instance($workshopid);
}
/**
* Stores the information about the allocation process
*
* Allocator's method init() returns instance of this class.
*/
class workshop_allocation_result implements renderable {
/** the init() called successfully but no actual allocation was done */
const STATUS_VOID = 0;
/** allocation was successfully executed */
const STATUS_EXECUTED = 1;
/** a serious error has occurred during the allocation (as a hole) */
const STATUS_FAILED = 2;
/** scheduled allocation was configured (to be executed later, for example) */
const STATUS_CONFIGURED = 3;
/** @var workshop_allocator the instance of the allocator that produced this result */
protected $allocator;
/** @var null|int the status of the init() call */
protected $status = null;
/** @var null|string optional result message to display */
protected $message = null;
/** @var int the timestamp of when the allocation process started */
protected $timestart = null;
/** @var int the timestamp of when the final status was set */
protected $timeend = null;
/** @var array of log message objects, {@see self::log()} */
protected $logs = array();
/**
* Creates new instance of the object
*
* @param workshop_allocator $allocator
*/
public function __construct(workshop_allocator $allocator) {
$this->allocator = $allocator;
$this->timestart = time();
}
/**
* Sets the result status of the allocation
*
* @param int $status the status code, eg {@link self::STATUS_OK}
* @param string $message optional status message
*/
public function set_status($status, $message = null) {
$this->status = $status;
$this->message = is_null($message) ? $this->message : $message;
$this->timeend = time();
}
/**
* @return int|null the result status
*/
public function get_status() {
return $this->status;
}
/**
* @return string|null status message
*/
public function get_message() {
return $this->message;
}
/**
* @return int|null the timestamp of when the final status was set
*/
public function get_timeend() {
return $this->timeend;
}
/**
* Appends a new message to the log
*
* The available levels are
* ok - success, eg. new allocation was created
* info - informational message
* error - error message, eg. no more peers available
* debug - debugging info
*
* @param string $message message text to display
* @param string $type the type of the message
* @param int $indent eventual indentation level (the message is related to the previous one with the lower indent)
*/
public function log($message, $type = 'ok', $indent = 0) {
$log = new stdClass();
$log->message = $message;
$log->type = $type;
$log->indent = $indent;
$this->logs[] = $log;
}
/**
* Returns list of logged messages
*
* Each object in the list has public properties
* message string, text to display
* type string, the type of the message
* indent int, indentation level
*
* @see self::log()
* @return array of log objects
*/
public function get_logs() {
return $this->logs;
}
}
@@ -0,0 +1,68 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the class {@link workshopallocation_manual\privacy\provider}
*
* @package workshopallocation_manual
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_manual\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy API implementation for the Manual allocation method.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\user_preference_provider {
/**
* Describe all the places where this plugin stores some personal data.
*
* @param collection $collection Collection of items to add metadata to.
* @return collection Collection with our added items.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_user_preference('workshopallocation_manual_perpage', 'privacy:metadata:preference:perpage');
return $collection;
}
/**
* Export user preferences controlled by this plugin.
*
* @param int $userid ID of the user we are exporting data form.
*/
public static function export_user_preferences(int $userid) {
$perpage = get_user_preferences('workshopallocation_manual_perpage', null, $userid);
if ($perpage !== null) {
writer::export_user_preference('workshopallocation_manual', 'workshopallocation_manual_perpage', $perpage,
get_string('privacy:metadata:preference:perpage', 'workshopallocation_manual'));
}
}
}
@@ -0,0 +1,35 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'workshopallocation_manual', language 'en', branch 'MOODLE_20_STABLE'
*
* @package workshopallocation
* @subpackage manual
* @copyright 2009 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addreviewee'] = 'Add reviewee';
$string['addreviewer'] = 'Add reviewer';
$string['allocationadded'] = 'The submission has been successfully allocated';
$string['allocationexists'] = 'The allocation already exists';
$string['areyousuretodeallocate'] = 'Are you sure you want to deallocate the selected assessment?';
$string['areyousuretodeallocategraded'] = 'You are going to remove the assessment that has already been graded. Are you really sure you want to do it?';
$string['pluginname'] = 'Manual allocation';
$string['privacy:metadata:preference:perpage'] = 'Number of allocated assessments the user prefers to see on one page.';
$string['showallparticipants'] = 'Show all participants';
+395
View File
@@ -0,0 +1,395 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Allows user to allocate the submissions manually
*
* @package workshopallocation
* @subpackage manual
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../lib.php'); // interface definition
require_once(__DIR__ . '/../../locallib.php'); // workshop internal API
/**
* Allows users to allocate submissions for review manually
*/
class workshop_manual_allocator implements workshop_allocator {
/** constants that are used to pass status messages between init() and ui() */
const MSG_ADDED = 1;
const MSG_NOSUBMISSION = 2;
const MSG_EXISTS = 3;
const MSG_CONFIRM_DEL = 4;
const MSG_DELETED = 5;
const MSG_DELETE_ERROR = 6;
/** @var workshop instance */
protected $workshop;
/**
* @param workshop $workshop Workshop API object
*/
public function __construct(workshop $workshop) {
$this->workshop = $workshop;
}
/**
* Allocate submissions as requested by user
*
* @return workshop_allocation_result
*/
public function init() {
global $PAGE;
$mode = optional_param('mode', 'display', PARAM_ALPHA);
$perpage = optional_param('perpage', null, PARAM_INT);
if ($perpage and $perpage > 0 and $perpage <= 1000) {
require_sesskey();
set_user_preference('workshopallocation_manual_perpage', $perpage);
redirect($PAGE->url);
}
$result = new workshop_allocation_result($this);
switch ($mode) {
case 'new':
if (!confirm_sesskey()) {
throw new moodle_exception('confirmsesskeybad');
}
$reviewerid = required_param('by', PARAM_INT);
$authorid = required_param('of', PARAM_INT);
$m = array(); // message object to be passed to the next page
$submission = $this->workshop->get_submission_by_author($authorid);
if (!$submission) {
// nothing submitted by the given user
$m[] = self::MSG_NOSUBMISSION;
$m[] = $authorid;
} else {
// ok, we have the submission
$res = $this->workshop->add_allocation($submission, $reviewerid);
if ($res == workshop::ALLOCATION_EXISTS) {
$m[] = self::MSG_EXISTS;
$m[] = $submission->authorid;
$m[] = $reviewerid;
} else {
$m[] = self::MSG_ADDED;
$m[] = $submission->authorid;
$m[] = $reviewerid;
}
}
$m = implode('-', $m); // serialize message object to be passed via URL
redirect($PAGE->url->out(false, array('m' => $m)));
break;
case 'del':
if (!confirm_sesskey()) {
throw new moodle_exception('confirmsesskeybad');
}
$assessmentid = required_param('what', PARAM_INT);
$confirmed = optional_param('confirm', 0, PARAM_INT);
$assessment = $this->workshop->get_assessment_by_id($assessmentid);
if ($assessment) {
if (!$confirmed) {
$m[] = self::MSG_CONFIRM_DEL;
$m[] = $assessment->id;
$m[] = $assessment->authorid;
$m[] = $assessment->reviewerid;
if (is_null($assessment->grade)) {
$m[] = 0;
} else {
$m[] = 1;
}
} else {
if($this->workshop->delete_assessment($assessment->id)) {
$m[] = self::MSG_DELETED;
$m[] = $assessment->authorid;
$m[] = $assessment->reviewerid;
} else {
$m[] = self::MSG_DELETE_ERROR;
$m[] = $assessment->authorid;
$m[] = $assessment->reviewerid;
}
}
$m = implode('-', $m); // serialize message object to be passed via URL
redirect($PAGE->url->out(false, array('m' => $m)));
}
break;
}
$result->set_status(workshop_allocation_result::STATUS_VOID);
return $result;
}
/**
* Prints user interface - current allocation and a form to edit it
*/
public function ui() {
global $PAGE, $DB;
$output = $PAGE->get_renderer('workshopallocation_manual');
$page = optional_param('page', 0, PARAM_INT);
$perpage = get_user_preferences('workshopallocation_manual_perpage', 10);
$groupid = groups_get_activity_group($this->workshop->cm, true);
$hlauthorid = -1; // highlight this author
$hlreviewerid = -1; // highlight this reviewer
$message = new workshop_message();
$m = optional_param('m', '', PARAM_ALPHANUMEXT); // message code
if ($m) {
$m = explode('-', $m);
switch ($m[0]) {
case self::MSG_ADDED:
$hlauthorid = $m[1];
$hlreviewerid = $m[2];
$message = new workshop_message(get_string('allocationadded', 'workshopallocation_manual'),
workshop_message::TYPE_OK);
break;
case self::MSG_EXISTS:
$hlauthorid = $m[1];
$hlreviewerid = $m[2];
$message = new workshop_message(get_string('allocationexists', 'workshopallocation_manual'),
workshop_message::TYPE_INFO);
break;
case self::MSG_NOSUBMISSION:
$hlauthorid = $m[1];
$message = new workshop_message(get_string('nosubmissionfound', 'workshop'),
workshop_message::TYPE_ERROR);
break;
case self::MSG_CONFIRM_DEL:
$hlauthorid = $m[2];
$hlreviewerid = $m[3];
if ($m[4] == 0) {
$message = new workshop_message(get_string('areyousuretodeallocate', 'workshopallocation_manual'),
workshop_message::TYPE_INFO);
} else {
$message = new workshop_message(get_string('areyousuretodeallocategraded', 'workshopallocation_manual'),
workshop_message::TYPE_ERROR);
}
$url = new moodle_url($PAGE->url, array('mode' => 'del', 'what' => $m[1], 'confirm' => 1, 'sesskey' => sesskey()));
$label = get_string('iamsure', 'workshop');
$message->set_action($url, $label);
break;
case self::MSG_DELETED:
$hlauthorid = $m[1];
$hlreviewerid = $m[2];
$message = new workshop_message(get_string('assessmentdeleted', 'workshop'),
workshop_message::TYPE_OK);
break;
case self::MSG_DELETE_ERROR:
$hlauthorid = $m[1];
$hlreviewerid = $m[2];
$message = new workshop_message(get_string('assessmentnotdeleted', 'workshop'),
workshop_message::TYPE_ERROR);
break;
}
}
// fetch the list of ids of all workshop participants
$numofparticipants = $this->workshop->count_participants(false, $groupid);
$participants = $this->workshop->get_participants(false, $groupid, $perpage * $page, $perpage);
if ($hlauthorid > 0 and $hlreviewerid > 0) {
// display just those two users
$participants = array_intersect_key($participants, array($hlauthorid => null, $hlreviewerid => null));
$button = $output->single_button($PAGE->url, get_string('showallparticipants', 'workshopallocation_manual'), 'get');
} else {
$button = '';
}
// this will hold the information needed to display user names and pictures
$userinfo = $participants;
// load the participants' submissions
$submissions = $this->workshop->get_submissions(array_keys($participants));
$allnames = \core_user\fields::get_name_fields();
foreach ($submissions as $submission) {
if (!isset($userinfo[$submission->authorid])) {
$userinfo[$submission->authorid] = new stdclass();
$userinfo[$submission->authorid]->id = $submission->authorid;
$userinfo[$submission->authorid]->picture = $submission->authorpicture;
$userinfo[$submission->authorid]->imagealt = $submission->authorimagealt;
$userinfo[$submission->authorid]->email = $submission->authoremail;
foreach ($allnames as $addname) {
$temp = 'author' . $addname;
$userinfo[$submission->authorid]->$addname = $submission->$temp;
}
}
}
// get current reviewers
$reviewers = array();
if ($submissions) {
list($submissionids, $params) = $DB->get_in_or_equal(array_keys($submissions), SQL_PARAMS_NAMED);
$userfieldsapi = \core_user\fields::for_userpic();
$picturefields = $userfieldsapi->get_sql('r', false, '', 'reviewerid', false)->selects;
$sql = "SELECT a.id AS assessmentid, a.submissionid, $picturefields,
s.id AS submissionid, s.authorid
FROM {workshop_assessments} a
JOIN {user} r ON (a.reviewerid = r.id)
JOIN {workshop_submissions} s ON (a.submissionid = s.id)
WHERE a.submissionid $submissionids";
$reviewers = $DB->get_records_sql($sql, $params);
foreach ($reviewers as $reviewer) {
if (!isset($userinfo[$reviewer->reviewerid])) {
$userinfo[$reviewer->reviewerid] = new stdclass();
$userinfo[$reviewer->reviewerid]->id = $reviewer->reviewerid;
$userinfo[$reviewer->reviewerid]->picture = $reviewer->picture;
$userinfo[$reviewer->reviewerid]->imagealt = $reviewer->imagealt;
$userinfo[$reviewer->reviewerid]->email = $reviewer->email;
foreach ($allnames as $addname) {
$userinfo[$reviewer->reviewerid]->$addname = $reviewer->$addname;
}
}
}
}
// get current reviewees
$reviewees = array();
if ($participants) {
list($participantids, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
$userfieldsapi = \core_user\fields::for_name();
$namefields = $userfieldsapi->get_sql('e', false, '', '', false)->selects;
$params['workshopid'] = $this->workshop->id;
$sql = "SELECT a.id AS assessmentid, a.submissionid,
u.id AS reviewerid,
s.id AS submissionid,
e.id AS revieweeid, e.lastname, e.firstname, $namefields, e.picture, e.imagealt, e.email
FROM {user} u
JOIN {workshop_assessments} a ON (a.reviewerid = u.id)
JOIN {workshop_submissions} s ON (a.submissionid = s.id)
JOIN {user} e ON (s.authorid = e.id)
WHERE u.id $participantids AND s.workshopid = :workshopid AND s.example = 0";
$reviewees = $DB->get_records_sql($sql, $params);
foreach ($reviewees as $reviewee) {
if (!isset($userinfo[$reviewee->revieweeid])) {
$userinfo[$reviewee->revieweeid] = new stdclass();
$userinfo[$reviewee->revieweeid]->id = $reviewee->revieweeid;
$userinfo[$reviewee->revieweeid]->firstname = $reviewee->firstname;
$userinfo[$reviewee->revieweeid]->lastname = $reviewee->lastname;
$userinfo[$reviewee->revieweeid]->picture = $reviewee->picture;
$userinfo[$reviewee->revieweeid]->imagealt = $reviewee->imagealt;
$userinfo[$reviewee->revieweeid]->email = $reviewee->email;
foreach ($allnames as $addname) {
$userinfo[$reviewee->revieweeid]->$addname = $reviewee->$addname;
}
}
}
}
// the information about the allocations
$allocations = array();
foreach ($participants as $participant) {
$allocations[$participant->id] = new stdClass();
$allocations[$participant->id]->userid = $participant->id;
$allocations[$participant->id]->submissionid = null;
$allocations[$participant->id]->reviewedby = array();
$allocations[$participant->id]->reviewerof = array();
}
unset($participants);
foreach ($submissions as $submission) {
$allocations[$submission->authorid]->submissionid = $submission->id;
$allocations[$submission->authorid]->submissiontitle = $submission->title;
$allocations[$submission->authorid]->submissiongrade = $submission->grade;
}
unset($submissions);
foreach($reviewers as $reviewer) {
$allocations[$reviewer->authorid]->reviewedby[$reviewer->reviewerid] = $reviewer->assessmentid;
}
unset($reviewers);
foreach($reviewees as $reviewee) {
$allocations[$reviewee->reviewerid]->reviewerof[$reviewee->revieweeid] = $reviewee->assessmentid;
}
unset($reviewees);
// prepare data to be rendered
$data = new workshopallocation_manual_allocations();
$data->workshop = $this->workshop;
$data->allocations = $allocations;
$data->userinfo = $userinfo;
$data->authors = $this->workshop->get_potential_authors();
$data->reviewers = $this->workshop->get_potential_reviewers();
$data->hlauthorid = $hlauthorid;
$data->hlreviewerid = $hlreviewerid;
$data->selfassessment = $this->workshop->useselfassessment;
// prepare the group selector
$groupselector = $output->container(groups_print_activity_menu($this->workshop->cm, $PAGE->url, true), 'groupwidget');
// prepare paging bar
$pagingbar = new paging_bar($numofparticipants, $page, $perpage, $PAGE->url, 'page');
$pagingbarout = $output->render($pagingbar);
$perpageselector = $output->perpage_selector($perpage);
return $groupselector . $pagingbarout . $output->render($message) . $output->render($data) . $button . $pagingbarout . $perpageselector;
}
/**
* Delete all data related to a given workshop module instance
*
* This plugin does not store any data.
*
* @see workshop_delete_instance()
* @param int $workshopid id of the workshop module instance being deleted
* @return void
*/
public static function delete_instance($workshopid) {
return;
}
}
/**
* Contains all information needed to render current allocations and the allocator UI
*
* @see workshop_manual_allocator::ui()
*/
class workshopallocation_manual_allocations implements renderable {
/** @var workshop module instance */
public $workshop;
/** @var array of stdClass, indexed by userid, properties userid, submissionid, (array)reviewedby, (array)reviewerof */
public $allocations;
/** @var array of stdClass contains the data needed to display the user name and picture */
public $userinfo;
/* var array of stdClass potential authors */
public $authors;
/* var array of stdClass potential reviewers */
public $reviewers;
/* var int the id of the user to highlight as the author */
public $hlauthorid;
/* var int the id of the user to highlight as the reviewer */
public $hlreviewerid;
/* var bool should the selfassessment be allowed */
public $selfassessment;
}
+211
View File
@@ -0,0 +1,211 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Renderer class for the manual allocation UI is defined here
*
* @package workshopallocation
* @subpackage manual
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Manual allocation renderer class
*/
class workshopallocation_manual_renderer extends mod_workshop_renderer {
/** @var workshop module instance */
protected $workshop;
////////////////////////////////////////////////////////////////////////////
// External rendering API
////////////////////////////////////////////////////////////////////////////
/**
* Display the table of all current allocations and widgets to modify them
*
* @param workshopallocation_manual_allocations $data to be displayed
* @return string html code
*/
protected function render_workshopallocation_manual_allocations(workshopallocation_manual_allocations $data) {
$this->workshop = $data->workshop;
$allocations = $data->allocations; // array prepared array of all allocations data
$userinfo = $data->userinfo; // names and pictures of all required users
$authors = $data->authors; // array potential reviewees
$reviewers = $data->reviewers; // array potential submission reviewers
$hlauthorid = $data->hlauthorid; // int id of the author to highlight
$hlreviewerid = $data->hlreviewerid; // int id of the reviewer to highlight
$selfassessment = $data->selfassessment; // bool is the self-assessment allowed in this workshop?
if (empty($allocations)) {
return '';
}
// convert user collections into drop down menus
$authors = array_map('fullname', $authors);
$reviewers = array_map('fullname', $reviewers);
$table = new html_table();
$table->attributes['class'] = 'allocations';
$table->head = array(get_string('participantreviewedby', 'workshop'),
get_string('participant', 'workshop'),
get_string('participantrevierof', 'workshop'));
$table->rowclasses = array();
$table->colclasses = array('reviewedby', 'peer', 'reviewerof');
$table->data = array();
foreach ($allocations as $allocation) {
$row = array();
$row[] = $this->helper_reviewers_of_participant($allocation, $userinfo, $reviewers, $selfassessment);
$row[] = $this->helper_participant($allocation, $userinfo);
$row[] = $this->helper_reviewees_of_participant($allocation, $userinfo, $authors, $selfassessment);
$thisrowclasses = array();
if ($allocation->userid == $hlauthorid) {
$thisrowclasses[] = 'highlightreviewedby';
}
if ($allocation->userid == $hlreviewerid) {
$thisrowclasses[] = 'highlightreviewerof';
}
$table->rowclasses[] = implode(' ', $thisrowclasses);
$table->data[] = $row;
}
return $this->output->container(html_writer::table($table), 'manual-allocator');
}
////////////////////////////////////////////////////////////////////////////
// Internal helper methods
////////////////////////////////////////////////////////////////////////////
/**
* Returns information about the workshop participant
*
* @return string HTML code
*/
protected function helper_participant(stdclass $allocation, array $userinfo) {
$o = $this->output->user_picture($userinfo[$allocation->userid], array('courseid' => $this->page->course->id));
$o .= fullname($userinfo[$allocation->userid]);
$o .= $this->output->container_start(array('submission'));
if (is_null($allocation->submissionid)) {
$o .= $this->output->container(get_string('nosubmissionfound', 'workshop'), 'info');
} else {
$link = $this->workshop->submission_url($allocation->submissionid);
$o .= $this->output->container(html_writer::link($link, format_string($allocation->submissiontitle)), 'title');
if (is_null($allocation->submissiongrade)) {
$o .= $this->output->container(get_string('nogradeyet', 'workshop'), array('grade', 'missing'));
} else {
$o .= $this->output->container(get_string('alreadygraded', 'workshop'), array('grade', 'missing'));
}
}
$o .= $this->output->container_end();
return $o;
}
/**
* Returns information about the current reviewers of the given participant and a selector do add new one
*
* @return string html code
*/
protected function helper_reviewers_of_participant(stdclass $allocation, array $userinfo, array $reviewers, $selfassessment) {
$o = '';
if (is_null($allocation->submissionid)) {
$o .= $this->output->container(get_string('nothingtoreview', 'workshop'), 'info');
} else {
$exclude = array();
if (! $selfassessment) {
$exclude[$allocation->userid] = true;
}
// todo add an option to exclude users without own submission
$options = array_diff_key($reviewers, $exclude);
if ($options) {
$handler = new moodle_url($this->page->url, array('mode' => 'new', 'of' => $allocation->userid, 'sesskey' => sesskey()));
$select = new single_select($handler, 'by', $options, '', array(''=>get_string('chooseuser', 'workshop')), 'addreviewof' . $allocation->userid);
$select->set_label(get_string('addreviewer', 'workshopallocation_manual'));
$o .= $this->output->render($select);
}
}
$o .= html_writer::start_tag('ul', array());
foreach ($allocation->reviewedby as $reviewerid => $assessmentid) {
$o .= html_writer::start_tag('li', array());
$o .= $this->output->user_picture($userinfo[$reviewerid], array('courseid' => $this->page->course->id, 'size' => 16));
$o .= fullname($userinfo[$reviewerid]);
// delete icon
$handler = new moodle_url($this->page->url, array('mode' => 'del', 'what' => $assessmentid, 'sesskey' => sesskey()));
$o .= $this->helper_remove_allocation_icon($handler);
$o .= html_writer::end_tag('li');
}
$o .= html_writer::end_tag('ul');
return $o;
}
/**
* Returns information about the current reviewees of the given participant and a selector do add new one
*
* @return string html code
*/
protected function helper_reviewees_of_participant(stdclass $allocation, array $userinfo, array $authors, $selfassessment) {
$o = '';
if (is_null($allocation->submissionid)) {
$o .= $this->output->container(get_string('withoutsubmission', 'workshop'), 'info');
}
$exclude = array();
if (! $selfassessment) {
$exclude[$allocation->userid] = true;
$o .= $this->output->container(get_string('selfassessmentdisabled', 'workshop'), 'info');
}
// todo add an option to exclude users without own submission
$options = array_diff_key($authors, $exclude);
if ($options) {
$handler = new moodle_url($this->page->url, array('mode' => 'new', 'by' => $allocation->userid, 'sesskey' => sesskey()));
$select = new single_select($handler, 'of', $options, '', array(''=>get_string('chooseuser', 'workshop')), 'addreviewby' . $allocation->userid);
$select->set_label(get_string('addreviewee', 'workshopallocation_manual'));
$o .= $this->output->render($select);
} else {
$o .= $this->output->container(get_string('nothingtoreview', 'workshop'), 'info');
}
$o .= html_writer::start_tag('ul', array());
foreach ($allocation->reviewerof as $authorid => $assessmentid) {
$o .= html_writer::start_tag('li', array());
$o .= $this->output->user_picture($userinfo[$authorid], array('courseid' => $this->page->course->id, 'size' => 16));
$o .= fullname($userinfo[$authorid]);
// delete icon
$handler = new moodle_url($this->page->url, array('mode' => 'del', 'what' => $assessmentid, 'sesskey' => sesskey()));
$o .= $this->helper_remove_allocation_icon($handler);
$o .= html_writer::end_tag('li');
}
$o .= html_writer::end_tag('ul');
return $o;
}
/**
* Generates an icon link to remove the allocation
*
* @param moodle_url $link to the action
* @return html code to be displayed
*/
protected function helper_remove_allocation_icon($link) {
return $this->output->action_icon($link, new pix_icon('t/delete', 'X'));
}
}
+64
View File
@@ -0,0 +1,64 @@
/**
* Manual allocator
*/
.path-mod-workshop .manual-allocator .allocations {
margin: 0 auto;
width: 100%;
}
.path-mod-workshop .manual-allocator .allocations tbody tr:nth-of-type(odd) {
background-color: #eee;
}
.path-mod-workshop .manual-allocator .allocations tbody tr:nth-of-type(odd).highlightreviewerof,
.path-mod-workshop .manual-allocator .allocations tbody tr:nth-of-type(odd).highlightreviewedby {
background-color: inherit;
}
.path-mod-workshop .manual-allocator .allocations .peer .image {
margin-right: 5px;
vertical-align: middle;
}
.path-mod-workshop .manual-allocator .allocations .reviewedby .image,
.path-mod-workshop .manual-allocator .allocations .reviewerof .image {
margin-right: 3px;
vertical-align: middle;
}
.path-mod-workshop .manual-allocator .allocations .highlightreviewedby .reviewedby,
.path-mod-workshop .manual-allocator .allocations .highlightreviewerof .reviewerof {
background-color: #fff3d2;
}
.path-mod-workshop .manual-allocator .allocations tr td {
vertical-align: top;
padding: 5px;
}
.path-mod-workshop .manual-allocator .allocations tr td ul {
margin: 0;
}
.path-mod-workshop .manual-allocator .allocations tr td ul li {
list-style: none;
}
.path-mod-workshop .manual-allocator .allocations tr td.peer {
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
}
.path-mod-workshop .manual-allocator .allocations .reviewedby .info,
.path-mod-workshop .manual-allocator .allocations .peer .info,
.path-mod-workshop .manual-allocator .allocations .reviewerof .info {
font-size: 80%;
color: #888;
font-style: italic;
}
.path-mod-workshop .manual-allocator .allocations .peer .submission {
font-size: 90%;
margin-top: 1em;
}
@@ -0,0 +1,122 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Steps definitions related to workshopallocation_manual.
*
* @package workshopallocation_manual
* @category test
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../../../../lib/behat/behat_field_manager.php');
use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementTextException as ElementTextException;
/**
* Steps definitions related to workshopallocation_manual.
*
* @package workshopallocation_manual
* @category test
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_workshopallocation_manual extends behat_base {
/**
* Manually adds a reviewer for workshop participant.
*
* This step should start on manual allocation page.
*
* @When /^I add a reviewer "(?P<reviewer_name_string>(?:[^"]|\\")*)" for workshop participant "(?P<participant_name_string>(?:[^"]|\\")*)"$/
* @param string $reviewername
* @param string $participantname
*/
public function i_add_a_reviewer_for_workshop_participant($reviewername, $participantname) {
$participantnameliteral = behat_context_helper::escape($participantname);
$xpathtd = "//table[contains(concat(' ', normalize-space(@class), ' '), ' allocations ')]/".
"tbody/tr[./td[contains(concat(' ', normalize-space(@class), ' '), ' peer ')]".
"[contains(.,$participantnameliteral)]]/".
"td[contains(concat(' ', normalize-space(@class), ' '), ' reviewedby ')]";
$xpathselect = $xpathtd . "/descendant::select";
try {
$selectnode = $this->find('xpath', $xpathselect);
} catch (Exception $ex) {
$this->find_button(get_string('showallparticipants', 'workshopallocation_manual'))->press();
$selectnode = $this->find('xpath', $xpathselect);
}
$this->execute('behat_forms::set_field_node_value', [
$selectnode,
$reviewername,
]);
if (!$this->running_javascript()) {
// Without Javascript we need to press the "Go" button.
$go = behat_context_helper::escape(get_string('go'));
$this->find('xpath', $xpathtd."/descendant::input[@value=$go]")->click();
}
// Check the success string to appear.
$allocatedtext = behat_context_helper::escape(get_string('allocationadded', 'workshopallocation_manual'));
$this->find('xpath', "//*[contains(.,$allocatedtext)]");
}
/**
* Manually allocates multiple reviewers in workshop.
*
* @When /^I allocate submissions in workshop "(?P<workshop_name_string>(?:[^"]|\\")*)" as:$/
* @When /^I allocate submissions in workshop "(?P<workshop_name_string>(?:[^"]|\\")*)" as:"$/
* @param string $workshopname
* @param TableNode $table should have one column with title 'Reviewer' and another with title 'Participant' (or 'Reviewee')
*/
public function i_allocate_submissions_in_workshop_as($workshopname, TableNode $table) {
$this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname);
$this->execute('behat_navigation::i_navigate_to_in_current_page_administration',
get_string('submissionsallocation', 'workshop'));
$rows = $table->getRows();
$reviewer = $participant = null;
for ($i = 0; $i < count($rows[0]); $i++) {
if (strtolower($rows[0][$i]) === 'reviewer') {
$reviewer = $i;
} else if (strtolower($rows[0][$i]) === 'reviewee' || strtolower($rows[0][$i]) === 'participant') {
$participant = $i;
} else {
throw new ElementTextException('Unrecognised column "'.$rows[0][$i].'"', $this->getSession());
}
}
if ($reviewer === null) {
throw new ElementTextException('Column "Reviewer" could not be located', $this->getSession());
}
if ($participant === null) {
throw new ElementTextException('Neither "Participant" nor "Reviewee" column could be located', $this->getSession());
}
for ($i = 1; $i < count($rows); $i++) {
$this->execute(
'behat_workshopallocation_manual::i_add_a_reviewer_for_workshop_participant',
[
$rows[$i][$reviewer],
$rows[$i][$participant],
]
);
}
}
}
@@ -0,0 +1,70 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@see workshopallocation_manual\privacy\provider_test} class.
*
* @package workshopallocation_manual
* @category test
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_manual\privacy;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for the privacy API implementation.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* When no preference exists, there should be no export.
*/
public function test_no_preference(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
\workshopallocation_manual\privacy\provider::export_user_preferences($USER->id);
$this->assertFalse(writer::with_context(\context_system::instance())->has_any_data());
}
/**
* Test that the recently selected perpage is exported.
*/
public function test_export_preferences(): void {
global $USER;
$this->resetAfterTest();
$this->setAdminUser();
set_user_preference('workshopallocation_manual_perpage', 81);
\workshopallocation_manual\privacy\provider::export_user_preferences($USER->id);
$this->assertTrue(writer::with_context(\context_system::instance())->has_any_data());
$prefs = writer::with_context(\context_system::instance())->get_user_preferences('workshopallocation_manual');
$this->assertNotEmpty($prefs->workshopallocation_manual_perpage);
$this->assertEquals(81, $prefs->workshopallocation_manual_perpage->value);
$this->assertStringContainsString(get_string('privacy:metadata:preference:perpage', 'workshopallocation_manual'),
$prefs->workshopallocation_manual_perpage->description);
}
}
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the version of the subplugin
*
* @package workshopallocation
* @subpackage manual
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200;
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'workshopallocation_manual';
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the class {@link workshopallocation_random\privacy\provider}
*
* @package workshopallocation_random
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_random\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy API implementation for the Random allocation method.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Explain that this plugin stores no personal data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,54 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'workshopallocation_random', language 'en', branch 'MOODLE_20_STABLE'
*
* @package workshopallocation
* @subpackage random
* @copyright 2009 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['addselfassessment'] = 'Add self-assessments';
$string['allocationaddeddetail'] = 'New assessment to be done: <strong>{$a->reviewername}</strong> is reviewer of <strong>{$a->authorname}</strong>';
$string['allocationdeallocategraded'] = 'Unable to deallocate already graded assessment: reviewer <strong>{$a->reviewername}</strong>, submission author <strong>{$a->authorname}</strong>';
$string['allocationreuseddetail'] = 'Reused assessment: <strong>{$a->reviewername}</strong> kept as reviewer of <strong>{$a->authorname}</strong>';
$string['allocationsettings'] = 'Allocation settings';
$string['assessmentdeleteddetail'] = 'Assessment deallocated: <strong>{$a->reviewername}</strong> is no longer reviewer of <strong>{$a->authorname}</strong>';
$string['assesswosubmission'] = 'Participants can assess without having submitted anything';
$string['confignumofreviews'] = 'Default number of submissions to be randomly allocated';
$string['excludesamegroup'] = 'Prevent reviews by peers from the same group';
$string['noallocationtoadd'] = 'No allocations to add';
$string['nogroupusers'] = '<p>Warning: If the workshop is in \'visible groups\' mode or \'separate groups\' mode, then users MUST be part of at least one group to have peer-assessments allocated to them by this tool. Non-grouped users can still be given new self-assessments or have existing assessments removed.</p>
<p>These users are currently not in a group: {$a}</p>';
$string['numofdeallocatedassessment'] = 'Deallocating {$a} assessment(s)';
$string['numofrandomlyallocatedsubmissions'] = 'Randomly assigning {$a} allocations';
$string['numofreviews'] = 'Number of reviews';
$string['numofselfallocatedsubmissions'] = 'Self-allocating {$a} submission(s)';
$string['numperauthor'] = 'per submission';
$string['numperreviewer'] = 'per reviewer';
$string['pluginname'] = 'Random allocation';
$string['privacy:metadata'] = 'The Random allocation plugin does not store any personal data. Actual personal data about who is going to assess whom are stored by the Workshop module itself and they form basis for exporting the assessments details.';
$string['randomallocationdone'] = 'Random allocation done';
$string['resultnomorepeers'] = 'No more peers available';
$string['resultnomorepeersingroup'] = 'No more peers available in this separate group';
$string['resultnotenoughpeers'] = 'Not enough peers available';
$string['resultnumperauthor'] = 'Trying to allocate {$a} review(s) per author';
$string['resultnumperreviewer'] = 'Trying to allocate {$a} review(s) per reviewer';
$string['removecurrentallocations'] = 'Remove current allocations';
$string['stats'] = 'Current allocation statistics';
+800
View File
@@ -0,0 +1,800 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Allocates the submissions randomly
*
* @package workshopallocation_random
* @subpackage mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG; // access to global variables during unit test
require_once(__DIR__ . '/../lib.php'); // interface definition
require_once(__DIR__ . '/../../locallib.php'); // workshop internal API
require_once(__DIR__ . '/settings_form.php'); // settings form
/**
* Allocates the submissions randomly
*/
class workshop_random_allocator implements workshop_allocator {
/** constants used to pass status messages between init() and ui() */
const MSG_SUCCESS = 1;
/** workshop instance */
protected $workshop;
/** mform with settings */
protected $mform;
/**
* @param workshop $workshop Workshop API object
*/
public function __construct(workshop $workshop) {
$this->workshop = $workshop;
}
/**
* Allocate submissions as requested by user
*
* @return workshop_allocation_result
*/
public function init() {
global $PAGE;
$result = new workshop_allocation_result($this);
$customdata = array();
$customdata['workshop'] = $this->workshop;
$this->mform = new workshop_random_allocator_form($PAGE->url, $customdata);
if ($this->mform->is_cancelled()) {
redirect($this->workshop->view_url());
} else if ($settings = $this->mform->get_data()) {
$settings = workshop_random_allocator_setting::instance_from_object($settings);
$this->execute($settings, $result);
return $result;
} else {
// this branch is executed if the form is submitted but the data
// doesn't validate and the form should be redisplayed
// or on the first display of the form.
$result->set_status(workshop_allocation_result::STATUS_VOID);
return $result;
}
}
/**
* Executes the allocation based on the given settings
*
* @param workshop_random_allocator_setting $setting
* @param workshop_allocation_result allocation result logger
*/
public function execute(workshop_random_allocator_setting $settings, workshop_allocation_result $result) {
$authors = $this->workshop->get_potential_authors();
$authors = $this->workshop->get_grouped($authors);
$reviewers = $this->workshop->get_potential_reviewers(!$settings->assesswosubmission);
$reviewers = $this->workshop->get_grouped($reviewers);
$assessments = $this->workshop->get_all_assessments();
$newallocations = array(); // array of array(reviewer => reviewee)
if ($settings->numofreviews) {
if ($settings->removecurrent) {
// behave as if there were no current assessments
$curassessments = array();
} else {
$curassessments = $assessments;
}
$options = array();
$options['numofreviews'] = $settings->numofreviews;
$options['numper'] = $settings->numper;
$options['excludesamegroup'] = $settings->excludesamegroup;
$randomallocations = $this->random_allocation($authors, $reviewers, $curassessments, $result, $options);
$newallocations = array_merge($newallocations, $randomallocations);
$result->log(get_string('numofrandomlyallocatedsubmissions', 'workshopallocation_random', count($randomallocations)));
unset($randomallocations);
}
if ($settings->addselfassessment) {
$selfallocations = $this->self_allocation($authors, $reviewers, $assessments);
$newallocations = array_merge($newallocations, $selfallocations);
$result->log(get_string('numofselfallocatedsubmissions', 'workshopallocation_random', count($selfallocations)));
unset($selfallocations);
}
if (empty($newallocations)) {
$result->log(get_string('noallocationtoadd', 'workshopallocation_random'), 'info');
} else {
$newnonexistingallocations = $newallocations;
$this->filter_current_assessments($newnonexistingallocations, $assessments);
$this->add_new_allocations($newnonexistingallocations, $authors, $reviewers);
$allreviewers = $reviewers[0];
$allreviewersreloaded = false;
foreach ($newallocations as $newallocation) {
$reviewerid = key($newallocation);
$authorid = current($newallocation);
$a = new stdClass();
if (isset($allreviewers[$reviewerid])) {
$a->reviewername = fullname($allreviewers[$reviewerid]);
} else {
// this may happen if $settings->assesswosubmission is false but the reviewer
// of the re-used assessment has not submitted anything. let us reload
// the list of reviewers name including those without their submission
if (!$allreviewersreloaded) {
$allreviewers = $this->workshop->get_potential_reviewers(false);
$allreviewersreloaded = true;
}
if (isset($allreviewers[$reviewerid])) {
$a->reviewername = fullname($allreviewers[$reviewerid]);
} else {
// this should not happen usually unless the list of participants was changed
// in between two cycles of allocations
$a->reviewername = '#'.$reviewerid;
}
}
if (isset($authors[0][$authorid])) {
$a->authorname = fullname($authors[0][$authorid]);
} else {
$a->authorname = '#'.$authorid;
}
if (in_array($newallocation, $newnonexistingallocations)) {
$result->log(get_string('allocationaddeddetail', 'workshopallocation_random', $a), 'ok', 1);
} else {
$result->log(get_string('allocationreuseddetail', 'workshopallocation_random', $a), 'ok', 1);
}
}
}
if ($settings->removecurrent) {
$delassessments = $this->get_unkept_assessments($assessments, $newallocations, $settings->addselfassessment);
// random allocator should not be able to delete assessments that have already been graded
// by reviewer
$result->log(get_string('numofdeallocatedassessment', 'workshopallocation_random', count($delassessments)), 'info');
foreach ($delassessments as $delassessmentkey => $delassessmentid) {
$author = (object) [];
$reviewer = (object) [];
username_load_fields_from_object($author, $assessments[$delassessmentid], 'author');
username_load_fields_from_object($reviewer, $assessments[$delassessmentid], 'reviewer');
$a = [
'authorname' => fullname($author),
'reviewername' => fullname($reviewer),
];
if (!is_null($assessments[$delassessmentid]->grade)) {
$result->log(get_string('allocationdeallocategraded', 'workshopallocation_random', $a), 'error', 1);
unset($delassessments[$delassessmentkey]);
} else {
$result->log(get_string('assessmentdeleteddetail', 'workshopallocation_random', $a), 'info', 1);
}
}
$this->workshop->delete_assessment($delassessments);
}
$result->set_status(workshop_allocation_result::STATUS_EXECUTED);
}
/**
* Returns the HTML code to print the user interface
*/
public function ui() {
global $PAGE;
$output = $PAGE->get_renderer('mod_workshop');
$m = optional_param('m', null, PARAM_INT); // status message code
$message = new workshop_message();
if ($m == self::MSG_SUCCESS) {
$message->set_text(get_string('randomallocationdone', 'workshopallocation_random'));
$message->set_type(workshop_message::TYPE_OK);
}
$out = $output->container_start('random-allocator');
$out .= $output->render($message);
// the nasty hack follows to bypass the sad fact that moodle quickforms do not allow to actually
// return the HTML content, just to display it
ob_start();
$this->mform->display();
$out .= ob_get_contents();
ob_end_clean();
// if there are some not-grouped participant in a group mode, warn the user
$gmode = groups_get_activity_groupmode($this->workshop->cm, $this->workshop->course);
if (VISIBLEGROUPS == $gmode or SEPARATEGROUPS == $gmode) {
$users = $this->workshop->get_potential_authors() + $this->workshop->get_potential_reviewers();
$users = $this->workshop->get_grouped($users);
if (isset($users[0])) {
$nogroupusers = $users[0];
foreach ($users as $groupid => $groupusers) {
if ($groupid == 0) {
continue;
}
foreach ($groupusers as $groupuserid => $groupuser) {
unset($nogroupusers[$groupuserid]);
}
}
if (!empty($nogroupusers)) {
$list = array();
foreach ($nogroupusers as $nogroupuser) {
$list[] = fullname($nogroupuser);
}
$a = implode(', ', $list);
$out .= $output->box(get_string('nogroupusers', 'workshopallocation_random', $a), 'generalbox warning nogroupusers');
}
}
}
// TODO $out .= $output->heading(get_string('stats', 'workshopallocation_random'));
$out .= $output->container_end();
return $out;
}
/**
* Delete all data related to a given workshop module instance
*
* This plugin does not store any data.
*
* @see workshop_delete_instance()
* @param int $workshopid id of the workshop module instance being deleted
* @return void
*/
public static function delete_instance($workshopid) {
return;
}
/**
* Return an array of possible numbers of reviews to be done
*
* Should contain numbers 1, 2, 3, ... 10 and possibly others up to a reasonable value
*
* @return array of integers
*/
public static function available_numofreviews_list() {
$options = [];
for ($i = 100; $i > 20; $i = $i - 10) {
$options[$i] = $i;
}
for ($i = 20; $i >= 0; $i--) {
$options[$i] = $i;
}
return $options;
}
/**
* Allocates submissions to their authors for review
*
* If the submission has already been allocated, it is skipped. If the author is not found among
* reviewers, the submission is not assigned.
*
* @param array $authors grouped of {@see workshop::get_potential_authors()}
* @param array $reviewers grouped by {@see workshop::get_potential_reviewers()}
* @param array $assessments as returned by {@see workshop::get_all_assessments()}
* @return array of new allocations to be created, array of array(reviewerid => authorid)
*/
protected function self_allocation($authors=array(), $reviewers=array(), $assessments=array()) {
if (!isset($authors[0]) || !isset($reviewers[0])) {
// no authors or no reviewers
return array();
}
$alreadyallocated = array();
foreach ($assessments as $assessment) {
if ($assessment->authorid == $assessment->reviewerid) {
$alreadyallocated[$assessment->authorid] = 1;
}
}
$add = array(); // list of new allocations to be created
foreach ($authors[0] as $authorid => $author) {
// for all authors in all groups
if (isset($reviewers[0][$authorid])) {
// if the author can be reviewer
if (!isset($alreadyallocated[$authorid])) {
// and the allocation does not exist yet, then
$add[] = array($authorid => $authorid);
}
}
}
return $add;
}
/**
* Creates new assessment records
*
* @param array $newallocations pairs 'reviewerid' => 'authorid'
* @param array $dataauthors authors by group, group [0] contains all authors
* @param array $datareviewers reviewers by group, group [0] contains all reviewers
* @return bool
*/
protected function add_new_allocations(array $newallocations, array $dataauthors, array $datareviewers) {
global $DB;
$newallocations = $this->get_unique_allocations($newallocations);
$authorids = $this->get_author_ids($newallocations);
$submissions = $this->workshop->get_submissions($authorids);
$submissions = $this->index_submissions_by_authors($submissions);
foreach ($newallocations as $newallocation) {
$reviewerid = key($newallocation);
$authorid = current($newallocation);
if (!isset($submissions[$authorid])) {
throw new moodle_exception('unabletoallocateauthorwithoutsubmission', 'workshop');
}
$submission = $submissions[$authorid];
$status = $this->workshop->add_allocation($submission, $reviewerid, 1, true); // todo configurable weight?
if (workshop::ALLOCATION_EXISTS == $status) {
debugging('newallocations array contains existing allocation, this should not happen');
}
}
}
/**
* Flips the structure of submission so it is indexed by authorid attribute
*
* It is the caller's responsibility to make sure the submissions are not teacher
* examples so no user is the author of more submissions.
*
* @param string $submissions array indexed by submission id
* @return array indexed by author id
*/
protected function index_submissions_by_authors($submissions) {
$byauthor = array();
if (is_array($submissions)) {
foreach ($submissions as $submissionid => $submission) {
if (isset($byauthor[$submission->authorid])) {
throw new moodle_exception('moresubmissionsbyauthor', 'workshop');
}
$byauthor[$submission->authorid] = $submission;
}
}
return $byauthor;
}
/**
* Extracts unique list of authors' IDs from the structure of new allocations
*
* @param array $newallocations of pairs 'reviewerid' => 'authorid'
* @return array of authorids
*/
protected function get_author_ids($newallocations) {
$authors = array();
foreach ($newallocations as $newallocation) {
$authorid = reset($newallocation);
if (!in_array($authorid, $authors)) {
$authors[] = $authorid;
}
}
return $authors;
}
/**
* Removes duplicate allocations
*
* @param mixed $newallocations array of 'reviewerid' => 'authorid' pairs
* @return array
*/
protected function get_unique_allocations($newallocations) {
return array_merge(array_map('unserialize', array_unique(array_map('serialize', $newallocations))));
}
/**
* Returns the list of assessments to remove
*
* If user selects "removecurrentallocations", we should remove all current assessment records
* and insert new ones. But this would needlessly waste table ids. Instead, let us find only those
* assessments that have not been re-allocated in this run of allocation. So, the once-allocated
* submissions are kept with their original id.
*
* @param array $assessments list of current assessments
* @param mixed $newallocations array of 'reviewerid' => 'authorid' pairs
* @param bool $keepselfassessments do not remove already allocated self assessments
* @return array of assessments ids to be removed
*/
protected function get_unkept_assessments($assessments, $newallocations, $keepselfassessments) {
$keepids = array(); // keep these assessments
foreach ($assessments as $assessmentid => $assessment) {
$aaid = $assessment->authorid;
$arid = $assessment->reviewerid;
if (($keepselfassessments) && ($aaid == $arid)) {
$keepids[$assessmentid] = null;
continue;
}
foreach ($newallocations as $newallocation) {
$nrid = key($newallocation);
$naid = current($newallocation);
if (array($arid, $aaid) == array($nrid, $naid)) {
// re-allocation found - let us continue with the next assessment
$keepids[$assessmentid] = null;
continue 2;
}
}
}
return array_keys(array_diff_key($assessments, $keepids));
}
/**
* Allocates submission reviews randomly
*
* The algorithm of this function has been described at http://moodle.org/mod/forum/discuss.php?d=128473
* Please see the PDF attached to the post before you study the implementation. The goal of the function
* is to connect each "circle" (circles are representing either authors or reviewers) with a required
* number of "squares" (the other type than circles are).
*
* The passed $options array must provide keys:
* (int)numofreviews - number of reviews to be allocated to each circle
* (int)numper - what user type the circles represent.
* (bool)excludesamegroup - whether to prevent peer submissions from the same group in visible group mode
*
* @param array $authors structure of grouped authors
* @param array $reviewers structure of grouped reviewers
* @param array $assessments currently assigned assessments to be kept
* @param workshop_allocation_result $result allocation result logger
* @param array $options allocation options
* @return array array of (reviewerid => authorid) pairs
*/
protected function random_allocation($authors, $reviewers, $assessments, $result, array $options) {
if (empty($authors) || empty($reviewers)) {
// nothing to be done
return array();
}
$numofreviews = $options['numofreviews'];
$numper = $options['numper'];
if (workshop_random_allocator_setting::NUMPER_SUBMISSION == $numper) {
// circles are authors, squares are reviewers
$result->log(get_string('resultnumperauthor', 'workshopallocation_random', $numofreviews), 'info');
$allcircles = $authors;
$allsquares = $reviewers;
// get current workload
list($circlelinks, $squarelinks) = $this->convert_assessments_to_links($assessments);
} elseif (workshop_random_allocator_setting::NUMPER_REVIEWER == $numper) {
// circles are reviewers, squares are authors
$result->log(get_string('resultnumperreviewer', 'workshopallocation_random', $numofreviews), 'info');
$allcircles = $reviewers;
$allsquares = $authors;
// get current workload
list($squarelinks, $circlelinks) = $this->convert_assessments_to_links($assessments);
} else {
throw new moodle_exception('unknownusertypepassed', 'workshop');
}
// get the users that are not in any group. in visible groups mode, these users are exluded
// from allocation by this method
// $nogroupcircles is array (int)$userid => undefined
if (isset($allcircles[0])) {
$nogroupcircles = array_flip(array_keys($allcircles[0]));
} else {
$nogroupcircles = array();
}
foreach ($allcircles as $circlegroupid => $circles) {
if ($circlegroupid == 0) {
continue;
}
foreach ($circles as $circleid => $circle) {
unset($nogroupcircles[$circleid]);
}
}
// $result->log('circle links = ' . json_encode($circlelinks), 'debug');
// $result->log('square links = ' . json_encode($squarelinks), 'debug');
$squareworkload = array(); // individual workload indexed by squareid
$squaregroupsworkload = array(); // group workload indexed by squaregroupid
foreach ($allsquares as $squaregroupid => $squares) {
$squaregroupsworkload[$squaregroupid] = 0;
foreach ($squares as $squareid => $square) {
if (!isset($squarelinks[$squareid])) {
$squarelinks[$squareid] = array();
}
$squareworkload[$squareid] = count($squarelinks[$squareid]);
$squaregroupsworkload[$squaregroupid] += $squareworkload[$squareid];
}
$squaregroupsworkload[$squaregroupid] /= count($squares);
}
unset($squaregroupsworkload[0]); // [0] is not real group, it contains all users
// $result->log('square workload = ' . json_encode($squareworkload), 'debug');
// $result->log('square group workload = ' . json_encode($squaregroupsworkload), 'debug');
$gmode = groups_get_activity_groupmode($this->workshop->cm, $this->workshop->course);
if (SEPARATEGROUPS == $gmode) {
// shuffle all groups but [0] which means "all users"
$circlegroups = array_keys(array_diff_key($allcircles, array(0 => null)));
shuffle($circlegroups);
} else {
// all users will be processed at once
$circlegroups = array(0);
}
// $result->log('circle groups = ' . json_encode($circlegroups), 'debug');
foreach ($circlegroups as $circlegroupid) {
$result->log('processing circle group id ' . $circlegroupid, 'debug');
$circles = $allcircles[$circlegroupid];
// iterate over all circles in the group until the requested number of links per circle exists
// or it is not possible to fulfill that requirment
// during the first iteration, we try to make sure that at least one circlelink exists. during the
// second iteration, we try to allocate two, etc.
for ($requiredreviews = 1; $requiredreviews <= $numofreviews; $requiredreviews++) {
$this->shuffle_assoc($circles);
$result->log('iteration ' . $requiredreviews, 'debug');
foreach ($circles as $circleid => $circle) {
if (VISIBLEGROUPS == $gmode and isset($nogroupcircles[$circleid])) {
$result->log('skipping circle id ' . $circleid, 'debug');
continue;
}
$result->log('processing circle id ' . $circleid, 'debug');
if (!isset($circlelinks[$circleid])) {
$circlelinks[$circleid] = array();
}
$keeptrying = true; // is there a chance to find a square for this circle?
$failedgroups = array(); // array of groupids where the square should be chosen from (because
// of their group workload) but it was not possible (for example there
// was the only square and it had been already connected
while ($keeptrying && (count($circlelinks[$circleid]) < $requiredreviews)) {
// firstly, choose a group to pick the square from
if (NOGROUPS == $gmode) {
if (in_array(0, $failedgroups)) {
$keeptrying = false;
$result->log(get_string('resultnomorepeers', 'workshopallocation_random'), 'error', 1);
break;
}
$targetgroup = 0;
} elseif (SEPARATEGROUPS == $gmode) {
if (in_array($circlegroupid, $failedgroups)) {
$keeptrying = false;
$result->log(get_string('resultnomorepeersingroup', 'workshopallocation_random'), 'error', 1);
break;
}
$targetgroup = $circlegroupid;
} elseif (VISIBLEGROUPS == $gmode) {
$trygroups = array_diff_key($squaregroupsworkload, array(0 => null)); // all but [0]
$trygroups = array_diff_key($trygroups, array_flip($failedgroups)); // without previous failures
if ($options['excludesamegroup']) {
// exclude groups the circle is member of
$excludegroups = array();
foreach (array_diff_key($allcircles, array(0 => null)) as $exgroupid => $exgroupmembers) {
if (array_key_exists($circleid, $exgroupmembers)) {
$excludegroups[$exgroupid] = null;
}
}
$trygroups = array_diff_key($trygroups, $excludegroups);
}
$targetgroup = $this->get_element_with_lowest_workload($trygroups);
}
if ($targetgroup === false) {
$keeptrying = false;
$result->log(get_string('resultnotenoughpeers', 'workshopallocation_random'), 'error', 1);
break;
}
$result->log('next square should be from group id ' . $targetgroup, 'debug', 1);
// now, choose a square from the target group
$trysquares = array_intersect_key($squareworkload, $allsquares[$targetgroup]);
// $result->log('individual workloads in this group are ' . json_encode($trysquares), 'debug', 1);
unset($trysquares[$circleid]); // can't allocate to self
$trysquares = array_diff_key($trysquares, array_flip($circlelinks[$circleid])); // can't re-allocate the same
$targetsquare = $this->get_element_with_lowest_workload($trysquares);
if (false === $targetsquare) {
$result->log('unable to find an available square. trying another group', 'debug', 1);
$failedgroups[] = $targetgroup;
continue;
}
$result->log('target square = ' . $targetsquare, 'debug', 1);
// ok - we have found the square
$circlelinks[$circleid][] = $targetsquare;
$squarelinks[$targetsquare][] = $circleid;
$squareworkload[$targetsquare]++;
$result->log('increasing square workload to ' . $squareworkload[$targetsquare], 'debug', 1);
if ($targetgroup) {
// recalculate the group workload
$squaregroupsworkload[$targetgroup] = 0;
foreach ($allsquares[$targetgroup] as $squareid => $square) {
$squaregroupsworkload[$targetgroup] += $squareworkload[$squareid];
}
$squaregroupsworkload[$targetgroup] /= count($allsquares[$targetgroup]);
$result->log('increasing group workload to ' . $squaregroupsworkload[$targetgroup], 'debug', 1);
}
} // end of processing this circle
} // end of one iteration of processing circles in the group
} // end of all iterations over circles in the group
} // end of processing circle groups
$returned = array();
if (workshop_random_allocator_setting::NUMPER_SUBMISSION == $numper) {
// circles are authors, squares are reviewers
foreach ($circlelinks as $circleid => $squares) {
foreach ($squares as $squareid) {
$returned[] = array($squareid => $circleid);
}
}
}
if (workshop_random_allocator_setting::NUMPER_REVIEWER == $numper) {
// circles are reviewers, squares are authors
foreach ($circlelinks as $circleid => $squares) {
foreach ($squares as $squareid) {
$returned[] = array($circleid => $squareid);
}
}
}
return $returned;
}
/**
* Extracts the information about reviews from the authors' and reviewers' perspectives
*
* @param array $assessments array of assessments as returned by {@link workshop::get_all_assessments()}
* @return array of two arrays
*/
protected function convert_assessments_to_links($assessments) {
$authorlinks = array(); // [authorid] => array(reviewerid, reviewerid, ...)
$reviewerlinks = array(); // [reviewerid] => array(authorid, authorid, ...)
foreach ($assessments as $assessment) {
if (!isset($authorlinks[$assessment->authorid])) {
$authorlinks[$assessment->authorid] = array();
}
if (!isset($reviewerlinks[$assessment->reviewerid])) {
$reviewerlinks[$assessment->reviewerid] = array();
}
$authorlinks[$assessment->authorid][] = $assessment->reviewerid;
$reviewerlinks[$assessment->reviewerid][] = $assessment->authorid;
}
return array($authorlinks, $reviewerlinks);
}
/**
* Selects an element with the lowest workload
*
* If there are more elements with the same workload, choose one of them randomly. This may be
* used to select a group or user.
*
* @param array $workload [groupid] => (int)workload
* @return mixed int|bool id of the selected element or false if it is impossible to choose
*/
protected function get_element_with_lowest_workload($workload) {
$precision = 10;
if (empty($workload)) {
return false;
}
$minload = round(min($workload), $precision);
$minkeys = array();
foreach ($workload as $key => $val) {
if (round($val, $precision) == $minload) {
$minkeys[$key] = $val;
}
}
return array_rand($minkeys);
}
/**
* Shuffle the order of array elements preserving the key=>values
*
* @param array $array to be shuffled
* @return true
*/
protected function shuffle_assoc(&$array) {
if (count($array) > 1) {
// $keys needs to be an array, no need to shuffle 1 item or empty arrays, anyway
$keys = array_keys($array);
shuffle($keys);
foreach($keys as $key) {
$new[$key] = $array[$key];
}
$array = $new;
}
return true; // because this behaves like in-built shuffle(), which returns true
}
/**
* Filter new allocations so that they do not contain an already existing assessment
*
* @param mixed $newallocations array of ('reviewerid' => 'authorid') tuples
* @param array $assessments array of assessment records
* @return void
*/
protected function filter_current_assessments(&$newallocations, $assessments) {
foreach ($assessments as $assessment) {
$allocation = array($assessment->reviewerid => $assessment->authorid);
$foundat = moodle_array_keys_filter($newallocations, $allocation);
$newallocations = array_diff_key($newallocations, array_flip($foundat));
}
}
}
/**
* Data object defining the settings structure for the random allocator
*/
class workshop_random_allocator_setting {
/** aim to a number of reviews per one submission {@see self::$numper} */
const NUMPER_SUBMISSION = 1;
/** aim to a number of reviews per one reviewer {@see self::$numper} */
const NUMPER_REVIEWER = 2;
/** @var int number of reviews */
public $numofreviews;
/** @var int either {@link self::NUMPER_SUBMISSION} or {@link self::NUMPER_REVIEWER} */
public $numper;
/** @var bool prevent reviews by peers from the same group */
public $excludesamegroup;
/** @var bool remove current allocations */
public $removecurrent;
/** @var bool participants can assess without having submitted anything */
public $assesswosubmission;
/** @var bool add self-assessments */
public $addselfassessment;
/** @var bool scheduled allocation status */
public $enablescheduled;
/**
* Use the factory method {@link self::instance_from_object()}
*/
protected function __construct() {
}
/**
* Factory method making the instance from data in the passed object
*
* @param stdClass $data an object holding the values for our public properties
* @return workshop_random_allocator_setting
*/
public static function instance_from_object(stdClass $data) {
$i = new self();
if (!isset($data->numofreviews)) {
throw new coding_exception('Missing value of the numofreviews property');
} else {
$i->numofreviews = (int)$data->numofreviews;
}
if (!isset($data->numper)) {
throw new coding_exception('Missing value of the numper property');
} else {
$i->numper = (int)$data->numper;
if ($i->numper !== self::NUMPER_SUBMISSION and $i->numper !== self::NUMPER_REVIEWER) {
throw new coding_exception('Invalid value of the numper property');
}
}
foreach (array('excludesamegroup', 'removecurrent', 'assesswosubmission', 'addselfassessment') as $k) {
if (isset($data->$k)) {
$i->$k = (bool)$data->$k;
} else {
$i->$k = false;
}
}
return $i;
}
/**
* Factory method making the instance from data in the passed text
*
* @param string $text as returned by {@link self::export_text()}
* @return workshop_random_allocator_setting
*/
public static function instance_from_text($text) {
return self::instance_from_object(json_decode($text));
}
/**
* Exports the instance data as a text for persistant storage
*
* The returned data can be later used by {@self::instance_from_text()} factory method
* to restore the instance data. The current implementation uses JSON export format.
*
* @return string JSON representation of our public properties
*/
public function export_text() {
$getvars = function($obj) { return get_object_vars($obj); };
return json_encode($getvars($this));
}
}
@@ -0,0 +1,34 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The configuration variables for "Random allocation" subplugin
*
* @package workshopallocation
* @subpackage random
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/lib.php');
$settings->add(new admin_setting_configselect('workshopallocation_random/numofreviews',
get_string('numofreviews', 'workshopallocation_random'),
get_string('confignumofreviews', 'workshopallocation_random'), 5,
workshop_random_allocator::available_numofreviews_list()));
@@ -0,0 +1,119 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Random allocator settings form
*
* @package workshopallocation
* @subpackage random
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
/**
* Allocator settings form
*
* This is used by {@see workshop_random_allocator::ui()} to set up allocation parameters.
*
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class workshop_random_allocator_form extends moodleform {
/**
* Definition of the setting form elements
*/
public function definition() {
$mform = $this->_form;
$workshop = $this->_customdata['workshop'];
$plugindefaults = get_config('workshopallocation_random');
$mform->addElement('header', 'randomallocationsettings', get_string('allocationsettings', 'workshopallocation_random'));
$gmode = groups_get_activity_groupmode($workshop->cm, $workshop->course);
switch ($gmode) {
case NOGROUPS:
$grouplabel = get_string('groupsnone', 'group');
break;
case VISIBLEGROUPS:
$grouplabel = get_string('groupsvisible', 'group');
break;
case SEPARATEGROUPS:
$grouplabel = get_string('groupsseparate', 'group');
break;
}
$mform->addElement('static', 'groupmode', get_string('groupmode', 'group'), $grouplabel);
$options_numper = array(
workshop_random_allocator_setting::NUMPER_SUBMISSION => get_string('numperauthor', 'workshopallocation_random'),
workshop_random_allocator_setting::NUMPER_REVIEWER => get_string('numperreviewer', 'workshopallocation_random')
);
$grpnumofreviews = array();
$grpnumofreviews[] = $mform->createElement('text', 'numofreviews', '', array('size' => 5, 'maxlength' => 20));
$mform->setType('numofreviews', PARAM_INT);
$mform->setDefault('numofreviews', $plugindefaults->numofreviews);
$grpnumofreviews[] = $mform->createElement('select', 'numper', '', $options_numper);
$mform->setDefault('numper', workshop_random_allocator_setting::NUMPER_SUBMISSION);
$mform->addGroup($grpnumofreviews, 'grpnumofreviews', get_string('numofreviews', 'workshopallocation_random'),
array(' '), false);
if (VISIBLEGROUPS == $gmode) {
$mform->addElement('checkbox', 'excludesamegroup', get_string('excludesamegroup', 'workshopallocation_random'));
$mform->setDefault('excludesamegroup', 0);
} else {
$mform->addElement('hidden', 'excludesamegroup', 0);
$mform->setType('excludesamegroup', PARAM_BOOL);
}
$mform->addElement('checkbox', 'removecurrent', get_string('removecurrentallocations', 'workshopallocation_random'));
$mform->setDefault('removecurrent', 0);
$mform->addElement('checkbox', 'assesswosubmission', get_string('assesswosubmission', 'workshopallocation_random'));
$mform->setDefault('assesswosubmission', 0);
if (empty($workshop->useselfassessment)) {
$mform->addElement('static', 'addselfassessment', get_string('addselfassessment', 'workshopallocation_random'),
get_string('selfassessmentdisabled', 'workshop'));
} else {
$mform->addElement('checkbox', 'addselfassessment', get_string('addselfassessment', 'workshopallocation_random'));
}
$this->add_action_buttons(false);
}
/**
* Validate the allocation settings.
*
* @param array $data
* @param array $files
* @return array
*/
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
if ($data['numofreviews'] < 0) {
$errors['grpnumofreviews'] = get_string('invalidnum', 'core_error');
}
return $errors;
}
}
@@ -0,0 +1,4 @@
.path-mod-workshop .random-allocator .warning {
width: 100%;
margin: 0 auto 15px auto;
}
@@ -0,0 +1,334 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for Random allocation
*
* @package workshopallocation_random
* @category test
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_random;
use workshop;
use workshop_random_allocator;
defined('MOODLE_INTERNAL') || die();
// Include the code to test
global $CFG;
require_once($CFG->dirroot . '/mod/workshop/locallib.php');
require_once($CFG->dirroot . '/mod/workshop/allocation/random/lib.php');
/**
* Unit tests for Random allocation
*
* @package workshopallocation_random
* @category test
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class allocator_test extends \advanced_testcase {
/** workshop instance emulation */
protected $workshop;
/** allocator instance */
protected $allocator;
protected function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course));
$cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id];
$this->workshop = new workshop($workshop, $cm, $course);
$this->allocator = new testable_workshop_random_allocator($this->workshop);
}
protected function tearDown(): void {
$this->allocator = null;
$this->workshop = null;
parent::tearDown();
}
public function test_self_allocation_empty_values(): void {
// fixture setup & exercise SUT & verify
$this->assertEquals(array(), $this->allocator->self_allocation());
}
public function test_self_allocation_equal_user_groups(): void {
// fixture setup
$authors = array(0 => array_fill_keys(array(4, 6, 10), new \stdClass()));
$reviewers = array(0 => array_fill_keys(array(4, 6, 10), new \stdClass()));
// exercise SUT
$newallocations = $this->allocator->self_allocation($authors, $reviewers);
// verify
$this->assertEquals(array(array(4 => 4), array(6 => 6), array(10 => 10)), $newallocations);
}
public function test_self_allocation_different_user_groups(): void {
// fixture setup
$authors = array(0 => array_fill_keys(array(1, 4, 5, 10, 13), new \stdClass()));
$reviewers = array(0 => array_fill_keys(array(4, 7, 10), new \stdClass()));
// exercise SUT
$newallocations = $this->allocator->self_allocation($authors, $reviewers);
// verify
$this->assertEquals(array(array(4 => 4), array(10 => 10)), $newallocations);
}
public function test_self_allocation_skip_existing(): void {
// fixture setup
$authors = array(0 => array_fill_keys(array(3, 4, 10), new \stdClass()));
$reviewers = array(0 => array_fill_keys(array(3, 4, 10), new \stdClass()));
$assessments = array(23 => (object)array('authorid' => 3, 'reviewerid' => 3));
// exercise SUT
$newallocations = $this->allocator->self_allocation($authors, $reviewers, $assessments);
// verify
$this->assertEquals(array(array(4 => 4), array(10 => 10)), $newallocations);
}
public function test_get_author_ids(): void {
// fixture setup
$newallocations = array(array(1 => 3), array(2 => 1), array(3 => 1));
// exercise SUT & verify
$this->assertEquals(array(3, 1), $this->allocator->get_author_ids($newallocations));
}
public function test_index_submissions_by_authors(): void {
// fixture setup
$submissions = array(
676 => (object)array('id' => 676, 'authorid' => 23),
121 => (object)array('id' => 121, 'authorid' => 56),
);
// exercise SUT
$submissions = $this->allocator->index_submissions_by_authors($submissions);
// verify
$this->assertEquals(array(
23 => (object)array('id' => 676, 'authorid' => 23),
56 => (object)array('id' => 121, 'authorid' => 56),
), $submissions);
}
public function test_index_submissions_by_authors_duplicate_author(): void {
// fixture setup
$submissions = array(
14 => (object)array('id' => 676, 'authorid' => 3),
87 => (object)array('id' => 121, 'authorid' => 3),
);
// exercise SUT
$this->expectException(\moodle_exception::class);
$submissions = $this->allocator->index_submissions_by_authors($submissions);
}
public function test_get_unique_allocations(): void {
// fixture setup
$newallocations = array(array(4 => 5), array(6 => 6), array(1 => 16), array(4 => 5), array(16 => 1));
// exercise SUT
$newallocations = $this->allocator->get_unique_allocations($newallocations);
// verify
$this->assertEquals(array(array(4 => 5), array(6 => 6), array(1 => 16), array(16 => 1)), $newallocations);
}
public function test_get_unkept_assessments_no_keep_selfassessments(): void {
// fixture setup
$assessments = array(
23 => (object)array('authorid' => 3, 'reviewerid' => 3),
45 => (object)array('authorid' => 5, 'reviewerid' => 11),
12 => (object)array('authorid' => 6, 'reviewerid' => 3),
);
$newallocations = array(array(4 => 5), array(11 => 5), array(1 => 16), array(4 => 5), array(16 => 1));
// exercise SUT
$delassessments = $this->allocator->get_unkept_assessments($assessments, $newallocations, false);
// verify
// we want to keep $assessments[45] because it has been re-allocated
$this->assertEquals(array(23, 12), $delassessments);
}
public function test_get_unkept_assessments_keep_selfassessments(): void {
// fixture setup
$assessments = array(
23 => (object)array('authorid' => 3, 'reviewerid' => 3),
45 => (object)array('authorid' => 5, 'reviewerid' => 11),
12 => (object)array('authorid' => 6, 'reviewerid' => 3),
);
$newallocations = array(array(4 => 5), array(11 => 5), array(1 => 16), array(4 => 5), array(16 => 1));
// exercise SUT
$delassessments = $this->allocator->get_unkept_assessments($assessments, $newallocations, true);
// verify
// we want to keep $assessments[45] because it has been re-allocated
// we want to keep $assessments[23] because if is self assessment
$this->assertEquals(array(12), $delassessments);
}
/**
* Aggregates assessment info per author and per reviewer
*/
public function test_convert_assessments_to_links(): void {
// fixture setup
$assessments = array(
23 => (object)array('authorid' => 3, 'reviewerid' => 3),
45 => (object)array('authorid' => 5, 'reviewerid' => 11),
12 => (object)array('authorid' => 5, 'reviewerid' => 3),
);
// exercise SUT
list($authorlinks, $reviewerlinks) = $this->allocator->convert_assessments_to_links($assessments);
// verify
$this->assertEquals(array(3 => array(3), 5 => array(11, 3)), $authorlinks);
$this->assertEquals(array(3 => array(3, 5), 11 => array(5)), $reviewerlinks);
}
/**
* Trivial case
*/
public function test_convert_assessments_to_links_empty(): void {
// fixture setup
$assessments = array();
// exercise SUT
list($authorlinks, $reviewerlinks) = $this->allocator->convert_assessments_to_links($assessments);
// verify
$this->assertEquals(array(), $authorlinks);
$this->assertEquals(array(), $reviewerlinks);
}
/**
* If there is a single element with the lowest workload, it should be chosen
*/
public function test_get_element_with_lowest_workload_deterministic(): void {
// fixture setup
$workload = array(4 => 6, 9 => 1, 10 => 2);
// exercise SUT
$chosen = $this->allocator->get_element_with_lowest_workload($workload);
// verify
$this->assertEquals(9, $chosen);
}
/**
* If there are no elements available, must return false
*/
public function test_get_element_with_lowest_workload_impossible(): void {
// fixture setup
$workload = array();
// exercise SUT
$chosen = $this->allocator->get_element_with_lowest_workload($workload);
// verify
$this->assertTrue($chosen === false);
}
/**
* If there are several elements with the lowest workload, one of them should be chosen randomly
*/
public function test_get_element_with_lowest_workload_random(): void {
// fixture setup
$workload = array(4 => 6, 9 => 2, 10 => 2);
// exercise SUT
$elements = $this->allocator->get_element_with_lowest_workload($workload);
// verify
// in theory, this test can fail even if the function works well. However, the probability of getting
// a row of a hundred same ids in this use case is 1/pow(2, 100)
// also, this just tests that each of the two elements has been chosen at least once. this is not to
// measure the quality or randomness of the algorithm
$counts = array(4 => 0, 9 => 0, 10 => 0);
for ($i = 0; $i < 100; $i++) {
$chosen = $this->allocator->get_element_with_lowest_workload($workload);
if (!in_array($chosen, array(4, 9, 10))) {
$this->fail('Invalid element ' . var_export($chosen, true) . ' chosen');
break;
} else {
$counts[$this->allocator->get_element_with_lowest_workload($workload)]++;
}
}
$this->assertTrue(($counts[9] > 0) && ($counts[10] > 0));
}
/**
* Floats should be rounded before they are compared
*
* This should test
*/
public function test_get_element_with_lowest_workload_random_floats(): void {
// fixture setup
$workload = array(1 => 1/13, 2 => 0.0769230769231); // should be considered as the same value
// exercise SUT
$elements = $this->allocator->get_element_with_lowest_workload($workload);
// verify
$counts = array(1 => 0, 2 => 0);
for ($i = 0; $i < 100; $i++) {
$chosen = $this->allocator->get_element_with_lowest_workload($workload);
if (!in_array($chosen, array(1, 2))) {
$this->fail('Invalid element ' . var_export($chosen, true) . ' chosen');
break;
} else {
$counts[$this->allocator->get_element_with_lowest_workload($workload)]++;
}
}
$this->assertTrue(($counts[1] > 0) && ($counts[2] > 0));
}
/**
* Filter new assessments so they do not contain existing
*/
public function test_filter_current_assessments(): void {
// fixture setup
$newallocations = array(array(3 => 5), array(11 => 5), array(2 => 9), array(3 => 5));
$assessments = array(
23 => (object)array('authorid' => 3, 'reviewerid' => 3),
45 => (object)array('authorid' => 5, 'reviewerid' => 11),
12 => (object)array('authorid' => 5, 'reviewerid' => 3),
);
// exercise SUT
$this->allocator->filter_current_assessments($newallocations, $assessments);
// verify
$this->assertEquals(array_values($newallocations), array(array(2 => 9)));
}
}
/**
* Make protected methods we want to test public
*/
class testable_workshop_random_allocator extends workshop_random_allocator {
public function self_allocation($authors=array(), $reviewers=array(), $assessments=array()) {
return parent::self_allocation($authors, $reviewers, $assessments);
}
public function get_author_ids($newallocations) {
return parent::get_author_ids($newallocations);
}
public function index_submissions_by_authors($submissions) {
return parent::index_submissions_by_authors($submissions);
}
public function get_unique_allocations($newallocations) {
return parent::get_unique_allocations($newallocations);
}
public function get_unkept_assessments($assessments, $newallocations, $keepselfassessments) {
return parent::get_unkept_assessments($assessments, $newallocations, $keepselfassessments);
}
public function convert_assessments_to_links($assessments) {
return parent::convert_assessments_to_links($assessments);
}
public function get_element_with_lowest_workload($workload) {
return parent::get_element_with_lowest_workload($workload);
}
public function filter_current_assessments(&$newallocations, $assessments) {
return parent::filter_current_assessments($newallocations, $assessments);
}
}
@@ -0,0 +1,31 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the version of the subplugin
*
* @package workshopallocation_random
* @subpackage mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'workshopallocation_random';
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->maturity = MATURITY_STABLE;
@@ -0,0 +1,121 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Event observers for workshopallocation_scheduled.
*
* @package workshopallocation_scheduled
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_scheduled;
defined('MOODLE_INTERNAL') || die();
/**
* Class for workshopallocation_scheduled observers.
*
* @package workshopallocation_scheduled
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class observer {
/**
* Triggered when the '\mod_workshop\event\course_module_viewed' event is triggered.
*
* This does the same job as {@link workshopallocation_scheduled_cron()} but for the
* single workshop. The idea is that we do not need to wait for cron to execute.
* Displaying the workshop main view.php can trigger the scheduled allocation, too.
*
* @param \mod_workshop\event\course_module_viewed $event
* @return bool
*/
public static function workshop_viewed($event) {
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/workshop/locallib.php');
$workshop = $event->get_record_snapshot('workshop', $event->objectid);
$course = $event->get_record_snapshot('course', $event->courseid);
$cm = $event->get_record_snapshot('course_modules', $event->contextinstanceid);
$workshop = new \workshop($workshop, $cm, $course);
$now = time();
// Non-expensive check to see if the scheduled allocation can even happen.
if ($workshop->phase == \workshop::PHASE_SUBMISSION and $workshop->submissionend > 0 and $workshop->submissionend < $now) {
// Make sure the scheduled allocation has been configured for this workshop, that it has not
// been executed yet and that the passed workshop record is still valid.
$sql = "SELECT a.id
FROM {workshopallocation_scheduled} a
JOIN {workshop} w ON a.workshopid = w.id
WHERE w.id = :workshopid
AND a.enabled = 1
AND w.phase = :phase
AND w.submissionend > 0
AND w.submissionend < :now
AND (a.timeallocated IS NULL OR a.timeallocated < w.submissionend)";
$params = array('workshopid' => $workshop->id, 'phase' => \workshop::PHASE_SUBMISSION, 'now' => $now);
if ($DB->record_exists_sql($sql, $params)) {
// Allocate submissions for assessments.
$allocator = $workshop->allocator_instance('scheduled');
$result = $allocator->execute();
// Todo inform the teachers about the results.
}
}
return true;
}
/**
* Called when the '\mod_workshop\event\phase_automatically_switched' event is triggered.
*
* This observer handles the phase_automatically_switched event triggered when phaseswithassesment is active
* and the phase is automatically switched.
*
* When this happens, this situation can occur:
*
* * cron_task transition the workshop to PHASE_ASESSMENT.
* * scheduled_allocator task executes.
* * scheduled_allocator task cannot allocate parcipants because workshop is not
* in PHASE_SUBMISSION state (it's in PHASE_ASSESMENT).
*
* @param \mod_workshop\event\phase_automatically_switched $event
*/
public static function phase_automatically_switched(\mod_workshop\event\phase_automatically_switched $event) {
if ($event->other['previousworkshopphase'] != \workshop::PHASE_SUBMISSION) {
return;
}
if ($event->other['targetworkshopphase'] != \workshop::PHASE_ASSESSMENT) {
return;
}
$workshop = $event->get_record_snapshot('workshop', $event->objectid);
$course = $event->get_record_snapshot('course', $event->courseid);
$cm = $event->get_record_snapshot('course_modules', $event->contextinstanceid);
$workshop = new \workshop($workshop, $cm, $course);
if ($workshop->phase != \workshop::PHASE_ASSESSMENT) {
return;
}
$allocator = $workshop->allocator_instance('scheduled');
// We know that we come from PHASE_SUBMISSION so we tell the allocator not to test for the PHASE_SUBMISSION state.
$allocator->execute(false);
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the class {@link workshopallocation_scheduled\privacy\provider}
*
* @package workshopallocation_scheduled
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_scheduled\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy API implementation for the Scheduled allocation method.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Explain that this plugin stores no personal data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,78 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A schedule task for scheduled allocation cron.
*
* @package workshopallocation_scheduled
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopallocation_scheduled\task;
defined('MOODLE_INTERNAL') || die();
/**
* The main schedule task for scheduled allocation cron.
*
* @package workshopallocation_scheduled
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cron_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('crontask', 'workshopallocation_scheduled');
}
/**
* Run scheduled allocation cron.
*/
public function execute() {
global $CFG, $DB;
$sql = "SELECT w.*
FROM {workshopallocation_scheduled} a
JOIN {workshop} w ON a.workshopid = w.id
WHERE a.enabled = 1
AND w.phase = 20
AND w.submissionend > 0
AND w.submissionend < ?
AND (a.timeallocated IS NULL OR a.timeallocated < w.submissionend)";
$workshops = $DB->get_records_sql($sql, array(time()));
if (empty($workshops)) {
mtrace('... no workshops awaiting scheduled allocation. ', '');
return;
}
mtrace('... executing scheduled allocation in ' . count($workshops) . ' workshop(s) ... ', '');
require_once($CFG->dirroot . '/mod/workshop/locallib.php');
foreach ($workshops as $workshop) {
$cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
$workshop = new \workshop($workshop, $cm, $course);
$allocator = $workshop->allocator_instance('scheduled');
$allocator->execute();
}
}
}
@@ -0,0 +1,40 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines event handlers
*
* @package workshopallocation_scheduled
* @subpackage mod_workshop
* @category event
* @copyright 2013 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = array(
array(
'eventname' => '\mod_workshop\event\course_module_viewed',
'callback' => '\workshopallocation_scheduled\observer::workshop_viewed',
),
array(
'eventname' => '\mod_workshop\event\phase_automatically_switched',
'callback' => '\workshopallocation_scheduled\observer::phase_automatically_switched'
)
);
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/workshop/allocation/scheduled/db" VERSION="20120330" COMMENT="XMLDB file for Moodle mod/workshop/allocation/scheduled"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="workshopallocation_scheduled" COMMENT="Stores the allocation settings for the scheduled allocator">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="workshopid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="workshop id we are part of"/>
<FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Is the scheduled allocation enabled"/>
<FIELD NAME="submissionend" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="What was the workshop's submissionend when this record was created or modified"/>
<FIELD NAME="timeallocated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="When was the last scheduled allocation executed"/>
<FIELD NAME="settings" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The pre-defined settings for the underlying random allocation to run it with"/>
<FIELD NAME="resultstatus" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The resulting status of the most recent execution"/>
<FIELD NAME="resultmessage" TYPE="char" LENGTH="1333" NOTNULL="false" SEQUENCE="false" COMMENT="Optional short message describing the resulting status"/>
<FIELD NAME="resultlog" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The log of the most recent execution"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="fkuq_workshopid" TYPE="foreign-unique" FIELDS="workshopid" REFTABLE="workshop" REFFIELDS="id" COMMENT="Max one record for each workshop"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition of scheduled allocation tasks.
*
* @package workshopallocation_scheduled
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = [
[
'classname' => '\workshopallocation_scheduled\task\cron_task',
'blocking' => 0,
'minute' => '*',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*'
]
];
@@ -0,0 +1,64 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for the Workshop's scheduled allocator
*
* @package workshopallocation_scheduled
* @subpackage mod_workshop
* @copyright 2012 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['crontask'] = 'Background processing for scheduled allocation';
$string['currentstatus'] = 'Current status';
$string['currentstatusexecution'] = 'Status';
$string['currentstatusexecution1'] = 'Executed on {$a->datetime}';
$string['currentstatusexecution2'] = 'To be executed again on {$a->datetime}';
$string['currentstatusexecution3'] = 'To be executed on {$a->datetime}';
$string['currentstatusexecution4'] = 'Awaiting execution';
$string['currentstatusreset'] = 'Reset';
$string['currentstatusresetinfo'] = 'Check the box and save the form to reset the execution result';
$string['currentstatusreset_help'] = 'Saving the form with this checkbox ticked will result in resetting the current status. All the information about the previous execution will be removed so the allocation can be executed again (if enabled above).';
$string['currentstatusresult'] = 'Recent execution result';
$string['currentstatusnext'] = 'Next execution';
$string['currentstatusnext_help'] = 'In some cases, the allocation is scheduled to be automatically executed again even if it was already executed. This may happen if the submissions deadline has been prolonged, for example.';
$string['enablescheduled'] = 'Enable scheduled allocation';
$string['enablescheduledinfo'] = 'Automatically allocate submissions at the end of the submission phase';
$string['scheduledallocationsettings'] = 'Scheduled allocation settings';
$string['scheduledallocationsettings_help'] = 'If enabled, the scheduled allocation method will automatically allocate submissions for the assessment at the end of the submission phase. The end of the phase can be defined in the workshop setting \'Submissions deadline\'.
Internally, the random allocation method is executed with the parameters pre-defined in this form. It means that the scheduled allocation works as if the teacher executed the random allocation themselves at the end of the submission phase using the allocation settings below.
Note that the scheduled allocation is *not* executed if you manually switch the workshop into the assessment phase before the submissions deadline. You have to allocate submissions yourself in that case. The scheduled allocation method is particularly useful when used together with the automatic phase switching feature.';
$string['pluginname'] = 'Scheduled allocation';
$string['privacy:metadata'] = 'The Scheduled allocation plugin does not store any personal data. Actual personal data about who is going to assess whom are stored by the Workshop module itself and they form basis for exporting the assessments details.';
$string['randomallocationsettings'] = 'Allocation settings';
$string['randomallocationsettings_help'] = 'Parameters for the random allocation method are defined here. They will be used by the random allocation plugin for the actual allocation of submissions.';
$string['resultdisabled'] = 'Scheduled allocation disabled';
$string['resultenabled'] = 'Scheduled allocation enabled';
$string['resultexecuted'] = 'Success';
$string['resultfailed'] = 'Unable to automatically allocate submissions';
$string['resultfailedconfig'] = 'Scheduled allocation misconfigured';
$string['resultfaileddeadline'] = 'Workshop does not have the submissions deadline defined';
$string['resultfailedphase'] = 'Workshop not in the submission phase';
$string['resultvoid'] = 'No submissions were allocated';
$string['resultvoiddeadline'] = 'Not after the submissions deadline yet';
$string['resultvoidexecuted'] = 'The allocation has been already executed';
$string['setup'] = 'Set up scheduled allocation';
+275
View File
@@ -0,0 +1,275 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled allocator that internally executes the random allocation later
*
* @package workshopallocation_scheduled
* @subpackage mod_workshop
* @copyright 2012 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../lib.php'); // interface definition
require_once(__DIR__ . '/../../locallib.php'); // workshop internal API
require_once(__DIR__ . '/../random/lib.php'); // random allocator
require_once(__DIR__ . '/settings_form.php'); // our settings form
/**
* Allocates the submissions randomly in a cronjob task
*/
class workshop_scheduled_allocator implements workshop_allocator {
/** workshop instance */
protected $workshop;
/** workshop_scheduled_allocator_form with settings for the random allocator */
protected $mform;
/**
* @param workshop $workshop Workshop API object
*/
public function __construct(workshop $workshop) {
$this->workshop = $workshop;
}
/**
* Save the settings for the random allocator to execute it later
*/
public function init() {
global $PAGE, $DB;
$result = new workshop_allocation_result($this);
$customdata = array();
$customdata['workshop'] = $this->workshop;
$current = $DB->get_record('workshopallocation_scheduled',
array('workshopid' => $this->workshop->id), '*', IGNORE_MISSING);
$customdata['current'] = $current;
$this->mform = new workshop_scheduled_allocator_form($PAGE->url, $customdata);
if ($this->mform->is_cancelled()) {
redirect($this->workshop->view_url());
} else if ($settings = $this->mform->get_data()) {
if (empty($settings->enablescheduled)) {
$enabled = false;
} else {
$enabled = true;
}
if (empty($settings->reenablescheduled)) {
$reset = false;
} else {
$reset = true;
}
$settings = workshop_random_allocator_setting::instance_from_object($settings);
$this->store_settings($enabled, $reset, $settings, $result);
if ($enabled) {
$msg = get_string('resultenabled', 'workshopallocation_scheduled');
} else {
$msg = get_string('resultdisabled', 'workshopallocation_scheduled');
}
$result->set_status(workshop_allocation_result::STATUS_CONFIGURED, $msg);
return $result;
} else {
// this branch is executed if the form is submitted but the data
// doesn't validate and the form should be redisplayed
// or on the first display of the form.
if ($current !== false) {
$data = workshop_random_allocator_setting::instance_from_text($current->settings);
$data->enablescheduled = $current->enabled;
$this->mform->set_data($data);
}
$result->set_status(workshop_allocation_result::STATUS_VOID);
return $result;
}
}
/**
* Returns the HTML code to print the user interface
*/
public function ui() {
global $PAGE;
$output = $PAGE->get_renderer('mod_workshop');
$out = $output->container_start('scheduled-allocator');
// the nasty hack follows to bypass the sad fact that moodle quickforms do not allow to actually
// return the HTML content, just to display it
ob_start();
$this->mform->display();
$out .= ob_get_contents();
ob_end_clean();
$out .= $output->container_end();
return $out;
}
/**
* Executes the allocation
*
* @param bool $checksubmissionphase Check that the workshop is in submission phase before doing anything else.
* @return workshop_allocation_result
*/
public function execute(bool $checksubmissionphase = true) {
global $DB;
$result = new workshop_allocation_result($this);
// Execution can occur in multiple places. Ensure we only allocate one at a time.
$lockfactory = \core\lock\lock_config::get_lock_factory('mod_workshop_allocation_scheduled_execution');
$executionlock = $lockfactory->get_lock($this->workshop->id, 1, 30);
if (!$executionlock) {
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfailed', 'workshopallocation_scheduled'));
}
try {
// Make sure the workshop itself is at the expected state.
if ($checksubmissionphase && $this->workshop->phase != workshop::PHASE_SUBMISSION) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfailedphase', 'workshopallocation_scheduled'));
return $result;
}
if (empty($this->workshop->submissionend)) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfaileddeadline', 'workshopallocation_scheduled'));
return $result;
}
if ($this->workshop->submissionend > time()) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_VOID,
get_string('resultvoiddeadline', 'workshopallocation_scheduled'));
return $result;
}
$current = $DB->get_record('workshopallocation_scheduled',
array('workshopid' => $this->workshop->id, 'enabled' => 1), '*', IGNORE_MISSING);
if ($current === false) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfailedconfig', 'workshopallocation_scheduled'));
return $result;
}
if (!$current->enabled) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_VOID,
get_string('resultdisabled', 'workshopallocation_scheduled'));
return $result;
}
if (!is_null($current->timeallocated) and $current->timeallocated >= $this->workshop->submissionend) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_VOID,
get_string('resultvoidexecuted', 'workshopallocation_scheduled'));
return $result;
}
// So now we know that we are after the submissions deadline and either the scheduled allocation was not
// executed yet or it was but the submissions deadline has been prolonged (and hence we should repeat the
// allocations).
$settings = workshop_random_allocator_setting::instance_from_text($current->settings);
$randomallocator = $this->workshop->allocator_instance('random');
$randomallocator->execute($settings, $result);
// Store the result in the instance's table.
$update = new stdClass();
$update->id = $current->id;
$update->timeallocated = $result->get_timeend();
$update->resultstatus = $result->get_status();
$update->resultmessage = $result->get_message();
$update->resultlog = json_encode($result->get_logs());
$DB->update_record('workshopallocation_scheduled', $update);
} catch (\Exception $e) {
$executionlock->release();
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfailed', 'workshopallocation_scheduled'));
throw $e;
}
$executionlock->release();
return $result;
}
/**
* Delete all data related to a given workshop module instance
*
* @see workshop_delete_instance()
* @param int $workshopid id of the workshop module instance being deleted
* @return void
*/
public static function delete_instance($workshopid) {
// TODO
return;
}
/**
* Stores the pre-defined random allocation settings for later usage
*
* @param bool $enabled is the scheduled allocation enabled
* @param bool $reset reset the recent execution info
* @param workshop_random_allocator_setting $settings settings form data
* @param workshop_allocation_result $result logger
*/
protected function store_settings($enabled, $reset, workshop_random_allocator_setting $settings, workshop_allocation_result $result) {
global $DB;
$data = new stdClass();
$data->workshopid = $this->workshop->id;
$data->enabled = $enabled;
$data->submissionend = $this->workshop->submissionend;
$data->settings = $settings->export_text();
if ($reset) {
$data->timeallocated = null;
$data->resultstatus = null;
$data->resultmessage = null;
$data->resultlog = null;
}
$result->log($data->settings, 'debug');
$current = $DB->get_record('workshopallocation_scheduled', array('workshopid' => $data->workshopid), '*', IGNORE_MISSING);
if ($current === false) {
$DB->insert_record('workshopallocation_scheduled', $data);
} else {
$data->id = $current->id;
$DB->update_record('workshopallocation_scheduled', $data);
}
}
}
@@ -0,0 +1,140 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled allocator's settings
*
* @package workshopallocation_scheduled
* @subpackage mod_workshop
* @copyright 2012 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/lib/formslib.php');
require_once(__DIR__ . '/../random/settings_form.php'); // parent form
/**
* Allocator settings form
*
* This is used by {@see workshop_scheduled_allocator::ui()} to set up allocation parameters.
*/
class workshop_scheduled_allocator_form extends workshop_random_allocator_form {
/**
* Definition of the setting form elements
*/
public function definition() {
global $OUTPUT;
$mform = $this->_form;
$workshop = $this->_customdata['workshop'];
$current = $this->_customdata['current'];
if (!empty($workshop->submissionend)) {
$strtimeexpected = workshop::timestamp_formats($workshop->submissionend);
}
if (!empty($current->timeallocated)) {
$strtimeexecuted = workshop::timestamp_formats($current->timeallocated);
}
$mform->addElement('header', 'scheduledallocationsettings', get_string('scheduledallocationsettings', 'workshopallocation_scheduled'));
$mform->addHelpButton('scheduledallocationsettings', 'scheduledallocationsettings', 'workshopallocation_scheduled');
$mform->addElement('checkbox', 'enablescheduled', get_string('enablescheduled', 'workshopallocation_scheduled'), get_string('enablescheduledinfo', 'workshopallocation_scheduled'), 1);
$mform->addElement('header', 'scheduledallocationinfo', get_string('currentstatus', 'workshopallocation_scheduled'));
if ($current === false) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('resultdisabled', 'workshopallocation_scheduled').' '. $OUTPUT->pix_icon('i/invalid', ''));
} else {
if (!empty($current->timeallocated)) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('currentstatusexecution1', 'workshopallocation_scheduled', $strtimeexecuted).' '.
$OUTPUT->pix_icon('i/valid', ''));
if ($current->resultstatus == workshop_allocation_result::STATUS_EXECUTED) {
$strstatus = get_string('resultexecuted', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/valid', '');
} else if ($current->resultstatus == workshop_allocation_result::STATUS_FAILED) {
$strstatus = get_string('resultfailed', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/invalid', '');
} else {
$strstatus = get_string('resultvoid', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/invalid', '');
}
if (!empty($current->resultmessage)) {
$strstatus .= html_writer::empty_tag('br').$current->resultmessage; // yes, this is ugly. better solution suggestions are welcome.
}
$mform->addElement('static', 'inforesult', get_string('currentstatusresult', 'workshopallocation_scheduled'), $strstatus);
if ($current->timeallocated < $workshop->submissionend) {
$mform->addElement('static', 'infoexpected', get_string('currentstatusnext', 'workshopallocation_scheduled'),
get_string('currentstatusexecution2', 'workshopallocation_scheduled', $strtimeexpected).' '.
$OUTPUT->pix_icon('i/caution', ''));
$mform->addHelpButton('infoexpected', 'currentstatusnext', 'workshopallocation_scheduled');
} else {
$mform->addElement('checkbox', 'reenablescheduled', get_string('currentstatusreset', 'workshopallocation_scheduled'),
get_string('currentstatusresetinfo', 'workshopallocation_scheduled'));
$mform->addHelpButton('reenablescheduled', 'currentstatusreset', 'workshopallocation_scheduled');
}
} else if (empty($current->enabled)) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('resultdisabled', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/invalid', ''));
} else if ($workshop->phase != workshop::PHASE_SUBMISSION) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('resultfailed', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/invalid', '') .
html_writer::empty_tag('br').
get_string('resultfailedphase', 'workshopallocation_scheduled'));
} else if (empty($workshop->submissionend)) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('resultfailed', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/invalid', '') .
html_writer::empty_tag('br').
get_string('resultfaileddeadline', 'workshopallocation_scheduled'));
} else if ($workshop->submissionend < time()) {
// next cron will execute it
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('currentstatusexecution4', 'workshopallocation_scheduled').' '.
$OUTPUT->pix_icon('i/caution', ''));
} else {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('currentstatusexecution3', 'workshopallocation_scheduled', $strtimeexpected).' '.
$OUTPUT->pix_icon('i/caution', ''));
}
}
parent::definition();
$mform->addHelpButton('randomallocationsettings', 'randomallocationsettings', 'workshopallocation_scheduled');
}
}
@@ -0,0 +1,199 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace workshopallocation_scheduled;
/**
* Test for the scheduled allocator.
*
* @package workshopallocation_scheduled
* @copyright 2020 Jaume I University <https://www.uji.es/>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class scheduled_allocator_test extends \advanced_testcase {
/** @var \stdClass $course The course where the tests will be run */
private $course;
/** @var \workshop $workshop The workshop where the tests will be run */
private $workshop;
/** @var \stdClass $workshopcm The workshop course module instance */
private $workshopcm;
/** @var \stdClass[] $students An array of student enrolled in $course */
private $students;
/**
* Tests that student submissions get automatically alocated after the submission deadline and when the workshop
* "Switch to the next phase after the submissions deadline" checkbox is active.
*/
public function test_that_allocator_in_executed_on_submission_end_when_phaseswitchassessment_is_active(): void {
global $DB;
$this->resetAfterTest();
$this->setup_test_course_and_workshop();
$this->activate_switch_to_the_next_phase_after_submission_deadline();
$this->set_the_submission_deadline_in_the_past();
$this->activate_the_scheduled_allocator();
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
\core\cron::setup_user();
// Let the students add submissions.
$this->workshop->switch_phase(\workshop::PHASE_SUBMISSION);
// Create some submissions.
foreach ($this->students as $student) {
$workshopgenerator->create_submission($this->workshop->id, $student->id);
}
// No allocations yet.
$this->assertEmpty($this->workshop->get_allocations());
/* Execute the tasks that will do the transition and allocation thing.
* We expect the workshop cron to do the whole work: change the phase and
* allocate the submissions.
*/
$this->execute_workshop_cron_task();
$workshopdb = $DB->get_record('workshop', ['id' => $this->workshop->id]);
$workshop = new \workshop($workshopdb, $this->workshopcm, $this->course);
$this->assertEquals(\workshop::PHASE_ASSESSMENT, $workshop->phase);
$this->assertNotEmpty($workshop->get_allocations());
}
/**
* No allocations are performed if the allocator is not enabled.
*/
public function test_that_allocator_is_not_executed_when_its_not_active(): void {
global $DB;
$this->resetAfterTest();
$this->setup_test_course_and_workshop();
$this->activate_switch_to_the_next_phase_after_submission_deadline();
$this->set_the_submission_deadline_in_the_past();
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
\core\cron::setup_user();
// Let the students add submissions.
$this->workshop->switch_phase(\workshop::PHASE_SUBMISSION);
// Create some submissions.
foreach ($this->students as $student) {
$workshopgenerator->create_submission($this->workshop->id, $student->id);
}
// No allocations yet.
$this->assertEmpty($this->workshop->get_allocations());
// Transition to the assessment phase.
$this->execute_workshop_cron_task();
$workshopdb = $DB->get_record('workshop', ['id' => $this->workshop->id]);
$workshop = new \workshop($workshopdb, $this->workshopcm, $this->course);
// No allocations too.
$this->assertEquals(\workshop::PHASE_ASSESSMENT, $workshop->phase);
$this->assertEmpty($workshop->get_allocations());
}
/**
* Activates and configures the scheduled allocator for the workshop.
*/
private function activate_the_scheduled_allocator(): void {
$settings = \workshop_random_allocator_setting::instance_from_object((object)[
'numofreviews' => count($this->students),
'numper' => 1,
'removecurrentuser' => true,
'excludesamegroup' => false,
'assesswosubmission' => true,
'addselfassessment' => false
]);
$allocator = new \workshop_scheduled_allocator($this->workshop);
$storesettingsmethod = new \ReflectionMethod('workshop_scheduled_allocator', 'store_settings');
$storesettingsmethod->invoke($allocator, true, true, $settings, new \workshop_allocation_result($allocator));
}
/**
* Creates a minimum common setup to execute tests:
*/
protected function setup_test_course_and_workshop(): void {
$this->setAdminUser();
$datagenerator = $this->getDataGenerator();
$this->course = $datagenerator->create_course();
$this->students = [];
for ($i = 0; $i < 10; $i++) {
$this->students[] = $datagenerator->create_and_enrol($this->course);
}
$workshopdb = $datagenerator->create_module('workshop', [
'course' => $this->course,
'name' => 'Test Workshop',
]);
$this->workshopcm = get_coursemodule_from_instance('workshop', $workshopdb->id, $this->course->id, false, MUST_EXIST);
$this->workshop = new \workshop($workshopdb, $this->workshopcm, $this->course);
}
/**
* Executes the workshop cron task.
*/
protected function execute_workshop_cron_task(): void {
ob_start();
$cron = new \mod_workshop\task\cron_task();
$cron->execute();
ob_end_clean();
}
/**
* Executes the scheduled allocator cron task.
*/
protected function execute_allocator_cron_task(): void {
ob_start();
$cron = new \workshopallocation_scheduled\task\cron_task();
$cron->execute();
ob_end_clean();
}
/**
* Activates the "Switch to the next phase after the submissions deadline" flag in the workshop.
*/
protected function activate_switch_to_the_next_phase_after_submission_deadline(): void {
global $DB;
$DB->set_field('workshop', 'phaseswitchassessment', 1, ['id' => $this->workshop->id]);
}
/**
* Sets the submission deadline in a past time.
*/
protected function set_the_submission_deadline_in_the_past(): void {
global $DB;
$DB->set_field('workshop', 'submissionend', time() - 1, ['id' => $this->workshop->id]);
}
}
@@ -0,0 +1,35 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Scheduled allocator that internally executes the random one
*
* @package workshopallocation_scheduled
* @subpackage mod_workshop
* @copyright 2012 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'workshopallocation_scheduled';
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->dependencies = [
'workshopallocation_random' => 2024041600,
];
$plugin->maturity = MATURITY_STABLE;
+10
View File
@@ -0,0 +1,10 @@
/**
* Additional javascript for the Workshop module form.
*
* @module mod_workshop/modform
* @copyright The Open University 2018
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_workshop/modform",["jquery"],(function($){var submissionTypes={text:{available:null,required:null,requiredHidden:null},file:{available:null,required:null,requiredHidden:null}};function checkAvailability(checkUnavailable,checkAvailable){checkUnavailable.available.prop("checked")||(checkUnavailable.required.prop("disabled",!0),checkUnavailable.required.prop("checked",!1),checkAvailable.available.prop("checked")&&(checkAvailable.required.prop("disabled",!0),checkAvailable.required.prop("checked",!0),checkAvailable.requiredHidden.val(1)))}function enableRequired(submissionType){submissionType.required.prop("disabled",!1),submissionType.required.prop("checked",!1),submissionType.requiredHidden.val(0)}function submissionTypeChanged(){checkAvailability(submissionTypes.file,submissionTypes.text),checkAvailability(submissionTypes.text,submissionTypes.file),submissionTypes.text.available.prop("checked")&&submissionTypes.file.available.prop("checked")&&(enableRequired(submissionTypes.text),enableRequired(submissionTypes.file))}return{init:function(){submissionTypes.text.available=$("#id_submissiontypetextavailable"),submissionTypes.text.required=$("#id_submissiontypetextrequired"),submissionTypes.text.requiredHidden=$('input[name="submissiontypetextrequired"][type="hidden"]'),submissionTypes.file.available=$("#id_submissiontypefileavailable"),submissionTypes.file.required=$("#id_submissiontypefilerequired"),submissionTypes.file.requiredHidden=$('input[name="submissiontypefilerequired"][type="hidden"]'),submissionTypes.text.available.on("change",submissionTypeChanged),submissionTypes.file.available.on("change",submissionTypeChanged),submissionTypeChanged()}}}));
//# sourceMappingURL=modform.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Sets the equal height to the user plan widget boxes.
*
* @module mod_workshop/workshopview
* @category output
* @copyright Loc Nguyen <loc.nguyendinh@harveynash.vn>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("mod_workshop/workshopview",["jquery"],(function($){function equalHeight(group){var tallest=0;group.height("auto"),group.each((function(){var thisHeight=$(this).height();thisHeight>tallest&&(tallest=thisHeight)})),group.height(tallest)}return{init:function(){var $dt=$(".path-mod-workshop .userplan dt"),$dd=$(".path-mod-workshop .userplan dd");equalHeight($dt),equalHeight($dd),$(window).on("resize",(function(){equalHeight($dt),equalHeight($dd)}))}}}));
//# sourceMappingURL=workshopview.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"workshopview.min.js","sources":["../src/workshopview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Sets the equal height to the user plan widget boxes.\n *\n * @module mod_workshop/workshopview\n * @category output\n * @copyright Loc Nguyen <loc.nguyendinh@harveynash.vn>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n\n /**\n * Sets the equal height to all elements in the group.\n *\n * @param {jQuery} group List of nodes.\n */\n function equalHeight(group) {\n var tallest = 0;\n group.height('auto');\n group.each(function() {\n var thisHeight = $(this).height();\n if (thisHeight > tallest) {\n tallest = thisHeight;\n }\n });\n group.height(tallest);\n }\n\n return /** @alias module:mod_workshop/workshopview */ {\n init: function() {\n var $dt = $('.path-mod-workshop .userplan dt');\n var $dd = $('.path-mod-workshop .userplan dd');\n equalHeight($dt);\n equalHeight($dd);\n $(window).on(\"resize\", function() {\n equalHeight($dt);\n equalHeight($dd);\n });\n }\n };\n});\n"],"names":["define","$","equalHeight","group","tallest","height","each","thisHeight","this","init","$dt","$dd","window","on"],"mappings":";;;;;;;;AAuBAA,mCAAO,CAAC,WAAW,SAASC,YAOfC,YAAYC,WACbC,QAAU,EACdD,MAAME,OAAO,QACbF,MAAMG,MAAK,eACHC,WAAaN,EAAEO,MAAMH,SACrBE,WAAaH,UACbA,QAAUG,eAGlBJ,MAAME,OAAOD,eAGqC,CAClDK,KAAM,eACEC,IAAMT,EAAE,mCACRU,IAAMV,EAAE,mCACZC,YAAYQ,KACZR,YAAYS,KACZV,EAAEW,QAAQC,GAAG,UAAU,WACnBX,YAAYQ,KACZR,YAAYS"}
+99
View File
@@ -0,0 +1,99 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Additional javascript for the Workshop module form.
*
* @module mod_workshop/modform
* @copyright The Open University 2018
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
var submissionTypes = {
text: {
available: null,
required: null,
requiredHidden: null
},
file: {
available: null,
required: null,
requiredHidden: null
}
};
/**
* Determine whether one of the submission types has been marked as not available.
*
* If it has been marked not available, clear and disable its required checkbox. Then determine if the other submission
* type is available, and if it is, check and disable its required checkbox.
*
* @param {Object} checkUnavailable
* @param {Object} checkAvailable
*/
function checkAvailability(checkUnavailable, checkAvailable) {
if (!checkUnavailable.available.prop('checked')) {
checkUnavailable.required.prop('disabled', true);
checkUnavailable.required.prop('checked', false);
if (checkAvailable.available.prop('checked')) {
checkAvailable.required.prop('disabled', true);
checkAvailable.required.prop('checked', true);
// Also set the checkbox's hidden field to 1 so a 'required' value is submitted for the submission type.
checkAvailable.requiredHidden.val(1);
}
}
}
/**
* Enable the submission type's required checkbox and uncheck it.
*
* @param {Object} submissionType
*/
function enableRequired(submissionType) {
submissionType.required.prop('disabled', false);
submissionType.required.prop('checked', false);
submissionType.requiredHidden.val(0);
}
/**
* Check which submission types have been marked as available, and disable required checkboxes as necessary.
*/
function submissionTypeChanged() {
checkAvailability(submissionTypes.file, submissionTypes.text);
checkAvailability(submissionTypes.text, submissionTypes.file);
if (submissionTypes.text.available.prop('checked') && submissionTypes.file.available.prop('checked')) {
enableRequired(submissionTypes.text);
enableRequired(submissionTypes.file);
}
}
return /** @alias module:mod_workshop/modform */ {
/**
* Find all the required fields, set up event listeners, and set the initial state of required checkboxes.
*/
init: function() {
submissionTypes.text.available = $('#id_submissiontypetextavailable');
submissionTypes.text.required = $('#id_submissiontypetextrequired');
submissionTypes.text.requiredHidden = $('input[name="submissiontypetextrequired"][type="hidden"]');
submissionTypes.file.available = $('#id_submissiontypefileavailable');
submissionTypes.file.required = $('#id_submissiontypefilerequired');
submissionTypes.file.requiredHidden = $('input[name="submissiontypefilerequired"][type="hidden"]');
submissionTypes.text.available.on('change', submissionTypeChanged);
submissionTypes.file.available.on('change', submissionTypeChanged);
submissionTypeChanged();
}
};
});
+55
View File
@@ -0,0 +1,55 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Sets the equal height to the user plan widget boxes.
*
* @module mod_workshop/workshopview
* @category output
* @copyright Loc Nguyen <loc.nguyendinh@harveynash.vn>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* Sets the equal height to all elements in the group.
*
* @param {jQuery} group List of nodes.
*/
function equalHeight(group) {
var tallest = 0;
group.height('auto');
group.each(function() {
var thisHeight = $(this).height();
if (thisHeight > tallest) {
tallest = thisHeight;
}
});
group.height(tallest);
}
return /** @alias module:mod_workshop/workshopview */ {
init: function() {
var $dt = $('.path-mod-workshop .userplan dt');
var $dd = $('.path-mod-workshop .userplan dd');
equalHeight($dt);
equalHeight($dd);
$(window).on("resize", function() {
equalHeight($dt);
equalHeight($dd);
});
}
};
});
+209
View File
@@ -0,0 +1,209 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Assess a submission or view the single assessment
*
* Assessment id parameter must be passed. The script displays the submission and
* the assessment form. If the current user is the reviewer and the assessing is
* allowed, new assessment can be saved.
* If the assessing is not allowed (for example, the assessment period is over
* or the current user is eg a teacher), the assessment form is opened
* in a non-editable mode.
* The capability 'mod/workshop:peerassess' is intentionally not checked here.
* The user is considered as a reviewer if the corresponding assessment record
* has been prepared for him/her (during the allocation). So even a user without the
* peerassess capability (like a 'teacher', for example) can become a reviewer.
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$asid = required_param('asid', PARAM_INT); // assessment id
$assessment = $DB->get_record('workshop_assessments', array('id' => $asid), '*', MUST_EXIST);
$submission = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'example' => 0), '*', MUST_EXIST);
$workshop = $DB->get_record('workshop', array('id' => $submission->workshopid), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id' => $workshop->course), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('workshop', $workshop->id, $course->id, false, MUST_EXIST);
require_login($course, false, $cm);
if (isguestuser()) {
throw new \moodle_exception('guestsarenotallowed');
}
$workshop = new workshop($workshop, $cm, $course);
$PAGE->set_url($workshop->assess_url($assessment->id));
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->activityheader->set_attrs([
"hidecompletion" => true,
"description" => ""
]);
$PAGE->navbar->add(get_string('assessingsubmission', 'workshop'));
$PAGE->set_secondary_active_tab('modulepage');
$cansetassessmentweight = has_capability('mod/workshop:allocate', $workshop->context);
$canoverridegrades = has_capability('mod/workshop:overridegrades', $workshop->context);
$isreviewer = ($USER->id == $assessment->reviewerid);
$workshop->check_view_assessment($assessment, $submission);
// only the reviewer is allowed to modify the assessment
if ($isreviewer and $workshop->assessing_allowed($USER->id)) {
$assessmenteditable = true;
} else {
$assessmenteditable = false;
}
// check that all required examples have been assessed by the user
if ($assessmenteditable) {
list($assessed, $notice) = $workshop->check_examples_assessed_before_assessment($assessment->reviewerid);
if (!$assessed) {
echo $output->header();
notice(get_string($notice, 'workshop'), new moodle_url('/mod/workshop/view.php', array('id' => $cm->id)));
echo $output->footer();
exit;
}
}
// load the grading strategy logic
$strategy = $workshop->grading_strategy_instance();
if (is_null($assessment->grade) and !$assessmenteditable) {
$mform = null;
} else {
// Are there any other pending assessments to do but this one?
if ($assessmenteditable) {
$pending = $workshop->get_pending_assessments_by_reviewer($assessment->reviewerid, $assessment->id);
} else {
$pending = array();
}
// load the assessment form and process the submitted data eventually
$mform = $strategy->get_assessment_form($PAGE->url, 'assessment', $assessment, $assessmenteditable,
array('editableweight' => $cansetassessmentweight, 'pending' => !empty($pending)));
// Set data managed by the workshop core, subplugins set their own data themselves.
$currentdata = (object)array(
'weight' => $assessment->weight,
'feedbackauthor' => $assessment->feedbackauthor,
'feedbackauthorformat' => $assessment->feedbackauthorformat,
);
if ($assessmenteditable and $workshop->overallfeedbackmode) {
$currentdata = file_prepare_standard_editor($currentdata, 'feedbackauthor', $workshop->overall_feedback_content_options(),
$workshop->context, 'mod_workshop', 'overallfeedback_content', $assessment->id);
if ($workshop->overallfeedbackfiles) {
$currentdata = file_prepare_standard_filemanager($currentdata, 'feedbackauthorattachment',
$workshop->overall_feedback_attachment_options(), $workshop->context, 'mod_workshop', 'overallfeedback_attachment',
$assessment->id);
}
}
$mform->set_data($currentdata);
if ($mform->is_cancelled()) {
redirect($workshop->view_url());
} elseif ($assessmenteditable and ($data = $mform->get_data())) {
// Add or update assessment.
$rawgrade = $workshop->edit_assessment($assessment, $submission, $data, $strategy);
// And finally redirect the user's browser.
if (!is_null($rawgrade) and isset($data->saveandclose)) {
redirect($workshop->view_url());
} else if (!is_null($rawgrade) and isset($data->saveandshownext)) {
$next = reset($pending);
if (!empty($next)) {
redirect($workshop->assess_url($next->id));
} else {
redirect($PAGE->url); // This should never happen but just in case...
}
} else {
// either it is not possible to calculate the $rawgrade
// or the reviewer has chosen "Save and continue"
redirect($PAGE->url);
}
}
}
// load the form to override gradinggrade and/or set weight and process the submitted data eventually
if ($canoverridegrades or $cansetassessmentweight) {
$options = array(
'editable' => true,
'editableweight' => $cansetassessmentweight,
'overridablegradinggrade' => $canoverridegrades);
$feedbackform = $workshop->get_feedbackreviewer_form($PAGE->url, $assessment, $options);
if ($data = $feedbackform->get_data()) {
$workshop->evaluate_assessment($assessment, $data, $cansetassessmentweight, $canoverridegrades);
$workshop->aggregate_grading_grades();
redirect($workshop->view_url());
}
}
// output starts here
$output = $PAGE->get_renderer('mod_workshop'); // workshop renderer
echo $output->header();
echo $output->heading(get_string('assessedsubmission', 'workshop'), 3);
$submission = $workshop->get_submission_by_id($submission->id); // reload so can be passed to the renderer
echo $output->render($workshop->prepare_submission($submission, has_capability('mod/workshop:viewauthornames', $workshop->context)));
// show instructions for assessing as they may contain important information
// for evaluating the assessment
if (trim($workshop->instructreviewers)) {
$instructions = file_rewrite_pluginfile_urls($workshop->instructreviewers, 'pluginfile.php', $PAGE->context->id,
'mod_workshop', 'instructreviewers', null, workshop::instruction_editors_options($PAGE->context));
print_collapsible_region_start('', 'workshop-viewlet-instructreviewers', get_string('instructreviewers', 'workshop'),
'workshop-viewlet-instructreviewers-collapsed');
echo $output->box(format_text($instructions, $workshop->instructreviewersformat, array('overflowdiv'=>true)), array('generalbox', 'instructions'));
print_collapsible_region_end();
}
// extend the current assessment record with user details
$assessment = $workshop->get_assessment_by_id($assessment->id);
if ($isreviewer) {
$options = array(
'showreviewer' => true,
'showauthor' => has_capability('mod/workshop:viewauthornames', $workshop->context),
'showform' => $assessmenteditable or !is_null($assessment->grade),
'showweight' => true,
);
$assessment = $workshop->prepare_assessment($assessment, $mform, $options);
$assessment->title = get_string('assessmentbyyourself', 'workshop');
echo $output->render($assessment);
} else {
$options = array(
'showreviewer' => has_capability('mod/workshop:viewreviewernames', $workshop->context),
'showauthor' => has_capability('mod/workshop:viewauthornames', $workshop->context),
'showform' => $assessmenteditable or !is_null($assessment->grade),
'showweight' => true,
);
$assessment = $workshop->prepare_assessment($assessment, $mform, $options);
echo $output->render($assessment);
}
if (!$assessmenteditable and $canoverridegrades) {
$feedbackform->display();
}
echo $output->footer();
+419
View File
@@ -0,0 +1,419 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides support for the conversion of moodle1 backup to the moodle2 format
*
* @package mod_workshop
* @copyright 2011 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Workshop conversion handler
*/
class moodle1_mod_workshop_handler extends moodle1_mod_handler {
/** @var array the temporary in-memory cache for the current <MOD> contents */
protected $currentworkshop = null;
/** @var array in-memory cache for the course module information for the current workshop */
protected $currentcminfo = null;
/** @var array the mapping of legacy elementno => newelementid for the current workshop */
protected $newelementids = array();
/** @var moodle1_file_manager for the current workshop */
protected $fileman = null;
/** @var moodle1_inforef_manager */
protected $inforefman = null;
/** @var array list of moodle1_workshopform_handler instances */
private $strategyhandlers = null;
/** @var int parent id for the rubric level */
private $currentelementid = null;
/**
* Declare the paths in moodle.xml we are able to convert
*
* The method returns list of {@link convert_path} instances. For each path returned,
* at least one of on_xxx_start(), process_xxx() and on_xxx_end() methods must be
* defined. The method process_xxx() is not executed if the associated path element is
* empty (i.e. it contains none elements or sub-paths only).
*
* Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP does not
* actually exist in the file. The last element with the module name was
* appended by the moodle1_converter class.
*
* @return array of {@link convert_path} instances
*/
public function get_paths() {
return array(
new convert_path('workshop', '/MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP'),
new convert_path('workshop_elements', '/MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP/ELEMENTS'),
new convert_path(
'workshop_element', '/MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP/ELEMENTS/ELEMENT',
array(
'dropfields' => array(
'stddev',
'totalassessments',
),
)
),
new convert_path('workshop_element_rubric', '/MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP/ELEMENTS/ELEMENT/RUBRICS/RUBRIC'),
);
}
/**
* This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP
* data available
*/
public function process_workshop($data, $raw) {
// re-use the upgrade function to convert workshop record
$fakerecord = (object)$data;
$fakerecord->course = 12345678;
$this->currentworkshop = (array)workshop_upgrade_transform_instance($fakerecord);
unset($this->currentworkshop['course']);
// add the new fields with the default values
$this->currentworkshop['id'] = $data['id'];
$this->currentworkshop['evaluation'] = 'best';
$this->currentworkshop['examplesmode'] = workshop::EXAMPLES_VOLUNTARY;
$this->currentworkshop['gradedecimals'] = 0;
$this->currentworkshop['instructauthors'] = '';
$this->currentworkshop['instructauthorsformat'] = FORMAT_HTML;
$this->currentworkshop['instructreviewers'] = '';
$this->currentworkshop['instructreviewersformat'] = FORMAT_HTML;
$this->currentworkshop['latesubmissions'] = 0;
$this->currentworkshop['conclusion'] = '';
$this->currentworkshop['conclusionformat'] = FORMAT_HTML;
foreach (array('submissionend', 'submissionstart', 'assessmentend', 'assessmentstart') as $field) {
if (!array_key_exists($field, $this->currentworkshop)) {
$this->currentworkshop[$field] = null;
}
}
// get the course module id and context id
$instanceid = $data['id'];
$this->currentcminfo = $this->get_cminfo($instanceid);
$moduleid = $this->currentcminfo['id'];
$contextid = $this->converter->get_contextid(CONTEXT_MODULE, $moduleid);
// get a fresh new inforef manager for this instance
$this->inforefman = $this->converter->get_inforef_manager('activity', $moduleid);
// get a fresh new file manager for this instance
$this->fileman = $this->converter->get_file_manager($contextid, 'mod_workshop');
// convert course files embedded into the intro
$this->fileman->filearea = 'intro';
$this->fileman->itemid = 0;
$this->currentworkshop['intro'] = moodle1_converter::migrate_referenced_files($this->currentworkshop['intro'], $this->fileman);
// write workshop.xml
$this->open_xml_writer("activities/workshop_{$moduleid}/workshop.xml");
$this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $moduleid,
'modulename' => 'workshop', 'contextid' => $contextid));
$this->xmlwriter->begin_tag('workshop', array('id' => $instanceid));
foreach ($this->currentworkshop as $field => $value) {
if ($field <> 'id') {
$this->xmlwriter->full_tag($field, $value);
}
}
return $this->currentworkshop;
}
/**
* This is executed when the parser reaches <ELEMENTS>
*
* The dimensions definition follows. One of the grading strategy subplugins
* will append dimensions data in {@link self::process_workshop_element()}
*/
public function on_workshop_elements_start() {
$this->xmlwriter->begin_tag('subplugin_workshopform_'.$this->currentworkshop['strategy'].'_workshop');
// inform the strategy handler that a new workshop instance is being processed
$handler = $this->get_strategy_handler($this->currentworkshop['strategy']);
$handler->use_xml_writer($this->xmlwriter);
$handler->on_elements_start();
}
/**
* Processes one <ELEMENT> tag from moodle.xml
*/
public function process_workshop_element($data, $raw) {
// generate artificial element id and remember it for later usage
$data['id'] = $this->converter->get_nextid();
$this->currentelementid = $data['id'];
$this->newelementids[$data['elementno']] = $data['id'];
// let the strategy subplugin do whatever it needs to
$handler = $this->get_strategy_handler($this->currentworkshop['strategy']);
return $handler->process_legacy_element($data, $raw);
}
/**
* Processes one <RUBRIC> tag from moodle.xml
*/
public function process_workshop_element_rubric($data, $raw) {
if ($this->currentworkshop['strategy'] == 'rubric') {
$handler = $this->get_strategy_handler('rubric');
$data['elementid'] = $this->currentelementid;
$handler->process_legacy_rubric($data, $raw);
}
}
/**
* This is executed when the parser reaches </ELEMENT>
*/
public function on_workshop_element_end() {
// give the strategy handlers a chance to write what they need
$handler = $this->get_strategy_handler($this->currentworkshop['strategy']);
$handler->on_legacy_element_end();
}
/**
* This is executed when the parser reaches </ELEMENTS>
*/
public function on_workshop_elements_end() {
// give the strategy hanlders last chance to write what they need
$handler = $this->get_strategy_handler($this->currentworkshop['strategy']);
$handler->on_elements_end();
// close the dimensions definition
$this->xmlwriter->end_tag('subplugin_workshopform_'.$this->currentworkshop['strategy'].'_workshop');
// as a temporary hack, we just write empty wrappers for the rest of data
$this->write_xml('examplesubmissions', array());
$this->write_xml('submissions', array());
$this->write_xml('aggregations', array());
}
/**
* This is executed when the parser reaches </MOD>
*/
public function on_workshop_end() {
// close workshop.xml
$this->xmlwriter->end_tag('workshop');
$this->xmlwriter->end_tag('activity');
$this->close_xml_writer();
// write inforef.xml
$this->inforefman->add_refs('file', $this->fileman->get_fileids());
$moduleid = $this->currentcminfo['id'];
$this->open_xml_writer("activities/workshop_{$moduleid}/inforef.xml");
$this->inforefman->write_refs($this->xmlwriter);
$this->close_xml_writer();
// get ready for the next instance
$this->currentworkshop = null;
$this->currentcminfo = null;
$this->newelementids = array();
}
/**
* Provides access to the current <workshop> data
*
* @return array|null
*/
public function get_current_workshop() {
return $this->currentworkshop;
}
/**
* Provides access to the instance's inforef manager
*
* @return moodle1_inforef_manager
*/
public function get_inforef_manager() {
return $this->inforefman;
}
/// internal implementation details follow /////////////////////////////////
/**
* Factory method returning the handler of the given grading strategy subplugin
*
* @param string $strategy the name of the grading strategy
* @throws moodle1_convert_exception
* @return moodle1_workshopform_handler the instance of the handler
*/
protected function get_strategy_handler($strategy) {
global $CFG; // we include other files here
if (is_null($this->strategyhandlers)) {
$this->strategyhandlers = array();
$subplugins = core_component::get_plugin_list('workshopform');
foreach ($subplugins as $name => $dir) {
$handlerfile = $dir.'/backup/moodle1/lib.php';
$handlerclass = "moodle1_workshopform_{$name}_handler";
if (!file_exists($handlerfile)) {
continue;
}
require_once($handlerfile);
if (!class_exists($handlerclass)) {
throw new moodle1_convert_exception('missing_handler_class', $handlerclass);
}
$this->log('preparing workshop grading strategy handler', backup::LOG_DEBUG, $handlerclass);
$this->strategyhandlers[$name] = new $handlerclass($this, $name);
if (!$this->strategyhandlers[$name] instanceof moodle1_workshopform_handler) {
throw new moodle1_convert_exception('wrong_handler_class', get_class($this->strategyhandlers[$name]));
}
}
}
if (!isset($this->strategyhandlers[$strategy])) {
throw new moodle1_convert_exception('usupported_subplugin', 'workshopform_'.$strategy);
}
return $this->strategyhandlers[$strategy];
}
}
/**
* Base class for the grading strategy subplugin handler
*/
abstract class moodle1_workshopform_handler extends moodle1_submod_handler {
/**
* @param moodle1_mod_handler $workshophandler the handler of a module we are subplugin of
* @param string $subpluginname the name of the subplugin
*/
public function __construct(moodle1_mod_handler $workshophandler, $subpluginname) {
parent::__construct($workshophandler, 'workshopform', $subpluginname);
}
/**
* Provides a xml_writer instance to this workshopform handler
*
* @param xml_writer $xmlwriter
*/
public function use_xml_writer(xml_writer $xmlwriter) {
$this->xmlwriter = $xmlwriter;
}
/**
* Called when we reach <ELEMENTS>
*
* Gives the handler a chance to prepare for a new workshop instance
*/
public function on_elements_start() {
// do nothing by default
}
/**
* Called everytime when legacy <ELEMENT> data are available
*
* @param array $data legacy element data
* @param array $raw raw element data
*
* @return array converted
*/
public function process_legacy_element(array $data, array $raw) {
return $data;
}
/**
* Called when we reach </ELEMENT>
*/
public function on_legacy_element_end() {
// do nothing by default
}
/**
* Called when we reach </ELEMENTS>
*/
public function on_elements_end() {
// do nothing by default
}
}
/**
* Given a record containing data from 1.9 workshop table, returns object containing data as should be saved in 2.0 workshop table
*
* @param stdClass $old record from 1.9 workshop table
* @return stdClass
*/
function workshop_upgrade_transform_instance(stdClass $old) {
global $CFG;
require_once(__DIR__ . '/../../locallib.php');
$new = new stdClass();
$new->course = $old->course;
$new->name = $old->name;
$new->intro = $old->description;
$new->introformat = $old->format;
if ($old->nattachments == 0) {
// Convert to the new method for disabling file submissions.
$new->submissiontypefile = WORKSHOP_SUBMISSION_TYPE_DISABLED;
$new->submissiontypetext = WORKSHOP_SUBMISSION_TYPE_REQUIRED;
$new->nattachments = 1;
} else {
$new->nattachments = $old->nattachments;
}
$new->maxbytes = $old->maxbytes;
$new->grade = $old->grade;
$new->gradinggrade = $old->gradinggrade;
$new->phase = workshop::PHASE_CLOSED;
$new->timemodified = time();
if ($old->ntassessments > 0) {
$new->useexamples = 1;
} else {
$new->useexamples = 0;
}
$new->usepeerassessment = 1;
$new->useselfassessment = $old->includeself;
switch ($old->gradingstrategy) {
case 0: // 'notgraded' - renamed
$new->strategy = 'comments';
break;
case 1: // 'accumulative'
$new->strategy = 'accumulative';
break;
case 2: // 'errorbanded' - renamed
$new->strategy = 'numerrors';
break;
case 3: // 'criterion' - will be migrated into 'rubric'
$new->strategy = 'rubric';
break;
case 4: // 'rubric'
$new->strategy = 'rubric';
break;
}
if ($old->submissionstart < $old->submissionend) {
$new->submissionstart = $old->submissionstart;
$new->submissionend = $old->submissionend;
}
if ($old->assessmentstart < $old->assessmentend) {
$new->assessmentstart = $old->assessmentstart;
$new->assessmentend = $old->assessmentend;
}
return $new;
}
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines {@link backup_workshop_activity_task} class
*
* @package mod_workshop
* @category backup
* @copyright 2010 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/workshop/backup/moodle2/backup_workshop_settingslib.php');
require_once($CFG->dirroot . '/mod/workshop/backup/moodle2/backup_workshop_stepslib.php');
/**
* Provides all the settings and steps to perform one complete backup of workshop activity
*/
class backup_workshop_activity_task extends backup_activity_task {
/**
* No specific settings for this activity
*/
protected function define_my_settings() {
}
/**
* Defines a backup step to store the instance data in the workshop.xml file
*/
protected function define_my_steps() {
$this->add_step(new backup_workshop_activity_structure_step('workshop_structure', 'workshop.xml'));
}
/**
* Encodes URLs to the index.php and view.php scripts
*
* @param string $content some HTML text that eventually contains URLs to the activity instance scripts
* @return string the content with the URLs encoded
*/
public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot,"/");
// Link to the list of workshops
$search = "/(" . $base . "\/mod\/workshop\/index.php\?id\=)([0-9]+)/";
$content = preg_replace($search, '$@WORKSHOPINDEX*$2@$', $content);
//Link to workshop view by moduleid
$search = "/(" . $base . "\/mod\/workshop\/view.php\?id\=)([0-9]+)/";
$content= preg_replace($search, '$@WORKSHOPVIEWBYID*$2@$', $content);
return $content;
}
}
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Workshop backup settings
*
* Workshop has no particular settings but the inherited from the generic
* {@link backup_activity_task}.
*
* @package mod_workshop
* @category backup
* @copyright 2010 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
@@ -0,0 +1,220 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines all the backup steps that will be used by {@link backup_workshop_activity_task}
*
* @package mod_workshop
* @category backup
* @copyright 2010 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Defines the complete workshop structure for backup, with file and id annotations
*
* @link http://docs.moodle.org/dev/Workshop for XML structure diagram
*/
class backup_workshop_activity_structure_step extends backup_activity_structure_step {
/**
* Defines the structure of the 'workshop' element inside the workshop.xml file
*
* @return backup_nested_element
*/
protected function define_structure() {
// are we including userinfo?
$userinfo = $this->get_setting_value('userinfo');
////////////////////////////////////////////////////////////////////////
// XML nodes declaration - non-user data
////////////////////////////////////////////////////////////////////////
// root element describing workshop instance
$workshop = new backup_nested_element('workshop', array('id'), array(
'name', 'intro', 'introformat', 'instructauthors',
'instructauthorsformat', 'instructreviewers',
'instructreviewersformat', 'timemodified', 'phase', 'useexamples',
'usepeerassessment', 'useselfassessment', 'grade', 'gradinggrade',
'strategy', 'evaluation', 'gradedecimals', 'submissiontypetext', 'submissiontypefile', 'nattachments',
'submissionfiletypes', 'latesubmissions', 'maxbytes', 'examplesmode', 'submissionstart',
'submissionend', 'assessmentstart', 'assessmentend',
'conclusion', 'conclusionformat', 'overallfeedbackmode',
'overallfeedbackfiles', 'overallfeedbackfiletypes', 'overallfeedbackmaxbytes'));
// assessment forms definition
$this->add_subplugin_structure('workshopform', $workshop, true);
// grading evaluations data
$this->add_subplugin_structure('workshopeval', $workshop, true);
// example submissions
$examplesubmissions = new backup_nested_element('examplesubmissions');
$examplesubmission = new backup_nested_element('examplesubmission', array('id'), array(
'timecreated', 'timemodified', 'title', 'content', 'contentformat',
'contenttrust', 'attachment'));
// reference assessment of the example submission
$referenceassessment = new backup_nested_element('referenceassessment', array('id'), array(
'timecreated', 'timemodified', 'grade', 'feedbackauthor', 'feedbackauthorformat',
'feedbackauthorattachment'));
// dimension grades for the reference assessment (that is how the form is filled)
$this->add_subplugin_structure('workshopform', $referenceassessment, true);
////////////////////////////////////////////////////////////////////////
// XML nodes declaration - user data
////////////////////////////////////////////////////////////////////////
// assessments of example submissions
$exampleassessments = new backup_nested_element('exampleassessments');
$exampleassessment = new backup_nested_element('exampleassessment', array('id'), array(
'reviewerid', 'weight', 'timecreated', 'timemodified', 'grade',
'gradinggrade', 'gradinggradeover', 'gradinggradeoverby',
'feedbackauthor', 'feedbackauthorformat', 'feedbackauthorattachment',
'feedbackreviewer', 'feedbackreviewerformat'));
// dimension grades for the assessment of example submission (that is assessment forms are filled)
$this->add_subplugin_structure('workshopform', $exampleassessment, true);
// submissions
$submissions = new backup_nested_element('submissions');
$submission = new backup_nested_element('submission', array('id'), array(
'authorid', 'timecreated', 'timemodified', 'title', 'content',
'contentformat', 'contenttrust', 'attachment', 'grade',
'gradeover', 'gradeoverby', 'feedbackauthor',
'feedbackauthorformat', 'timegraded', 'published', 'late'));
// allocated assessments
$assessments = new backup_nested_element('assessments');
$assessment = new backup_nested_element('assessment', array('id'), array(
'reviewerid', 'weight', 'timecreated', 'timemodified', 'grade',
'gradinggrade', 'gradinggradeover', 'gradinggradeoverby',
'feedbackauthor', 'feedbackauthorformat', 'feedbackauthorattachment',
'feedbackreviewer', 'feedbackreviewerformat'));
// dimension grades for the assessment (that is assessment forms are filled)
$this->add_subplugin_structure('workshopform', $assessment, true);
// aggregations of grading grades in this workshop
$aggregations = new backup_nested_element('aggregations');
$aggregation = new backup_nested_element('aggregation', array('id'), array(
'userid', 'gradinggrade', 'timegraded'));
////////////////////////////////////////////////////////////////////////
// build the tree in the order needed for restore
////////////////////////////////////////////////////////////////////////
$workshop->add_child($examplesubmissions);
$examplesubmissions->add_child($examplesubmission);
$examplesubmission->add_child($referenceassessment);
$examplesubmission->add_child($exampleassessments);
$exampleassessments->add_child($exampleassessment);
$workshop->add_child($submissions);
$submissions->add_child($submission);
$submission->add_child($assessments);
$assessments->add_child($assessment);
$workshop->add_child($aggregations);
$aggregations->add_child($aggregation);
////////////////////////////////////////////////////////////////////////
// data sources - non-user data
////////////////////////////////////////////////////////////////////////
$workshop->set_source_table('workshop', array('id' => backup::VAR_ACTIVITYID));
$examplesubmission->set_source_sql("
SELECT *
FROM {workshop_submissions}
WHERE workshopid = ? AND example = 1",
array(backup::VAR_PARENTID));
$referenceassessment->set_source_sql("
SELECT *
FROM {workshop_assessments}
WHERE weight = 1 AND submissionid = ?",
array(backup::VAR_PARENTID));
////////////////////////////////////////////////////////////////////////
// data sources - user related data
////////////////////////////////////////////////////////////////////////
if ($userinfo) {
$exampleassessment->set_source_sql("
SELECT *
FROM {workshop_assessments}
WHERE weight = 0 AND submissionid = ?",
array(backup::VAR_PARENTID));
$submission->set_source_sql("
SELECT *
FROM {workshop_submissions}
WHERE workshopid = ? AND example = 0",
array(backup::VAR_PARENTID)); // must use SQL here, for the same reason as above
$assessment->set_source_table('workshop_assessments', array('submissionid' => backup::VAR_PARENTID));
$aggregation->set_source_table('workshop_aggregations', array('workshopid' => backup::VAR_PARENTID));
}
////////////////////////////////////////////////////////////////////////
// id annotations
////////////////////////////////////////////////////////////////////////
$exampleassessment->annotate_ids('user', 'reviewerid');
$submission->annotate_ids('user', 'authorid');
$submission->annotate_ids('user', 'gradeoverby');
$assessment->annotate_ids('user', 'reviewerid');
$assessment->annotate_ids('user', 'gradinggradeoverby');
$aggregation->annotate_ids('user', 'userid');
////////////////////////////////////////////////////////////////////////
// file annotations
////////////////////////////////////////////////////////////////////////
$workshop->annotate_files('mod_workshop', 'intro', null); // no itemid used
$workshop->annotate_files('mod_workshop', 'instructauthors', null); // no itemid used
$workshop->annotate_files('mod_workshop', 'instructreviewers', null); // no itemid used
$workshop->annotate_files('mod_workshop', 'conclusion', null); // no itemid used
$examplesubmission->annotate_files('mod_workshop', 'submission_content', 'id');
$examplesubmission->annotate_files('mod_workshop', 'submission_attachment', 'id');
$referenceassessment->annotate_files('mod_workshop', 'overallfeedback_content', 'id');
$referenceassessment->annotate_files('mod_workshop', 'overallfeedback_attachment', 'id');
$exampleassessment->annotate_files('mod_workshop', 'overallfeedback_content', 'id');
$exampleassessment->annotate_files('mod_workshop', 'overallfeedback_attachment', 'id');
$submission->annotate_files('mod_workshop', 'submission_content', 'id');
$submission->annotate_files('mod_workshop', 'submission_attachment', 'id');
$assessment->annotate_files('mod_workshop', 'overallfeedback_content', 'id');
$assessment->annotate_files('mod_workshop', 'overallfeedback_attachment', 'id');
// return the root element (workshop), wrapped into standard activity structure
return $this->prepare_activity_structure($workshop);
}
}
@@ -0,0 +1,147 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_workshop
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/workshop/backup/moodle2/restore_workshop_stepslib.php'); // Because it exists (must)
/**
* workshop restore task that provides all the settings and steps to perform one
* complete restore of the activity
*/
class restore_workshop_activity_task extends restore_activity_task {
/**
* Define (add) particular settings this activity can have
*/
protected function define_my_settings() {
// No particular settings for this activity
}
/**
* Define (add) particular steps this activity can have
*/
protected function define_my_steps() {
// Choice only has one structure step
$this->add_step(new restore_workshop_activity_structure_step('workshop_structure', 'workshop.xml'));
}
/**
* Define the contents in the activity that must be
* processed by the link decoder
*/
public static function define_decode_contents() {
$contents = array();
$contents[] = new restore_decode_content('workshop',
array('intro', 'instructauthors', 'instructreviewers', 'conclusion'), 'workshop');
$contents[] = new restore_decode_content('workshop_submissions',
array('content', 'feedbackauthor'), 'workshop_submission');
$contents[] = new restore_decode_content('workshop_assessments',
array('feedbackauthor', 'feedbackreviewer'), 'workshop_assessment');
return $contents;
}
/**
* Define the decoding rules for links belonging
* to the activity to be executed by the link decoder
*/
public static function define_decode_rules() {
$rules = array();
$rules[] = new restore_decode_rule('WORKSHOPVIEWBYID', '/mod/workshop/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('WORKSHOPINDEX', '/mod/workshop/index.php?id=$1', 'course');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* workshop logs. It must return one array
* of {@link restore_log_rule} objects
*/
public static function define_restore_log_rules() {
$rules = array();
$rules[] = new restore_log_rule('workshop', 'add', 'view.php?id={course_module}', '{workshop}');
$rules[] = new restore_log_rule('workshop', 'update', 'view.php?id={course_module}', '{workshop}');
$rules[] = new restore_log_rule('workshop', 'view', 'view.php?id={course_module}', '{workshop}');
$rules[] = new restore_log_rule('workshop', 'add assessment',
'assessment.php?asid={workshop_assessment}', '{workshop_submission}');
$rules[] = new restore_log_rule('workshop', 'update assessment',
'assessment.php?asid={workshop_assessment}', '{workshop_submission}');
$rules[] = new restore_log_rule('workshop', 'add reference assessment',
'exassessment.php?asid={workshop_referenceassessment}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'update reference assessment',
'exassessment.php?asid={workshop_referenceassessment}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'add example assessment',
'exassessment.php?asid={workshop_exampleassessment}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'update example assessment',
'exassessment.php?asid={workshop_exampleassessment}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'view submission',
'submission.php?cmid={course_module}&id={workshop_submission}', '{workshop_submission}');
$rules[] = new restore_log_rule('workshop', 'add submission',
'submission.php?cmid={course_module}&id={workshop_submission}', '{workshop_submission}');
$rules[] = new restore_log_rule('workshop', 'update submission',
'submission.php?cmid={course_module}&id={workshop_submission}', '{workshop_submission}');
$rules[] = new restore_log_rule('workshop', 'view example',
'exsubmission.php?cmid={course_module}&id={workshop_examplesubmission}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'add example',
'exsubmission.php?cmid={course_module}&id={workshop_examplesubmission}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'update example',
'exsubmission.php?cmid={course_module}&id={workshop_examplesubmission}', '{workshop_examplesubmission}');
$rules[] = new restore_log_rule('workshop', 'update aggregate grades', 'view.php?id={course_module}', '{workshop}');
$rules[] = new restore_log_rule('workshop', 'update switch phase', 'view.php?id={course_module}', '[phase]');
$rules[] = new restore_log_rule('workshop', 'update clear aggregated grades', 'view.php?id={course_module}', '{workshop}');
$rules[] = new restore_log_rule('workshop', 'update clear assessments', 'view.php?id={course_module}', '{workshop}');
return $rules;
}
/**
* Define the restore log rules that will be applied
* by the {@link restore_logs_processor} when restoring
* course logs. It must return one array
* of {@link restore_log_rule} objects
*
* Note this rules are applied when restoring course logs
* by the restore final task, but are defined here at
* activity level. All them are rules not linked to any module instance (cmid = 0)
*/
public static function define_restore_log_rules_for_course() {
$rules = array();
$rules[] = new restore_log_rule('workshop', 'view all', 'index.php?id={course}', null);
return $rules;
}
}
@@ -0,0 +1,237 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package mod_workshop
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Define all the restore steps that will be used by the restore_workshop_activity_task
*/
/**
* Structure step to restore one workshop activity
*/
class restore_workshop_activity_structure_step extends restore_activity_structure_step {
protected function define_structure() {
$paths = array();
$userinfo = $this->get_setting_value('userinfo'); // are we including userinfo?
////////////////////////////////////////////////////////////////////////
// XML interesting paths - non-user data
////////////////////////////////////////////////////////////////////////
// root element describing workshop instance
$workshop = new restore_path_element('workshop', '/activity/workshop');
$paths[] = $workshop;
// Apply for 'workshopform' subplugins optional paths at workshop level
$this->add_subplugin_structure('workshopform', $workshop);
// Apply for 'workshopeval' subplugins optional paths at workshop level
$this->add_subplugin_structure('workshopeval', $workshop);
// example submissions
$paths[] = new restore_path_element('workshop_examplesubmission',
'/activity/workshop/examplesubmissions/examplesubmission');
// reference assessment of the example submission
$referenceassessment = new restore_path_element('workshop_referenceassessment',
'/activity/workshop/examplesubmissions/examplesubmission/referenceassessment');
$paths[] = $referenceassessment;
// Apply for 'workshopform' subplugins optional paths at referenceassessment level
$this->add_subplugin_structure('workshopform', $referenceassessment);
// End here if no-user data has been selected
if (!$userinfo) {
return $this->prepare_activity_structure($paths);
}
////////////////////////////////////////////////////////////////////////
// XML interesting paths - user data
////////////////////////////////////////////////////////////////////////
// assessments of example submissions
$exampleassessment = new restore_path_element('workshop_exampleassessment',
'/activity/workshop/examplesubmissions/examplesubmission/exampleassessments/exampleassessment');
$paths[] = $exampleassessment;
// Apply for 'workshopform' subplugins optional paths at exampleassessment level
$this->add_subplugin_structure('workshopform', $exampleassessment);
// submissions
$paths[] = new restore_path_element('workshop_submission', '/activity/workshop/submissions/submission');
// allocated assessments
$assessment = new restore_path_element('workshop_assessment',
'/activity/workshop/submissions/submission/assessments/assessment');
$paths[] = $assessment;
// Apply for 'workshopform' subplugins optional paths at assessment level
$this->add_subplugin_structure('workshopform', $assessment);
// aggregations of grading grades in this workshop
$paths[] = new restore_path_element('workshop_aggregation', '/activity/workshop/aggregations/aggregation');
// Return the paths wrapped into standard activity structure
return $this->prepare_activity_structure($paths);
}
protected function process_workshop($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->course = $this->get_courseid();
$data->strategy = clean_param($data->strategy, PARAM_PLUGIN);
$data->evaluation = clean_param($data->evaluation, PARAM_PLUGIN);
// Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
// See MDL-9367.
$data->submissionstart = $this->apply_date_offset($data->submissionstart);
$data->submissionend = $this->apply_date_offset($data->submissionend);
$data->assessmentstart = $this->apply_date_offset($data->assessmentstart);
$data->assessmentend = $this->apply_date_offset($data->assessmentend);
if ($data->nattachments == 0) {
// Convert to the new method for disabling file submissions.
$data->submissiontypefile = WORKSHOP_SUBMISSION_TYPE_DISABLED;
$data->submissiontypetext = WORKSHOP_SUBMISSION_TYPE_REQUIRED;
$data->nattachments = 1;
}
// insert the workshop record
$newitemid = $DB->insert_record('workshop', $data);
// immediately after inserting "activity" record, call this
$this->apply_activity_instance($newitemid);
}
protected function process_workshop_examplesubmission($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->workshopid = $this->get_new_parentid('workshop');
$data->example = 1;
$data->authorid = $this->task->get_userid();
$newitemid = $DB->insert_record('workshop_submissions', $data);
$this->set_mapping('workshop_examplesubmission', $oldid, $newitemid, true); // Mapping with files
}
protected function process_workshop_referenceassessment($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->submissionid = $this->get_new_parentid('workshop_examplesubmission');
$data->reviewerid = $this->task->get_userid();
$newitemid = $DB->insert_record('workshop_assessments', $data);
$this->set_mapping('workshop_referenceassessment', $oldid, $newitemid, true); // Mapping with files
}
protected function process_workshop_exampleassessment($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->submissionid = $this->get_new_parentid('workshop_examplesubmission');
$data->reviewerid = $this->get_mappingid('user', $data->reviewerid);
$newitemid = $DB->insert_record('workshop_assessments', $data);
$this->set_mapping('workshop_exampleassessment', $oldid, $newitemid, true); // Mapping with files
}
protected function process_workshop_submission($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->workshopid = $this->get_new_parentid('workshop');
$data->example = 0;
$data->authorid = $this->get_mappingid('user', $data->authorid);
$newitemid = $DB->insert_record('workshop_submissions', $data);
$this->set_mapping('workshop_submission', $oldid, $newitemid, true); // Mapping with files
}
protected function process_workshop_assessment($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->submissionid = $this->get_new_parentid('workshop_submission');
$data->reviewerid = $this->get_mappingid('user', $data->reviewerid);
$newitemid = $DB->insert_record('workshop_assessments', $data);
$this->set_mapping('workshop_assessment', $oldid, $newitemid, true); // Mapping with files
}
protected function process_workshop_aggregation($data) {
global $DB;
$data = (object)$data;
$oldid = $data->id;
$data->workshopid = $this->get_new_parentid('workshop');
$data->userid = $this->get_mappingid('user', $data->userid);
$newitemid = $DB->insert_record('workshop_aggregations', $data);
$this->set_mapping('workshop_aggregation', $oldid, $newitemid, true);
}
protected function after_execute() {
// Add workshop related files, no need to match by itemname (just internally handled context)
$this->add_related_files('mod_workshop', 'intro', null);
$this->add_related_files('mod_workshop', 'instructauthors', null);
$this->add_related_files('mod_workshop', 'instructreviewers', null);
$this->add_related_files('mod_workshop', 'conclusion', null);
// Add example submission related files, matching by 'workshop_examplesubmission' itemname
$this->add_related_files('mod_workshop', 'submission_content', 'workshop_examplesubmission');
$this->add_related_files('mod_workshop', 'submission_attachment', 'workshop_examplesubmission');
// Add reference assessment related files, matching by 'workshop_referenceassessment' itemname
$this->add_related_files('mod_workshop', 'overallfeedback_content', 'workshop_referenceassessment');
$this->add_related_files('mod_workshop', 'overallfeedback_attachment', 'workshop_referenceassessment');
// Add example assessment related files, matching by 'workshop_exampleassessment' itemname
$this->add_related_files('mod_workshop', 'overallfeedback_content', 'workshop_exampleassessment');
$this->add_related_files('mod_workshop', 'overallfeedback_attachment', 'workshop_exampleassessment');
// Add submission related files, matching by 'workshop_submission' itemname
$this->add_related_files('mod_workshop', 'submission_content', 'workshop_submission');
$this->add_related_files('mod_workshop', 'submission_attachment', 'workshop_submission');
// Add assessment related files, matching by 'workshop_assessment' itemname
$this->add_related_files('mod_workshop', 'overallfeedback_content', 'workshop_assessment');
$this->add_related_files('mod_workshop', 'overallfeedback_attachment', 'workshop_assessment');
}
}
@@ -0,0 +1,64 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Activity base class.
*
* @package mod_workshop
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Activity base class.
*
* @package mod_workshop
* @copyright 2017 onwards Ankit Agarwal <ankit.agrr@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends \core_analytics\local\indicator\community_of_inquiry_activity {
/**
* feedback_check_grades
*
* @return bool
*/
protected function feedback_check_grades() {
return true;
}
/**
* feedback_viewed_events
*
* @return string[]
*/
protected function feedback_viewed_events() {
return array('\mod_workshop\event\course_module_viewed', '\mod_workshop\event\submission_viewed');
}
/**
* Returns the name of the field that controls activity availability.
*
* @return null|string
*/
protected function get_timeclose_field() {
return 'submissionend';
}
}
@@ -0,0 +1,76 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Cognitive depth indicator - workshop.
*
* @package mod_workshop
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Cognitive depth indicator - workshop.
*
* @package mod_workshop
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:cognitivedepth', 'mod_workshop');
}
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
public function get_cognitive_depth_level(\cm_info $cm) {
return self::COGNITIVE_LEVEL_5;
}
/**
* feedback_replied_events
*
* @return string[]
*/
protected function feedback_replied_events() {
return array('\mod_workshop\event\submission_assessed', '\mod_workshop\event\submission_reassessed');
}
/**
* feedback_submitted_events
*
* @return string[]
*/
protected function feedback_submitted_events() {
// Can't use assessable_uploaded instead of submission_* as mod_workshop only triggers it during submission_updated.
return array('\mod_workshop\event\submission_updated', '\mod_workshop\event\submission_created',
'\mod_workshop\event\submission_reassessed');
}
}
@@ -0,0 +1,56 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Social breadth indicator - workshop.
*
* @package mod_workshop
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\analytics\indicator;
defined('MOODLE_INTERNAL') || die();
/**
* Social breadth indicator - workshop.
*
* @package mod_workshop
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return \lang_string
*/
public static function get_name(): \lang_string {
return new \lang_string('indicator:socialbreadth', 'mod_workshop');
}
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
public function get_social_breadth_level(\cm_info $cm) {
return self::SOCIAL_LEVEL_2;
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the class for fetching the important dates in mod_workshop for a given module instance and a user.
*
* @package mod_workshop
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types=1);
namespace mod_workshop;
use core\activity_dates;
/**
* Class for fetching the important dates in mod_workshop for a given module instance and a user.
*
* @copyright 2021 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dates extends activity_dates {
/**
* Returns a list of important dates in mod_workshop
*
* @return array
*/
protected function get_dates(): array {
$submissionstart = $this->cm->customdata['submissionstart'] ?? null;
$submissionend = $this->cm->customdata['submissionend'] ?? null;
$assessmentstart = $this->cm->customdata['assessmentstart'] ?? null;
$assessmentend = $this->cm->customdata['assessmentend'] ?? null;
$now = time();
$dates = [];
if ($submissionstart) {
$openlabelid = $submissionstart > $now ? 'activitydate:submissionsopen' : 'activitydate:submissionsopened';
$dates[] = [
'dataid' => 'submissionstart',
'label' => get_string($openlabelid, 'mod_workshop'),
'timestamp' => (int) $submissionstart,
];
}
if ($submissionend) {
$closelabelid = $submissionend > $now ? 'activitydate:submissionsclose' : 'activitydate:submissionsclosed';
$dates[] = [
'dataid' => 'submissionend',
'label' => get_string($closelabelid, 'mod_workshop'),
'timestamp' => (int) $submissionend,
];
}
if ($assessmentstart) {
$openlabelid = $assessmentstart > $now ? 'activitydate:assessmentsopen' : 'activitydate:assessmentsopened';
$dates[] = [
'dataid' => 'assessmentstart',
'label' => get_string($openlabelid, 'mod_workshop'),
'timestamp' => (int) $assessmentstart,
];
}
if ($assessmentend) {
$closelabelid = $assessmentend > $now ? 'activitydate:assessmentsclose' : 'activitydate:assessmentsclosed';
$dates[] = [
'dataid' => 'assessmentend',
'label' => get_string($closelabelid, 'mod_workshop'),
'timestamp' => (int) $assessmentend,
];
}
return $dates;
}
}
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop assessable uploaded event.
*
* @package mod_workshop
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop assessable uploaded event class.
*
* @package mod_workshop
* @since Moodle 2.6
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessable_uploaded extends \core\event\assessable_uploaded {
/**
* Legacy log data.
*
* @var array
*/
protected $legacylogdata = null;
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has uploaded the submission with id '$this->objectid' " .
"to the workshop activity with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventassessableuploaded', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->contextinstanceid, 'id' => $this->objectid));
}
/**
* Init method.
*
* @return void
*/
protected function init() {
parent::init();
$this->data['objecttable'] = 'workshop_submissions';
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
}
}
@@ -0,0 +1,92 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop assessment evaluated event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop assessment evaluated event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - string currentgrade: (may be null) current saved grade.
* - string finalgrade: (may be null) final grade.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessment_evaluated extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'workshop_aggregations';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has had their assessment attempt evaluated for the workshop with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventassessmentevaluated', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/view.php', array('id' => $this->contextinstanceid));
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_aggregations', 'restore' => 'workshop_aggregation');
}
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,103 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop assessment_evaluations reset event.
*
* @package mod_workshop
* @category event
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop assessment_evaluations reset event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int workshopid: the ID of the workshop.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessment_evaluations_reset extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has reset the assessment evaluations for the workshop with course module id " .
"'$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventassessmentevaluationsreset', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/view.php', array('id' => $this->contextinstanceid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['workshopid'])) {
throw new \coding_exception('The \'workshopid\' value must be set in other.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['workshopid'] = array('db' => 'workshop', 'restore' => 'workshop');
return $othermapped;
}
}
@@ -0,0 +1,92 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop assessment_reevaluated event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop assessment_reevaluated event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - float currentgrade: (may be null) current saved grade.
* - float finalgrade: (may be null) final grade.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessment_reevaluated extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'workshop_aggregations';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has had their assessment attempt reevaluated for the workshop with " .
"course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventassessmentreevaluated', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/view.php', array('id' => $this->contextinstanceid));
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_aggregations', 'restore' => 'workshop_aggregation');
}
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,102 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission assessments reset event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission assessments reset event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int workshopid: the ID of the workshop.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessments_reset extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_TEACHING;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has reset the assessments for the workshop with course module id " .
"'$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissionassessmentsreset', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/view.php', array('id' => $this->contextinstanceid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['workshopid'])) {
throw new \coding_exception('The \'workshopid\' value must be set in other.');
}
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['workshopid'] = array('db' => 'workshop', 'restore' => 'workshop');
return $othermapped;
}
}
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop instance list viewed event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop instance list viewed event class.
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed {
}
@@ -0,0 +1,54 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop course module viewed event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->dirroot/mod/workshop/locallib.php");
/**
* The mod_workshop course module viewed event class.
*
* @package mod_workshop
* @since Moodle 2.6
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop';
}
public static function get_objectid_mapping() {
return array('db' => 'workshop', 'restore' => 'workshop');
}
}
@@ -0,0 +1,110 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_workshop\event;
/**
* This event is triggered when a phase is automatically switched, usually from cron_task.
*
* @property-read array $other {
* Extra information about the event.
*
* - int previousworkshopphase: Previous workshop phase.
* - int targetworkshopphase: Target workshop phase.
* }
*
* @package mod_workshop
* @copyright 2020 Universitat Jaume I <https://www.uji.es/>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class phase_automatically_switched extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'workshop';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The phase of the workshop with course module id " .
"'$this->contextinstanceid' has been automatically switched from " .
"'{$this->other['previousworkshopphase']} to '{$this->other['currentworkshopphase']}'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventphaseautomaticallyswitched', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/view.php', array('id' => $this->contextinstanceid));
}
/**
* Custom validation.
*
* @return void
* @throws \coding_exception
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['previousworkshopphase'])) {
throw new \coding_exception('The \'previousworkshopphase\' value must be set in other.');
}
if (!isset($this->other['targetworkshopphase'])) {
throw new \coding_exception('The \'targetworkshopphase\' value must be set in other.');
}
}
/**
* Map the objectid information in order to restore the event accurately. In this event
* objectid is the workshop id.
*
* @return array
*/
public static function get_objectid_mapping() {
return array('db' => 'workshop', 'restore' => 'workshop');
}
/**
* No need to map the 'other' field as it only stores phases and they don't need to be mapped.
*
* @return bool
*/
public static function get_other_mapping() {
return false;
}
}
@@ -0,0 +1,105 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop phase switched event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop phase switched event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int workshopphase: Workshop phase.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class phase_switched extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_TEACHING;
$this->data['objecttable'] = 'workshop';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has switched the phase of the workshop with course module id " .
"'$this->contextinstanceid' to '{$this->other['workshopphase']}'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventphaseswitched', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/view.php', array('id' => $this->contextinstanceid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->other['workshopphase'])) {
throw new \coding_exception('The \'workshopphase\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'workshop', 'restore' => 'workshop');
}
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,113 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission assessed event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission assessed event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int submissionid: Submission ID.
* - int workshopid: (optional) Workshop ID.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_assessed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop_assessments';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' assessed the submission with id '$this->objectid' for the user with " .
"id '$this->relateduserid' in the workshop with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissionassessed', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/assessment.php', array('asid' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['submissionid'])) {
throw new \coding_exception('The \'submissionid\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_assessments', 'restore' => 'workshop_assessment');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['submissionid'] = array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
$othermapped['workshopid'] = array('db' => 'workshop', 'restore' => 'workshop');
return $othermapped;
}
}
@@ -0,0 +1,89 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission created event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission created event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - string submissiontitle: (optional) Submission title.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_created extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'c';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop_submissions';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' created the submission with id of '$this->objectid' for the workshop " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissioncreated', 'workshop');
}
/**
* Returns relevant URL.
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->contextinstanceid, 'id' => $this->objectid));
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
}
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,100 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission deleted event.
*
* @package mod_workshop
* @copyright 2015 Paul Nicholls
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission deleted event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - string submissiontitle: (optional) Submission title.
* }
*
* @package mod_workshop
* @since Moodle 3.1
* @copyright 2015 Paul Nicholls
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_deleted extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'd';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop_submissions';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' deleted the submission with id '$this->objectid' for the workshop " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissiondeleted', 'workshop');
}
/**
* Returns relevant URL.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->contextinstanceid, 'id' => $this->objectid));
}
/**
* Defines mapping of the 'objectid' property when restoring course logs.
*
* @return array
*/
public static function get_objectid_mapping() {
return array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
}
/**
* Defines mapping of the 'other' property when restoring course logs.
*
* @return array|bool
*/
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,114 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission reassessed event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission reassessed event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int submissionid: Submission ID.
* - int workshopid: (optional) Workshop ID.
* - float grade: (optional) Assessment grade.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_reassessed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop_assessments';
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' reassessed the submission with id '$this->objectid' for the user with " .
"id '$this->relateduserid' in the workshop with course module id '$this->contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissionreassessed', 'mod_workshop');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/assessment.php?', array('asid' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
if (!isset($this->other['submissionid'])) {
throw new \coding_exception('The \'submissionid\' value must be set in other.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_assessments', 'restore' => 'workshop_assessment');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['submissionid'] = array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
$othermapped['workshopid'] = array('db' => 'workshop', 'restore' => 'workshop');
return $othermapped;
}
}
@@ -0,0 +1,89 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission updated event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission updated event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - string submissiontitle: (optional) Submission title.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_updated extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop_submissions';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' updated the submission with id '$this->objectid' for the workshop " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissionupdated', 'workshop');
}
/**
* Returns relevant URL.
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->contextinstanceid, 'id' => $this->objectid));
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
}
public static function get_other_mapping() {
// Nothing to map.
return false;
}
}
@@ -0,0 +1,105 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The mod_workshop submission viewed event.
*
* @package mod_workshop
* @copyright 2013 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\event;
defined('MOODLE_INTERNAL') || die();
/**
* The mod_workshop submission viewed event class.
*
* @property-read array $other {
* Extra information about the event.
*
* - int workshopid: (optional) workshop ID.
* }
*
* @package mod_workshop
* @since Moodle 2.7
* @copyright 2013 Adrian Greeve
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_viewed extends \core\event\base {
/**
* Init method.
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'workshop_submissions';
}
/**
* Returns non-localised description of what happened.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' viewed the submission with id '$this->objectid' for the workshop " .
"with course module id '$this->contextinstanceid'.";
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventsubmissionviewed', 'workshop');
}
/**
* Returns relevant URL.
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->contextinstanceid, 'id' => $this->objectid));
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if (!isset($this->relateduserid)) {
throw new \coding_exception('The \'relateduserid\' must be set.');
}
}
public static function get_objectid_mapping() {
return array('db' => 'workshop_submissions', 'restore' => 'workshop_submission');
}
public static function get_other_mapping() {
$othermapped = array();
$othermapped['workshopid'] = array('db' => 'workshop', 'restore' => 'workshop');
return $othermapped;
}
}
File diff suppressed because it is too large Load Diff
+179
View File
@@ -0,0 +1,179 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting assessment data.
*
* @package mod_workshop
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use core_external\util as external_util;
use core_external\external_files;
/**
* Class for exporting assessment data.
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assessment_exporter extends exporter {
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The primary key of the record.',
),
'submissionid' => array(
'type' => PARAM_INT,
'description' => 'The id of the assessed submission',
),
'reviewerid' => array(
'type' => PARAM_INT,
'description' => 'The id of the reviewer who makes this assessment',
),
'weight' => array(
'type' => PARAM_INT,
'default' => 1,
'description' => 'The weight of the assessment for the purposes of aggregation',
),
'timecreated' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => 0,
'description' => 'If 0 then the assessment was allocated but the reviewer has not assessed yet.
If greater than 0 then the timestamp of when the reviewer assessed for the first time',
),
'timemodified' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => 0,
'description' => 'If 0 then the assessment was allocated but the reviewer has not assessed yet.
If greater than 0 then the timestamp of when the reviewer assessed for the last time',
),
'grade' => array(
'type' => PARAM_FLOAT,
'null' => NULL_ALLOWED,
'description' => 'The aggregated grade for submission suggested by the reviewer.
The grade 0..100 is computed from the values assigned to the assessment dimensions fields. If NULL then it has not been aggregated yet.',
),
'gradinggrade' => array(
'type' => PARAM_FLOAT,
'null' => NULL_ALLOWED,
'description' => 'The computed grade 0..100 for this assessment. If NULL then it has not been computed yet.',
),
'gradinggradeover' => array(
'type' => PARAM_FLOAT,
'null' => NULL_ALLOWED,
'description' => 'Grade for the assessment manually overridden by a teacher.
Grade is always from interval 0..100. If NULL then the grade is not overriden.',
),
'gradinggradeoverby' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'description' => 'The id of the user who has overridden the grade for submission.',
),
'feedbackauthor' => array(
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'description' => 'The comment/feedback from the reviewer for the author.',
),
'feedbackauthorformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Feedback text format.',
),
'feedbackauthorattachment' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => 0,
'description' => 'Are there some files attached to the feedbackauthor field?
Sets to 1 by file_postupdate_standard_filemanager().',
),
'feedbackreviewer' => array(
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'description' => 'The comment/feedback from the teacher for the reviewer.
For example the reason why the grade for assessment was overridden',
'optional' => true,
),
'feedbackreviewerformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Feedback text format.',
),
'feedbackauthorformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Feedback text format.',
),
);
}
protected static function define_related() {
return array(
'context' => 'context'
);
}
protected static function define_other_properties() {
return array(
'feedbackcontentfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
),
'feedbackattachmentfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
),
);
}
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$values['feedbackcontentfiles'] =
external_util::get_area_files($context->id, 'mod_workshop', 'overallfeedback_content', $this->data->id);
$values['feedbackattachmentfiles'] =
external_util::get_area_files($context->id, 'mod_workshop', 'overallfeedback_attachment', $this->data->id);
return $values;
}
/**
* Get the formatting parameters for the content.
*
* @return array
*/
protected function get_format_parameters_for_feedbackauthor() {
return [
'component' => 'mod_workshop',
'filearea' => 'overallfeedback_content',
'itemid' => $this->data->id,
];
}
}
+195
View File
@@ -0,0 +1,195 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting submission data.
*
* @package mod_workshop
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use core_external\util as external_util;
use core_external\external_files;
/**
* Class for exporting submission data.
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class submission_exporter extends exporter {
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The primary key of the record.',
),
'workshopid' => array(
'type' => PARAM_INT,
'description' => 'The id of the workshop instance.',
),
'example' => array(
'type' => PARAM_BOOL,
'null' => NULL_ALLOWED,
'default' => false,
'description' => 'Is this submission an example from teacher.',
),
'authorid' => array(
'type' => PARAM_INT,
'description' => 'The author of the submission.',
),
'timecreated' => array(
'type' => PARAM_INT,
'description' => 'Timestamp when the work was submitted for the first time.',
),
'timemodified' => array(
'type' => PARAM_INT,
'description' => 'Timestamp when the submission has been updated.',
),
'title' => array(
'type' => PARAM_RAW,
'description' => 'The submission title.',
),
'content' => array(
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'description' => 'Submission text.',
),
'contentformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Submission text format.',
),
'contenttrust' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => 'The trust mode of the data.',
),
'attachment' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'default' => 0,
'description' => 'Used by File API file_postupdate_standard_filemanager.',
),
'grade' => array(
'type' => PARAM_FLOAT,
'null' => NULL_ALLOWED,
'description' => 'Aggregated grade for the submission. The grade is a decimal number from interval 0..100.
If NULL then the grade for submission has not been aggregated yet.',
'optional' => true,
),
'gradeover' => array(
'type' => PARAM_FLOAT,
'null' => NULL_ALLOWED,
'description' => 'Grade for the submission manually overridden by a teacher. Grade is always from interval 0..100.
If NULL then the grade is not overriden.',
'optional' => true,
),
'gradeoverby' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'description' => 'The id of the user who has overridden the grade for submission.',
'optional' => true,
),
'feedbackauthor' => array(
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'description' => 'Teacher comment/feedback for the author of the submission, for example describing the reasons
for the grade overriding.',
'optional' => true,
),
'feedbackauthorformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Feedback text format.',
),
'timegraded' => array(
'type' => PARAM_INT,
'null' => NULL_ALLOWED,
'description' => 'The timestamp when grade or gradeover was recently modified.',
'optional' => true,
),
'published' => array(
'type' => PARAM_BOOL,
'null' => NULL_ALLOWED,
'default' => false,
'description' => 'Shall the submission be available to other when the workshop is closed.',
),
'late' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => 'Has this submission been submitted after the deadline or during the assessment phase?',
),
);
}
protected static function define_related() {
return array(
'context' => 'context'
);
}
protected static function define_other_properties() {
return array(
'contentfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
'optional' => true
),
'attachmentfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
'optional' => true
),
);
}
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
if (!empty($this->data->content)) {
$values['contentfiles'] =
external_util::get_area_files($context->id, 'mod_workshop', 'submission_content', $this->data->id);
}
$values['attachmentfiles'] =
external_util::get_area_files($context->id, 'mod_workshop', 'submission_attachment', $this->data->id);
return $values;
}
/**
* Get the formatting parameters for the content.
*
* @return array
*/
protected function get_format_parameters_for_content() {
return [
'component' => 'mod_workshop',
'filearea' => 'submission_content',
'itemid' => $this->data->id,
];
}
}
@@ -0,0 +1,375 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Class for exporting partial workshop data.
*
* @package mod_workshop
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\external;
defined('MOODLE_INTERNAL') || die();
use core\external\exporter;
use renderer_base;
use core_external\util as external_util;
use core_external\external_files;
/**
* Class for exporting partial workshop data (some fields are only viewable by admins).
*
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class workshop_summary_exporter extends exporter {
protected static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
'description' => 'The primary key of the record.',
),
'course' => array(
'type' => PARAM_INT,
'description' => 'Course id this workshop is part of.',
),
'name' => array(
'type' => PARAM_TEXT,
'description' => 'Workshop name.',
),
'intro' => array(
'default' => '',
'type' => PARAM_RAW,
'description' => 'Workshop introduction text.',
'null' => NULL_ALLOWED,
),
'introformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Workshop intro text format.',
),
'lang' => array(
'type' => PARAM_LANG,
'description' => 'Forced activity language',
'null' => NULL_ALLOWED,
),
'instructauthors' => array(
'type' => PARAM_RAW,
'description' => 'Instructions for the submission phase.',
'optional' => true,
'null' => NULL_ALLOWED,
),
'instructauthorsformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Instructions text format.',
),
'instructreviewers' => array(
'type' => PARAM_RAW,
'description' => 'Instructions for the assessment phase.',
'optional' => true,
'null' => NULL_ALLOWED,
),
'instructreviewersformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Instructions text format.',
),
'timemodified' => array(
'type' => PARAM_INT,
'description' => 'The timestamp when the module was modified.',
'optional' => true,
),
'phase' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => 'The current phase of workshop (0 = not available, 1 = submission, 2 = assessment, 3 = closed).',
'optional' => true,
),
'useexamples' => array(
'type' => PARAM_BOOL,
'default' => false,
'description' => 'Optional feature: students practise evaluating on example submissions from teacher.',
'optional' => true,
),
'usepeerassessment' => array(
'type' => PARAM_BOOL,
'default' => false,
'description' => 'Optional feature: students perform peer assessment of others\' work.',
'optional' => true,
),
'useselfassessment' => array(
'type' => PARAM_BOOL,
'default' => false,
'description' => 'Optional feature: students perform self assessment of their own work.',
'optional' => true,
),
'grade' => array(
'type' => PARAM_FLOAT,
'default' => 80,
'description' => 'The maximum grade for submission.',
'optional' => true,
),
'gradinggrade' => array(
'type' => PARAM_FLOAT,
'default' => 20,
'description' => 'The maximum grade for assessment.',
'optional' => true,
),
'strategy' => array(
'type' => PARAM_PLUGIN,
'description' => 'The type of the current grading strategy used in this workshop.',
'optional' => true,
),
'evaluation' => array(
'type' => PARAM_PLUGIN,
'description' => 'The recently used grading evaluation method.',
'optional' => true,
),
'gradedecimals' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => 'Number of digits that should be shown after the decimal point when displaying grades.',
'optional' => true,
),
'submissiontypetext' => array (
'type' => PARAM_INT,
'default' => 1,
'description' => 'Indicates whether text is required as part of each submission. ' .
'0 for no, 1 for optional, 2 for required.',
'optional' => true
),
'submissiontypefile' => array (
'type' => PARAM_INT,
'default' => 1,
'description' => 'Indicates whether a file upload is required as part of each submission. ' .
'0 for no, 1 for optional, 2 for required.',
'optional' => true
),
'nattachments' => array(
'type' => PARAM_INT,
'default' => 1,
'description' => 'Maximum number of submission attachments.',
'optional' => true,
),
'submissionfiletypes' => array(
'type' => PARAM_RAW,
'description' => 'Comma separated list of file extensions.',
'optional' => true,
'null' => NULL_ALLOWED,
),
'latesubmissions' => array(
'type' => PARAM_BOOL,
'default' => false,
'description' => 'Allow submitting the work after the deadline.',
'optional' => true,
),
'maxbytes' => array(
'type' => PARAM_INT,
'default' => 100000,
'description' => 'Maximum size of the one attached file.',
'optional' => true,
),
'examplesmode' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => '0 = example assessments are voluntary, 1 = examples must be assessed before submission,
2 = examples are available after own submission and must be assessed before peer/self assessment phase.',
'optional' => true,
),
'submissionstart' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => '0 = will be started manually, greater than 0 the timestamp of the start of the submission phase.',
'optional' => true,
),
'submissionend' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => '0 = will be closed manually, greater than 0 the timestamp of the end of the submission phase.',
'optional' => true,
),
'assessmentstart' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => '0 = will be started manually, greater than 0 the timestamp of the start of the assessment phase.',
'optional' => true,
),
'assessmentend' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => '0 = will be closed manually, greater than 0 the timestamp of the end of the assessment phase.',
'optional' => true,
),
'phaseswitchassessment' => array(
'type' => PARAM_BOOL,
'default' => false,
'description' => 'Automatically switch to the assessment phase after the submissions deadline.',
'optional' => true,
),
'conclusion' => array(
'type' => PARAM_RAW,
'description' => 'A text to be displayed at the end of the workshop.',
'optional' => true,
'null' => NULL_ALLOWED,
),
'conclusionformat' => array(
'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN),
'type' => PARAM_INT,
'default' => FORMAT_MOODLE,
'description' => 'Workshop conclusion text format.',
),
'overallfeedbackmode' => array(
'type' => PARAM_INT,
'default' => 1,
'description' => 'Mode of the overall feedback support.',
'optional' => true,
),
'overallfeedbackfiles' => array(
'type' => PARAM_INT,
'default' => 0,
'description' => 'Number of allowed attachments to the overall feedback.',
'optional' => true,
),
'overallfeedbackfiletypes' => array(
'type' => PARAM_RAW,
'description' => 'Comma separated list of file extensions.',
'optional' => true,
'null' => NULL_ALLOWED,
),
'overallfeedbackmaxbytes' => array(
'type' => PARAM_INT,
'default' => 100000,
'description' => 'Maximum size of one file attached to the overall feedback.',
'optional' => true,
),
);
}
protected static function define_related() {
return array(
'context' => 'context'
);
}
protected static function define_other_properties() {
return array(
'coursemodule' => array(
'type' => PARAM_INT
),
'introfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true
),
'instructauthorsfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
'optional' => true
),
'instructreviewersfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
'optional' => true
),
'conclusionfiles' => array(
'type' => external_files::get_properties_for_exporter(),
'multiple' => true,
'optional' => true
),
);
}
protected function get_other_values(renderer_base $output) {
$context = $this->related['context'];
$values = array(
'coursemodule' => $context->instanceid,
);
$values['introfiles'] = external_util::get_area_files($context->id, 'mod_workshop', 'intro', false, false);
if (!empty($this->data->instructauthors)) {
$values['instructauthorsfiles'] = external_util::get_area_files($context->id, 'mod_workshop', 'instructauthors');
}
if (!empty($this->data->instructreviewers)) {
$values['instructreviewersfiles'] = external_util::get_area_files($context->id, 'mod_workshop', 'instructreviewers');
}
if (!empty($this->data->conclusion)) {
$values['conclusionfiles'] = external_util::get_area_files($context->id, 'mod_workshop', 'conclusion');
}
return $values;
}
/**
* Get the formatting parameters for the intro.
*
* @return array with the formatting parameters
*/
protected function get_format_parameters_for_intro() {
return [
'component' => 'mod_workshop',
'filearea' => 'intro',
'options' => array('noclean' => true),
];
}
/**
* Get the formatting parameters for the instructauthors.
*
* @return array with the formatting parameters
*/
protected function get_format_parameters_for_instructauthors() {
return [
'component' => 'mod_workshop',
'filearea' => 'instructauthors',
'itemid' => 0
];
}
/**
* Get the formatting parameters for the instructreviewers.
*
* @return array with the formatting parameters
*/
protected function get_format_parameters_for_instructreviewers() {
return [
'component' => 'mod_workshop',
'filearea' => 'instructreviewers',
'itemid' => 0
];
}
/**
* Get the formatting parameters for the conclusion.
*
* @return array with the formatting parameters
*/
protected function get_format_parameters_for_conclusion() {
return [
'component' => 'mod_workshop',
'filearea' => 'conclusion',
'itemid' => 0
];
}
}
@@ -0,0 +1,72 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grade item mappings for the activity.
*
* @package mod_workshop
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
declare(strict_types = 1);
namespace mod_workshop\grades;
use core_grades\component_gradeitems;
use core_grades\local\gradeitem\fieldname_mapping;
use \core_grades\local\gradeitem\itemnumber_mapping;
/**
* Grade item mappings for the activity.
*
* @package mod_workshop
* @copyright Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradeitems implements itemnumber_mapping, fieldname_mapping {
/**
* Return the list of grade item mappings for the workshop.
*
* @return array
*/
public static function get_itemname_mapping_for_component(): array {
return [
0 => 'submission',
1 => 'grading',
];
}
/**
* Get the suffixed field name for an activity field mapped from its itemnumber.
*
* For legacy reasons, the first itemnumber has no suffix on field names.
*
* @param string $component The component that the grade item belongs to
* @param int $itemnumber The grade itemnumber
* @param string $fieldname The name of the field to be rewritten
* @return string The translated field name
*/
public static function get_field_name_for_itemnumber(string $component, int $itemnumber, string $fieldname): string {
$itemname = component_gradeitems::get_itemname_from_itemnumber($component, $itemnumber);
if ($itemname) {
return "{$itemname}{$fieldname}";
}
return $fieldname;
}
}
+79
View File
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_workshop\output;
use moodle_url;
use renderer_base;
use url_select;
use renderable;
use templatable;
/**
* Output the rendered elements for the tertiary nav for page action.
*
* @package mod_workshop
* @copyright 2021 Sujith Haridasan <sujith@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class actionbar implements renderable, templatable {
/**
* The current url.
*
* @var moodle_url $currenturl
*/
private $currenturl;
/**
* The workshop object.
* @var \workshop $workshop
*/
private $workshop;
/**
* actionbar constructor.
*
* @param moodle_url $currenturl The current URL.
* @param \workshop $workshop The workshop object.
*/
public function __construct(moodle_url $currenturl, \workshop $workshop) {
$this->currenturl = $currenturl;
$this->workshop = $workshop;
}
/**
* Export the data so it can be used as the context for a mustache template.
*
* @param renderer_base $output
* @return array The urlselect menu and the heading to be used
*/
public function export_for_template(renderer_base $output): array {
$allocators = \workshop::installed_allocators();
$menu = [];
foreach (array_keys($allocators) as $methodid) {
$selectorname = get_string('pluginname', 'workshopallocation_' . $methodid);
$menu[$this->workshop->allocation_url($methodid)->out(false)] = $selectorname;
}
$urlselect = new url_select($menu, $this->currenturl->out(false), null, 'allocationsetting');
return [
'urlselect' => $urlselect->export_for_template($output),
'heading' => $menu[$this->currenturl->out(false)] ?? null
];
}
}
@@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Subplugin info class.
*
* @package mod_workshop
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
class workshopallocation extends base {
public function is_uninstall_allowed() {
if ($this->is_standard()) {
return false;
}
return true;
}
}
@@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Subplugin info class.
*
* @package mod_workshop
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
class workshopeval extends base {
public function is_uninstall_allowed() {
if ($this->is_standard()) {
return false;
}
return true;
}
}
@@ -0,0 +1,38 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Subplugin info class.
*
* @package mod_workshop
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\plugininfo;
use core\plugininfo\base;
defined('MOODLE_INTERNAL') || die();
class workshopform extends base {
public function is_uninstall_allowed() {
if ($this->is_standard()) {
return false;
}
return true;
}
}
+533
View File
@@ -0,0 +1,533 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link mod_workshop_portfolio_caller} class.
*
* @package mod_workshop
* @category portfolio
* @copyright Loc Nguyen <ndloc1905@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/portfolio/caller.php');
/**
* Workshop portfolio caller class to integrate with portfolio API.
*
* @package mod_workshop
* @copyright Loc Nguyen <ndloc1905@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_workshop_portfolio_caller extends portfolio_module_caller_base {
/** @var workshop The workshop instance where the export is happening. */
protected $workshop;
/** @var int ID if the exported submission, set via the constructor. */
protected $submissionid;
/** @var object The submission being exported. */
protected $submission;
/** @var array of objects List of assessments of the exported submission. */
protected $assessments = [];
/**
* Explicit constructor to set the properties declared by the parent class.
*
* Firstly we call the parent's constructor to set the $this->id property
* from the passed argument. Then we populate the $this->cm so that the
* default parent class methods work well.
*
* @param array $callbackargs
*/
public function __construct($callbackargs) {
// Let the parent class set the $this->id property.
parent::__construct($callbackargs);
// Populate the $this->cm property.
$this->cm = get_coursemodule_from_id('workshop', $this->id, 0, false, MUST_EXIST);
}
/**
* Return array of expected callback arguments and whether they are required or not.
*
* The 'id' argument is supposed to be our course module id (cmid) - see
* the parent class' properties.
*
* @return array of (string)callbackname => (bool)required
*/
public static function expected_callbackargs() {
return [
'id' => true,
'submissionid' => true,
];
}
/**
* Load data required for the export.
*/
public function load_data() {
global $DB, $USER;
// Note that require_login() is normally called later as a part of
// portfolio_export_pagesetup() in the portfolio/add.php file. But we
// load various data depending of capabilities so it makes sense to
// call it explicitly here, too.
require_login($this->get('course'), false, $this->cm, false, true);
if (isguestuser()) {
throw new portfolio_caller_exception('guestsarenotallowed', 'core_error');
}
$workshoprecord = $DB->get_record('workshop', ['id' => $this->cm->instance], '*', MUST_EXIST);
$this->workshop = new workshop($workshoprecord, $this->cm, $this->get('course'));
$this->submission = $this->workshop->get_submission_by_id($this->submissionid);
// Is the user exporting her/his own submission?
$ownsubmission = $this->submission->authorid == $USER->id;
// Does the user have permission to see all submissions (aka is it a teacher)?
$canviewallsubmissions = has_capability('mod/workshop:viewallsubmissions', $this->workshop->context);
$canviewallsubmissions = $canviewallsubmissions && $this->workshop->check_group_membership($this->submission->authorid);
// Is the user exporting a submission that she/he has peer-assessed?
$userassessment = $this->workshop->get_assessment_of_submission_by_user($this->submission->id, $USER->id);
if ($userassessment) {
$this->assessments[$userassessment->id] = $userassessment;
$isreviewer = true;
}
if (!$ownsubmission and !$canviewallsubmissions and !$isreviewer) {
throw new portfolio_caller_exception('nopermissions', 'core_error');
}
// Does the user have permission to see all assessments (aka is it a teacher)?
$canviewallassessments = has_capability('mod/workshop:viewallassessments', $this->workshop->context);
// Load other assessments eventually if the user can see them.
if ($canviewallassessments or ($ownsubmission and $this->workshop->assessments_available())) {
foreach ($this->workshop->get_assessments_of_submission($this->submission->id) as $assessment) {
if ($assessment->reviewerid == $USER->id) {
// User's own assessment is already loaded.
continue;
}
if (is_null($assessment->grade) and !$canviewallassessments) {
// Students do not see peer-assessment that are not graded.
continue;
}
$this->assessments[$assessment->id] = $assessment;
}
}
// Prepare embedded and attached files for the export.
$this->multifiles = [];
$this->add_area_files('submission_content', $this->submission->id);
$this->add_area_files('submission_attachment', $this->submission->id);
foreach ($this->assessments as $assessment) {
$this->add_area_files('overallfeedback_content', $assessment->id);
$this->add_area_files('overallfeedback_attachment', $assessment->id);
}
$this->add_area_files('instructauthors', 0);
// If there are no files to be exported, we can offer plain HTML file export.
if (empty($this->multifiles)) {
$this->add_format(PORTFOLIO_FORMAT_PLAINHTML);
}
}
/**
* Prepare the package ready to be passed off to the portfolio plugin.
*/
public function prepare_package() {
$canviewauthornames = has_capability('mod/workshop:viewauthornames', $this->workshop->context, $this->get('user'));
// Prepare the submission record for rendering.
$workshopsubmission = $this->workshop->prepare_submission($this->submission, $canviewauthornames);
// Set up the LEAP2A writer if we need it.
$writingleap = false;
if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
$leapwriter = $this->exporter->get('format')->leap2a_writer();
$writingleap = true;
}
// If writing to HTML file, accumulate the exported hypertext here.
$html = '';
// If writing LEAP2A, keep track of all entry ids so we can add a selection element.
$leapids = [];
$html .= $this->export_header($workshopsubmission);
$content = $this->export_content($workshopsubmission);
// Get rid of the JS relics left by moodleforms.
$content = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $content);
$html .= $content;
if ($writingleap) {
$leapids[] = $this->export_content_leap2a($leapwriter, $workshopsubmission, $content);
}
// Export the files.
foreach ($this->multifiles as $file) {
$this->exporter->copy_existing_file($file);
}
if ($writingleap) {
// Add an extra LEAP2A selection entry. In Mahara, this maps to a journal.
$selection = new portfolio_format_leap2a_entry('workshop'.$this->workshop->id,
get_string('pluginname', 'mod_workshop').': '.s($this->workshop->name), 'selection');
$leapwriter->add_entry($selection);
$leapwriter->make_selection($selection, $leapids, 'Grouping');
$leapxml = $leapwriter->to_xml();
$name = $this->exporter->get('format')->manifest_name();
$this->exporter->write_new_file($leapxml, $name, true);
} else {
$this->exporter->write_new_file($html, 'submission.html', true);
}
}
/**
* Helper method to add all files from the given location to $this->multifiles
*
* @param string $filearea
* @param int $itemid
*/
protected function add_area_files($filearea, $itemid) {
$fs = get_file_storage();
$areafiles = $fs->get_area_files($this->workshop->context->id, 'mod_workshop', $filearea, $itemid, null, false);
if ($areafiles) {
$this->multifiles = array_merge($this->multifiles, array_values($areafiles));
}
}
/**
* Render the header of the exported content.
*
* This is mainly used for the HTML output format. In case of LEAP2A
* export, this is not used as the information is stored in metadata and
* displayed as a part of the journal and entry title in Mahara.
*
* @param workshop_submission $workshopsubmission
* @return string HTML
*/
protected function export_header(workshop_submission $workshopsubmission) {
$output = '';
$output .= html_writer::tag('h2', get_string('pluginname', 'mod_workshop').': '.s($this->workshop->name));
$output .= html_writer::tag('h3', s($workshopsubmission->title));
$created = get_string('userdatecreated', 'workshop', userdate($workshopsubmission->timecreated));
$created = html_writer::tag('span', $created);
if ($workshopsubmission->timemodified > $workshopsubmission->timecreated) {
$modified = get_string('userdatemodified', 'workshop', userdate($workshopsubmission->timemodified));
$modified = ' | ' . html_writer::tag('span', $modified);
} else {
$modified = '';
}
$output .= html_writer::div($created.$modified);
$output .= html_writer::empty_tag('br');
return $output;
}
/**
* Render the content of the submission.
*
* @param workshop_submission $workshopsubmission
* @return string
*/
protected function export_content(workshop_submission $workshopsubmission) {
$output = '';
if (!$workshopsubmission->is_anonymous()) {
$author = username_load_fields_from_object((object)[], $workshopsubmission, 'author');
$output .= html_writer::div(get_string('byfullnamewithoutlink', 'mod_workshop', fullname($author)));
}
$content = $this->format_exported_text($workshopsubmission->content, $workshopsubmission->contentformat);
$content = portfolio_rewrite_pluginfile_urls($content, $this->workshop->context->id, 'mod_workshop',
'submission_content', $workshopsubmission->id, $this->exporter->get('format'));
$output .= html_writer::div($content);
$output .= $this->export_files_list('submission_attachment');
$strategy = $this->workshop->grading_strategy_instance();
$canviewauthornames = has_capability('mod/workshop:viewauthornames', $this->workshop->context, $this->get('user'));
$canviewreviewernames = has_capability('mod/workshop:viewreviewernames', $this->workshop->context, $this->get('user'));
foreach ($this->assessments as $assessment) {
$mform = $strategy->get_assessment_form(null, 'assessment', $assessment, false);
$options = [
'showreviewer' => $canviewreviewernames,
'showauthor' => $canviewauthornames,
'showform' => true,
'showweight' => true,
];
if ($assessment->reviewerid == $this->get('user')->id) {
$options['showreviewer'] = true;
}
$workshopassessment = $this->workshop->prepare_assessment($assessment, $mform, $options);
if ($assessment->reviewerid == $this->get('user')->id) {
$workshopassessment->title = get_string('assessmentbyyourself', 'mod_workshop');
} else {
$workshopassessment->title = get_string('assessment', 'mod_workshop');
}
$output .= html_writer::empty_tag('hr');
$output .= $this->export_assessment($workshopassessment);
}
if (trim($this->workshop->instructauthors)) {
$output .= html_writer::tag('h3', get_string('instructauthors', 'mod_workshop'));
$content = $this->format_exported_text($this->workshop->instructauthors, $this->workshop->instructauthorsformat);
$content = portfolio_rewrite_pluginfile_urls($content, $this->workshop->context->id, 'mod_workshop',
'instructauthors', 0, $this->exporter->get('format'));
$output .= $content;
}
return html_writer::div($output);
}
/**
* Render the content of an assessment.
*
* @param workshop_assessment $assessment
* @return string HTML
*/
protected function export_assessment(workshop_assessment $assessment) {
$output = '';
if (empty($assessment->title)) {
$title = get_string('assessment', 'workshop');
} else {
$title = s($assessment->title);
}
$output .= html_writer::tag('h3', $title);
if ($assessment->reviewer) {
$output .= html_writer::div(get_string('byfullnamewithoutlink', 'mod_workshop', fullname($assessment->reviewer)));
$output .= html_writer::empty_tag('br');
}
if ($this->workshop->overallfeedbackmode) {
if ($assessment->feedbackauthorattachment || trim($assessment->feedbackauthor ?? '') !== '') {
$output .= html_writer::tag('h3', get_string('overallfeedback', 'mod_workshop'));
$content = $this->format_exported_text($assessment->feedbackauthor, $assessment->feedbackauthorformat);
$content = portfolio_rewrite_pluginfile_urls($content, $this->workshop->context->id, 'mod_workshop',
'overallfeedback_content', $assessment->id , $this->exporter->get('format'));
$output .= $content;
$output .= $this->export_files_list('overallfeedback_attachment');
}
}
if ($assessment->form) {
$output .= $assessment->form->render();
}
return $output;
}
/**
* Export the files in the given file area in a list.
*
* @param string $filearea
* @return string HTML
*/
protected function export_files_list($filearea) {
$output = '';
$files = [];
foreach ($this->multifiles as $file) {
if ($file->is_directory()) {
continue;
}
if ($file->get_filearea() !== $filearea) {
continue;
}
if ($file->is_valid_image()) {
// Not optimal but looks better than original images.
$files[] = html_writer::tag('li', $this->exporter->get('format')->file_output($file,
['attributes' => ['style' => 'max-height:24px; max-width:24px']]).' '.s($file->get_filename()));
} else {
$files[] = html_writer::tag('li', $this->exporter->get('format')->file_output($file));
}
}
if ($files) {
$output .= html_writer::tag('ul', implode('', $files));
}
return $output;
}
/**
* Helper function to call {@link format_text()} on exported text.
*
* We need to call {@link format_text()} to convert the text into HTML, but
* we have to keep the original @@PLUGINFILE@@ placeholder there without a
* warning so that {@link portfolio_rewrite_pluginfile_urls()} can do its work.
*
* @param string $text
* @param int $format
* @return string HTML
*/
protected function format_exported_text($text, $format) {
$text = str_replace('@@PLUGINFILE@@', '@@ORIGINALPLUGINFILE@@', $text);
$html = format_text($text, $format, portfolio_format_text_options());
$html = str_replace('@@ORIGINALPLUGINFILE@@', '@@PLUGINFILE@@', $html);
return $html;
}
/**
* Add a LEAP2A entry element that corresponds to a submission including attachments.
*
* @param portfolio_format_leap2a_writer $leapwriter Writer object to add entries to.
* @param workshop_submission $workshopsubmission
* @param string $html The exported HTML content of the submission
* @return int id of new entry
*/
protected function export_content_leap2a(portfolio_format_leap2a_writer $leapwriter,
workshop_submission $workshopsubmission, $html) {
$entry = new portfolio_format_leap2a_entry('workshopsubmission'.$workshopsubmission->id, s($workshopsubmission->title),
'resource', $html);
$entry->published = $workshopsubmission->timecreated;
$entry->updated = $workshopsubmission->timemodified;
$entry->author = (object)[
'id' => $workshopsubmission->authorid,
'email' => $workshopsubmission->authoremail
];
username_load_fields_from_object($entry->author, $workshopsubmission);
$leapwriter->link_files($entry, $this->multifiles);
$entry->add_category('web', 'resource_type');
$leapwriter->add_entry($entry);
return $entry->id;
}
/**
* Return URL for redirecting the user back to where the export started.
*
* @return string
*/
public function get_return_url() {
$returnurl = new moodle_url('/mod/workshop/submission.php', ['cmid' => $this->cm->id, 'id' => $this->submissionid]);
return $returnurl->out();
}
/**
* Get navigation that logically follows from the place the user was before.
*
* @return array
*/
public function get_navigation() {
$navlinks = [
['name' => s($this->submission->title)],
];
return [$navlinks, $this->cm];
}
/**
* How long might we expect this export to take.
*
* @return string such as PORTFOLIO_TIME_LOW
*/
public function expected_time() {
return $this->expected_time_file();
}
/**
* Make sure that the current user is allowed to do the export.
*
* @return boolean
*/
public function check_permissions() {
return has_capability('mod/workshop:exportsubmissions', context_module::instance($this->cm->id));
}
/**
* Return the SHA1 hash of the exported content.
*
* @return string
*/
public function get_sha1() {
$identifier = 'submission:'.$this->submission->id.'@'.$this->submission->timemodified;
if ($this->assessments) {
$ids = array_keys($this->assessments);
sort($ids);
$identifier .= '/assessments:'.implode(',', $ids);
}
if ($this->multifiles) {
$identifier .= '/files:'.$this->get_sha1_file();
}
return sha1($identifier);
}
/**
* Return a nice name to be displayed about this export location.
*
* @return string
*/
public static function display_name() {
return get_string('pluginname', 'mod_workshop');
}
/**
* What export formats the workshop generally supports.
*
* If there are no files embedded/attached, the plain HTML format is added
* in {@link self::load_data()}.
*
* @return array
*/
public static function base_supported_formats() {
return [
PORTFOLIO_FORMAT_RICHHTML,
PORTFOLIO_FORMAT_LEAP2A,
];
}
}
+885
View File
@@ -0,0 +1,885 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines {@link \mod_workshop\privacy\provider} class.
*
* @package mod_workshop
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\deletion_criteria;
use core_privacy\local\request\helper;
use core_privacy\local\request\transform;
use core_privacy\local\request\userlist;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/mod/workshop/locallib.php');
/**
* Privacy API implementation for the Workshop activity module.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\user_preference_provider,
\core_privacy\local\request\plugin\provider {
/**
* Describe all the places where the Workshop module stores some personal data.
*
* @param collection $collection Collection of items to add metadata to.
* @return collection Collection with our added items.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('workshop_submissions', [
'workshopid' => 'privacy:metadata:workshopid',
'authorid' => 'privacy:metadata:authorid',
'example' => 'privacy:metadata:example',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
'title' => 'privacy:metadata:submissiontitle',
'content' => 'privacy:metadata:submissioncontent',
'contentformat' => 'privacy:metadata:submissioncontentformat',
'grade' => 'privacy:metadata:submissiongrade',
'gradeover' => 'privacy:metadata:submissiongradeover',
'feedbackauthor' => 'privacy:metadata:feedbackauthor',
'feedbackauthorformat' => 'privacy:metadata:feedbackauthorformat',
'published' => 'privacy:metadata:published',
'late' => 'privacy:metadata:late',
], 'privacy:metadata:workshopsubmissions');
$collection->add_database_table('workshop_assessments', [
'submissionid' => 'privacy:metadata:submissionid',
'reviewerid' => 'privacy:metadata:reviewerid',
'weight' => 'privacy:metadata:weight',
'timecreated' => 'privacy:metadata:timecreated',
'timemodified' => 'privacy:metadata:timemodified',
'grade' => 'privacy:metadata:assessmentgrade',
'gradinggrade' => 'privacy:metadata:assessmentgradinggrade',
'gradinggradeover' => 'privacy:metadata:assessmentgradinggradeover',
'feedbackauthor' => 'privacy:metadata:feedbackauthor',
'feedbackauthorformat' => 'privacy:metadata:feedbackauthorformat',
'feedbackreviewer' => 'privacy:metadata:feedbackreviewer',
'feedbackreviewerformat' => 'privacy:metadata:feedbackreviewerformat',
], 'privacy:metadata:workshopassessments');
$collection->add_database_table('workshop_grades', [
'assessmentid' => 'privacy:metadata:assessmentid',
'strategy' => 'privacy:metadata:strategy',
'dimensionid' => 'privacy:metadata:dimensionid',
'grade' => 'privacy:metadata:dimensiongrade',
'peercomment' => 'privacy:metadata:peercomment',
'peercommentformat' => 'privacy:metadata:peercommentformat',
], 'privacy:metadata:workshopgrades');
$collection->add_database_table('workshop_aggregations', [
'workshopid' => 'privacy:metadata:workshopid',
'userid' => 'privacy:metadata:userid',
'gradinggrade' => 'privacy:metadata:aggregatedgradinggrade',
'timegraded' => 'privacy:metadata:timeaggregated',
], 'privacy:metadata:workshopaggregations');
$collection->add_subsystem_link('core_files', [], 'privacy:metadata:subsystem:corefiles');
$collection->add_subsystem_link('core_plagiarism', [], 'privacy:metadata:subsystem:coreplagiarism');
$userprefs = self::get_user_prefs();
foreach ($userprefs as $userpref) {
if ($userpref === 'workshop_perpage') {
$collection->add_user_preference('workshop_perpage', 'privacy:metadata:preference:perpage');
} else {
$summary = str_replace('workshop-', '', $userpref);
$collection->add_user_preference($userpref, "privacy:metadata:preference:$summary");
}
}
return $collection;
}
/**
* Get the list of contexts that contain personal data for the specified user.
*
* User has personal data in the workshop if any of the following cases happens:
*
* - the user has submitted in the workshop
* - the user has overridden a submission grade
* - the user has been assigned as a reviewer of a submission
* - the user has overridden a grading grade
* - the user has a grading grade (existing or to be calculated)
*
* @param int $userid ID of the user.
* @return contextlist List of contexts containing the user's personal data.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$contextlist = new contextlist();
$sql = "SELECT ctx.id
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
JOIN {workshop} w ON cm.instance = w.id
LEFT JOIN {workshop_submissions} ws ON ws.workshopid = w.id
LEFT JOIN {workshop_assessments} wa ON wa.submissionid = ws.id AND (
wa.reviewerid = :wareviewerid
OR
wa.gradinggradeoverby = :wagradinggradeoverby
)
LEFT JOIN {workshop_aggregations} wr ON wr.workshopid = w.id AND wr.userid = :wruserid
WHERE ws.authorid = :wsauthorid
OR ws.gradeoverby = :wsgradeoverby
OR wa.id IS NOT NULL
OR wr.id IS NOT NULL";
$params = [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'wsauthorid' => $userid,
'wsgradeoverby' => $userid,
'wareviewerid' => $userid,
'wagradinggradeoverby' => $userid,
'wruserid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist To be filled list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
global $DB;
$context = $userlist->get_context();
if (!$context instanceof \context_module) {
return;
}
$params = [
'instanceid' => $context->instanceid,
'module' => 'workshop',
];
// One query to fetch them all, one query to find them, one query to bring them all and into the userlist add them.
$sql = "SELECT ws.authorid, ws.gradeoverby, wa.reviewerid, wa.gradinggradeoverby, wr.userid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
LEFT JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
LEFT JOIN {workshop_aggregations} wr ON wr.workshopid = w.id
WHERE cm.id = :instanceid";
$userids = [];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $r) {
if ($r->authorid) {
$userids[$r->authorid] = true;
}
if ($r->gradeoverby) {
$userids[$r->gradeoverby] = true;
}
if ($r->reviewerid) {
$userids[$r->reviewerid] = true;
}
if ($r->gradinggradeoverby) {
$userids[$r->gradinggradeoverby] = true;
}
if ($r->userid) {
$userids[$r->userid] = true;
}
}
$rs->close();
if ($userids) {
$userlist->add_users(array_keys($userids));
}
}
/**
* Export personal data stored in the given contexts.
*
* @param approved_contextlist $contextlist List of contexts approved for export.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
if (!count($contextlist)) {
return;
}
$user = $contextlist->get_user();
// Export general information about all workshops.
foreach ($contextlist->get_contexts() as $context) {
if ($context->contextlevel != CONTEXT_MODULE) {
continue;
}
$data = helper::get_context_data($context, $user);
static::append_extra_workshop_data($context, $user, $data, []);
writer::with_context($context)->export_data([], $data);
helper::export_context_files($context, $user);
}
// Export the user's own submission and all example submissions he/she created.
static::export_submissions($contextlist);
// Export all given assessments.
static::export_assessments($contextlist);
}
/**
* Export user preferences controlled by this plugin.
*
* @param int $userid ID of the user we are exporting data for
*/
public static function export_user_preferences(int $userid) {
$userprefs = self::get_user_prefs();
$expandstr = get_string('expand');
$collapsestr = get_string('collapse');
foreach ($userprefs as $userpref) {
$userprefval = get_user_preferences($userpref, null, $userid);
if ($userprefval !== null) {
$langid = str_replace('workshop-', '', $userpref);
$description = get_string("privacy:metadata:preference:$langid", 'mod_workshop');
if ($userpref === 'workshop_perpage') {
writer::export_user_preference('mod_workshop', $userpref, $userprefval,
get_string('privacy:metadata:preference:perpage', 'mod_workshop'));
} else {
writer::export_user_preference('mod_workshop', $userpref,
$userprefval == 1 ? $collapsestr : $expandstr, $description);
}
}
}
}
/**
* Append additional relevant data into the base data about the workshop instance.
*
* Relevant are data that are important for interpreting or evaluating the performance of the user expressed in
* his/her exported personal data. For example, we need to know what were the instructions for submissions or what
* was the phase of the workshop when it was exported.
*
* @param \context $context Workshop module content.
* @param stdClass $user User for which we are exporting data.
* @param stdClass $data Base data about the workshop instance to append to.
* @param array $subcontext Subcontext path items to eventually write files into.
*/
protected static function append_extra_workshop_data(\context $context, \stdClass $user, \stdClass $data, array $subcontext) {
global $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
throw new \coding_exception('Unexpected context provided');
}
$sql = "SELECT w.instructauthors, w.instructauthorsformat, w.instructreviewers, w.instructreviewersformat, w.phase,
w.strategy, w.evaluation, w.latesubmissions, w.submissionstart, w.submissionend, w.assessmentstart,
w.assessmentend, w.conclusion, w.conclusionformat
FROM {course_modules} cm
JOIN {workshop} w ON cm.instance = w.id
WHERE cm.id = :cmid";
$params = [
'cmid' => $context->instanceid,
];
$record = $DB->get_record_sql($sql, $params, MUST_EXIST);
$writer = writer::with_context($context);
if ($record->phase >= \workshop::PHASE_SUBMISSION) {
$data->instructauthors = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'instructauthors', 0,
$record->instructauthors);
$data->instructauthorsformat = $record->instructauthorsformat;
}
if ($record->phase >= \workshop::PHASE_ASSESSMENT) {
$data->instructreviewers = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'instructreviewers', 0,
$record->instructreviewers);
$data->instructreviewersformat = $record->instructreviewersformat;
}
if ($record->phase >= \workshop::PHASE_CLOSED) {
$data->conclusion = $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop', 'conclusion', 0, $record->conclusion);
$data->conclusionformat = $record->conclusionformat;
}
$data->strategy = \workshop::available_strategies_list()[$record->strategy];
$data->evaluation = \workshop::available_evaluators_list()[$record->evaluation];
$data->latesubmissions = transform::yesno($record->latesubmissions);
$data->submissionstart = $record->submissionstart ? transform::datetime($record->submissionstart) : null;
$data->submissionend = $record->submissionend ? transform::datetime($record->submissionend) : null;
$data->assessmentstart = $record->assessmentstart ? transform::datetime($record->assessmentstart) : null;
$data->assessmentend = $record->assessmentend ? transform::datetime($record->assessmentend) : null;
switch ($record->phase) {
case \workshop::PHASE_SETUP:
$data->phase = get_string('phasesetup', 'mod_workshop');
break;
case \workshop::PHASE_SUBMISSION:
$data->phase = get_string('phasesubmission', 'mod_workshop');
break;
case \workshop::PHASE_ASSESSMENT:
$data->phase = get_string('phaseassessment', 'mod_workshop');
break;
case \workshop::PHASE_EVALUATION:
$data->phase = get_string('phaseevaluation', 'mod_workshop');
break;
case \workshop::PHASE_CLOSED:
$data->phase = get_string('phaseclosed', 'mod_workshop');
break;
}
$writer->export_area_files($subcontext, 'mod_workshop', 'instructauthors', 0);
$writer->export_area_files($subcontext, 'mod_workshop', 'instructreviewers', 0);
$writer->export_area_files($subcontext, 'mod_workshop', 'conclusion', 0);
}
/**
* Export all user's submissions and example submissions he/she created in the given contexts.
*
* @param approved_contextlist $contextlist List of contexts approved for export.
*/
protected static function export_submissions(approved_contextlist $contextlist) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$user = $contextlist->get_user();
$sql = "SELECT ws.id, ws.authorid, ws.example, ws.timecreated, ws.timemodified, ws.title,
ws.content, ws.contentformat, ws.grade, ws.gradeover, ws.feedbackauthor, ws.feedbackauthorformat,
ws.published, ws.late,
w.phase, w.course, cm.id AS cmid, ".\context_helper::get_preload_record_columns_sql('ctx')."
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id AND ws.authorid = :authorid
WHERE ctx.id {$contextsql}";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'authorid' => $user->id,
];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $record) {
\context_helper::preload_from_record($record);
$context = \context_module::instance($record->cmid);
$writer = \core_privacy\local\request\writer::with_context($context);
if ($record->example) {
$subcontext = [get_string('examplesubmissions', 'mod_workshop'), $record->id];
$mysubmission = null;
} else {
$subcontext = [get_string('mysubmission', 'mod_workshop')];
$mysubmission = $record;
}
$phase = $record->phase;
$courseid = $record->course;
$data = (object) [
'example' => transform::yesno($record->example),
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
'title' => $record->title,
'content' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
'submission_content', $record->id, $record->content),
'contentformat' => $record->contentformat,
'grade' => $record->grade,
'gradeover' => $record->gradeover,
'feedbackauthor' => $record->feedbackauthor,
'feedbackauthorformat' => $record->feedbackauthorformat,
'published' => transform::yesno($record->published),
'late' => transform::yesno($record->late),
];
$writer->export_data($subcontext, $data);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_content', $record->id);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_attachment', $record->id);
// Export peer-assessments of my submission if the workshop was closed. We do not export received
// assessments from peers before they were actually effective. Before the workshop is closed, grades are not
// pushed into the gradebook. So peer assessments did not affect evaluation of the user's performance and
// they should not be considered as their personal data. This is different from assessments given by the
// user that are always exported.
if ($mysubmission && $phase == \workshop::PHASE_CLOSED) {
$assessments = $DB->get_records('workshop_assessments', ['submissionid' => $mysubmission->id], '',
'id, reviewerid, weight, timecreated, timemodified, grade, feedbackauthor, feedbackauthorformat');
foreach ($assessments as $assessment) {
$assid = $assessment->id;
$assessment->selfassessment = transform::yesno($assessment->reviewerid == $user->id);
$assessment->timecreated = transform::datetime($assessment->timecreated);
$assessment->timemodified = $assessment->timemodified ? transform::datetime($assessment->timemodified) : null;
$assessment->feedbackauthor = $writer->rewrite_pluginfile_urls($subcontext,
'mod_workshop', 'overallfeedback_content', $assid, $assessment->feedbackauthor);
$assessmentsubcontext = array_merge($subcontext, [get_string('assessments', 'mod_workshop'), $assid]);
unset($assessment->id);
unset($assessment->reviewerid);
$writer->export_data($assessmentsubcontext, $assessment);
$writer->export_area_files($assessmentsubcontext, 'mod_workshop', 'overallfeedback_content', $assid);
$writer->export_area_files($assessmentsubcontext, 'mod_workshop', 'overallfeedback_attachment', $assid);
// Export details of how the assessment forms were filled.
static::export_assessment_forms($user, $context, $assessmentsubcontext, $assid);
}
}
// Export plagiarism data related to the submission content.
// The last $linkarray argument consistent with how we call {@link plagiarism_get_links()} in the renderer.
\core_plagiarism\privacy\provider::export_plagiarism_user_data($user->id, $context, $subcontext, [
'userid' => $user->id,
'content' => format_text($data->content, $data->contentformat, ['overflowdiv' => true]),
'cmid' => $context->instanceid,
'course' => $courseid,
]);
}
$rs->close();
}
/**
* Export all assessments given by the user.
*
* @param approved_contextlist $contextlist List of contexts approved for export.
*/
protected static function export_assessments(approved_contextlist $contextlist) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$user = $contextlist->get_user();
$sql = "SELECT ws.authorid, ws.example, ws.timecreated, ws.timemodified, ws.title, ws.content, ws.contentformat,
wa.id, wa.submissionid, wa.reviewerid, wa.weight, wa.timecreated, wa.timemodified, wa.grade,
wa.gradinggrade, wa.gradinggradeover, wa.feedbackauthor, wa.feedbackauthorformat, wa.feedbackreviewer,
wa.feedbackreviewerformat, cm.id AS cmid, ".\context_helper::get_preload_record_columns_sql('ctx')."
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id AND wa.reviewerid = :reviewerid
WHERE ctx.id {$contextsql}";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'reviewerid' => $user->id,
];
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $record) {
\context_helper::preload_from_record($record);
$context = \context_module::instance($record->cmid);
$writer = \core_privacy\local\request\writer::with_context($context);
$subcontext = [get_string('myassessments', 'mod_workshop'), $record->id];
$data = (object) [
'weight' => $record->weight,
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
'grade' => $record->grade,
'gradinggrade' => $record->gradinggrade,
'gradinggradeover' => $record->gradinggradeover,
'feedbackauthor' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
'overallfeedback_content', $record->id, $record->feedbackauthor),
'feedbackauthorformat' => $record->feedbackauthorformat,
'feedbackreviewer' => $record->feedbackreviewer,
'feedbackreviewerformat' => $record->feedbackreviewerformat,
];
$submission = (object) [
'myownsubmission' => transform::yesno($record->authorid == $user->id),
'example' => transform::yesno($record->example),
'timecreated' => transform::datetime($record->timecreated),
'timemodified' => $record->timemodified ? transform::datetime($record->timemodified) : null,
'title' => $record->title,
'content' => $writer->rewrite_pluginfile_urls($subcontext, 'mod_workshop',
'submission_content', $record->submissionid, $record->content),
'contentformat' => $record->contentformat,
];
$writer->export_data($subcontext, $data);
$writer->export_related_data($subcontext, 'submission', $submission);
$writer->export_area_files($subcontext, 'mod_workshop', 'overallfeedback_content', $record->id);
$writer->export_area_files($subcontext, 'mod_workshop', 'overallfeedback_attachment', $record->id);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_content', $record->submissionid);
$writer->export_area_files($subcontext, 'mod_workshop', 'submission_attachment', $record->submissionid);
// Export details of how the assessment forms were filled.
static::export_assessment_forms($user, $context, $subcontext, $record->id);
}
$rs->close();
}
/**
* Export the grading strategy data related to the particular assessment.
*
* @param stdClass $user User we are exporting for
* @param context $context Workshop activity content
* @param array $subcontext Subcontext path of the assessment
* @param int $assessmentid ID of the exported assessment
*/
protected static function export_assessment_forms(\stdClass $user, \context $context, array $subcontext, int $assessmentid) {
foreach (\workshop::available_strategies_list() as $strategy => $title) {
$providername = '\workshopform_'.$strategy.'\privacy\provider';
if (is_subclass_of($providername, '\mod_workshop\privacy\workshopform_provider')) {
component_class_callback($providername, 'export_assessment_form',
[
$user,
$context,
array_merge($subcontext, [get_string('assessmentform', 'mod_workshop'), $title]),
$assessmentid,
]
);
} else {
debugging('Missing class '.$providername.' implementing workshopform_provider interface', DEBUG_DEVELOPER);
}
}
}
/**
* Delete personal data for all users in the context.
*
* @param context $context Context to delete personal data from.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
global $CFG, $DB;
require_once($CFG->libdir.'/gradelib.php');
if ($context->contextlevel != CONTEXT_MODULE) {
return;
}
$cm = get_coursemodule_from_id('workshop', $context->instanceid, 0, false, IGNORE_MISSING);
if (!$cm) {
// Probably some kind of expired context.
return;
}
$workshop = $DB->get_record('workshop', ['id' => $cm->instance], 'id, course', MUST_EXIST);
$submissions = $DB->get_records('workshop_submissions', ['workshopid' => $workshop->id], '', 'id');
$assessments = $DB->get_records_list('workshop_assessments', 'submissionid', array_keys($submissions), '', 'id');
$DB->delete_records('workshop_aggregations', ['workshopid' => $workshop->id]);
$DB->delete_records_list('workshop_grades', 'assessmentid', array_keys($assessments));
$DB->delete_records_list('workshop_assessments', 'id', array_keys($assessments));
$DB->delete_records_list('workshop_submissions', 'id', array_keys($submissions));
$fs = get_file_storage();
$fs->delete_area_files($context->id, 'mod_workshop', 'submission_content');
$fs->delete_area_files($context->id, 'mod_workshop', 'submission_attachment');
$fs->delete_area_files($context->id, 'mod_workshop', 'overallfeedback_content');
$fs->delete_area_files($context->id, 'mod_workshop', 'overallfeedback_attachment');
grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 0, null, ['reset' => true]);
grade_update('mod/workshop', $workshop->course, 'mod', 'workshop', $workshop->id, 1, null, ['reset' => true]);
\core_plagiarism\privacy\provider::delete_plagiarism_for_context($context);
}
/**
* Delete personal data for the user in a list of contexts.
*
* Removing assessments of submissions from the Workshop is not trivial. Removing one user's data can easily affect
* other users' grades and completion criteria. So we replace the non-essential contents with a "deleted" message,
* but keep the actual info in place. The argument is that one's right for privacy should not overweight others'
* right for accessing their own personal data and be evaluated on their basis.
*
* @param approved_contextlist $contextlist List of contexts to delete data from.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
global $DB;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$user = $contextlist->get_user();
$fs = get_file_storage();
// Replace sensitive data in all submissions by the user in the given contexts.
$sql = "SELECT ws.id AS submissionid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id AND ws.authorid = :authorid
WHERE ctx.id {$contextsql}";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'authorid' => $user->id,
];
$submissionids = $DB->get_fieldset_sql($sql, $params);
if ($submissionids) {
list($submissionidsql, $submissionidparams) = $DB->get_in_or_equal($submissionids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_submissions', 'title', get_string('privacy:request:delete:title',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$DB->set_field_select('workshop_submissions', 'content', get_string('privacy:request:delete:content',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$DB->set_field_select('workshop_submissions', 'feedbackauthor', get_string('privacy:request:delete:content',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
foreach ($contextlist->get_contextids() as $contextid) {
$fs->delete_area_files_select($contextid, 'mod_workshop', 'submission_content',
$submissionidsql, $submissionidparams);
$fs->delete_area_files_select($contextid, 'mod_workshop', 'submission_attachment',
$submissionidsql, $submissionidparams);
}
}
// Replace personal data in received assessments - feedback is seen as belonging to the recipient.
$sql = "SELECT wa.id AS assessmentid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id AND ws.authorid = :authorid
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE ctx.id {$contextsql}";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'authorid' => $user->id,
];
$assessmentids = $DB->get_fieldset_sql($sql, $params);
if ($assessmentids) {
list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_assessments', 'feedbackauthor', get_string('privacy:request:delete:content',
'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
foreach ($contextlist->get_contextids() as $contextid) {
$fs->delete_area_files_select($contextid, 'mod_workshop', 'overallfeedback_content',
$assessmentidsql, $assessmentidparams);
$fs->delete_area_files_select($contextid, 'mod_workshop', 'overallfeedback_attachment',
$assessmentidsql, $assessmentidparams);
}
}
// Replace sensitive data in provided assessments records.
$sql = "SELECT wa.id AS assessmentid
FROM {course_modules} cm
JOIN {modules} m ON cm.module = m.id AND m.name = :module
JOIN {context} ctx ON cm.id = ctx.instanceid AND ctx.contextlevel = :contextlevel
JOIN {workshop} w ON cm.instance = w.id
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id AND wa.reviewerid = :reviewerid
WHERE ctx.id {$contextsql}";
$params = $contextparams + [
'module' => 'workshop',
'contextlevel' => CONTEXT_MODULE,
'reviewerid' => $user->id,
];
$assessmentids = $DB->get_fieldset_sql($sql, $params);
if ($assessmentids) {
list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_assessments', 'feedbackreviewer', get_string('privacy:request:delete:content',
'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
}
foreach ($contextlist as $context) {
\core_plagiarism\privacy\provider::delete_plagiarism_for_user($user->id, $context);
}
}
/**
* Delete personal data for multiple users within a single workshop context.
*
* See documentation for {@link self::delete_data_for_user()} for more details on what we do and don't actually
* delete and why.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
global $DB;
$context = $userlist->get_context();
$fs = get_file_storage();
if ($context->contextlevel != CONTEXT_MODULE) {
// This should not happen but let's be double sure when it comes to deleting data.
return;
}
$cm = get_coursemodule_from_id('workshop', $context->instanceid, 0, false, IGNORE_MISSING);
if (!$cm) {
// Probably some kind of expired context.
return;
}
$userids = $userlist->get_userids();
if (!$userids) {
return;
}
list($usersql, $userparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
// Erase sensitive data in all submissions by all the users in the given context.
$sql = "SELECT ws.id AS submissionid
FROM {workshop} w
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
WHERE w.id = :workshopid AND ws.authorid $usersql";
$params = $userparams + [
'workshopid' => $cm->instance,
];
$submissionids = $DB->get_fieldset_sql($sql, $params);
if ($submissionids) {
list($submissionidsql, $submissionidparams) = $DB->get_in_or_equal($submissionids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_submissions', 'title', get_string('privacy:request:delete:title',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$DB->set_field_select('workshop_submissions', 'content', get_string('privacy:request:delete:content',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$DB->set_field_select('workshop_submissions', 'feedbackauthor', get_string('privacy:request:delete:content',
'mod_workshop'), "id $submissionidsql", $submissionidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'submission_content',
$submissionidsql, $submissionidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'submission_attachment',
$submissionidsql, $submissionidparams);
}
// Erase personal data in received assessments - feedback is seen as belonging to the recipient.
$sql = "SELECT wa.id AS assessmentid
FROM {workshop} w
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE w.id = :workshopid AND ws.authorid $usersql";
$params = $userparams + [
'workshopid' => $cm->instance,
];
$assessmentids = $DB->get_fieldset_sql($sql, $params);
if ($assessmentids) {
list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_assessments', 'feedbackauthor', get_string('privacy:request:delete:content',
'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'overallfeedback_content',
$assessmentidsql, $assessmentidparams);
$fs->delete_area_files_select($context->id, 'mod_workshop', 'overallfeedback_attachment',
$assessmentidsql, $assessmentidparams);
}
// Erase sensitive data in provided assessments records.
$sql = "SELECT wa.id AS assessmentid
FROM {workshop} w
JOIN {workshop_submissions} ws ON ws.workshopid = w.id
JOIN {workshop_assessments} wa ON wa.submissionid = ws.id
WHERE w.id = :workshopid AND wa.reviewerid $usersql";
$params = $userparams + [
'workshopid' => $cm->instance,
];
$assessmentids = $DB->get_fieldset_sql($sql, $params);
if ($assessmentids) {
list($assessmentidsql, $assessmentidparams) = $DB->get_in_or_equal($assessmentids, SQL_PARAMS_NAMED);
$DB->set_field_select('workshop_assessments', 'feedbackreviewer', get_string('privacy:request:delete:content',
'mod_workshop'), "id $assessmentidsql", $assessmentidparams);
}
foreach ($userids as $userid) {
\core_plagiarism\privacy\provider::delete_plagiarism_for_user($userid, $context);
}
}
/**
* Get the user preferences.
*
* @return array List of user preferences
*/
protected static function get_user_prefs(): array {
return [
'workshop_perpage',
'workshop-viewlet-allexamples-collapsed',
'workshop-viewlet-allsubmissions-collapsed',
'workshop-viewlet-assessmentform-collapsed',
'workshop-viewlet-assignedassessments-collapsed',
'workshop-viewlet-cleargrades-collapsed',
'workshop-viewlet-conclusion-collapsed',
'workshop-viewlet-examples-collapsed',
'workshop-viewlet-examplesfail-collapsed',
'workshop-viewlet-gradereport-collapsed',
'workshop-viewlet-instructauthors-collapsed',
'workshop-viewlet-instructreviewers-collapsed',
'workshop-viewlet-intro-collapsed',
'workshop-viewlet-overallfeedback-collapsed',
'workshop-viewlet-ownsubmission-collapsed',
'workshop-viewlet-publicsubmissions-collapsed',
'workshop-viewlet-yourgrades-collapsed'
];
}
}
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link mod_workshop\privacy\workshopform_legacy_polyfill} trait.
*
* @package mod_workshop
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Trait allowing additional (contrib) plugins to have single codebase for 3.3 and 3.4.
*
* The signature of the method in the {@link \mod_workshop\privacy\workshopform_provider} interface makes use of scalar
* type hinting that is available in PHP 7.0 only. If a plugin wants to implement the interface in 3.3 (and therefore
* PHP 5.6) with the same codebase, they can make use of this trait. Instead of implementing the interface directly, the
* workshopform plugin can implement the required logic in the method (note the underscore and missing "int" hint):
*
* public static function _export_assessment_form(\stdClass $user, \context $context, array $subcontext, $assessmentid)
*
* and then simply use this trait in their provider class.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait workshopform_legacy_polyfill {
/**
* Return details of the filled assessment form.
*
* @param stdClass $user User we are exporting data for
* @param context $context The workshop activity context
* @param array $subcontext Subcontext within the context to export to
* @param int $assessmentid ID of the assessment
*/
public static function export_assessment_form(\stdClass $user, \context $context, array $subcontext, int $assessmentid) {
return static::_export_assessment_form($user, $context, $subcontext, $assessmentid);
}
}
@@ -0,0 +1,50 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link mod_workshop\privacy\workshopform_provider} interface.
*
* @package mod_workshop
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Interface for grading strategy subplugins implementing the privacy API.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface workshopform_provider extends
\core_privacy\local\request\plugin\subplugin_provider,
\core_privacy\local\request\shared_userlist_provider
{
/**
* Return details of the filled assessment form.
*
* @param stdClass $user User we are exporting data for
* @param context $context The workshop activity context
* @param array $subcontext Subcontext within the context to export to
* @param int $assessmentid ID of the assessment
*/
public static function export_assessment_form(\stdClass $user, \context $context, array $subcontext, int $assessmentid);
}
+58
View File
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Search area for mod_workshop activities.
*
* @package mod_workshop
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\search;
defined('MOODLE_INTERNAL') || die();
/**
* Search area for mod_workshop activities.
*
* @package mod_workshop
* @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class activity extends \core_search\base_activity {
/**
* Returns true if this area uses file indexing.
*
* @return bool
*/
public function uses_file_indexing() {
return true;
}
/**
* Return the context info required to index files for
* this search area.
*
* @return array
*/
public function get_search_fileareas() {
$fileareas = array('intro', 'instructauthors', 'instructreviewers', 'conclusion'); // Fileareas.
return $fileareas;
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A scheduled task for workshop cron.
*
* @package mod_workshop
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_workshop\task;
defined('MOODLE_INTERNAL') || die();
/**
* The main scheduled task for the workshop.
*
* @package mod_workshop
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cron_task extends \core\task\scheduled_task {
/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('crontask', 'mod_workshop');
}
/**
* Run workshop cron.
*/
public function execute() {
global $CFG, $DB;
$now = time();
mtrace(' processing workshop subplugins ...');
// Check if there are some workshops to switch into the assessment phase.
$workshops = $DB->get_records_select("workshop",
"phase = 20 AND phaseswitchassessment = 1 AND submissionend > 0 AND submissionend < ?", [$now]);
if (!empty($workshops)) {
mtrace('Processing automatic assessment phase switch in ' . count($workshops) . ' workshop(s) ... ', '');
require_once($CFG->dirroot . '/mod/workshop/locallib.php');
foreach ($workshops as $workshop) {
$cm = get_coursemodule_from_instance('workshop', $workshop->id, $workshop->course, false, MUST_EXIST);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
$workshop = new \workshop($workshop, $cm, $course);
$workshop->switch_phase(\workshop::PHASE_ASSESSMENT);
$params = [
'objectid' => $workshop->id,
'context' => $workshop->context,
'courseid' => $workshop->course->id,
'other' => [
'targetworkshopphase' => $workshop->phase,
'previousworkshopphase' => \workshop::PHASE_SUBMISSION,
]
];
$event = \mod_workshop\event\phase_automatically_switched::create($params);
$event->trigger();
// Disable the automatic switching now so that it is not executed again by accident.
// That can happen if the teacher changes the phase back to the submission one.
$DB->set_field('workshop', 'phaseswitchassessment', 0, ['id' => $workshop->id]);
}
mtrace('done');
}
}
}
+260
View File
@@ -0,0 +1,260 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Capability definitions for the workshop module
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
// Ability to see that the workshop exists, and the basic information
// about it, for example the intro field
'mod/workshop:view' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'guest' => CAP_ALLOW,
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to add a new workshop to the course.
'mod/workshop:addinstance' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/course:manageactivities'
),
// Ability to change the current phase (stage) of the workshop, for example
// allow submitting, start assessment period, close workshop etc.
'mod/workshop:switchphase' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to modify the assessment forms, gives access to editform.php
'mod/workshop:editdimensions' => array(
'riskbitmask' => RISK_XSS, // can embed flash and javascript into wysiwyg
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to submit own work. All users having this capability are expected to participate
// in the workshop as the authors
'mod/workshop:submit' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
)
),
// Ability to be a reviewer of a submission. All users with this capability are considered
// as potential reviewers for the allocation purposes and can train assessment process on the
// example submissions.
'mod/workshop:peerassess' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
)
),
// Ability to submit and referentially assess the examples and to see all other
// assessments of these examples
'mod/workshop:manageexamples' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to allocate (assign) a submission for a review
'mod/workshop:allocate' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to publish submissions, i.e. make them available when workshop is closed
'mod/workshop:publishsubmissions' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to identify the author of the work that has been allocated to them for a review
// Reviewers without this capability will see the author as Anonymous
'mod/workshop:viewauthornames' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to identify the reviewer of the given submission (i.e. the owner of the assessment)
'mod/workshop:viewreviewernames' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to view the work submitted by an other user. In separate groups mode,
// the user has to be allowed to access all groups or be the member of the
// submission author's group.
'mod/workshop:viewallsubmissions' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to view published submission when the workshop is closed. Group mode
// restrictions do not apply here, published submissions are available in all
// groups even in the separate groups mode.
'mod/workshop:viewpublishedsubmissions' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to view the authors of published submissions.
'mod/workshop:viewauthorpublished' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to always view the assessments of other users' work and the calculated grades,
// regardless the phase. The separate groups membership is checked against the submission
// author only, not against the reviewer. In other words, if the user has this capability
// and is allowed to see some submission, then they are implicitly allowed to see all
// assessments of that submissions even if they do not share a group with the reviewer.
'mod/workshop:viewallassessments' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to override grade for submission or the calculated grades for assessment
// and to run aggregation tasks that computes the total grade
'mod/workshop:overridegrades' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to ignore time restrictions (submission start/end time and assessment
// start/end time) if they are defined
'mod/workshop:ignoredeadlines' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to delete other users' submissions.
'mod/workshop:deletesubmissions' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
),
// Ability to export submissions to a portfolio. Applies to all submissions
// the user has access to.
'mod/workshop:exportsubmissions' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'manager' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'student' => CAP_ALLOW,
)
),
);
+138
View File
@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/workshop/db" VERSION="20210302" COMMENT="XMLDB file for Moodle mod/workshop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="workshop" COMMENT="This table keeps information about the module instances and their settings">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the parent course"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Name of the activity"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The introduction or description of the activity"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The format of the intro field"/>
<FIELD NAME="instructauthors" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Instructions for the submission phase"/>
<FIELD NAME="instructauthorsformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="instructreviewers" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Instructions for the assessment phase"/>
<FIELD NAME="instructreviewersformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The timestamp when the module was modified"/>
<FIELD NAME="phase" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The current phase of workshop (0 = not available, 1 = submission, 2 = assessment, 3 = closed)"/>
<FIELD NAME="useexamples" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="optional feature: students practise evaluating on example submissions from teacher"/>
<FIELD NAME="usepeerassessment" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="optional feature: students perform peer assessment of others' work"/>
<FIELD NAME="useselfassessment" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="optional feature: students perform self assessment of their own work"/>
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="false" DEFAULT="80" SEQUENCE="false" DECIMALS="5" COMMENT="The maximum grade for submission"/>
<FIELD NAME="gradinggrade" TYPE="number" LENGTH="10" NOTNULL="false" DEFAULT="20" SEQUENCE="false" DECIMALS="5" COMMENT="The maximum grade for assessment"/>
<FIELD NAME="strategy" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" COMMENT="The type of the current grading strategy used in this workshop"/>
<FIELD NAME="evaluation" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" COMMENT="The recently used grading evaluation method"/>
<FIELD NAME="gradedecimals" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Number of digits that should be shown after the decimal point when displaying grades"/>
<FIELD NAME="submissiontypetext" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Can students enter text for their submissions? 0 for no, 1 for optional, 2 for required."/>
<FIELD NAME="submissiontypefile" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Can students attach files for their submissions? 0 for no, 1 for optional, 2 for required."/>
<FIELD NAME="nattachments" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="1" SEQUENCE="false" COMMENT="Maximum number of submission attachments"/>
<FIELD NAME="submissionfiletypes" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="comma separated list of file extensions"/>
<FIELD NAME="latesubmissions" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Allow submitting the work after the deadline"/>
<FIELD NAME="maxbytes" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="100000" SEQUENCE="false" COMMENT="Maximum size of the one attached file"/>
<FIELD NAME="examplesmode" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="0 = example assessments are voluntary, 1 = examples must be assessed before submission, 2 = examples are available after own submission and must be assessed before peer/self assessment phase"/>
<FIELD NAME="submissionstart" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="0 = will be started manually, greater than 0 the timestamp of the start of the submission phase"/>
<FIELD NAME="submissionend" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="0 = will be closed manually, greater than 0 the timestamp of the end of the submission phase"/>
<FIELD NAME="assessmentstart" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="0 = will be started manually, greater than 0 the timestamp of the start of the assessment phase"/>
<FIELD NAME="assessmentend" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="0 = will be closed manually, greater than 0 the timestamp of the end of the assessment phase"/>
<FIELD NAME="phaseswitchassessment" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Automatically switch to the assessment phase after the submissions deadline"/>
<FIELD NAME="conclusion" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="A text to be displayed at the end of the workshop."/>
<FIELD NAME="conclusionformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="The format of the conclusion field content."/>
<FIELD NAME="overallfeedbackmode" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="1" SEQUENCE="false" COMMENT="Mode of the overall feedback support."/>
<FIELD NAME="overallfeedbackfiles" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Number of allowed attachments to the overall feedback."/>
<FIELD NAME="overallfeedbackfiletypes" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="comma separated list of file extensions"/>
<FIELD NAME="overallfeedbackmaxbytes" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="100000" SEQUENCE="false" COMMENT="Maximum size of one file attached to the overall feedback."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="course_fk" TYPE="foreign" FIELDS="course" REFTABLE="course" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="workshop_submissions" COMMENT="Info about the submission and the aggregation of the grade for submission, grade for assessment and final grade. Both grade for submission and grade for assessment can be overridden by teacher. Final grade is always the sum of them. All grades are stored as of 0-100.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="workshopid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="the id of the workshop instance"/>
<FIELD NAME="example" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Is this submission an example from teacher"/>
<FIELD NAME="authorid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The author of the submission"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp when the work was submitted for the first time"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp when the submission has been updated"/>
<FIELD NAME="title" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The submission title"/>
<FIELD NAME="content" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Submission text"/>
<FIELD NAME="contentformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The format of submission text"/>
<FIELD NAME="contenttrust" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The trust mode of the data"/>
<FIELD NAME="attachment" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Used by File API file_postupdate_standard_filemanager"/>
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Aggregated grade for the submission. The grade is a decimal number from interval 0..100. If NULL then the grade for submission has not been aggregated yet."/>
<FIELD NAME="gradeover" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Grade for the submission manually overridden by a teacher. Grade is always from interval 0..100. If NULL then the grade is not overriden."/>
<FIELD NAME="gradeoverby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The id of the user who has overridden the grade for submission."/>
<FIELD NAME="feedbackauthor" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Teacher comment/feedback for the author of the submission, for example describing the reasons for the grade overriding"/>
<FIELD NAME="feedbackauthorformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timegraded" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The timestamp when grade or gradeover was recently modified"/>
<FIELD NAME="published" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Shall the submission be available to other when the workshop is closed"/>
<FIELD NAME="late" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Has this submission been submitted after the deadline or during the assessment phase?"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="workshop_fk" TYPE="foreign" FIELDS="workshopid" REFTABLE="workshop" REFFIELDS="id" COMMENT="Workshop foreign key"/>
<KEY NAME="overriddenby_fk" TYPE="foreign" FIELDS="gradeoverby" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="author_fk" TYPE="foreign" FIELDS="authorid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="workshop_assessments" COMMENT="Info about the made assessment and automatically calculated grade for it. The proposed grade can be overridden by teacher.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="submissionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the assessed submission"/>
<FIELD NAME="reviewerid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the reviewer who makes this assessment"/>
<FIELD NAME="weight" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="The weight of the assessment for the purposes of aggregation"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="If 0 then the assessment was allocated but the reviewer has not assessed yet. If greater than 0 then the timestamp of when the reviewer assessed for the first time"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="If 0 then the assessment was allocated but the reviewer has not assessed yet. If greater than 0 then the timestamp of when the reviewer assessed for the last time"/>
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="The aggregated grade for submission suggested by the reviewer. The grade 0..100 is computed from the values assigned to the assessment dimensions fields. If NULL then it has not been aggregated yet."/>
<FIELD NAME="gradinggrade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="The computed grade 0..100 for this assessment. If NULL then it has not been computed yet."/>
<FIELD NAME="gradinggradeover" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Grade for the assessment manually overridden by a teacher. Grade is always from interval 0..100. If NULL then the grade is not overriden."/>
<FIELD NAME="gradinggradeoverby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The id of the user who has overridden the grade for submission."/>
<FIELD NAME="feedbackauthor" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The comment/feedback from the reviewer for the author."/>
<FIELD NAME="feedbackauthorformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="feedbackauthorattachment" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Are there some files attached to the feedbackauthor field? Sets to 1 by file_postupdate_standard_filemanager()."/>
<FIELD NAME="feedbackreviewer" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The comment/feedback from the teacher for the reviewer. For example the reason why the grade for assessment was overridden"/>
<FIELD NAME="feedbackreviewerformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="submission_fk" TYPE="foreign" FIELDS="submissionid" REFTABLE="workshop_submissions" REFFIELDS="id"/>
<KEY NAME="overriddenby_fk" TYPE="foreign" FIELDS="gradinggradeoverby" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="reviewer_fk" TYPE="foreign" FIELDS="reviewerid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="workshop_grades" COMMENT="How the reviewers filled-up the grading forms, given grades and comments">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="assessmentid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Part of which assessment this grade is of"/>
<FIELD NAME="strategy" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="dimensionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Foreign key. References dimension id in one of the grading strategy tables."/>
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="Given grade in the referenced assessment dimension."/>
<FIELD NAME="peercomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Reviewer's comment to the grade value."/>
<FIELD NAME="peercommentformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The format of peercomment field"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="assessment_fk" TYPE="foreign" FIELDS="assessmentid" REFTABLE="workshop_assessments" REFFIELDS="id"/>
<KEY NAME="formfield_uk" TYPE="unique" FIELDS="assessmentid, strategy, dimensionid" COMMENT="The combination of assessmentid, strategy and dimensionid must be unique"/>
</KEYS>
</TABLE>
<TABLE NAME="workshop_aggregations" COMMENT="Aggregated grades for assessment are stored here. The aggregated grade for submission is stored in workshop_submissions">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="workshopid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="the id of the workshop instance"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the user which aggregated grades are calculated for"/>
<FIELD NAME="gradinggrade" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="The aggregated grade for all assessments made by this reviewer. The grade is a number from interval 0..100. If NULL then the grade for assessments has not been aggregated yet."/>
<FIELD NAME="timegraded" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The timestamp of when the participant's gradinggrade was recently aggregated."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="workshop_fk" TYPE="foreign" FIELDS="workshopid" REFTABLE="workshop" REFFIELDS="id"/>
<KEY NAME="user_fk" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="workshopuser" TYPE="unique" FIELDS="workshopid, userid" COMMENT="The combination of workshopid with userid must be unique in this table"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
+55
View File
@@ -0,0 +1,55 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition of log events
*
* @package mod_workshop
* @category log
* @copyright 2010 Petr Skoda (http://skodak.org)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$logs = array(
// workshop instance log actions
array('module'=>'workshop', 'action'=>'add', 'mtable'=>'workshop', 'field'=>'name'),
array('module'=>'workshop', 'action'=>'update', 'mtable'=>'workshop', 'field'=>'name'),
array('module'=>'workshop', 'action'=>'view', 'mtable'=>'workshop', 'field'=>'name'),
array('module'=>'workshop', 'action'=>'view all', 'mtable'=>'workshop', 'field'=>'name'),
// submission log actions
array('module'=>'workshop', 'action'=>'add submission', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'update submission', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'view submission', 'mtable'=>'workshop_submissions', 'field'=>'title'),
// assessment log actions
array('module'=>'workshop', 'action'=>'add assessment', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'update assessment', 'mtable'=>'workshop_submissions', 'field'=>'title'),
// example log actions
array('module'=>'workshop', 'action'=>'add example', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'update example', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'view example', 'mtable'=>'workshop_submissions', 'field'=>'title'),
// example assessment log actions
array('module'=>'workshop', 'action'=>'add reference assessment', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'update reference assessment', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'add example assessment', 'mtable'=>'workshop_submissions', 'field'=>'title'),
array('module'=>'workshop', 'action'=>'update example assessment', 'mtable'=>'workshop_submissions', 'field'=>'title'),
// grading evaluation log actions
array('module'=>'workshop', 'action'=>'update aggregate grades', 'mtable'=>'workshop', 'field'=>'name'),
array('module'=>'workshop', 'action'=>'update clear aggregated grades', 'mtable'=>'workshop', 'field'=>'name'),
array('module'=>'workshop', 'action'=>'update clear assessments', 'mtable'=>'workshop', 'field'=>'name'),
);
+173
View File
@@ -0,0 +1,173 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Workshop external functions and service definitions.
*
* @package mod_workshop
* @category external
* @copyright 2017 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.4
*/
defined('MOODLE_INTERNAL') || die;
$functions = array(
'mod_workshop_get_workshops_by_courses' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_workshops_by_courses',
'description' => 'Returns a list of workshops in a provided list of courses, if no list is provided all workshops that
the user can view will be returned.',
'type' => 'read',
'capabilities' => 'mod/workshop:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'mod_workshop_get_workshop_access_information' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_workshop_access_information',
'description' => 'Return access information for a given workshop.',
'type' => 'read',
'capabilities' => 'mod/workshop:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'mod_workshop_get_user_plan' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_user_plan',
'description' => 'Return the planner information for the given user.',
'type' => 'read',
'capabilities' => 'mod/workshop:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
'mod_workshop_view_workshop' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'view_workshop',
'description' => 'Trigger the course module viewed event and update the module completion status.',
'type' => 'write',
'capabilities' => 'mod/workshop:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_add_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'add_submission',
'description' => 'Add a new submission to a given workshop.',
'type' => 'write',
'capabilities' => 'mod/workshop:submit',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_update_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'update_submission',
'description' => 'Update the given submission.',
'type' => 'write',
'capabilities' => 'mod/workshop:submit',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_delete_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'delete_submission',
'description' => 'Deletes the given submission.',
'type' => 'write',
'capabilities' => 'mod/workshop:submit',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_submissions' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_submissions',
'description' => 'Retrieves all the workshop submissions or the one done by the given user (except example submissions).',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_submission',
'description' => 'Retrieves the given submission.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_submission_assessments' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_submission_assessments',
'description' => 'Retrieves all the assessments of the given submission.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_assessment' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_assessment',
'description' => 'Retrieves the given assessment.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_assessment_form_definition' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_assessment_form_definition',
'description' => 'Retrieves the assessment form definition.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_reviewer_assessments' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_reviewer_assessments',
'description' => 'Retrieves all the assessments reviewed by the given user.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_update_assessment' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'update_assessment',
'description' => 'Add information to an allocated assessment.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_grades' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_grades',
'description' => 'Returns the assessment and submission grade for the given user.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_evaluate_assessment' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'evaluate_assessment',
'description' => 'Evaluates an assessment (used by teachers for provide feedback to the reviewer).',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_get_grades_report' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'get_grades_report',
'description' => 'Retrieves the assessment grades report.',
'type' => 'read',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_view_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'view_submission',
'description' => 'Trigger the submission viewed event.',
'type' => 'write',
'capabilities' => 'mod/workshop:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_workshop_evaluate_submission' => array(
'classname' => 'mod_workshop_external',
'methodname' => 'evaluate_submission',
'description' => 'Evaluates a submission (used by teachers for provide feedback or override the submission grade).',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
);
+7
View File
@@ -0,0 +1,7 @@
{
"plugintypes": {
"workshopform": "mod\/workshop\/form",
"workshopallocation": "mod\/workshop\/allocation",
"workshopeval": "mod\/workshop\/eval"
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Definition of workshop scheduled tasks.
*
* @package mod_workshop
* @copyright 2019 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = [
[
'classname' => '\mod_workshop\task\cron_task',
'blocking' => 0,
'minute' => '*',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*'
]
];
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file replaces the legacy STATEMENTS section in db/install.xml,
* lib.php/modulename_install() post installation hook and partially defaults.php
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* This is called at the beginning of the uninstallation process to give the module
* a chance to clean-up its hacks, bits etc. where possible.
*
* @return bool true if success
*/
function mod_workshop_uninstall() {
// global $DB;
// $dbman = $DB->get_manager();
return true;
}
+49
View File
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Keeps track of upgrades to the workshop module
*
* @package mod_workshop
* @category upgrade
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Performs upgrade of the database structure and data
*
* Workshop supports upgrades from version 1.9.0 and higher only. During 1.9 > 2.0 upgrade,
* there are significant database changes.
*
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_workshop_upgrade($oldversion) {
// Automatically generated Moodle v4.1.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+84
View File
@@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Edit grading form in for a particular instance of workshop
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$cmid = required_param('cmid', PARAM_INT);
$cm = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
require_login($course, false, $cm);
require_capability('mod/workshop:editdimensions', $PAGE->context);
$workshop = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
// todo: check if there already is some assessment done and do not allowed the change of the form
// once somebody already used it to assess
$PAGE->set_url($workshop->editform_url());
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->activityheader->set_attrs([
'hidecompletion' => true,
'description' => ''
]);
$PAGE->navbar->add(get_string('editingassessmentform', 'workshop'));
// load the grading strategy logic
$strategy = $workshop->grading_strategy_instance();
// load the form to edit the grading strategy dimensions
$mform = $strategy->get_edit_strategy_form($PAGE->url);
if ($mform->is_cancelled()) {
redirect($workshop->view_url());
} elseif ($data = $mform->get_data()) {
if (($data->workshopid != $workshop->id) or ($data->strategy != $workshop->strategy)) {
// this may happen if someone changes the workshop setting while the user had the
// editing form opened
throw new invalid_parameter_exception('Invalid workshop ID or the grading strategy has changed.');
}
$strategy->save_edit_strategy_form($data);
if (isset($data->saveandclose)) {
redirect($workshop->view_url());
} elseif (isset($data->saveandpreview)) {
redirect($workshop->previewform_url());
} else {
// save and continue - redirect to self to prevent data being re-posted by pressing "Reload"
redirect($PAGE->url);
}
}
// Output starts here
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('pluginname', 'workshopform_' . $workshop->strategy), 3);
$mform->display();
echo $OUTPUT->footer();
+63
View File
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Preview the assessment form.
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$cmid = required_param('cmid', PARAM_INT);
$cm = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
$workshop = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
require_login($course, false, $cm);
if (isguestuser()) {
throw new \moodle_exception('guestsarenotallowed');
}
$workshop = new workshop($workshop, $cm, $course);
require_capability('mod/workshop:editdimensions', $workshop->context);
$PAGE->set_url($workshop->previewform_url());
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add(get_string('editingassessmentform', 'workshop'), $workshop->editform_url(), navigation_node::TYPE_CUSTOM);
$PAGE->navbar->add(get_string('previewassessmentform', 'workshop'));
$PAGE->set_secondary_active_tab('workshopassessement');
$PAGE->activityheader->set_attrs([
"hidecompletion" => true,
"description" => ''
]);
$currenttab = 'editform';
// load the grading strategy logic
$strategy = $workshop->grading_strategy_instance();
// load the assessment form
$mform = $strategy->get_assessment_form($workshop->editform_url(), 'preview');
// output starts here
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('assessmentform', 'workshop'), 3);
$mform->display();
echo $OUTPUT->footer();
@@ -0,0 +1,53 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package workshopeval
* @subpackage best
* @copyright 2010 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* Provides the information to backup grading evaluation method 'Comparison with the best assessment'
*
* This evaluator just stores a single integer value - the recently used comparison
* strictness factor. It adds its XML data to workshop tag.
*/
class backup_workshopeval_best_subplugin extends backup_subplugin {
/**
* Returns the subplugin information to attach to workshop element
*/
protected function define_workshop_subplugin_structure() {
// create XML elements
$subplugin = $this->get_subplugin_element(); // virtual optigroup element
$subplugin_wrapper = new backup_nested_element($this->get_recommended_name());
$subplugin_table_settings = new backup_nested_element('workshopeval_best_settings', null, array('comparison'));
// connect XML elements into the tree
$subplugin->add_child($subplugin_wrapper);
$subplugin_wrapper->add_child($subplugin_table_settings);
// set source to populate the data
$subplugin_table_settings->set_source_table('workshopeval_best_settings', array('workshopid' => backup::VAR_ACTIVITYID));
return $subplugin;
}
}
@@ -0,0 +1,63 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package workshopeval
* @subpackage best
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* restore subplugin class that provides the necessary information
* needed to restore one workshopeval_best subplugin.
*/
class restore_workshopeval_best_subplugin extends restore_subplugin {
////////////////////////////////////////////////////////////////////////////
// mappings of XML paths to the processable methods
////////////////////////////////////////////////////////////////////////////
/**
* Returns the paths to be handled by the subplugin at workshop level
*/
protected function define_workshop_subplugin_structure() {
$paths = array();
$elename = $this->get_namefor('setting');
$elepath = $this->get_pathfor('/workshopeval_best_settings'); // we used get_recommended_name() so this works
$paths[] = new restore_path_element($elename, $elepath);
return $paths; // And we return the interesting paths
}
////////////////////////////////////////////////////////////////////////////
// defined path elements are dispatched to the following methods
////////////////////////////////////////////////////////////////////////////
/**
* Processes one workshopeval_best_settings element
*/
public function process_workshopeval_best_setting($data) {
global $DB;
$data = (object)$data;
$data->workshopid = $this->get_new_parentid('workshop');
$DB->insert_record('workshopeval_best_settings', $data);
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the class {@link workshopeval_best\privacy\provider}
*
* @package workshopeval_best
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopeval_best\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy API implementation for the Comparison with the best assessment method.
*
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Explain that this plugin stores no personal data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/workshop/eval/best/db" VERSION="20120122" COMMENT="XMLDB file for Moodle mod/workshop/eval/best"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="workshopeval_best_settings" COMMENT="Settings for the grading evaluation subplugin Comparison with the best assessment.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="workshopid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="comparison" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="5" SEQUENCE="false" COMMENT="Here we store the recently set factor of comparison of assessment in the given workshop. Reasonable values are from 1 to 10 or so. Default to 5."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="fkuq_workshop" TYPE="foreign-unique" FIELDS="workshopid" REFTABLE="workshop" REFFIELDS="id" COMMENT="Every workshop can have only one settings record"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>
@@ -0,0 +1,36 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'workshopeval_best', language 'en', branch 'MOODLE_20_STABLE'
*
* @package workshopeval
* @subpackage best
* @copyright 2009 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['comparison'] = 'Comparison of assessments';
$string['comparison_help'] = 'This setting specifies how strict the comparison of assessments should be. The stricter the comparison, the more similar the assessments need to be in order for a high grade to be obtained.';
$string['comparisonlevel1'] = 'very strict';
$string['comparisonlevel3'] = 'strict';
$string['comparisonlevel5'] = 'fair';
$string['comparisonlevel7'] = 'lax';
$string['comparisonlevel9'] = 'very lax';
$string['configcomparison'] = 'Default value of the factor that influence the grading evaluation.';
$string['pluginname'] = 'Comparison with the best assessment';
$string['privacy:metadata'] = 'The Comparison with the best assessment plugin does not store any personal data. Actual personal data user\'s grades are stored by the Workshop module itself and are attached to the exported submissions and assessments data.';
+440
View File
@@ -0,0 +1,440 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains logic class and interface for the grading evaluation plugin "Comparison
* with the best assessment".
*
* @package workshopeval
* @subpackage best
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../lib.php'); // interface definition
require_once($CFG->libdir . '/gradelib.php');
/**
* Defines the computation login of the grading evaluation subplugin
*/
class workshop_best_evaluation extends workshop_evaluation {
/** @var the recently used settings in this workshop */
protected $settings;
/**
* Constructor
*
* @param workshop $workshop The workshop api instance
* @return void
*/
public function __construct(workshop $workshop) {
global $DB;
$this->workshop = $workshop;
$this->settings = $DB->get_record('workshopeval_best_settings', array('workshopid' => $this->workshop->id));
}
/**
* Calculates the grades for assessment and updates 'gradinggrade' fields in 'workshop_assessments' table
*
* This function relies on the grading strategy subplugin providing get_assessments_recordset() method.
* {@see self::process_assessments()} for the required structure of the recordset.
*
* @param stdClass $settings The settings for this round of evaluation
* @param null|int|array $restrict If null, update all reviewers, otherwise update just grades for the given reviewers(s)
*
* @return void
*/
public function update_grading_grades(stdclass $settings, $restrict=null) {
global $DB;
// Remember the recently used settings for this workshop.
if (empty($this->settings)) {
$record = new stdclass();
$record->workshopid = $this->workshop->id;
$record->comparison = $settings->comparison;
$DB->insert_record('workshopeval_best_settings', $record);
} elseif ($this->settings->comparison != $settings->comparison) {
$DB->set_field('workshopeval_best_settings', 'comparison', $settings->comparison,
array('workshopid' => $this->workshop->id));
}
// Get the grading strategy instance.
$grader = $this->workshop->grading_strategy_instance();
// get the information about the assessment dimensions
$diminfo = $grader->get_dimensions_info();
// fetch a recordset with all assessments to process
$rs = $grader->get_assessments_recordset($restrict);
$batch = array(); // will contain a set of all assessments of a single submission
$previous = null; // a previous record in the recordset
foreach ($rs as $current) {
if (is_null($previous)) {
// we are processing the very first record in the recordset
$previous = $current;
}
if ($current->submissionid == $previous->submissionid) {
$batch[] = $current;
} else {
// process all the assessments of a single submission
$this->process_assessments($batch, $diminfo, $settings);
// start with a new batch to be processed
$batch = array($current);
$previous = $current;
}
}
// do not forget to process the last batch!
$this->process_assessments($batch, $diminfo, $settings);
$rs->close();
}
/**
* Returns an instance of the form to provide evaluation settings.
*
* @return workshop_best_evaluation_settings_form
*/
public function get_settings_form(moodle_url $actionurl=null) {
$customdata['workshop'] = $this->workshop;
$customdata['current'] = $this->settings;
$attributes = array('class' => 'evalsettingsform best');
return new workshop_best_evaluation_settings_form($actionurl, $customdata, 'post', '', $attributes);
}
/**
* Delete all data related to a given workshop module instance
*
* @see workshop_delete_instance()
* @param int $workshopid id of the workshop module instance being deleted
* @return void
*/
public static function delete_instance($workshopid) {
global $DB;
$DB->delete_records('workshopeval_best_settings', array('workshopid' => $workshopid));
}
////////////////////////////////////////////////////////////////////////////////
// Internal methods //
////////////////////////////////////////////////////////////////////////////////
/**
* Given a list of all assessments of a single submission, updates the grading grades in database
*
* @param array $assessments of stdclass (->assessmentid ->assessmentweight ->reviewerid ->gradinggrade ->submissionid ->dimensionid ->grade)
* @param array $diminfo of stdclass (->id ->weight ->max ->min)
* @param stdClass grading evaluation settings
* @return void
*/
protected function process_assessments(array $assessments, array $diminfo, stdclass $settings) {
global $DB;
if (empty($assessments)) {
return;
}
// reindex the passed flat structure to be indexed by assessmentid
$assessments = $this->prepare_data_from_recordset($assessments);
// normalize the dimension grades to the interval 0 - 100
$assessments = $this->normalize_grades($assessments, $diminfo);
// get a hypothetical average assessment
$average = $this->average_assessment($assessments);
// if unable to calculate the average assessment, set the grading grades to null
if (is_null($average)) {
foreach ($assessments as $asid => $assessment) {
if (!is_null($assessment->gradinggrade)) {
$DB->set_field('workshop_assessments', 'gradinggrade', null, array('id' => $asid));
}
}
return;
}
// calculate variance of dimension grades
$variances = $this->weighted_variance($assessments);
foreach ($variances as $dimid => $variance) {
$diminfo[$dimid]->variance = $variance;
}
// for every assessment, calculate its distance from the average one
$distances = array();
foreach ($assessments as $asid => $assessment) {
$distances[$asid] = $this->assessments_distance($assessment, $average, $diminfo, $settings);
}
// identify the best assessments - that is those with the shortest distance from the best assessment
$bestids = moodle_array_keys_filter($distances, min($distances));
// for every assessment, calculate its distance from the nearest best assessment
$distances = array();
foreach ($bestids as $bestid) {
$best = $assessments[$bestid];
foreach ($assessments as $asid => $assessment) {
$d = $this->assessments_distance($assessment, $best, $diminfo, $settings);
if (!is_null($d) and (!isset($distances[$asid]) or $d < $distances[$asid])) {
$distances[$asid] = $d;
}
}
}
// calculate the grading grade
foreach ($distances as $asid => $distance) {
$gradinggrade = (100 - $distance);
if ($gradinggrade < 0) {
$gradinggrade = 0;
}
if ($gradinggrade > 100) {
$gradinggrade = 100;
}
$grades[$asid] = grade_floatval($gradinggrade);
}
// if the new grading grade differs from the one stored in database, update it
// we do not use set_field() here because we want to pass $bulk param
foreach ($grades as $assessmentid => $grade) {
if (grade_floats_different($grade, $assessments[$assessmentid]->gradinggrade)) {
// the value has changed
$record = new stdclass();
$record->id = $assessmentid;
$record->gradinggrade = grade_floatval($grade);
// do not set timemodified here, it contains the timestamp of when the form was
// saved by the peer reviewer, not when it was aggregated
$DB->update_record('workshop_assessments', $record, true); // bulk operations expected
}
}
// done. easy, heh? ;-)
}
/**
* Prepares a structure of assessments and given grades
*
* @param array $assessments batch of recordset items as returned by the grading strategy
* @return array
*/
protected function prepare_data_from_recordset($assessments) {
$data = array(); // to be returned
foreach ($assessments as $a) {
$id = $a->assessmentid; // just an abbreviation
if (!isset($data[$id])) {
$data[$id] = new stdclass();
$data[$id]->assessmentid = $a->assessmentid;
$data[$id]->weight = $a->assessmentweight;
$data[$id]->reviewerid = $a->reviewerid;
$data[$id]->gradinggrade = $a->gradinggrade;
$data[$id]->submissionid = $a->submissionid;
$data[$id]->dimgrades = array();
}
$data[$id]->dimgrades[$a->dimensionid] = $a->grade;
}
return $data;
}
/**
* Normalizes the dimension grades to the interval 0.00000 - 100.00000
*
* Note: this heavily relies on PHP5 way of handling references in array of stdclasses. Hopefully
* it will not change again soon.
*
* @param array $assessments of stdclass as returned by {@see self::prepare_data_from_recordset()}
* @param array $diminfo of stdclass
* @return array of stdclass with the same structure as $assessments
*/
protected function normalize_grades(array $assessments, array $diminfo) {
foreach ($assessments as $asid => $assessment) {
foreach ($assessment->dimgrades as $dimid => $dimgrade) {
$dimmin = $diminfo[$dimid]->min;
$dimmax = $diminfo[$dimid]->max;
if ($dimmin == $dimmax) {
$assessment->dimgrades[$dimid] = grade_floatval($dimmax);
} else {
$assessment->dimgrades[$dimid] = grade_floatval(($dimgrade - $dimmin) / ($dimmax - $dimmin) * 100);
}
}
}
return $assessments;
}
/**
* Given a set of a submission's assessments, returns a hypothetical average assessment
*
* The passed structure must be array of assessments objects with ->weight and ->dimgrades properties.
* Returns null if all passed assessments have zero weight as there is nothing to choose
* from then.
*
* @param array $assessments as prepared by {@link self::prepare_data_from_recordset()}
* @return null|stdClass
*/
protected function average_assessment(array $assessments) {
$sumdimgrades = array();
foreach ($assessments as $a) {
foreach ($a->dimgrades as $dimid => $dimgrade) {
if (!isset($sumdimgrades[$dimid])) {
$sumdimgrades[$dimid] = 0;
}
$sumdimgrades[$dimid] += $dimgrade * $a->weight;
}
}
$sumweights = 0;
foreach ($assessments as $a) {
$sumweights += $a->weight;
}
if ($sumweights == 0) {
// unable to calculate average assessment
return null;
}
$average = new stdclass();
$average->dimgrades = array();
foreach ($sumdimgrades as $dimid => $sumdimgrade) {
$average->dimgrades[$dimid] = grade_floatval($sumdimgrade / $sumweights);
}
return $average;
}
/**
* Given a set of a submission's assessments, returns standard deviations of all their dimensions
*
* The passed structure must be array of assessments objects with at least ->weight
* and ->dimgrades properties. This implementation uses weighted incremental algorithm as
* suggested in "D. H. D. West (1979). Communications of the ACM, 22, 9, 532-535:
* Updating Mean and Variance Estimates: An Improved Method"
* {@link http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Weighted_incremental_algorithm}
*
* @param array $assessments as prepared by {@link self::prepare_data_from_recordset()}
* @return null|array indexed by dimension id
*/
protected function weighted_variance(array $assessments) {
$first = reset($assessments);
if (empty($first)) {
return null;
}
$dimids = array_keys($first->dimgrades);
$asids = array_keys($assessments);
$vars = array(); // to be returned
foreach ($dimids as $dimid) {
$n = 0;
$s = 0;
$sumweight = 0;
foreach ($asids as $asid) {
$x = $assessments[$asid]->dimgrades[$dimid]; // value (data point)
$weight = $assessments[$asid]->weight; // the values' weight
if ($weight == 0) {
continue;
}
if ($n == 0) {
$n = 1;
$mean = $x;
$s = 0;
$sumweight = $weight;
} else {
$n++;
$temp = $weight + $sumweight;
$q = $x - $mean;
$r = $q * $weight / $temp;
$s = $s + $sumweight * $q * $r;
$mean = $mean + $r;
$sumweight = $temp;
}
}
if ($sumweight > 0 and $n > 1) {
// for the sample: $vars[$dimid] = ($s * $n) / (($n - 1) * $sumweight);
// for the population:
$vars[$dimid] = $s / $sumweight;
} else {
$vars[$dimid] = null;
}
}
return $vars;
}
/**
* Measures the distance of the assessment from a referential one
*
* The passed data structures must contain ->dimgrades property. The referential
* assessment is supposed to be close to the average assessment. All dimension grades are supposed to be
* normalized to the interval 0 - 100.
* Returned value is rounded to 4 valid decimals to prevent some rounding issues - see the unit test
* for an example.
*
* @param stdClass $assessment the assessment being measured
* @param stdClass $referential assessment
* @param array $diminfo of stdclass(->weight ->min ->max ->variance) indexed by dimension id
* @param stdClass $settings
* @return float|null rounded to 4 valid decimals
*/
protected function assessments_distance(stdclass $assessment, stdclass $referential, array $diminfo, stdclass $settings) {
$distance = 0;
$n = 0;
foreach (array_keys($assessment->dimgrades) as $dimid) {
$agrade = $assessment->dimgrades[$dimid];
$rgrade = $referential->dimgrades[$dimid];
$var = $diminfo[$dimid]->variance;
$weight = $diminfo[$dimid]->weight;
$n += $weight;
// variations very close to zero are too sensitive to a small change of data values
$var = max($var, 0.01);
if ($agrade != $rgrade) {
$absdelta = abs($agrade - $rgrade);
$reldelta = pow($agrade - $rgrade, 2) / ($settings->comparison * $var);
$distance += $absdelta * $reldelta * $weight;
}
}
if ($n > 0) {
// average distance across all dimensions
return round($distance / $n, 4);
} else {
return null;
}
}
}
/**
* Represents the settings form for this plugin.
*/
class workshop_best_evaluation_settings_form extends workshop_evaluation_settings_form {
/**
* Defines specific fields for this evaluation method.
*/
protected function definition_sub() {
$mform = $this->_form;
$plugindefaults = get_config('workshopeval_best');
$current = $this->_customdata['current'];
$options = array();
for ($i = 9; $i >= 1; $i = $i-2) {
$options[$i] = get_string('comparisonlevel' . $i, 'workshopeval_best');
}
$mform->addElement('select', 'comparison', get_string('comparison', 'workshopeval_best'), $options);
$mform->addHelpButton('comparison', 'comparison', 'workshopeval_best');
$mform->setDefault('comparison', $plugindefaults->comparison);
$this->set_data($current);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The configuration variables for "Best" grading evaluation
*
* The values defined here are used as defaults for all module instances.
*
* @package workshopeval
* @subpackage best
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$options = array();
for ($i = 9; $i >= 1; $i = $i-2) {
$options[$i] = get_string('comparisonlevel' . $i, 'workshopeval_best');
}
$settings->add(new admin_setting_configselect('workshopeval_best/comparison', get_string('comparison', 'workshopeval_best'),
get_string('configcomparison', 'workshopeval_best'), 5, $options));
+344
View File
@@ -0,0 +1,344 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for grading evaluation method "best"
*
* @package workshopeval_best
* @category test
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace workshopeval_best;
use workshop;
use workshop_best_evaluation;
defined('MOODLE_INTERNAL') || die();
// Include the code to test
global $CFG;
require_once($CFG->dirroot . '/mod/workshop/locallib.php');
require_once($CFG->dirroot . '/mod/workshop/eval/best/lib.php');
require_once($CFG->libdir . '/gradelib.php');
/**
* Unit tests for grading evaluation lib.php
*/
class lib_test extends \advanced_testcase {
/** workshop instance emulation */
protected $workshop;
/** instance of the grading evaluator being tested */
protected $evaluator;
/**
* Setup testing environment
*/
protected function setUp(): void {
parent::setUp();
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$workshop = $this->getDataGenerator()->create_module('workshop', array('evaluation' => 'best', 'course' => $course));
$cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id];
$this->workshop = new workshop($workshop, $cm, $course);
$this->evaluator = new testable_workshop_best_evaluation($this->workshop);
}
protected function tearDown(): void {
$this->workshop = null;
$this->evaluator = null;
parent::tearDown();
}
public function test_normalize_grades(): void {
// fixture set-up
$assessments = array();
$assessments[1] = (object)array(
'dimgrades' => array(3 => 1.0000, 4 => 13.42300),
);
$assessments[3] = (object)array(
'dimgrades' => array(3 => 2.0000, 4 => 19.1000),
);
$assessments[7] = (object)array(
'dimgrades' => array(3 => 3.0000, 4 => 0.00000),
);
$diminfo = array(
3 => (object)array('min' => 1, 'max' => 3),
4 => (object)array('min' => 0, 'max' => 20),
);
// exercise SUT
$norm = $this->evaluator->normalize_grades($assessments, $diminfo);
// validate
$this->assertEquals(gettype($norm), 'array');
// the following grades from a scale
$this->assertEquals($norm[1]->dimgrades[3], 0);
$this->assertEquals($norm[3]->dimgrades[3], 50);
$this->assertEquals($norm[7]->dimgrades[3], 100);
// the following grades from an interval 0 - 20
$this->assertEquals($norm[1]->dimgrades[4], grade_floatval(13.423 / 20 * 100));
$this->assertEquals($norm[3]->dimgrades[4], grade_floatval(19.1 / 20 * 100));
$this->assertEquals($norm[7]->dimgrades[4], 0);
}
public function test_normalize_grades_max_equals_min(): void {
// fixture set-up
$assessments = array();
$assessments[1] = (object)array(
'dimgrades' => array(3 => 100.0000),
);
$diminfo = array(
3 => (object)array('min' => 100, 'max' => 100),
);
// exercise SUT
$norm = $this->evaluator->normalize_grades($assessments, $diminfo);
// validate
$this->assertEquals(gettype($norm), 'array');
$this->assertEquals($norm[1]->dimgrades[3], 100);
}
public function test_average_assessment_same_weights(): void {
// fixture set-up
$assessments = array();
$assessments[18] = (object)array(
'weight' => 1,
'dimgrades' => array(1 => 50, 2 => 33.33333),
);
$assessments[16] = (object)array(
'weight' => 1,
'dimgrades' => array(1 => 0, 2 => 66.66667),
);
// exercise SUT
$average = $this->evaluator->average_assessment($assessments);
// validate
$this->assertEquals(gettype($average->dimgrades), 'array');
$this->assertEquals(grade_floatval($average->dimgrades[1]), grade_floatval(25));
$this->assertEquals(grade_floatval($average->dimgrades[2]), grade_floatval(50));
}
public function test_average_assessment_different_weights(): void {
// fixture set-up
$assessments = array();
$assessments[11] = (object)array(
'weight' => 1,
'dimgrades' => array(3 => 10.0, 4 => 13.4, 5 => 95.0),
);
$assessments[13] = (object)array(
'weight' => 3,
'dimgrades' => array(3 => 11.0, 4 => 10.1, 5 => 92.0),
);
$assessments[17] = (object)array(
'weight' => 1,
'dimgrades' => array(3 => 11.0, 4 => 8.1, 5 => 88.0),
);
// exercise SUT
$average = $this->evaluator->average_assessment($assessments);
// validate
$this->assertEquals(gettype($average->dimgrades), 'array');
$this->assertEquals(grade_floatval($average->dimgrades[3]), grade_floatval((10.0 + 11.0*3 + 11.0)/5));
$this->assertEquals(grade_floatval($average->dimgrades[4]), grade_floatval((13.4 + 10.1*3 + 8.1)/5));
$this->assertEquals(grade_floatval($average->dimgrades[5]), grade_floatval((95.0 + 92.0*3 + 88.0)/5));
}
public function test_average_assessment_noweight(): void {
// fixture set-up
$assessments = array();
$assessments[11] = (object)array(
'weight' => 0,
'dimgrades' => array(3 => 10.0, 4 => 13.4, 5 => 95.0),
);
$assessments[17] = (object)array(
'weight' => 0,
'dimgrades' => array(3 => 11.0, 4 => 8.1, 5 => 88.0),
);
// exercise SUT
$average = $this->evaluator->average_assessment($assessments);
// validate
$this->assertNull($average);
}
public function test_weighted_variance(): void {
// fixture set-up
$assessments[11] = (object)array(
'weight' => 1,
'dimgrades' => array(3 => 11, 4 => 2),
);
$assessments[13] = (object)array(
'weight' => 3,
'dimgrades' => array(3 => 11, 4 => 4),
);
$assessments[17] = (object)array(
'weight' => 2,
'dimgrades' => array(3 => 11, 4 => 5),
);
$assessments[20] = (object)array(
'weight' => 1,
'dimgrades' => array(3 => 11, 4 => 7),
);
$assessments[25] = (object)array(
'weight' => 1,
'dimgrades' => array(3 => 11, 4 => 9),
);
// exercise SUT
$variance = $this->evaluator->weighted_variance($assessments);
// validate
// dimension [3] have all the grades equal to 11
$this->assertEquals($variance[3], 0);
// dimension [4] represents data 2, 4, 4, 4, 5, 5, 7, 9 having stdev=2 (stdev is sqrt of variance)
$this->assertEquals($variance[4], 4);
}
public function test_assessments_distance_zero(): void {
// fixture set-up
$diminfo = array(
3 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567),
4 => (object)array('weight' => 1, 'min' => 1, 'max' => 5, 'variance' => 98.76543),
);
$assessment1 = (object)array('dimgrades' => array(3 => 15, 4 => 2));
$assessment2 = (object)array('dimgrades' => array(3 => 15, 4 => 2));
$settings = (object)array('comparison' => 5);
// exercise SUT and validate
$this->assertEquals($this->evaluator->assessments_distance($assessment1, $assessment2, $diminfo, $settings), 0);
}
public function test_assessments_distance_equals(): void {
/*
// fixture set-up
$diminfo = array(
3 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567),
4 => (object)array('weight' => 1, 'min' => 0, 'max' => 100, 'variance' => 12.34567),
);
$assessment1 = (object)array('dimgrades' => array(3 => 25, 4 => 4));
$assessment2 = (object)array('dimgrades' => array(3 => 75, 4 => 2));
$referential = (object)array('dimgrades' => array(3 => 50, 4 => 3));
$settings = (object)array('comparison' => 5);
// exercise SUT and validate
$this->assertEquals($this->evaluator->assessments_distance($assessment1, $referential, $diminfo, $settings),
$this->evaluator->assessments_distance($assessment2, $referential, $diminfo, $settings));
*/
// fixture set-up
$diminfo = array(
1 => (object)array('min' => 0, 'max' => 2, 'weight' => 1, 'variance' => 625),
2 => (object)array('min' => 0, 'max' => 3, 'weight' => 1, 'variance' => 277.7778888889),
);
$assessment1 = (object)array('dimgrades' => array(1 => 0, 2 => 66.66667));
$assessment2 = (object)array('dimgrades' => array(1 => 50, 2 => 33.33333));
$referential = (object)array('dimgrades' => array(1 => 25, 2 => 50));
$settings = (object)array('comparison' => 9);
// exercise SUT and validate
$this->assertEquals($this->evaluator->assessments_distance($assessment1, $referential, $diminfo, $settings),
$this->evaluator->assessments_distance($assessment2, $referential, $diminfo, $settings));
}
public function test_assessments_distance_zero_variance(): void {
// Fixture set-up: an assessment form of the strategy "Number of errors",
// three assertions, same weight.
$diminfo = array(
1 => (object)array('min' => 0, 'max' => 1, 'weight' => 1),
2 => (object)array('min' => 0, 'max' => 1, 'weight' => 1),
3 => (object)array('min' => 0, 'max' => 1, 'weight' => 1),
);
// Simulate structure returned by {@link workshop_best_evaluation::prepare_data_from_recordset()}
$assessments = array(
// The first assessment has weight 0 and the assessment was No, No, No.
10 => (object)array(
'assessmentid' => 10,
'weight' => 0,
'reviewerid' => 56,
'gradinggrade' => null,
'submissionid' => 99,
'dimgrades' => array(
1 => 0,
2 => 0,
3 => 0,
),
),
// The second assessment has weight 1 and assessments was Yes, Yes, Yes.
20 => (object)array(
'assessmentid' => 20,
'weight' => 1,
'reviewerid' => 76,
'gradinggrade' => null,
'submissionid' => 99,
'dimgrades' => array(
1 => 1,
2 => 1,
3 => 1,
),
),
// The third assessment has weight 1 and assessments was Yes, Yes, Yes too.
30 => (object)array(
'assessmentid' => 30,
'weight' => 1,
'reviewerid' => 97,
'gradinggrade' => null,
'submissionid' => 99,
'dimgrades' => array(
1 => 1,
2 => 1,
3 => 1,
),
),
);
// Process assessments in the same way as in the {@link workshop_best_evaluation::process_assessments()}
$assessments = $this->evaluator->normalize_grades($assessments, $diminfo);
$average = $this->evaluator->average_assessment($assessments);
$variances = $this->evaluator->weighted_variance($assessments);
foreach ($variances as $dimid => $variance) {
$diminfo[$dimid]->variance = $variance;
}
// Simulate the chosen comparison of assessments "fair" (does not really matter here but we need something).
$settings = (object)array('comparison' => 5);
// Exercise SUT: for every assessment, calculate its distance from the average one.
$distances = array();
foreach ($assessments as $asid => $assessment) {
$distances[$asid] = $this->evaluator->assessments_distance($assessment, $average, $diminfo, $settings);
}
// Validate: the first assessment is far far away from the average one ...
$this->assertTrue($distances[10] > 0);
// ... while the two others were both picked as the referential ones.
$this->assertTrue($distances[20] == 0);
$this->assertTrue($distances[30] == 0);
}
}
/**
* Test subclass that makes all the protected methods we want to test public.
*/
class testable_workshop_best_evaluation extends workshop_best_evaluation {
public function normalize_grades(array $assessments, array $diminfo) {
return parent::normalize_grades($assessments, $diminfo);
}
public function average_assessment(array $assessments) {
return parent::average_assessment($assessments);
}
public function weighted_variance(array $assessments) {
return parent::weighted_variance($assessments);
}
public function assessments_distance(\stdClass $assessment, \stdClass $referential, array $diminfo, \stdClass $settings) {
return parent::assessments_distance($assessment, $referential, $diminfo, $settings);
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines the version of the subplugin
*
* @package workshopeval
* @subpackage best
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200;
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'workshopeval_best';
+101
View File
@@ -0,0 +1,101 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file defines interface of all grading evaluation classes
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/lib/formslib.php');
/**
* Base class for all grading evaluation subplugins.
*/
abstract class workshop_evaluation {
/** @var workshop the parent workshop instance */
protected $workshop;
/**
* Calculates grades for assessment and updates 'gradinggrade' fields in 'workshop_assessments' table
*
* @param stdClass $settings settings for this round of evaluation
* @param null|int|array $restrict if null, update all reviewers, otherwise update just grades for the given reviewers(s)
*/
abstract public function update_grading_grades(stdClass $settings, $restrict=null);
/**
* Returns an instance of the form to provide evaluation settings.
*
* This is called by view.php (to display) and aggregate.php (to process and dispatch).
* It returns the basic form with just the submit button by default. Evaluators may
* extend or overwrite the default form to include some custom settings.
*
* @return workshop_evaluation_settings_form
*/
public function get_settings_form(moodle_url $actionurl=null) {
$customdata = array('workshop' => $this->workshop);
$attributes = array('class' => 'evalsettingsform');
return new workshop_evaluation_settings_form($actionurl, $customdata, 'post', '', $attributes);
}
/**
* Delete all data related to a given workshop module instance
*
* This is called from {@link workshop_delete_instance()}.
*
* @param int $workshopid id of the workshop module instance being deleted
* @return void
*/
public static function delete_instance($workshopid) {
}
}
/**
* Base form to hold eventual evaluation settings.
*/
class workshop_evaluation_settings_form extends moodleform {
/**
* Defines the common form fields.
*/
public function definition() {
$mform = $this->_form;
$workshop = $this->_customdata['workshop'];
$mform->addElement('header', 'general', get_string('evaluationsettings', 'mod_workshop'));
$this->definition_sub();
$mform->addElement('submit', 'submit', get_string('aggregategrades', 'workshop'));
}
/**
* Defines the subplugin specific fields.
*/
protected function definition_sub() {
}
}
+190
View File
@@ -0,0 +1,190 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Assess an example submission
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$asid = required_param('asid', PARAM_INT); // assessment id
$assessment = $DB->get_record('workshop_assessments', array('id' => $asid), '*', MUST_EXIST);
$example = $DB->get_record('workshop_submissions', array('id' => $assessment->submissionid, 'example' => 1), '*', MUST_EXIST);
$workshop = $DB->get_record('workshop', array('id' => $example->workshopid), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id' => $workshop->course), '*', MUST_EXIST);
$cm = get_coursemodule_from_instance('workshop', $workshop->id, $course->id, false, MUST_EXIST);
require_login($course, false, $cm);
if (isguestuser()) {
throw new \moodle_exception('guestsarenotallowed');
}
$workshop = new workshop($workshop, $cm, $course);
$PAGE->set_url($workshop->exassess_url($assessment->id));
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add(get_string('assessingexample', 'workshop'));
$PAGE->set_secondary_active_tab('modulepage');
$currenttab = 'assessment';
$canmanage = has_capability('mod/workshop:manageexamples', $workshop->context);
$isreviewer = ($USER->id == $assessment->reviewerid);
if ($isreviewer or $canmanage) {
// such a user can continue
} else {
throw new \moodle_exception('nopermissions', 'error', $workshop->view_url(), 'assess example submission');
}
// only the reviewer is allowed to modify the assessment
if (($canmanage and $assessment->weight == 1) or ($isreviewer and $workshop->assessing_examples_allowed())) {
$assessmenteditable = true;
} else {
$assessmenteditable = false;
}
// load the grading strategy logic
$strategy = $workshop->grading_strategy_instance();
// load the assessment form and process the submitted data eventually
$mform = $strategy->get_assessment_form($PAGE->url, 'assessment', $assessment, $assessmenteditable);
// Set data managed by the workshop core, subplugins set their own data themselves.
$currentdata = (object)array(
'feedbackauthor' => $assessment->feedbackauthor,
'feedbackauthorformat' => $assessment->feedbackauthorformat,
);
if ($assessmenteditable and $workshop->overallfeedbackmode) {
$currentdata = file_prepare_standard_editor($currentdata, 'feedbackauthor', $workshop->overall_feedback_content_options(),
$workshop->context, 'mod_workshop', 'overallfeedback_content', $assessment->id);
if ($workshop->overallfeedbackfiles) {
$currentdata = file_prepare_standard_filemanager($currentdata, 'feedbackauthorattachment',
$workshop->overall_feedback_attachment_options(), $workshop->context, 'mod_workshop', 'overallfeedback_attachment',
$assessment->id);
}
}
$mform->set_data($currentdata);
if ($mform->is_cancelled()) {
redirect($workshop->view_url());
} elseif ($assessmenteditable and ($data = $mform->get_data())) {
// Let the grading strategy subplugin save its data.
$rawgrade = $strategy->save_assessment($assessment, $data);
// Store the data managed by the workshop core.
$coredata = (object)array('id' => $assessment->id);
if (isset($data->feedbackauthor_editor)) {
$coredata->feedbackauthor_editor = $data->feedbackauthor_editor;
$coredata = file_postupdate_standard_editor($coredata, 'feedbackauthor', $workshop->overall_feedback_content_options(),
$workshop->context, 'mod_workshop', 'overallfeedback_content', $assessment->id);
unset($coredata->feedbackauthor_editor);
}
if (isset($data->feedbackauthorattachment_filemanager)) {
$coredata->feedbackauthorattachment_filemanager = $data->feedbackauthorattachment_filemanager;
$coredata = file_postupdate_standard_filemanager($coredata, 'feedbackauthorattachment',
$workshop->overall_feedback_attachment_options(), $workshop->context, 'mod_workshop', 'overallfeedback_attachment',
$assessment->id);
unset($coredata->feedbackauthorattachment_filemanager);
if (empty($coredata->feedbackauthorattachment)) {
$coredata->feedbackauthorattachment = 0;
}
}
if ($canmanage) {
// Remember the last one who edited the reference assessment.
$coredata->reviewerid = $USER->id;
}
// Update the assessment data if there is something other than just the 'id'.
if (count((array)$coredata) > 1 ) {
$DB->update_record('workshop_assessments', $coredata);
}
if (!is_null($rawgrade) and isset($data->saveandclose)) {
if ($canmanage) {
redirect($workshop->view_url());
} else {
redirect($workshop->excompare_url($example->id, $assessment->id));
}
} else {
// either it is not possible to calculate the $rawgrade
// or the reviewer has chosen "Save and continue"
redirect($PAGE->url);
}
}
// output starts here
$output = $PAGE->get_renderer('mod_workshop'); // workshop renderer
echo $output->header();
if (!$PAGE->has_secondary_navigation()) {
echo $output->heading(format_string($workshop->name));
}
echo $output->heading(get_string('assessedexample', 'workshop'), 3);
$example = $workshop->get_example_by_id($example->id); // reload so can be passed to the renderer
echo $output->render($workshop->prepare_example_submission(($example)));
// show instructions for assessing as thay may contain important information
// for evaluating the assessment
if (trim($workshop->instructreviewers)) {
$instructions = file_rewrite_pluginfile_urls($workshop->instructreviewers, 'pluginfile.php', $PAGE->context->id,
'mod_workshop', 'instructreviewers', null, workshop::instruction_editors_options($PAGE->context));
print_collapsible_region_start('', 'workshop-viewlet-instructreviewers', get_string('instructreviewers', 'workshop'),
'workshop-viewlet-instructreviewers-collapsed');
echo $output->box(format_text($instructions, $workshop->instructreviewersformat, array('overflowdiv'=>true)), array('generalbox', 'instructions'));
print_collapsible_region_end();
}
// extend the current assessment record with user details
$assessment = $workshop->get_assessment_by_id($assessment->id);
if ($canmanage and $assessment->weight == 1) {
$options = array(
'showreviewer' => false,
'showauthor' => false,
'showform' => true,
);
$assessment = $workshop->prepare_example_reference_assessment($assessment, $mform, $options);
$assessment->title = get_string('assessmentreference', 'workshop');
echo $output->render($assessment);
} else if ($isreviewer) {
$options = array(
'showreviewer' => true,
'showauthor' => false,
'showform' => true,
);
$assessment = $workshop->prepare_example_assessment($assessment, $mform, $options);
$assessment->title = get_string('assessmentbyyourself', 'workshop');
echo $output->render($assessment);
} else if ($canmanage) {
$options = array(
'showreviewer' => true,
'showauthor' => false,
'showform' => true,
'showweight' => false,
);
$assessment = $workshop->prepare_example_assessment($assessment, $mform, $options);
echo $output->render($assessment);
}
echo $output->footer();
+125
View File
@@ -0,0 +1,125 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Display example submission followed by its reference assessment and the user's assessment to compare them
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$cmid = required_param('cmid', PARAM_INT); // course module id
$sid = required_param('sid', PARAM_INT); // example submission id
$aid = required_param('aid', PARAM_INT); // the user's assessment id
$cm = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
require_login($course, false, $cm);
if (isguestuser()) {
throw new \moodle_exception('guestsarenotallowed');
}
$workshop = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
$strategy = $workshop->grading_strategy_instance();
$PAGE->set_url($workshop->excompare_url($sid, $aid));
$example = $workshop->get_example_by_id($sid);
$assessment = $workshop->get_assessment_by_id($aid);
if ($assessment->submissionid != $example->id) {
throw new \moodle_exception('invalidarguments');
}
$mformassessment = $strategy->get_assessment_form($PAGE->url, 'assessment', $assessment, false);
if ($refasid = $DB->get_field('workshop_assessments', 'id', array('submissionid' => $example->id, 'weight' => 1))) {
$reference = $workshop->get_assessment_by_id($refasid);
$mformreference = $strategy->get_assessment_form($PAGE->url, 'assessment', $reference, false);
}
$canmanage = has_capability('mod/workshop:manageexamples', $workshop->context);
$isreviewer = ($USER->id == $assessment->reviewerid);
if ($canmanage) {
// ok you can go
} elseif ($isreviewer and $workshop->assessing_examples_allowed()) {
// ok you can go
} else {
throw new \moodle_exception('nopermissions', 'error', $workshop->view_url(), 'compare example assessment');
}
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->navbar->add(get_string('examplecomparing', 'workshop'));
// Output starts here
$output = $PAGE->get_renderer('mod_workshop');
echo $output->header();
// Output the back button.
echo $output->single_button($workshop->view_url(), get_string('back'), 'get', ['class' => 'mb-3']);
if (!$PAGE->has_secondary_navigation()) {
echo $output->heading(format_string($workshop->name));
}
echo $output->heading(get_string('assessedexample', 'workshop'), 3);
echo $output->render($workshop->prepare_example_submission($example));
// if the reference assessment is available, display it
if (!empty($mformreference)) {
$options = array(
'showreviewer' => false,
'showauthor' => false,
'showform' => true,
);
$reference = $workshop->prepare_example_reference_assessment($reference, $mformreference, $options);
$reference->title = get_string('assessmentreference', 'workshop');
if ($canmanage) {
$reference->url = $workshop->exassess_url($reference->id);
}
echo $output->render($reference);
}
if ($isreviewer) {
$options = array(
'showreviewer' => true,
'showauthor' => false,
'showform' => true,
);
$assessment = $workshop->prepare_example_assessment($assessment, $mformassessment, $options);
$assessment->title = get_string('assessmentbyyourself', 'workshop');
if ($workshop->assessing_examples_allowed()) {
$assessment->add_action(
new moodle_url($workshop->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey())),
get_string('reassess', 'workshop')
);
}
echo $output->render($assessment);
} elseif ($canmanage) {
$options = array(
'showreviewer' => true,
'showauthor' => false,
'showform' => true,
);
$assessment = $workshop->prepare_example_assessment($assessment, $mformassessment, $options);
echo $output->render($assessment);
}
echo $output->footer();
+233
View File
@@ -0,0 +1,233 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* View, create or edit single example submission
*
* @package mod_workshop
* @copyright 2009 David Mudrak <david.mudrak@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__.'/../../config.php');
require_once(__DIR__.'/locallib.php');
$cmid = required_param('cmid', PARAM_INT); // course module id
$id = required_param('id', PARAM_INT); // example submission id, 0 for the new one
$edit = optional_param('edit', false, PARAM_BOOL); // open for editing?
$delete = optional_param('delete', false, PARAM_BOOL); // example removal requested
$confirm = optional_param('confirm', false, PARAM_BOOL); // example removal request confirmed
$assess = optional_param('assess', false, PARAM_BOOL); // assessment required
$cm = get_coursemodule_from_id('workshop', $cmid, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
require_login($course, false, $cm);
if (isguestuser()) {
throw new \moodle_exception('guestsarenotallowed');
}
$workshop = $DB->get_record('workshop', array('id' => $cm->instance), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
$PAGE->set_url($workshop->exsubmission_url($id), array('edit' => $edit));
$PAGE->set_title($workshop->name);
$PAGE->set_heading($course->fullname);
$PAGE->set_secondary_active_tab('modulepage');
if ($edit) {
$PAGE->navbar->add(get_string('exampleediting', 'workshop'));
} else {
$PAGE->navbar->add(get_string('example', 'workshop'));
}
$output = $PAGE->get_renderer('mod_workshop');
if ($id) { // example is specified
$example = $workshop->get_example_by_id($id);
} else { // no example specified - create new one
require_capability('mod/workshop:manageexamples', $workshop->context);
$example = new stdclass();
$example->id = null;
$example->authorid = $USER->id;
$example->example = 1;
}
$canmanage = has_capability('mod/workshop:manageexamples', $workshop->context);
$canassess = has_capability('mod/workshop:peerassess', $workshop->context);
$refasid = $DB->get_field('workshop_assessments', 'id', array('submissionid' => $example->id, 'weight' => 1));
if ($example->id and ($canmanage or ($workshop->assessing_examples_allowed() and $canassess))) {
// ok you can go
} elseif (is_null($example->id) and $canmanage) {
// ok you can go
} else {
throw new \moodle_exception('nopermissions', 'error', $workshop->view_url(), 'view or manage example submission');
}
if ($id and $delete and $confirm and $canmanage) {
require_sesskey();
$workshop->delete_submission($example);
redirect($workshop->view_url());
}
if ($id and $assess and $canmanage) {
// reference assessment of an example is the assessment with the weight = 1. There should be just one
// such assessment
require_sesskey();
if (!$refasid) {
$refasid = $workshop->add_allocation($example, $USER->id, 1);
}
redirect($workshop->exassess_url($refasid));
}
if ($id and $assess and $canassess) {
// training assessment of an example is the assessment with the weight = 0
require_sesskey();
$asid = $DB->get_field('workshop_assessments', 'id',
array('submissionid' => $example->id, 'weight' => 0, 'reviewerid' => $USER->id));
if (!$asid) {
$asid = $workshop->add_allocation($example, $USER->id, 0);
}
if ($asid == workshop::ALLOCATION_EXISTS) {
// the training assessment of the example was not found but the allocation already
// exists. this probably means that the user is the author of the reference assessment.
echo $output->header();
echo $output->box(get_string('assessmentreferenceconflict', 'workshop'));
echo $output->continue_button($workshop->view_url());
echo $output->footer();
die();
}
redirect($workshop->exassess_url($asid));
}
if ($edit and $canmanage) {
require_once(__DIR__.'/submission_form.php');
$example = file_prepare_standard_editor($example, 'content', $workshop->submission_content_options(),
$workshop->context, 'mod_workshop', 'submission_content', $example->id);
$example = file_prepare_standard_filemanager($example, 'attachment', $workshop->submission_attachment_options(),
$workshop->context, 'mod_workshop', 'submission_attachment', $example->id);
$mform = new workshop_submission_form($PAGE->url, array('current' => $example, 'workshop' => $workshop,
'contentopts' => $workshop->submission_content_options(), 'attachmentopts' => $workshop->submission_attachment_options()));
if ($mform->is_cancelled()) {
redirect($workshop->view_url());
} elseif ($canmanage and $formdata = $mform->get_data()) {
if ($formdata->example == 1) {
// this was used just for validation, it must be set to one when dealing with example submissions
unset($formdata->example);
} else {
throw new coding_exception('Invalid submission form data value: example');
}
$timenow = time();
if (is_null($example->id)) {
$formdata->workshopid = $workshop->id;
$formdata->example = 1;
$formdata->authorid = $USER->id;
$formdata->timecreated = $timenow;
$formdata->feedbackauthorformat = editors_get_preferred_format();
}
$formdata->timemodified = $timenow;
$formdata->title = trim($formdata->title);
$formdata->content = ''; // updated later
$formdata->contentformat = FORMAT_HTML; // updated later
$formdata->contenttrust = 0; // updated later
if (is_null($example->id)) {
$example->id = $formdata->id = $DB->insert_record('workshop_submissions', $formdata);
} else {
if (empty($formdata->id) or empty($example->id) or ($formdata->id != $example->id)) {
throw new moodle_exception('err_examplesubmissionid', 'workshop');
}
}
// Save and relink embedded images and save attachments.
// To be used when Online text is allowed as a submission type.
if (!empty($formdata->content_editor)) {
$formdata = file_postupdate_standard_editor($formdata, 'content', $workshop->submission_content_options(),
$workshop->context, 'mod_workshop', 'submission_content', $example->id);
$formdata = file_postupdate_standard_filemanager($formdata, 'attachment', $workshop->submission_attachment_options(),
$workshop->context, 'mod_workshop', 'submission_attachment', $example->id);
}
if (empty($formdata->attachment)) {
// explicit cast to zero integer
$formdata->attachment = 0;
}
// store the updated values or re-save the new example (re-saving needed because URLs are now rewritten)
$DB->update_record('workshop_submissions', $formdata);
redirect($workshop->exsubmission_url($formdata->id));
}
}
// Output starts here
echo $output->header();
if (!$PAGE->has_secondary_navigation()) {
echo $output->heading(format_string($workshop->name), 2);
}
// show instructions for submitting as they may contain some list of questions and we need to know them
// while reading the submitted answer
if (trim($workshop->instructauthors)) {
$instructions = file_rewrite_pluginfile_urls($workshop->instructauthors, 'pluginfile.php', $PAGE->context->id,
'mod_workshop', 'instructauthors', null, workshop::instruction_editors_options($PAGE->context));
print_collapsible_region_start('', 'workshop-viewlet-instructauthors', get_string('instructauthors', 'workshop'),
'workshop-viewlet-instructauthors-collapsed');
echo $output->box(format_text($instructions, $workshop->instructauthorsformat, array('overflowdiv'=>true)), array('generalbox', 'instructions'));
print_collapsible_region_end();
}
// if in edit mode, display the form to edit the example
if ($edit and $canmanage) {
$mform->display();
echo $output->footer();
die();
}
// else display the example...
if ($example->id) {
if ($canmanage and $delete) {
echo $output->confirm(get_string('exampledeleteconfirm', 'workshop'),
new moodle_url($PAGE->url, array('delete' => 1, 'confirm' => 1)), $workshop->view_url());
}
if ($canmanage and !$delete and !$DB->record_exists_select('workshop_assessments',
'grade IS NOT NULL AND weight=1 AND submissionid = ?', array($example->id))) {
echo $output->confirm(get_string('assessmentreferenceneeded', 'workshop'),
new moodle_url($PAGE->url, array('assess' => 1)), $workshop->view_url());
}
echo $output->render($workshop->prepare_example_submission($example));
}
// ...with an option to edit or remove it
echo $output->container_start('buttonsbar');
if ($canmanage) {
if (empty($edit) and empty($delete)) {
$aurl = new moodle_url($workshop->exsubmission_url($example->id), array('edit' => 'on'));
echo $output->single_button($aurl, get_string('exampleedit', 'workshop'), 'get');
$aurl = new moodle_url($workshop->exsubmission_url($example->id), array('delete' => 'on'));
echo $output->single_button($aurl, get_string('exampledelete', 'workshop'), 'get');
}
}
// ...and optionally assess it
if ($canassess or ($canmanage and empty($edit) and empty($delete))) {
$aurl = new moodle_url($workshop->exsubmission_url($example->id), array('assess' => 'on', 'sesskey' => sesskey()));
echo $output->single_button($aurl, get_string('exampleassess', 'workshop'), 'get');
}
echo $output->container_end(); // buttonsbar
// and possibly display the example's review(s) - todo
echo $output->footer();

Some files were not shown because too many files have changed in this diff Show More