MDL-54945 workshop: Workshop portfolio API integration

Users can export their own submission
Users can export feedback/assessment received from other users
This commit is contained in:
Loc Nguyen 2016-07-22 20:20:42 +07:00 committed by David Mudrák
parent d9520bc04e
commit 5fd75ed889
9 changed files with 1246 additions and 3 deletions

View File

@ -0,0 +1,571 @@
<?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 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
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/portfolio/caller.php');
/**
* Workshop portfolio caller subclass to handle export submission and assessment to portfolio.
*
* @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 int ID of submission.
*/
protected $submissionid;
/**
* @var int ID of assessment.
*/
protected $assessmentid;
/**
* @var stdClass Course module.
*/
protected $cm;
/**
* @var array Submission files list.
*/
protected $submissionfiles = array();
/**
* @var array Assessment files list.
*/
protected $assessmentfiles = array();
/**
* @var stdClass Submission object load from DB.
*/
private $submission;
/**
* @var stdClass Assessment object.
*/
private $assessment;
/**
* @var stdClass User object.
*/
private $author;
/**
* @var boolean Show author permission.
*/
private $showauthor;
/**
* Return array of expected call back arguments
*
* and whether they are required or not.
*
* @return array
*/
public static function expected_callbackargs() {
return array(
'submissionid' => false,
'assessmentid' => false,
);
}
/**
* Load data required for the export.
*
* Load submission and assessment by submissionid, assessmentid.
*
* @return void
*/
public function load_data() {
global $DB;
$this->submission = $DB->get_record('workshop_submissions', array('id' => $this->submissionid), '*', MUST_EXIST);
$this->cm = get_coursemodule_from_instance('workshop', $this->submission->workshopid, 0, false, MUST_EXIST);
$this->author = $DB->get_record('user', array('id' => $this->submission->authorid));
$workshoprecord = $DB->get_record('workshop', array('id' => $this->cm->instance), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id' => $this->cm->course), '*', MUST_EXIST);
$this->workshop = new workshop($workshoprecord, $this->cm, $course);
// Load data for export assessment.
if ($this->submissionid && $this->assessmentid) {
$assessments = $this->workshop->get_assessments_of_submission($this->submission->id);
$this->assessment = new stdClass();
$this->assessment = $assessments[$this->assessmentid];
$fs = get_file_storage();
$this->assessmentfiles = $fs->get_area_files($this->workshop->context->id, 'mod_workshop',
'overallfeedback_attachment', $this->assessment->id);
}
// Load data for export submission.
if ($this->submissionid && !$this->assessmentid) {
$fs = get_file_storage();
$submissioncontentfiles = $fs->get_area_files($this->workshop->context->id,
'mod_workshop', 'submission_content', $this->submissionid);
$submissionattachmentfiles = $fs->get_area_files($this->workshop->context->id,
'mod_workshop', 'submission_attachment', $this->submissionid);
$submissioninstructionfiles = $fs->get_area_files($this->workshop->context->id,
'mod_workshop', 'instructauthors');
$this->submissionfiles = array_merge($submissioncontentfiles, $submissionattachmentfiles, $submissioninstructionfiles);
// Check anonymity of exported, show author or not.
$ispublished = ($this->workshop->phase == workshop::PHASE_CLOSED
and $this->submission->published == 1
and has_capability('mod/workshop:viewpublishedsubmissions', $this->workshop->context));
$seenaspublished = false; // Is the submission seen as a published submission?
if ($ispublished) {
$seenaspublished = true;
}
if ($seenaspublished) {
$this->showauthor = has_capability('mod/workshop:viewauthorpublished', $this->workshop->context);
} else {
$this->showauthor = has_capability('mod/workshop:viewauthornames', $this->workshop->context);
}
}
}
/**
* Prepare the package ready to be passed off to the portfolio plugin.
*
* @return void
*/
public function prepare_package() {
// 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 ($this->submissionid && $this->assessmentid) {
$assessmenthtml = $this->prepare_assessment($this->assessment);
$content = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $assessmenthtml);
$name = 'assessment.html';
if ($writingleap) {
$this->prepare_assessment_leap2a($leapwriter, $this->assessment, $content);
$content = $leapwriter->to_xml();
$name = $this->exporter->get('format')->manifest_name();
}
}
if ($this->submissionid && !$this->assessmentid) {
$submissionhtml = $this->prepare_submission($this->submission);
$content = $submissionhtml;
$name = 'submission.html';
if ($writingleap) {
$this->prepare_submission_leap2a($leapwriter, $this->submission, $content);
$content = $leapwriter->to_xml();
$name = $this->exporter->get('format')->manifest_name();
}
}
$manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH);
$this->get('exporter')->write_new_file($content, $name, $manifest);
}
/**
* Helper function to output submission for export.
*
* This is cut down version of workshop submission renderer function.
*
* @param object $submission
* @return string
*/
private function prepare_submission($submission) {
global $CFG;
$output = '';
$output .= '<meta charset="UTF-8">';
$output .= html_writer::tag("h2", $this->workshop->name);
// Write submission content.
$output .= html_writer::start_tag('div', array('class' => 'submissionheader', 'style' => 'background-color: #ddd;'));
$output .= html_writer::tag("h3", format_string($submission->title));
if ($this->showauthor) {
$output .= html_writer::tag('div', get_string('byfullnamewithoutlink', 'workshop', fullname($this->author)));
}
$created = get_string('userdatecreated', 'workshop', userdate($submission->timecreated));
$output .= html_writer::tag("div", $created, array('style' => 'font-size:x-small'));
if ($submission->timemodified > $submission->timecreated) {
$modified = get_string('userdatemodified', 'workshop', userdate($submission->timemodified));
$output .= html_writer::tag("div", $modified, array('style' => 'font-size:x-small'));
}
$output .= html_writer::end_tag('div');
$format = $this->get('exporter')->get('format');
$content = portfolio_rewrite_pluginfile_urls($submission->content, $this->workshop->context->id, 'mod_workshop',
'submission_content', $submission->id, $format);
$content = format_text($content, $submission->contentformat);
$output .= html_writer::tag("div", $content);
if ($this->submissionfiles) {
$outputfiles = '';
foreach ($this->submissionfiles as $file) {
if ($file->is_directory()) {
continue;
}
$filename = $file->get_filename();
$filearea = $file->get_filearea();
$type = $file->get_mimetype();
$linkhtml = $format->file_output($file);
if ($filearea == "submission_attachment") {
$outputfiles .= html_writer::tag('li', $linkhtml, array('class' => $type));
}
if ($filename) {
$this->get('exporter')->copy_existing_file($file);
}
if (!empty($CFG->enableplagiarism)) {
require_once($CFG->libdir . '/plagiarismlib.php');
$outputfiles .= plagiarism_get_links(array('userid' => $file->get_userid(),
'file' => $file,
'cmid' => $this->cm->id,
'course' => $this->cm->course));
}
}
$output .= $outputfiles;
}
// Write submission instruction.
if (trim($this->workshop->instructauthors)) {
$output .= html_writer::tag("h3", get_string('instructauthors', 'workshop'));
$format = $this->get('exporter')->get('format');
$instructauthorscontent = portfolio_rewrite_pluginfile_urls($this->workshop->instructauthors,
$this->workshop->context->id, 'mod_workshop', 'instructauthors', 0, $format);
$output .= $instructauthorscontent;
}
return $output;
}
/**
* Helper function to add a leap2a entry element that corresponds to a submission,
* including any attachments.
*
* The entry/ies are added directly to the leapwriter, which is passed by ref.
*
* @param portfolio_format_leap2a_writer $leapwriter writer object to add entries to
* @param object $submission the stdclass object representing the database record
* @param string $submissionhtml the content of the submission
*
* @return int id of new entry
*/
private function prepare_submission_leap2a(portfolio_format_leap2a_writer $leapwriter, $submission, $submissionhtml) {
global $DB;
$entry = new portfolio_format_leap2a_entry('workshopsubmission' . $submission->id, $submission->title,
'resource', $submissionhtml);
$entry->published = $submission->timecreated;
$entry->updated = $submission->timemodified;
$entry->author = $DB->get_record('user', array('id' => $submission->authorid), 'id,firstname,lastname,email');
if (is_array($this->submissionfiles) && array_key_exists($submission->id, $this->submissionfiles)
&& is_array($this->submissionfiles[$submission->id])) {
$leapwriter->link_files($entry, $this->submissionfiles[$submission->id], 'workshopsubmission'
. $submission->id . 'attachment');
}
$entry->add_category('web', 'resource_type');
$leapwriter->add_entry($entry);
return $entry->id;
}
/**
* Helper function to output assessment for export.
*
* This is a cut down function of workshop assessment renderer function.
*
* @param object $assessment the stdclass object representing the database record
* @return string
*/
private function prepare_assessment($assessment) {
global $PAGE;
global $CFG;
global $USER;
$strategy = $this->workshop->grading_strategy_instance();
$mform = $strategy->get_assessment_form($PAGE->url, 'assessment', $assessment, false);
$userassessment = $this->workshop->get_assessment_of_submission_by_user($assessment->submissionid, $USER->id);
$isreviewer = !empty($userassessment);
if ($isreviewer) {
$showreviewer = true;
} else {
$showreviewer = has_capability('mod/workshop:viewreviewernames', $this->workshop->context);
}
$fs = get_file_storage();
$options = array(
'showreviewer' => $showreviewer,
'showauthor' => $this->showauthor,
'showform' => !is_null($assessment->grade),
'showweight' => true,
);
$displayassessment = $this->workshop->prepare_assessment($assessment, $mform, $options);
$output = '';
$output .= '<meta charset="UTF-8">';
// Start write assessment content.
$anonymous = is_null($displayassessment->reviewer);
$output .= html_writer::start_tag('div', array('class' => 'assessmentheader', 'style' => 'background-color: #ddd;'));
if (!empty($displayassessment->title)) {
$title = s($displayassessment->title);
} else {
$title = get_string('assessmentbyyourself', 'workshop');
}
$output .= html_writer::tag("div", $title);
if (!$anonymous) {
$reviewer = $displayassessment->reviewer;
$output .= html_writer::tag("div", get_string('byfullnamewithoutlink', 'workshop', fullname($reviewer)));
}
if (is_null($displayassessment->realgrade)) {
$output .= html_writer::tag("div", get_string('notassessed', 'workshop'));
} else {
$a = new stdClass();
$a->max = $displayassessment->maxgrade;
$a->received = $displayassessment->realgrade;
$output .= html_writer::tag("div", get_string('gradeinfo', 'workshop', $a));
if (!is_null($displayassessment->weight) and $displayassessment->weight != 1) {
$output .= html_writer::tag("div", get_string('weightinfo', 'workshop', $displayassessment->weight));
}
}
$output .= html_writer::end_tag('div');
$dir = $this->get('exporter')->get('format')->get_file_directory();
if (!is_null($displayassessment->form)) {
$output .= html_writer::tag("div", get_string('assessmentform', 'workshop'));
$contentform = self::moodleform($displayassessment->form);
$contentform = $this->portfolio_caller_rewrite_pluginfile_urls($contentform, $dir);
$output .= html_writer::tag("div", $contentform);
if (!$displayassessment->form->is_editable()) {
$content = $displayassessment->get_overall_feedback_content();
$content = $this->portfolio_caller_rewrite_pluginfile_urls($content, $dir);
if ($content != '') {
$output .= html_writer::tag("div", get_string('overallfeedback', 'workshop'));
$output .= html_writer::tag("div", $content);
}
// Export overall feedback files.
$overallfeedbackfiles = $fs->get_area_files($this->workshop->context->id,
'mod_workshop', 'overallfeedback_content', $assessment->id);
foreach ($overallfeedbackfiles as $file) {
$this->get('exporter')->copy_existing_file($file);
}
}
// Export description files.
$assessmentdescriptionfiles = $fs->get_area_files($this->workshop->context->id,
'workshopform_' . $this->workshop->strategy, 'description');
foreach ($assessmentdescriptionfiles as $file) {
$this->get('exporter')->copy_existing_file($file);
}
}
if ($this->assessmentfiles) {
$outputfiles = '';
$format = $this->get('exporter')->get('format');
foreach ($this->assessmentfiles as $file) {
if ($file->is_directory()) {
continue;
}
$type = $file->get_mimetype();
$linkhtml = $format->file_output($file);
$outputfiles .= html_writer::tag('li', $linkhtml, array('class' => $type));
$this->get('exporter')->copy_existing_file($file);
if (!empty($CFG->enableplagiarism)) {
require_once($CFG->libdir.'/plagiarismlib.php');
$outputfiles .= plagiarism_get_links(array('userid' => $file->get_userid(),
'file' => $file,
'cmid' => $this->cm->id,
'course' => $this->cm->course));
}
}
$output .= $outputfiles;
}
return $output;
}
/**
* Helper function to add a leap2a entry element that corresponds to a assessment,
* including any attachments.
*
* The entry/ies are added directly to the leapwriter, which is passed by ref.
*
* @param portfolio_format_leap2a_writer $leapwriter writer object to add entries to
* @param object $assessment the stdclass object representing the database record
* @param string $assessmenthtml the content of the assessment
*
* @return int id of new entry
*/
private function prepare_assessment_leap2a(portfolio_format_leap2a_writer $leapwriter, $assessment, $assessmenthtml) {
global $DB;
$entry = new portfolio_format_leap2a_entry('workshopassessment' . $assessment->id,
$assessment->title, 'resource', $assessmenthtml);
$entry->published = $assessment->timecreated;
$entry->updated = $assessment->timemodified;
$entry->author = $DB->get_record('user', array('id' => $assessment->reviewerid), 'id,firstname,lastname,email');
if (is_array($this->assessmentfiles) && array_key_exists($assessment->id, $this->assessmentfiles)
&& is_array($this->assessmentfiles[$assessment->id])) {
$leapwriter->link_files($entry, $this->assessmentfiles[$assessment->id], 'workshopassessment'
. $assessment->id . 'attachment');
}
$entry->add_category('web', 'resource_type');
$leapwriter->add_entry($entry);
return $entry->id;
}
/**
* Return url for redirecting user when cancel or go back.
*
* @return string
*/
public function get_return_url() {
$returnurl = new moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->cm->id));
return $returnurl->out();
}
/**
* Get navigation that logically follows from the place the user was before.
*
* @return array
*/
public function get_navigation() {
$link = new moodle_url('/mod/workshop/submission.php',
array('cmid' => $this->cm->id));
$navlinks = array();
$navlinks[] = array(
'name' => format_string($this->submission->title),
'link' => $link->out(),
'type' => 'title'
);
return array($navlinks, $this->cm);
}
/**
* How long might we expect this export to take.
*
* @return constant one of PORTFOLIO_TIME_XX
*/
public function expected_time() {
return PORTFOLIO_TIME_LOW;
}
/**
* Make sure that the current user is allowed to do the export.
*
* @uses CONTEXT_MODULE
* @return boolean
*/
public function check_permissions() {
$context = context_module::instance($this->cm->id);
if ($this->submissionid && $this->assessmentid) {
return has_capability('mod/workshop:exportownsubmission', $context)
&& has_capability('mod/workshop:exportownsubmissionassessment', $context);
}
return has_capability('mod/workshop:exportownsubmission', $context);
}
/**
* Return the sha1 of this content.
*
* @return string
*/
public function get_sha1() {
if ($this->submissionid && $this->assessmentid) {
return sha1($this->assessment->id . ',' . $this->assessment->title . ',' . $this->assessment->timecreated);
}
if ($this->submissionid && !$this->assessmentid) {
return sha1($this->submission->id . ',' . $this->submission->title . ',' . $this->submission->timecreated);
}
}
/**
* Return a nice name to be displayed about this export location.
*
* @return string
*/
public static function display_name() {
return get_string('modulename', 'workshop');
}
/**
* What formats this function *generally* supports.
*
* @return array
*/
public static function base_supported_formats() {
return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A);
}
/**
* Helper method dealing with the fact we can not just fetch the output of moodleforms.
*
* @param moodleform $mform
* @return string HTML
*/
protected static function moodleform(moodleform $mform) {
ob_start();
$mform->display();
$o = ob_get_contents();
ob_end_clean();
return $o;
}
/**
* Helper method dealing with the fact we can not just fetch the output of moodleforms.
*
* @param string $text HTML string to rewrite url
* @param string $dir rewrite file directory
* @return string HTML
*/
public static function portfolio_caller_rewrite_pluginfile_urls($text, $dir) {
$doc = new DOMDocument();
$doc->loadHTML($text);
$imagetags = $doc->getElementsByTagName('img');
foreach ($imagetags as $tag) {
$src = $tag->getAttribute('src');
if (strpos($src, 'pluginfile.php') !== false) {
$rewriteurl = $dir . basename($src);
$text = str_replace($src, $rewriteurl, $text);
}
}
$atags = $doc->getElementsByTagName('a');
foreach ($atags as $atag) {
$href = $atag->getAttribute('href');
if (strpos($href, 'pluginfile.php') !== false) {
$rewriteurl = $dir . basename($href);
$text = str_replace($href, $rewriteurl, $text);
}
}
return $text;
}
}

View File

@ -244,4 +244,22 @@ $capabilities = array(
'manager' => CAP_ALLOW
)
),
// Ability to export to portfolio of submission only.
'mod/workshop:exportownsubmission' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW
)
),
// Ability to export to portfolio of submission's assessment.
'mod/workshop:exportownsubmissionassessment' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'student' => CAP_ALLOW
)
),
);

View File

@ -73,6 +73,7 @@ $string['assignedassessments'] = 'Assigned submissions to assess';
$string['assignedassessmentsnone'] = 'You have no assigned submission to assess';
$string['backtoeditform'] = 'Back to editing form';
$string['byfullname'] = 'by <a href="{$a->url}">{$a->name}</a>';
$string['byfullnamewithoutlink'] = 'by {$a}';
$string['calculategradinggrades'] = 'Calculate assessment grades';
$string['calculategradinggradesdetails'] = 'expected: {$a->expected}<br />calculated: {$a->calculated}';
$string['calculatesubmissiongrades'] = 'Calculate submission grades';
@ -143,6 +144,8 @@ $string['examplesbeforesubmission'] = 'Examples must be assessed before own subm
$string['examplesmode'] = 'Mode of examples assessment';
$string['examplesubmissions'] = 'Example submissions';
$string['examplesvoluntary'] = 'Assessment of example submission is voluntary';
$string['exportsubmission'] = 'Export submission to portfolio';
$string['exportsubmissionassessment'] = 'Export assessment to portfolio';
$string['feedbackauthor'] = 'Feedback for the author';
$string['feedbackauthorattachment'] = 'Attachment';
$string['feedbackby'] = 'Feedback by {$a}';
@ -331,6 +334,8 @@ $string['withoutsubmission'] = 'Reviewer without own submission';
$string['workshop:addinstance'] = 'Add a new workshop';
$string['workshop:allocate'] = 'Allocate submissions for review';
$string['workshop:editdimensions'] = 'Edit assessment forms';
$string['workshop:exportownsubmission'] = 'Export own submission only';
$string['workshop:exportownsubmissionassessment'] = 'Export own assessment in submission';
$string['workshop:deletesubmissions'] = 'Delete submissions';
$string['workshop:ignoredeadlines'] = 'Ignore time restrictions';
$string['workshop:manageexamples'] = 'Manage example submissions';

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -87,6 +86,12 @@ $canallocate = has_capability('mod/workshop:allocate', $workshop->context);
$canpublish = has_capability('mod/workshop:publishsubmissions', $workshop->context);
$canoverride = (($workshop->phase == workshop::PHASE_EVALUATION) and has_capability('mod/workshop:overridegrades', $workshop->context));
$candeleteall = has_capability('mod/workshop:deletesubmissions', $workshop->context);
$canexportownsubmission = !empty($CFG->enableportfolios)
&& (has_capability('mod/workshop:exportownsubmission', $workshop->context)) && $ownsubmission && !empty($submission->id);
$canexportownsubmissionassessment = !empty($CFG->enableportfolios)
&& (has_capability('mod/workshop:exportownsubmission', $workshop->context))
&& (has_capability('mod/workshop:exportownsubmissionassessment', $workshop->context))
&& $ownsubmission && !empty($submission->id);
$userassessment = $workshop->get_assessment_of_submission_by_user($submission->id, $USER->id);
$isreviewer = !empty($userassessment);
$editable = ($cansubmit and $ownsubmission);
@ -139,6 +144,11 @@ if (!$candeleteall and $ownsubmission and $editable) {
}
}
// Load portfolio lib if user has capability to export.
if ($canexportownsubmission || $canexportownsubmissionassessment) {
require_once($CFG->libdir.'/portfoliolib.php');
}
if ($submission->id and $delete and $confirm and $deletable) {
require_sesskey();
$workshop->delete_submission($submission);
@ -386,6 +396,23 @@ if (!$delete) {
$url = new moodle_url($PAGE->url, array('assess' => 1));
echo $output->single_button($url, get_string('assess', 'workshop'), 'post');
}
// Add portfolio export button for submission.
if ($canexportownsubmission) {
$button = new portfolio_add_button();
$button->set_callback_options('mod_workshop_portfolio_caller', array('submissionid' => $submission->id), 'mod_workshop');
$fs = get_file_storage();
$files = array_merge($fs->get_area_files($workshop->context->id, 'mod_workshop', 'submission_attachment', $submission->id)
, $fs->get_area_files($workshop->context->id, 'mod_workshop', 'submission_content', $submission->id));
if ($files) {
$button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
} else {
$button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
}
echo html_writer::start_tag('div', array('class' => 'singlebutton'));
echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmission', 'workshop'));
echo html_writer::end_tag('div');
}
}
if (($workshop->phase == workshop::PHASE_CLOSED) and ($ownsubmission or $canviewall)) {
@ -427,9 +454,31 @@ if ($isreviewer) {
echo $output->render(new workshop_feedback_reviewer($userassessment));
}
}
// Add portfolio export button for assessment.
if ($canexportownsubmissionassessment) {
$button = new portfolio_add_button();
$button->set_callback_options('mod_workshop_portfolio_caller', array('submissionid' => $submission->id,
'assessmentid' => $assessment->id), 'mod_workshop');
$fs = get_file_storage();
$files = array_merge($fs->get_area_files($workshop->context->id, 'mod_workshop'
, 'overallfeedback_attachment', $assessment->id)
, $fs->get_area_files($workshop->context->id, 'workshopform_' . $workshop->strategy, 'description')
, $fs->get_area_files($workshop->context->id, 'mod_workshop', 'overallfeedback', $assessment->id));
if ($files) {
$button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
} else {
$button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
}
echo html_writer::start_tag('div', array('class' => 'singlebutton'));
echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmissionassessment', 'workshop'));
echo html_writer::end_tag('div');
}
}
if (has_capability('mod/workshop:viewallassessments', $workshop->context) or ($ownsubmission and $workshop->assessments_available())) {
if (has_capability('mod/workshop:viewallassessments', $workshop->context)
or ($ownsubmission and $workshop->assessments_available())) {
// other assessments
$strategy = $workshop->grading_strategy_instance();
$assessments = $workshop->get_assessments_of_submission($submission->id);
@ -461,6 +510,27 @@ if (has_capability('mod/workshop:viewallassessments', $workshop->context) or ($o
echo $output->render(new workshop_feedback_reviewer($assessment));
}
}
// Add portfolio export button for assessment.
if ($canexportownsubmissionassessment) {
$button = new portfolio_add_button();
$button->set_callback_options('mod_workshop_portfolio_caller', array('submissionid' => $submission->id,
'assessmentid' => $assessment->id), 'mod_workshop');
$fs = get_file_storage();
$files = array_merge($fs->get_area_files($workshop->context->id, 'mod_workshop'
, 'overallfeedback_attachment', $assessment->id)
, $fs->get_area_files($workshop->context->id, 'workshopform_' . $workshop->strategy, 'description')
, $fs->get_area_files($workshop->context->id, 'mod_workshop', 'overallfeedback', $assessment->id));
if ($files) {
$button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
} else {
$button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
}
echo html_writer::start_tag('div', array('class' => 'singlebutton'));
echo $button->to_html(PORTFOLIO_ADD_FULL_FORM, get_string('exportsubmissionassessment', 'workshop'));
echo html_writer::end_tag('div');
}
}
}

View File

@ -160,4 +160,18 @@ class behat_mod_workshop extends behat_base {
}
$this->find('xpath', $xpath);
}
/**
* Configure portfolio plugin, set value for portfolio instance
*
* @When /^I set portfolio instance "(?P<portfolioinstance_string>(?:[^"]|\\")*)" to "(?P<value_string>(?:[^"]|\\")*)"$/
* @param string $portfolioinstance
* @param string $value
*/
public function i_set_portfolio_instance_to($portfolioinstance, $value) {
$xpath = "//table[contains(@class, 'generaltable')]//tr//td[contains(text(), '"
. $portfolioinstance . "')]/following-sibling::td//select";
$select = $this->find('xpath', $xpath);
$select->selectOption($value);
}
}

View File

@ -0,0 +1,141 @@
@mod @mod_workshop
@javascript
Feature: Workshop submission's assessment export to portfolio
In order to be able to reuse my assessment content in my submission
As a student
I need to be able to export my own submission's assessment from other people
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Sam1 | Student1 | student1@example.com |
| student2 | Sam2 | Student2 | student2@example.com |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course1 | c2 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | c2 | student |
| student2 | c2 | student |
| teacher1 | c2 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| workshop | TestWorkshop | Test workshop description | c2 | workshop1 |
# Admin enable portfolio plugin
And I log in as "admin"
And I expand "Site administration" node
And I follow "Advanced features"
And I set the following administration settings values:
| Enable portfolios | 1 |
And I expand "Plugins" node
And I expand "Portfolios" node
And I follow "Manage portfolios"
And I set portfolio instance "File download" to "Enabled and visible"
And I click on "Save" "button"
And I log out
# Teacher sets up assessment form and changes the phase to submission.
And I log in as "teacher1"
And I follow "Course1"
And I edit assessment form in workshop "TestWorkshop" as:"
| id_description__idx_0_editor | Aspect1 |
| id_description__idx_1_editor | Aspect2 |
| id_description__idx_2_editor | |
And I change phase in workshop "TestWorkshop" to "Submission phase"
And I log out
# Student1 submits.
And I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission1 |
| Submission content | Some content |
And I log out
# Student2 submits.
And I log in as "student2"
And I follow "Course1"
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission2 |
| Submission content | Some content |
And I log out
# teacher1 allocates reviewers and changes the phase to assessment
And I log in as "teacher1"
And I follow "Course1"
And I follow "TestWorkshop"
And I allocate submissions in workshop "TestWorkshop" as:"
| Participant | Reviewer |
| Sam1 Student1 | Sam2 Student2 |
| Sam2 Student2 | Sam1 Student1 |
And I follow "TestWorkshop"
And I change phase in workshop "TestWorkshop" to "Assessment phase"
And I log out
# student1 assesses work of student2
And I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
And I assess submission "Sam2" in workshop "TestWorkshop" as:"
| grade__idx_0 | 5 / 10 |
| peercomment__idx_0 | You can do better |
| grade__idx_1 | 10 / 10 |
| peercomment__idx_1 | Amazing |
| Feedback for the author | Good work |
And I log out
# student2 assesses work of student1
And I log in as "student2"
And I follow "Course1"
And I follow "TestWorkshop"
And I assess submission "Sam1" in workshop "TestWorkshop" as:"
| grade__idx_0 | 6 / 10 |
| peercomment__idx_0 | |
| grade__idx_1 | 7 / 10 |
| peercomment__idx_1 | |
| Feedback for the author | Keep it up |
And I log out
Scenario: Hide export to portfolio button of assessment if admin disable portfolio feature
Given I log in as "admin"
And I expand "Site administration" node
And I follow "Advanced features"
And I set the following administration settings values:
| Enable portfolios | 0 |
And I log out
When I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
And I follow "My submission"
Then I should see "Submission1"
Then "Export assessment to portfolio" "button" should not exist
And I log out
Scenario: Students can export to portfolio their own submission's assessments
Given I log in as "teacher1"
And I follow "Course1"
And I change phase in workshop "TestWorkshop" to "Closed"
And I log out
When I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
And I follow "My submission"
Then I should see "Submission1"
Then "Export assessment to portfolio" "button" should exist
And I click on "Export assessment to portfolio" "button"
Then I should see "Available export formats"
And I click on "Next" "button"
Then I should see "Summary of your export"
And I click on "Continue" "button"
Then I should see "Return to where you were"
And I log out
Scenario: Students can not export to portfolio the assessment content when viewing submission of other people
Given I log in as "teacher1"
And I follow "Course1"
And I change phase in workshop "TestWorkshop" to "Closed"
And I log out
When I log in as "student2"
And I follow "Course1"
And I follow "TestWorkshop"
And I follow "Submission1"
Then I should see "Assessment"
Then "Export assessment to portfolio" "button" should not exist
And I log out

View File

@ -0,0 +1,120 @@
@mod @mod_workshop
@javascript
Feature: Workshop submission export to portfolio
In order to be able to reuse my submission content
As a student
I need to be able to export my submission
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Sam1 | Student1 | student1@example.com |
| student2 | Sam2 | Student2 | student2@example.com |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname |
| Course1 | c1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | c1 | student |
| student2 | c1 | student |
| teacher1 | c1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber |
| workshop | TestWorkshop | Test workshop description | c1 | workshop1 |
# Admin enable portfolio plugin
And I log in as "admin"
And I expand "Site administration" node
And I follow "Advanced features"
And I set the following administration settings values:
| Enable portfolios | 1 |
And I expand "Plugins" node
And I expand "Portfolios" node
And I follow "Manage portfolios"
And I set portfolio instance "File download" to "Enabled and visible"
And I click on "Save" "button"
And I log out
# Teacher sets up assessment form and changes the phase to submission.
And I log in as "teacher1"
And I follow "Course1"
And I edit assessment form in workshop "TestWorkshop" as:"
| id_description__idx_0_editor | Aspect1 |
| id_description__idx_1_editor | Aspect2 |
| id_description__idx_2_editor | |
And I change phase in workshop "TestWorkshop" to "Submission phase"
And I log out
# Student1 submits.
And I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission1 |
| Submission content | Some content |
And I log out
# Student2 submits.
And I log in as "student2"
And I follow "Course1"
And I add a submission in workshop "TestWorkshop" as:"
| Title | Submission2 |
| Submission content | Some content |
And I log out
# teacher1 allocates reviewers and changes the phase to assessment
And I log in as "teacher1"
And I follow "Course1"
And I follow "TestWorkshop"
And I should see "to allocate: 2"
Then I should see "Workshop submissions report"
And I should see "Submitted (2) / not submitted (0)"
And I should see "Submission1" in the "Sam1 Student1" "table_row"
And I should see "Submission2" in the "Sam2 Student2" "table_row"
And I allocate submissions in workshop "TestWorkshop" as:"
| Participant | Reviewer |
| Sam1 Student1 | Sam2 Student2 |
| Sam2 Student2 | Sam1 Student1 |
And I follow "TestWorkshop"
And I should see "to allocate: 0"
And I change phase in workshop "TestWorkshop" to "Assessment phase"
And I log out
Scenario: Hide export to portfolio button if admin disable portfolio feature
Given I log in as "admin"
And I expand "Site administration" node
And I follow "Advanced features"
And I set the following administration settings values:
| Enable portfolios | 0 |
And I log out
When I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
And I follow "My submission"
Then I should see "Submission1"
Then "Export submission to portfolio" "button" should not exist
And I log out
Scenario: Students can export to portfolio their own submission
Given I log in as "student1"
And I follow "Course1"
And I follow "TestWorkshop"
When I follow "My submission"
Then I should see "Submission1"
Then "Export submission to portfolio" "button" should exist
And I click on "Export submission to portfolio" "button"
Then I should see "Available export formats"
And I click on "Next" "button"
Then I should see "Summary of your export"
And I click on "Continue" "button"
Then I should see "Return to where you were"
And I log out
Scenario: Students can not export to portfolio when viewing submission of other people
Given I log in as "teacher1"
And I follow "Course1"
And I change phase in workshop "TestWorkshop" to "Closed"
And I log out
When I log in as "student2"
And I follow "Course1"
And I follow "TestWorkshop"
And I follow "Submission1"
Then "Export submission to portfolio" "button" should not exist
And I log out

View File

@ -0,0 +1,304 @@
<?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 mod_workshop_portfolio_caller class defined in mod/workshop/classes/portfolio_caller.php
*
* @package mod_workshop
* @copyright 2016 An Pham Van <an.phamvan@harveynash.vn>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/workshop/locallib.php');
require_once(__DIR__ . '/fixtures/testable.php');
require_once($CFG->dirroot . '/mod/workshop/classes/portfolio_caller.php');
/**
* Unit tests for mod_workshop_portfolio_caller class
*
* @package mod_workshop
* @copyright 2016 An Pham Van <an.phamvan@harveynash.vn>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_workshop_porfolio_caller_testcase extends advanced_testcase {
/** @var stdClass $workshop Basic workshop data stored in an object. */
protected $workshop;
/** @var stdClass mod info */
protected $cm;
/**
* Setup testing environment.
*/
protected function setUp() {
parent::setUp();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course();
$workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course));
$this->cm = get_coursemodule_from_instance('workshop', $workshop->id, $course->id, false, MUST_EXIST);
$this->workshop = new testable_workshop($workshop, $this->cm, $course);
}
/**
* Tear down.
*/
protected function tearDown() {
$this->workshop = null;
$this->cm = null;
parent::tearDown();
}
/**
* Test function load_data()
* Case 1: User exports the assessment of his/her own submission.
* Assert that this function can load the correct assessment.
*/
public function test_load_data_for_own_submissionassessment() {
$this->resetAfterTest(true);
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
$this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1, 'assessmentid' => $asid1));
$portfoliocaller->load_data();
$reflector = new ReflectionObject($portfoliocaller);
$assessment = $reflector->getProperty('assessment');
$assessment->setAccessible(true);
$result = $assessment->getValue($portfoliocaller);
$this->assertEquals($asid1, $result->id);
}
/**
* Test function load_data()
* Case 2: User exports his/her own submission.
* Assert that this function can load the correct submission.
*/
public function test_load_data_for_own_submission() {
$this->resetAfterTest(true);
$student1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
$portfoliocaller->load_data();
$reflector = new ReflectionObject($portfoliocaller);
$submission = $reflector->getProperty('submission');
$submission->setAccessible(true);
$result = $submission->getValue($portfoliocaller);
$this->assertEquals($subid1, $result->id);
}
/**
* Test function get_return_url()
* Assert that this function can return the correct url.
*/
public function test_get_return_url() {
$this->resetAfterTest(true);
$student1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
$reflector = new ReflectionObject($portfoliocaller);
$cm = $reflector->getProperty('cm');
$cm->setAccessible(true);
$cm->setValue($portfoliocaller, $this->cm);
$expected = 'http://www.example.com/moodle/mod/workshop/submission.php?cmid='.$this->cm->id;
$this->assertEquals($expected, $portfoliocaller->get_return_url());
}
/**
* Test function get_navigation()
* Assert that this function can return the navigation array.
*/
public function test_get_navigation() {
$this->resetAfterTest(true);
$student1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
$portfoliocaller->load_data();
$reflector = new ReflectionObject($portfoliocaller);
$cm = $reflector->getProperty('cm');
$cm->setAccessible(true);
$cm->setValue($portfoliocaller, $this->cm);
$this->assertTrue(is_array($portfoliocaller->get_navigation()));
}
/**
* Test function check_permissions()
* Case 1: User exports assessment.
* Assert that this function can return a boolean value
* to indicate that the user has capability to export the assessment.
*/
public function test_check_permissions_exportownsubmissionassessment() {
global $DB;
$this->resetAfterTest(true);
$context = context_module::instance($this->cm->id);
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id, $roleids['student']);
$this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id, $roleids['student']);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
$this->setUser($student1);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1, 'assessmentid' => $asid1));
$reflector = new ReflectionObject($portfoliocaller);
$cm = $reflector->getProperty('cm');
$cm->setAccessible(true);
$cm->setValue($portfoliocaller, $this->cm);
// Case 1: If user has capabilities exportownsubmission prevented and exportownsubmissionassessment prevented
// then check_permissions should return false.
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_PREVENT);
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_PREVENT);
$this->assertFalse($portfoliocaller->check_permissions());
// Case 2: If user has capabilities exportownsubmission allowed and exportownsubmissionassessment prevented
// then check_permissions should return false.
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_ALLOW);
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_PREVENT);
$this->assertFalse($portfoliocaller->check_permissions());
// Case 3: If user has capabilities exportownsubmission prevented and exportownsubmissionassessment allowed
// then check_permissions should return false.
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_PREVENT);
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_ALLOW);
$this->assertFalse($portfoliocaller->check_permissions());
// Case 4: If user has capabilities exportownsubmission allowed and exportownsubmissionassessment allowed
// then check_permissions should return true.
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_ALLOW);
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmissionassessment', CAP_ALLOW);
$this->assertTrue($portfoliocaller->check_permissions());
}
/**
* Test function check_permissions()
* Case 2: User exports submission.
* Assert that this function can return a boolean value
* to indicate that the user has capability to export submission.
*/
public function test_check_permissions_exportownsubmission() {
global $DB;
$this->resetAfterTest(true);
$context = context_module::instance($this->cm->id);
$student1 = $this->getDataGenerator()->create_user();
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id, $roleids['student']);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$this->setUser($student1);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
$reflector = new ReflectionObject($portfoliocaller);
$cm = $reflector->getProperty('cm');
$cm->setAccessible(true);
$cm->setValue($portfoliocaller, $this->cm);
// Case 1: If user has capability to export submission then check_permissions should return true.
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_ALLOW);
$this->assertTrue($portfoliocaller->check_permissions());
// Case 2: If user doesn't have capability to export submission then check_permissions should return false.
role_change_permission($roleids['student'], $context, 'mod/workshop:exportownsubmission', CAP_PREVENT);
$this->assertFalse($portfoliocaller->check_permissions());
}
/**
* Test function get_sha1()
* Case 1: User exports the assessment of his/her own submission.
* Assert that this function can return a hash string.
*/
public function test_get_sha1_assessment() {
$this->resetAfterTest(true);
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
$this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$asid1 = $workshopgenerator->create_assessment($subid1, $student2->id);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1, 'assessmentid' => $asid1));
$portfoliocaller->load_data();
$this->assertTrue(is_string($portfoliocaller->get_sha1()));
}
/**
* Test function get_sha1()
* Case 2: User exports his/her own submission.
* Assert that this function can return a hash string.
*/
public function test_get_sha1_submission() {
$this->resetAfterTest(true);
$student1 = $this->getDataGenerator()->create_user();
$student2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student1->id, $this->workshop->course->id);
$this->getDataGenerator()->enrol_user($student2->id, $this->workshop->course->id);
$workshopgenerator = $this->getDataGenerator()->get_plugin_generator('mod_workshop');
$subid1 = $workshopgenerator->create_submission($this->workshop->id, $student1->id);
$portfoliocaller = new mod_workshop_portfolio_caller(array('submissionid' => $subid1));
$portfoliocaller->load_data();
$this->assertTrue(is_string($portfoliocaller->get_sha1()));
}
/**
* Test function display_name()
* Assert that this function can return the name of the module ('Workshop').
*/
public function test_display_name() {
$this->resetAfterTest(true);
$this->assertEquals('Workshop', mod_workshop_portfolio_caller::display_name());
}
}

View File

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016052300; // The current module version (YYYYMMDDXX)
$plugin->version = 2016062901; // The current module version (YYYYMMDDXX)
$plugin->requires = 2016051900; // Requires this Moodle version.
$plugin->component = 'mod_workshop';
$plugin->cron = 60; // Give as a chance every minute.