Merge branch 'MDL-26099-workshop-phase-switch' of git://github.com/mudrd8mz/moodle

This commit is contained in:
Sam Hemelryk 2012-04-16 15:00:20 +12:00
commit e3d34313ab
21 changed files with 1133 additions and 274 deletions

View file

@ -516,7 +516,7 @@ class plugin_manager {
),
'workshopallocation' => array(
'manual', 'random'
'manual', 'random', 'scheduled'
),
'workshopeval' => array(

View file

@ -74,13 +74,14 @@ if (!empty($allocators)) {
$tabs[] = $row;
print_tabs($tabs, $currenttab, $inactive, $activated);
if (!empty($initresult)) {
echo $output->container_start('allocator-init-results');
echo $output->render(new workshop_allocation_init_result($initresult, $workshop->allocation_url($method)));
echo $output->container_end();
} else {
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();

View file

@ -43,11 +43,10 @@ interface workshop_allocator {
*
* 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. The returned value is processed by
* {@see workshop_allocation_init_result} class and rendered.
* It must not produce any output.
*
* @throws moodle_exception
* @return void|string
* @return workshop_allocation_result
*/
public function init();
@ -71,3 +70,114 @@ interface workshop_allocator {
*/
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;
}
}

View file

@ -54,12 +54,16 @@ class workshop_manual_allocator implements workshop_allocator {
/**
* Allocate submissions as requested by user
*
* @return workshop_allocation_result
*/
public function init() {
global $PAGE;
$mode = optional_param('mode', 'display', PARAM_ALPHA);
$result = new workshop_allocation_result($this);
switch ($mode) {
case 'new':
if (!confirm_sesskey()) {
@ -124,6 +128,9 @@ class workshop_manual_allocator implements workshop_allocator {
}
break;
}
$result->set_status(workshop_allocation_result::STATUS_VOID);
return $result;
}
/**

View file

@ -18,8 +18,8 @@
/**
* Allocates the submissions randomly
*
* @package workshopallocation
* @subpackage random
* @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
*/
@ -40,10 +40,6 @@ class workshop_random_allocator implements workshop_allocator {
/** constants used to pass status messages between init() and ui() */
const MSG_SUCCESS = 1;
/** constants used in allocation settings form */
const USERTYPE_AUTHOR = 1;
const USERTYPE_REVIEWER = 2;
/** workshop instance */
protected $workshop;
@ -59,130 +55,134 @@ class workshop_random_allocator implements workshop_allocator {
/**
* 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($PAGE->url->out(false));
redirect($this->workshop->view_url());
} else if ($settings = $this->mform->get_data()) {
// process validated data
if (!confirm_sesskey()) {
throw new moodle_exception('confirmsesskeybad');
}
$o = array(); // list of output messages
$numofreviews = required_param('numofreviews', PARAM_INT);
$numper = required_param('numper', PARAM_INT);
$excludesamegroup = optional_param('excludesamegroup', false, PARAM_BOOL);
$removecurrent = optional_param('removecurrent', false, PARAM_BOOL);
$assesswosubmission = optional_param('assesswosubmission', false, PARAM_BOOL);
$addselfassessment = optional_param('addselfassessment', false, PARAM_BOOL);
$musthavesubmission = empty($assesswosubmission);
$authors = $this->workshop->get_potential_authors();
$authors = $this->workshop->get_grouped($authors);
$reviewers = $this->workshop->get_potential_reviewers($musthavesubmission);
$reviewers = $this->workshop->get_grouped($reviewers);
$assessments = $this->workshop->get_all_assessments();
$newallocations = array(); // array of array(reviewer => reviewee)
if ($numofreviews) {
if ($removecurrent) {
// behave as if there were no current assessments
$curassessments = array();
} else {
$curassessments = $assessments;
}
$options = array();
$options['numofreviews'] = $numofreviews;
$options['numper'] = $numper;
$options['excludesamegroup'] = $excludesamegroup;
$randomallocations = $this->random_allocation($authors, $reviewers, $curassessments, $o, $options);
$newallocations = array_merge($newallocations, $randomallocations);
$o[] = 'ok::' . get_string('numofrandomlyallocatedsubmissions', 'workshopallocation_random', count($randomallocations));
unset($randomallocations);
}
if ($addselfassessment) {
$selfallocations = $this->self_allocation($authors, $reviewers, $assessments);
$newallocations = array_merge($newallocations, $selfallocations);
$o[] = 'ok::' . get_string('numofselfallocatedsubmissions', 'workshopallocation_random', count($selfallocations));
unset($selfallocations);
}
if (empty($newallocations)) {
$o[] = 'info::' . get_string('noallocationtoadd', 'workshopallocation_random');
} 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) {
list($reviewerid, $authorid) = each($newallocation);
$a = new stdClass();
if (isset($allreviewers[$reviewerid])) {
$a->reviewername = fullname($allreviewers[$reviewerid]);
} else {
// this may happen if $musthavesubmission is true 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)) {
$o[] = 'ok::indent::' . get_string('allocationaddeddetail', 'workshopallocation_random', $a);
} else {
$o[] = 'ok::indent::' . get_string('allocationreuseddetail', 'workshopallocation_random', $a);
}
}
}
if ($removecurrent) {
$delassessments = $this->get_unkept_assessments($assessments, $newallocations, $addselfassessment);
// random allocator should not be able to delete assessments that have already been graded
// by reviewer
$o[] = 'info::' . get_string('numofdeallocatedassessment', 'workshopallocation_random', count($delassessments));
foreach ($delassessments as $delassessmentkey => $delassessmentid) {
$a = new stdclass();
$a->authorname = fullname((object)array(
'lastname' => $assessments[$delassessmentid]->authorlastname,
'firstname' => $assessments[$delassessmentid]->authorfirstname));
$a->reviewername = fullname((object)array(
'lastname' => $assessments[$delassessmentid]->reviewerlastname,
'firstname' => $assessments[$delassessmentid]->reviewerfirstname));
if (!is_null($assessments[$delassessmentid]->grade)) {
$o[] = 'error::indent::' . get_string('allocationdeallocategraded', 'workshopallocation_random', $a);
unset($delassessments[$delassessmentkey]);
} else {
$o[] = 'info::indent::' . get_string('assessmentdeleteddetail', 'workshopallocation_random', $a);
}
}
$this->workshop->delete_assessment($delassessments);
}
return $o;
$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) {
list($reviewerid, $authorid) = each($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) {
$a = new stdclass();
$a->authorname = fullname((object)array(
'lastname' => $assessments[$delassessmentid]->authorlastname,
'firstname' => $assessments[$delassessmentid]->authorfirstname));
$a->reviewername = fullname((object)array(
'lastname' => $assessments[$delassessmentid]->reviewerlastname,
'firstname' => $assessments[$delassessmentid]->reviewerfirstname));
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
*/
@ -434,11 +434,11 @@ class workshop_random_allocator implements workshop_allocator {
* @param array $authors structure of grouped authors
* @param array $reviewers structure of grouped reviewers
* @param array $assessments currently assigned assessments to be kept
* @param array $o reference to an array of log messages
* @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, &$o, array $options) {
protected function random_allocation($authors, $reviewers, $assessments, $result, array $options) {
if (empty($authors) || empty($reviewers)) {
// nothing to be done
return array();
@ -447,16 +447,16 @@ class workshop_random_allocator implements workshop_allocator {
$numofreviews = $options['numofreviews'];
$numper = $options['numper'];
if (self::USERTYPE_AUTHOR == $numper) {
if (workshop_random_allocator_setting::NUMPER_SUBMISSION == $numper) {
// circles are authors, squares are reviewers
$o[] = 'info::'.get_string('resultnumperauthor', 'workshopallocation_random', $numofreviews);
$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 (self::USERTYPE_REVIEWER == $numper) {
} elseif (workshop_random_allocator_setting::NUMPER_REVIEWER == $numper) {
// circles are reviewers, squares are authors
$o[] = 'info::'.get_string('resultnumperreviewer', 'workshopallocation_random', $numofreviews);
$result->log(get_string('resultnumperreviewer', 'workshopallocation_random', $numofreviews), 'info');
$allcircles = $reviewers;
$allsquares = $authors;
// get current workload
@ -480,8 +480,8 @@ class workshop_random_allocator implements workshop_allocator {
unset($nogroupcircles[$circleid]);
}
}
// $o[] = 'debug::circle links = ' . json_encode($circlelinks);
// $o[] = 'debug::square links = ' . json_encode($squarelinks);
// $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) {
@ -496,8 +496,8 @@ class workshop_random_allocator implements workshop_allocator {
$squaregroupsworkload[$squaregroupid] /= count($squares);
}
unset($squaregroupsworkload[0]); // [0] is not real group, it contains all users
// $o[] = 'debug::square workload = ' . json_encode($squareworkload);
// $o[] = 'debug::square group workload = ' . json_encode($squaregroupsworkload);
// $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"
@ -507,9 +507,9 @@ class workshop_random_allocator implements workshop_allocator {
// all users will be processed at once
$circlegroups = array(0);
}
// $o[] = 'debug::circle groups = ' . json_encode($circlegroups);
// $result->log('circle groups = ' . json_encode($circlegroups), 'debug');
foreach ($circlegroups as $circlegroupid) {
$o[] = 'debug::processing circle group id ' . $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
@ -517,13 +517,13 @@ class workshop_random_allocator implements workshop_allocator {
// second iteration, we try to allocate two, etc.
for ($requiredreviews = 1; $requiredreviews <= $numofreviews; $requiredreviews++) {
$this->shuffle_assoc($circles);
$o[] = 'debug::iteration ' . $requiredreviews;
$result->log('iteration ' . $requiredreviews, 'debug');
foreach ($circles as $circleid => $circle) {
if (VISIBLEGROUPS == $gmode and isset($nogroupcircles[$circleid])) {
$o[] = 'debug::skipping circle id ' . $circleid;
$result->log('skipping circle id ' . $circleid, 'debug');
continue;
}
$o[] = 'debug::processing circle id ' . $circleid;
$result->log('processing circle id ' . $circleid, 'debug');
if (!isset($circlelinks[$circleid])) {
$circlelinks[$circleid] = array();
}
@ -536,14 +536,14 @@ class workshop_random_allocator implements workshop_allocator {
if (NOGROUPS == $gmode) {
if (in_array(0, $failedgroups)) {
$keeptrying = false;
$o[] = 'error::indent::'.get_string('resultnomorepeers', 'workshopallocation_random');
$result->log(get_string('resultnomorepeers', 'workshopallocation_random'), 'error', 1);
break;
}
$targetgroup = 0;
} elseif (SEPARATEGROUPS == $gmode) {
if (in_array($circlegroupid, $failedgroups)) {
$keeptrying = false;
$o[] = 'error::indent::'.get_string('resultnomorepeersingroup', 'workshopallocation_random');
$result->log(get_string('resultnomorepeersingroup', 'workshopallocation_random'), 'error', 1);
break;
}
$targetgroup = $circlegroupid;
@ -564,27 +564,27 @@ class workshop_random_allocator implements workshop_allocator {
}
if ($targetgroup === false) {
$keeptrying = false;
$o[] = 'error::indent::'.get_string('resultnotenoughpeers', 'workshopallocation_random');
$result->log(get_string('resultnotenoughpeers', 'workshopallocation_random'), 'error', 1);
break;
}
$o[] = 'debug::indent::next square should be from group id ' . $targetgroup;
$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]);
// $o[] = 'debug::indent::individual workloads in this group are ' . json_encode($trysquares);
// $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) {
$o[] = 'debug::indent::unable to find an available square. trying another group';
$result->log('unable to find an available square. trying another group', 'debug', 1);
$failedgroups[] = $targetgroup;
continue;
}
$o[] = 'debug::indent::target square = ' . $targetsquare;
$result->log('target square = ' . $targetsquare, 'debug', 1);
// ok - we have found the square
$circlelinks[$circleid][] = $targetsquare;
$squarelinks[$targetsquare][] = $circleid;
$squareworkload[$targetsquare]++;
$o[] = 'debug::indent::increasing square workload to ' . $squareworkload[$targetsquare];
$result->log('increasing square workload to ' . $squareworkload[$targetsquare], 'debug', 1);
if ($targetgroup) {
// recalculate the group workload
$squaregroupsworkload[$targetgroup] = 0;
@ -592,14 +592,14 @@ class workshop_random_allocator implements workshop_allocator {
$squaregroupsworkload[$targetgroup] += $squareworkload[$squareid];
}
$squaregroupsworkload[$targetgroup] /= count($allsquares[$targetgroup]);
$o[] = 'debug::indent::increasing group workload to ' . $squaregroupsworkload[$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 (self::USERTYPE_AUTHOR == $numper) {
if (workshop_random_allocator_setting::NUMPER_SUBMISSION == $numper) {
// circles are authors, squares are reviewers
foreach ($circlelinks as $circleid => $squares) {
foreach ($squares as $squareid) {
@ -607,7 +607,7 @@ class workshop_random_allocator implements workshop_allocator {
}
}
}
if (self::USERTYPE_REVIEWER == $numper) {
if (workshop_random_allocator_setting::NUMPER_REVIEWER == $numper) {
// circles are reviewers, squares are authors
foreach ($circlelinks as $circleid => $squares) {
foreach ($squares as $squareid) {
@ -699,3 +699,92 @@ class workshop_random_allocator implements workshop_allocator {
}
}
}
/**
* 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;
/**
* 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));
}
}

View file

@ -46,7 +46,7 @@ class workshop_random_allocator_form extends moodleform {
$workshop = $this->_customdata['workshop'];
$plugindefaults = get_config('workshopallocation_random');
$mform->addElement('header', 'settings', get_string('allocationsettings', 'workshopallocation_random'));
$mform->addElement('header', 'randomallocationsettings', get_string('allocationsettings', 'workshopallocation_random'));
$gmode = groups_get_activity_groupmode($workshop->cm, $workshop->course);
switch ($gmode) {
@ -63,15 +63,15 @@ class workshop_random_allocator_form extends moodleform {
$mform->addElement('static', 'groupmode', get_string('groupmode', 'group'), $grouplabel);
$options_numper = array(
workshop_random_allocator::USERTYPE_AUTHOR => get_string('numperauthor', 'workshopallocation_random'),
workshop_random_allocator::USERTYPE_REVIEWER => get_string('numperreviewer', 'workshopallocation_random')
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('select', 'numofreviews', '',
workshop_random_allocator::available_numofreviews_list());
$mform->setDefault('numofreviews', $plugindefaults->numofreviews);
$grpnumofreviews[] = $mform->createElement('select', 'numper', '', $options_numper);
$mform->setDefault('numper', workshop_random_allocator::USERTYPE_AUTHOR);
$mform->setDefault('numper', workshop_random_allocator_setting::NUMPER_SUBMISSION);
$mform->addGroup($grpnumofreviews, 'grpnumofreviews', get_string('numofreviews', 'workshopallocation_random'),
array(' '), false);

View file

@ -17,14 +17,15 @@
/**
* Defines the version of the subplugin
*
* @package workshopallocation
* @subpackage random
* @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->version = 2010090501;
$plugin->requires = 2010090501; // Requires this Moodle version
$plugin->component = 'workshopallocation_random';
$plugin->component = 'workshopallocation_random';
$plugin->version = 2012032800;
$plugin->requires = 2012032300;
$plugin->maturity = MATURITY_STABLE;

View file

@ -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" NEXT="workshopid"/>
<FIELD NAME="workshopid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="workshop id we are part of" PREVIOUS="id" NEXT="enabled"/>
<FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Is the scheduled allocation enabled" PREVIOUS="workshopid" NEXT="submissionend"/>
<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" PREVIOUS="enabled" NEXT="timeallocated"/>
<FIELD NAME="timeallocated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="When was the last scheduled allocation executed" PREVIOUS="submissionend" NEXT="settings"/>
<FIELD NAME="settings" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The pre-defined settings for the underlying random allocation to run it with" PREVIOUS="timeallocated" NEXT="resultstatus"/>
<FIELD NAME="resultstatus" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The resulting status of the most recent execution" PREVIOUS="settings" NEXT="resultmessage"/>
<FIELD NAME="resultmessage" TYPE="char" LENGTH="1333" NOTNULL="false" SEQUENCE="false" COMMENT="Optional short message describing the resulting status" PREVIOUS="resultstatus" NEXT="resultlog"/>
<FIELD NAME="resultlog" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The log of the most recent execution" PREVIOUS="resultmessage"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="fkuq_workshopid"/>
<KEY NAME="fkuq_workshopid" TYPE="foreign-unique" FIELDS="workshopid" REFTABLE="workshop" REFFIELDS="id" COMMENT="Max one record for each workshop" PREVIOUS="primary"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

View file

@ -0,0 +1,62 @@
<?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['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['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';

View file

@ -0,0 +1,287 @@
<?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(dirname(dirname(__FILE__)) . '/lib.php'); // interface definition
require_once(dirname(dirname(dirname(__FILE__))) . '/locallib.php'); // workshop internal API
require_once(dirname(dirname(__FILE__)) . '/random/lib.php'); // random allocator
require_once(dirname(__FILE__) . '/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
*
* @return workshop_allocation_result
*/
public function execute() {
global $DB;
$result = new workshop_allocation_result($this);
// make sure the workshop itself is at the expected state
if ($this->workshop->phase != workshop::PHASE_SUBMISSION) {
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfailedphase', 'workshopallocation_scheduled'));
return $result;
}
if (empty($this->workshop->submissionend)) {
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfaileddeadline', 'workshopallocation_scheduled'));
return $result;
}
if ($this->workshop->submissionend > time()) {
$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) {
$result->set_status(workshop_allocation_result::STATUS_FAILED,
get_string('resultfailedconfig', 'workshopallocation_scheduled'));
return $result;
}
if (!$current->enabled) {
$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) {
$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);
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);
}
}
}
/**
* Regular jobs to execute via cron
*/
function workshopallocation_scheduled_cron() {
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) ... ', '');
// let's have some fun!
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', array('id' => $cm->course), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
$allocator = $workshop->allocator_instance('scheduled');
$result = $allocator->execute();
// todo inform the teachers about the results
}
}

View file

@ -0,0 +1,138 @@
<?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(dirname(dirname(__FILE__)) . '/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').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/block'))));
} else {
if (!empty($current->timeallocated)) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('currentstatusexecution1', 'workshopallocation_scheduled', $strtimeexecuted).' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/tick_green_big'))));
if ($current->resultstatus == workshop_allocation_result::STATUS_EXECUTED) {
$strstatus = get_string('resultexecuted', 'workshopallocation_scheduled').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/tick_green_big')));
} else if ($current->resultstatus == workshop_allocation_result::STATUS_FAILED) {
$strstatus = get_string('resultfailed', 'workshopallocation_scheduled').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/cross_red_big')));
} else {
$strstatus = get_string('resultvoid', 'workshopallocation_scheduled').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/cross_red_big')));
}
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).' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/tick_amber_big'))));
$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').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/block'))));
} else if ($workshop->phase != workshop::PHASE_SUBMISSION) {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('resultfailed', 'workshopallocation_scheduled').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/block'))).
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').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/block'))).
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').' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/tick_amber_big'))));
} else {
$mform->addElement('static', 'infostatus', get_string('currentstatusexecution', 'workshopallocation_scheduled'),
get_string('currentstatusexecution3', 'workshopallocation_scheduled', $strtimeexpected).' '.
html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('i/tick_amber_big'))));
}
}
parent::definition();
$mform->addHelpButton('randomallocationsettings', 'randomallocationsettings', 'workshopallocation_scheduled');
}
}

View file

@ -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/>.
/**
* 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 = 2012033000;
$plugin->requires = 2012032300;
$plugin->dependencies = array(
'workshopallocation_random' => 2012032800,
);
$plugin->maturity = MATURITY_ALPHA;
$plugin->cron = 60;

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/workshop/db" VERSION="20120122" COMMENT="XMLDB file for Moodle mod/workshop"
<XMLDB PATH="mod/workshop/db" VERSION="20120331" COMMENT="XMLDB file for Moodle mod/workshop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -9,11 +9,11 @@
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="course"/>
<FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the parent course" PREVIOUS="id" NEXT="name"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Name of the activity" PREVIOUS="course" NEXT="intro"/>
<FIELD NAME="intro" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="The introduction or description of the activity" PREVIOUS="name" NEXT="introformat"/>
<FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The introduction or description of the activity" PREVIOUS="name" NEXT="introformat"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The format of the intro field" PREVIOUS="intro" NEXT="instructauthors"/>
<FIELD NAME="instructauthors" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="Instructions for the submission phase" PREVIOUS="introformat" NEXT="instructauthorsformat"/>
<FIELD NAME="instructauthors" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Instructions for the submission phase" PREVIOUS="introformat" NEXT="instructauthorsformat"/>
<FIELD NAME="instructauthorsformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="instructauthors" NEXT="instructreviewers"/>
<FIELD NAME="instructreviewers" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="Instructions for the assessment phase" PREVIOUS="instructauthorsformat" NEXT="instructreviewersformat"/>
<FIELD NAME="instructreviewers" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Instructions for the assessment phase" PREVIOUS="instructauthorsformat" NEXT="instructreviewersformat"/>
<FIELD NAME="instructreviewersformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="instructreviewers" NEXT="timemodified"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The timestamp when the module was modified" PREVIOUS="instructreviewersformat" NEXT="phase"/>
<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)" PREVIOUS="timemodified" NEXT="useexamples"/>
@ -32,7 +32,8 @@
<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" PREVIOUS="examplesmode" NEXT="submissionend"/>
<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" PREVIOUS="submissionstart" NEXT="assessmentstart"/>
<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" PREVIOUS="submissionend" NEXT="assessmentend"/>
<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" PREVIOUS="assessmentstart"/>
<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" PREVIOUS="assessmentstart" NEXT="phaseswitchassessment"/>
<FIELD NAME="phaseswitchassessment" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Automatically switch to the assessment phase after the submissions deadline" PREVIOUS="assessmentend"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="course_fk"/>
@ -48,14 +49,14 @@
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp when the work was submitted for the first time" PREVIOUS="authorid" NEXT="timemodified"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Timestamp when the submission has been updated" PREVIOUS="timecreated" NEXT="title"/>
<FIELD NAME="title" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The submission title" PREVIOUS="timemodified" NEXT="content"/>
<FIELD NAME="content" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="Submission text" PREVIOUS="title" NEXT="contentformat"/>
<FIELD NAME="content" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Submission text" PREVIOUS="title" NEXT="contentformat"/>
<FIELD NAME="contentformat" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The format of submission text" PREVIOUS="content" NEXT="contenttrust"/>
<FIELD NAME="contenttrust" TYPE="int" LENGTH="3" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The trust mode of the data" PREVIOUS="contentformat" NEXT="attachment"/>
<FIELD NAME="attachment" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Used by File API file_postupdate_standard_filemanager" PREVIOUS="contenttrust" NEXT="grade"/>
<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." PREVIOUS="attachment" NEXT="gradeover"/>
<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." PREVIOUS="grade" NEXT="gradeoverby"/>
<FIELD NAME="gradeoverby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The id of the user who has overridden the grade for submission." PREVIOUS="gradeover" NEXT="feedbackauthor"/>
<FIELD NAME="feedbackauthor" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="Teacher comment/feedback for the author of the submission, for example describing the reasons for the grade overriding" PREVIOUS="gradeoverby" NEXT="feedbackauthorformat"/>
<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" PREVIOUS="gradeoverby" NEXT="feedbackauthorformat"/>
<FIELD NAME="feedbackauthorformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="feedbackauthor" NEXT="timegraded"/>
<FIELD NAME="timegraded" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The timestamp when grade or gradeover was recently modified" PREVIOUS="feedbackauthorformat" NEXT="published"/>
<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" PREVIOUS="timegraded" NEXT="late"/>
@ -80,9 +81,9 @@
<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." PREVIOUS="grade" NEXT="gradinggradeover"/>
<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." PREVIOUS="gradinggrade" NEXT="gradinggradeoverby"/>
<FIELD NAME="gradinggradeoverby" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The id of the user who has overridden the grade for submission." PREVIOUS="gradinggradeover" NEXT="feedbackauthor"/>
<FIELD NAME="feedbackauthor" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="The comment/feedback from the reviewer for the author." PREVIOUS="gradinggradeoverby" NEXT="feedbackauthorformat"/>
<FIELD NAME="feedbackauthor" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The comment/feedback from the reviewer for the author." PREVIOUS="gradinggradeoverby" NEXT="feedbackauthorformat"/>
<FIELD NAME="feedbackauthorformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="feedbackauthor" NEXT="feedbackreviewer"/>
<FIELD NAME="feedbackreviewer" TYPE="text" LENGTH="big" 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" PREVIOUS="feedbackauthorformat" NEXT="feedbackreviewerformat"/>
<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" PREVIOUS="feedbackauthorformat" NEXT="feedbackreviewerformat"/>
<FIELD NAME="feedbackreviewerformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" PREVIOUS="feedbackreviewer"/>
</FIELDS>
<KEYS>
@ -99,7 +100,7 @@
<FIELD NAME="strategy" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" PREVIOUS="assessmentid" NEXT="dimensionid"/>
<FIELD NAME="dimensionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="Foreign key. References dimension id in one of the grading strategy tables." PREVIOUS="strategy" NEXT="grade"/>
<FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="true" SEQUENCE="false" DECIMALS="5" COMMENT="Given grade in the referenced assessment dimension." PREVIOUS="dimensionid" NEXT="peercomment"/>
<FIELD NAME="peercomment" TYPE="text" LENGTH="big" NOTNULL="false" SEQUENCE="false" COMMENT="Reviewer's comment to the grade value." PREVIOUS="grade" NEXT="peercommentformat"/>
<FIELD NAME="peercomment" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Reviewer's comment to the grade value." PREVIOUS="grade" NEXT="peercommentformat"/>
<FIELD NAME="peercommentformat" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="The format of peercomment field" PREVIOUS="peercomment"/>
</FIELDS>
<KEYS>

View file

@ -18,8 +18,8 @@
/**
* Keeps track of upgrades to the workshop module
*
* @package mod
* @subpackage workshop
* @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
*/
@ -38,9 +38,17 @@ function xmldb_workshop_upgrade($oldversion) {
$dbman = $DB->get_manager();
// Moodle v2.2.0 release upgrade line
// Put any upgrade step following this
if ($oldversion < 2012033100) {
// add the field 'phaseswitchassessment' to the 'workshop' table
$table = new xmldb_table('workshop');
$field = new xmldb_field('phaseswitchassessment', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'assessmentend');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
upgrade_mod_savepoint(true, 2012033100, 'workshop');
}
return true;
}

View file

@ -32,6 +32,7 @@ $string['allocatedetails'] = 'expected: {$a->expected}<br />submitted: {$a->subm
$string['allocation'] = 'Submission allocation';
$string['allocationdone'] = 'Allocation done';
$string['allocationerror'] = 'Allocation error';
$string['allocationconfigured'] = 'Allocation configured';
$string['allsubmissions'] = 'All submissions';
$string['alreadygraded'] = 'Already graded';
$string['areainstructauthors'] = 'Instructions for submission';
@ -49,6 +50,7 @@ $string['assessmentbyfullname'] = 'Assessment by {$a}';
$string['assessmentbyyourself'] = 'Your assessment';
$string['assessmentdeleted'] = 'Assessment deallocated';
$string['assessmentend'] = 'Deadline for assessment';
$string['assessmentendbeforestart'] = 'Deadline for assessment can not be specified before the open for assessment date';
$string['assessmentenddatetime'] = 'Assessment deadline: {$a->daydatetime} ({$a->distanceday})';
$string['assessmentform'] = 'Assessment form';
$string['assessmentofsubmission'] = '<a href="{$a->assessmenturl}">Assessment</a> of <a href="{$a->submissionurl}">{$a->submissiontitle}</a>';
@ -173,6 +175,7 @@ $string['participantreviewedby'] = 'Participant is reviewed by';
$string['phaseassessment'] = 'Assessment phase';
$string['phaseclosed'] = 'Closed';
$string['phaseevaluation'] = 'Grading evaluation phase';
$string['phasesoverlap'] = 'The submission phase and the assessment phase can not overlap';
$string['phasesetup'] = 'Setup phase';
$string['phasesubmission'] = 'Submission phase';
$string['pluginadministration'] = 'Workshop administration';
@ -206,7 +209,12 @@ $string['submissionby'] = 'Submission by {$a}';
$string['submissionattachment'] = 'Attachment';
$string['submissioncontent'] = 'Submission content';
$string['submissionend'] = 'Submissions deadline';
$string['submissionendbeforestart'] = 'Submissions deadline can not be specified before the open for submissions date';
$string['submissionenddatetime'] = 'Submissions deadline: {$a->daydatetime} ({$a->distanceday})';
$string['submissionendswitch'] = 'Switch to the next phase after the submissions deadline';
$string['submissionendswitch_help'] = 'If the submissions deadline is specified and this box is checked, the workshop will automatically switch to the assessment phase after the submissions deadline.
If you enable this feature, it is recommended to set up the scheduled allocation method, too. If the submissions are not allocated, no assessment can be done even if the workshop itself is in the assessment phase.';
$string['submissiongrade'] = 'Grade for submission';
$string['submissiongrade_help'] = 'This setting specifies the maximum grade that may be obtained for submitted work.';
$string['submissiongradeof'] = 'Grade for submission (of {$a})';
@ -224,6 +232,7 @@ $string['switchingphase'] = 'Switching phase';
$string['switchphase'] = 'Switch phase';
$string['switchphase10info'] = 'You are about to switch the workshop into the <strong>Setup phase</strong>. In this phase, users cannot modify their submissions or their assessments. Teachers may use this phase to change workshop settings, modify the grading strategy of tweak assessment forms.';
$string['switchphase20info'] = 'You are about to switch the workshop into the <strong>Submission phase</strong>. Students may submit their work during this phase (within the submission access control dates, if set). Teachers may allocate submissions for peer review.';
$string['switchphase30auto'] = 'Workshop will automatically switch into the assessment phase after {$a->daydatetime} ({$a->distanceday})';
$string['switchphase30info'] = 'You are about to switch the workshop into the <strong>Assessment phase</strong>. In this phase, reviewers may assess the submissions they have been allocated (within the assessment access control dates, if set).';
$string['switchphase40info'] = 'You are about to switch the workshop into the <strong>Grading evaluation phase</strong>. In this phase, users cannot modify their submissions or their assessments. Teachers may use the grading evaluation tools to calculate final grades and provide feedback for reviewers.';
$string['switchphase50info'] = 'You are about to close the workshop. This will result in the calculated grades appearing in the gradebook. Students may view their submissions and their submission assessments.';

View file

@ -70,14 +70,15 @@ function workshop_add_instance(stdclass $workshop) {
global $CFG, $DB;
require_once(dirname(__FILE__) . '/locallib.php');
$workshop->phase = workshop::PHASE_SETUP;
$workshop->timecreated = time();
$workshop->timemodified = $workshop->timecreated;
$workshop->useexamples = (int)!empty($workshop->useexamples); // unticked checkbox hack
$workshop->usepeerassessment = (int)!empty($workshop->usepeerassessment); // unticked checkbox hack
$workshop->useselfassessment = (int)!empty($workshop->useselfassessment); // unticked checkbox hack
$workshop->latesubmissions = (int)!empty($workshop->latesubmissions); // unticked checkbox hack
$workshop->evaluation = 'best';
$workshop->phase = workshop::PHASE_SETUP;
$workshop->timecreated = time();
$workshop->timemodified = $workshop->timecreated;
$workshop->useexamples = (int)!empty($workshop->useexamples);
$workshop->usepeerassessment = (int)!empty($workshop->usepeerassessment);
$workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
$workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
$workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
$workshop->evaluation = 'best';
// insert the new record so we get the id
$workshop->id = $DB->insert_record('workshop', $workshop);
@ -122,13 +123,14 @@ function workshop_update_instance(stdclass $workshop) {
global $CFG, $DB;
require_once(dirname(__FILE__) . '/locallib.php');
$workshop->timemodified = time();
$workshop->id = $workshop->instance;
$workshop->useexamples = (int)!empty($workshop->useexamples); // unticked checkbox hack
$workshop->usepeerassessment = (int)!empty($workshop->usepeerassessment); // unticked checkbox hack
$workshop->useselfassessment = (int)!empty($workshop->useselfassessment); // unticked checkbox hack
$workshop->latesubmissions = (int)!empty($workshop->latesubmissions); // unticked checkbox hack
$workshop->evaluation = 'best';
$workshop->timemodified = time();
$workshop->id = $workshop->instance;
$workshop->useexamples = (int)!empty($workshop->useexamples);
$workshop->usepeerassessment = (int)!empty($workshop->usepeerassessment);
$workshop->useselfassessment = (int)!empty($workshop->useselfassessment);
$workshop->latesubmissions = (int)!empty($workshop->latesubmissions);
$workshop->phaseswitchassessment = (int)!empty($workshop->phaseswitchassessment);
$workshop->evaluation = 'best';
// todo - if the grading strategy is being changed, we must replace all aggregated peer grades with nulls
// todo - if maximum grades are being changed, we should probably recalculate or invalidate them
@ -882,14 +884,41 @@ function workshop_print_recent_mod_activity($activity, $courseid, $detail, $modn
}
/**
* Function to be run periodically according to the moodle cron
* This function searches for things that need to be done, such
* as sending out mail, toggling flags etc ...
* Regular jobs to execute via cron
*
* @return boolean
* @todo Finish documenting this function
**/
function workshop_cron () {
* @return boolean true on success, false otherwise
*/
function workshop_cron() {
global $CFG, $DB;
$now = time();
mtrace(' processing workshop subplugins ...');
cron_execute_plugin_type('workshopallocation', 'workshop allocation methods');
// now when the scheduled allocator had a chance to do its job, 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 < ?", array($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', array('id' => $cm->course), '*', MUST_EXIST);
$workshop = new workshop($workshop, $cm, $course);
$workshop->switch_phase(workshop::PHASE_ASSESSMENT);
$workshop->log('update switch phase', $workshop->view_url(), $workshop->phase);
// disable the automatic switching now so that it is not executed again by accident
// if the teacher changes the phase back to the submission one
$DB->set_field('workshop', 'phaseswitchassessment', 0, array('id' => $workshop->id));
// todo inform the teachers
}
mtrace('done');
}
return true;
}

View file

@ -44,9 +44,8 @@ require_once($CFG->libdir . '/filelib.php');
*/
class workshop {
/** return statuses of {@link add_allocation} to be passed to a workshop renderer method */
/** error status of the {@link self::add_allocation()} */
const ALLOCATION_EXISTS = -9999;
const ALLOCATION_ERROR = -9998;
/** the internal code of the workshop phases as are stored in the database */
const PHASE_SETUP = 10;
@ -147,6 +146,9 @@ class workshop {
/** @var int if greater than 0 then the peer assessment is not allowed after this timestamp */
public $assessmentend;
/** @var bool automatically switch to the assessment phase after the submissions deadline */
public $phaseswitchassessment;
/**
* @var workshop_strategy grading strategy instance
* Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
@ -965,7 +967,7 @@ class workshop {
* @param int $reviewerid User ID
* @param int $weight of the new assessment, from 0 to 16
* @param bool $bulk repeated inserts into DB expected
* @return int ID of the new assessment or an error code
* @return int ID of the new assessment or an error code {@link self::ALLOCATION_EXISTS} if the allocation already exists
*/
public function add_allocation(stdclass $submission, $reviewerid, $weight=1, $bulk=false) {
global $DB;
@ -1391,6 +1393,7 @@ class workshop {
}
$DB->set_field('workshop', 'phase', $newphase, array('id' => $this->id));
$this->phase = $newphase;
return true;
}
@ -2281,6 +2284,22 @@ class workshop_user_plan implements renderable {
$phase->tasks['submit'] = $task;
}
if (has_capability('mod/workshop:allocate', $workshop->context, $userid)) {
if ($workshop->phaseswitchassessment) {
$task = new stdClass();
$allocator = $DB->get_record('workshopallocation_scheduled', array('workshopid' => $workshop->id));
if (empty($allocator)) {
$task->completed = false;
} else if ($allocator->enabled and is_null($allocator->resultstatus)) {
$task->completed = true;
} else if ($workshop->submissionend > time()) {
$task->completed = null;
} else {
$task->completed = false;
}
$task->title = get_string('setup', 'workshopallocation_scheduled');
$task->link = $workshop->allocation_url('scheduled');
$phase->tasks['allocatescheduled'] = $task;
}
$task = new stdclass();
$task->title = get_string('allocate', 'workshop');
$task->link = $workshop->allocation_url();
@ -2316,6 +2335,7 @@ class workshop_user_plan implements renderable {
$task->completed = 'info';
$phase->tasks['allocateinfo'] = $task;
}
}
if ($workshop->submissionstart) {
$task = new stdclass();
@ -2352,6 +2372,13 @@ class workshop_user_plan implements renderable {
$phase->title = get_string('phaseassessment', 'workshop');
$phase->tasks = array();
$phase->isreviewer = has_capability('mod/workshop:peerassess', $workshop->context, $userid);
if ($workshop->phase == workshop::PHASE_SUBMISSION and $workshop->phaseswitchassessment
and has_capability('mod/workshop:switchphase', $workshop->context, $userid)) {
$task = new stdClass();
$task->title = get_string('switchphase30auto', 'mod_workshop', workshop::timestamp_formats($workshop->submissionend));
$task->completed = 'info';
$phase->tasks['autoswitchinfo'] = $task;
}
if ($workshop->useexamples and $workshop->examplesmode == workshop::EXAMPLES_BEFORE_ASSESSMENT
and $phase->isreviewer and !has_capability('mod/workshop:manageexamples', $workshop->context, $userid)) {
$task = new stdclass();
@ -3011,61 +3038,6 @@ class workshop_message implements renderable {
}
}
/**
* Renderable output of submissions allocation process
*/
class workshop_allocation_init_result implements renderable {
/** @var workshop_message */
protected $message;
/** @var array of steps */
protected $info = array();
/** @var moodle_url */
protected $continue;
/**
* Supplied argument can be either integer status code or an array of string messages. Messages
* in a array can have optional prefix or prefixes, using '::' as delimiter. Prefixes determine
* the type of the message and may influence its visualisation.
*
* @param mixed $result int|array returned by {@see workshop_allocator::init()}
* @param moodle_url to continue
*/
public function __construct($result, moodle_url $continue) {
if ($result === workshop::ALLOCATION_ERROR) {
$this->message = new workshop_message(get_string('allocationerror', 'workshop'), workshop_message::TYPE_ERROR);
} else {
$this->message = new workshop_message(get_string('allocationdone', 'workshop'), workshop_message::TYPE_OK);
if (is_array($result)) {
$this->info = $result;
}
}
$this->continue = $continue;
}
/**
* @return workshop_message instance to render
*/
public function get_message() {
return $this->message;
}
/**
* @return array of strings with allocation process details
*/
public function get_info() {
return $this->info;
}
/**
* @return moodle_url where the user shoudl continue
*/
public function get_continue_url() {
return $this->continue;
}
}
/**
* Renderable component containing all the data needed to display the grading report

View file

@ -183,6 +183,12 @@ class mod_workshop_mod_form extends moodleform_mod {
$mform->addElement('date_time_selector', 'submissionend', $label, array('optional' => true));
$mform->setAdvanced('submissionend');
$label = get_string('submissionendswitch', 'mod_workshop');
$mform->addElement('checkbox', 'phaseswitchassessment', $label);
$mform->setAdvanced('phaseswitchassessment');
$mform->disabledIf('phaseswitchassessment', 'submissionend[enabled]');
$mform->addHelpButton('phaseswitchassessment', 'submissionendswitch', 'mod_workshop');
$label = get_string('assessmentstart', 'workshop');
$mform->addElement('date_time_selector', 'assessmentstart', $label, array('optional' => true));
$mform->setAdvanced('assessmentstart');
@ -283,4 +289,42 @@ class mod_workshop_mod_form extends moodleform_mod {
parent::definition_after_data();
}
/**
* Validates the form input
*
* @param array $data submitted data
* @param array $files submitted files
* @return array eventual errors indexed by the field name
*/
public function validation($data, $files) {
$errors = array();
// check the phases borders are valid
if ($data['submissionstart'] > 0 and $data['submissionend'] > 0 and $data['submissionstart'] >= $data['submissionend']) {
$errors['submissionend'] = get_string('submissionendbeforestart', 'mod_workshop');
}
if ($data['assessmentstart'] > 0 and $data['assessmentend'] > 0 and $data['assessmentstart'] >= $data['assessmentend']) {
$errors['assessmentend'] = get_string('assessmentendbeforestart', 'mod_workshop');
}
// check the phases do not overlap
if (max($data['submissionstart'], $data['submissionend']) > 0 and max($data['assessmentstart'], $data['assessmentend']) > 0) {
$phasesubmissionend = max($data['submissionstart'], $data['submissionend']);
$phaseassessmentstart = min($data['assessmentstart'], $data['assessmentend']);
if ($phaseassessmentstart == 0) {
$phaseassessmentstart = max($data['assessmentstart'], $data['assessmentend']);
}
if ($phasesubmissionend > 0 and $phaseassessmentstart > 0 and $phaseassessmentstart < $phasesubmissionend) {
foreach (array('submissionend', 'submissionstart', 'assessmentstart', 'assessmentend') as $f) {
if ($data[$f] > 0) {
$errors[$f] = get_string('phasesoverlap', 'mod_workshop');
break;
}
}
}
}
return $errors;
}
}

View file

@ -292,7 +292,13 @@ class mod_workshop_renderer extends plugin_renderer_base {
foreach ($phase->actions as $action) {
switch ($action->type) {
case 'switchphase':
$actions .= $this->output->action_icon($action->url, new pix_icon('i/marker', get_string('switchphase', 'workshop')));
$icon = 'i/marker';
if ($phasecode == workshop::PHASE_ASSESSMENT
and $plan->workshop->phase == workshop::PHASE_SUBMISSION
and $plan->workshop->phaseswitchassessment) {
$icon = 'i/scheduled';
}
$actions .= $this->output->action_icon($action->url, new pix_icon($icon, get_string('switchphase', 'workshop')));
break;
}
}
@ -319,33 +325,68 @@ class mod_workshop_renderer extends plugin_renderer_base {
/**
* Renders the result of the submissions allocation process
*
* @param workshop_allocation_init_result
* @return string html to be echoed
* @param workshop_allocation_result $result as returned by the allocator's init() method
* @return string HTML to be echoed
*/
protected function render_workshop_allocation_init_result(workshop_allocation_init_result $result) {
protected function render_workshop_allocation_result(workshop_allocation_result $result) {
$status = $result->get_status();
if (is_null($status) or $status == workshop_allocation_result::STATUS_VOID) {
debugging('Attempt to render workshop_allocation_result with empty status', DEBUG_DEVELOPER);
return '';
}
switch ($status) {
case workshop_allocation_result::STATUS_FAILED:
if ($message = $result->get_message()) {
$message = new workshop_message($message, workshop_message::TYPE_ERROR);
} else {
$message = new workshop_message(get_string('allocationerror', 'workshop'), workshop_message::TYPE_ERROR);
}
break;
case workshop_allocation_result::STATUS_CONFIGURED:
if ($message = $result->get_message()) {
$message = new workshop_message($message, workshop_message::TYPE_INFO);
} else {
$message = new workshop_message(get_string('allocationconfigured', 'workshop'), workshop_message::TYPE_INFO);
}
break;
case workshop_allocation_result::STATUS_EXECUTED:
if ($message = $result->get_message()) {
$message = new workshop_message($message, workshop_message::TYPE_OK);
} else {
$message = new workshop_message(get_string('allocationdone', 'workshop'), workshop_message::TYPE_OK);
}
break;
default:
throw new coding_exception('Unknown allocation result status', $status);
}
// start with the message
$o = $this->render($result->get_message());
$o = $this->render($message);
// display the details about the process if available
$info = $result->get_info();
if (is_array($info) and !empty($info)) {
$logs = $result->get_logs();
if (is_array($logs) and !empty($logs)) {
$o .= html_writer::start_tag('ul', array('class' => 'allocation-init-results'));
foreach ($info as $message) {
$parts = explode('::', $message);
$text = array_pop($parts);
$class = implode(' ', $parts);
if (in_array('debug', $parts) && !debugging('', DEBUG_DEVELOPER)) {
// do not display allocation debugging messages
foreach ($logs as $log) {
if ($log->type == 'debug' and !debugging('', DEBUG_DEVELOPER)) {
// display allocation debugging messages for developers only
continue;
}
$o .= html_writer::tag('li', $text, array('class' => $class)) . "\n";
$class = $log->type;
if ($log->indent) {
$class .= ' indent';
}
$o .= html_writer::tag('li', $log->message, array('class' => $class)).PHP_EOL;
}
$o .= html_writer::end_tag('ul');
}
$o .= $this->output->continue_button($result->get_continue_url());
return $o;
}

View file

@ -17,15 +17,14 @@
/**
* Defines the version of workshop
*
* @package mod
* @subpackage 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
*/
defined('MOODLE_INTERNAL') || die();
$module->version = 2012030700; // The current module version (Date: YYYYMMDDXX)
$module->requires = 2012030100.04; // Requires this Moodle version
$module->component = 'mod_workshop'; // Full name of the plugin (used for diagnostics)
$module->cron = 0;
$module->version = 2012033100; // the current module version (YYYYMMDDXX)
$module->requires = 2012032300; // requires this Moodle version
$module->component = 'mod_workshop'; // full name of the plugin (used for diagnostics)
$module->cron = 60; // give as a chance every minute

BIN
pix/i/scheduled.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB