Commit 041ec5f4 authored by Matt Clarkson's avatar Matt Clarkson

Bug 1789297: module/lti: LTI assignment submission

behatnotneeded
Change-Id: I4341264ec1b2d301b9ca9dd5d777fe90ce91a9e6
parent 4467a370
......@@ -1279,7 +1279,7 @@ class ArtefactTypeAnnotationfeedback extends ArtefactType {
// we are dealing with an annotation added since smartevidence was added
$defaultval = $evidence->state;
if ($options = Framework::get_my_assessment_options_for_user($view->get('owner'), $evidence->framework)) {
if ($options = Framework::get_my_assessment_options_for_user($view, $evidence->framework)) {
if (!array_key_exists($defaultval, $options)) {
$defaultval = null;
}
......
......@@ -1258,7 +1258,7 @@ class Collection {
* @param int $owner The owner of the collection (if not just $USER)
* @throws SystemException
*/
public function submit($group = null, $submittedhost = null, $owner = null) {
public function submit($group = null, $submittedhost = null, $owner = null, $sendnotification=true) {
global $USER;
if ($this->is_submitted()) {
......@@ -1318,7 +1318,7 @@ class Collection {
'name' => $this->name,
'group' => ($group) ? $group->id : null,
'groupname' => ($group) ? $group->name : null));
if ($group) {
if ($group && $sendnotification) {
activity_occurred(
'groupmessage',
array(
......
......@@ -6922,7 +6922,7 @@ class View {
return $data;
}
public function submit($group) {
public function submit($group, $sendnotification=true) {
global $USER;
if ($this->is_submitted()) {
......@@ -6937,31 +6937,34 @@ class View {
'name' => $this->title,
'group' => $group->id,
'groupname' => $group->name));
activity_occurred(
'groupmessage',
array(
'group' => $group->id,
'roles' => $group->roles,
'url' => $this->get_url(false),
'strings' => (object) array(
'urltext' => (object) array('key' => 'view'),
'subject' => (object) array(
'key' => 'viewsubmittedsubject1',
'section' => 'activity',
'args' => array($group->name),
),
'message' => (object) array(
'key' => 'viewsubmittedmessage1',
'section' => 'activity',
'args' => array(
display_name($USER, null, false, true),
$this->title,
$group->name,
if ($sendnotification) {
activity_occurred(
'groupmessage',
array(
'group' => $group->id,
'roles' => $group->roles,
'url' => $this->get_url(false),
'strings' => (object) array(
'urltext' => (object) array('key' => 'view'),
'subject' => (object) array(
'key' => 'viewsubmittedsubject1',
'section' => 'activity',
'args' => array($group->name),
),
'message' => (object) array(
'key' => 'viewsubmittedmessage1',
'section' => 'activity',
'args' => array(
display_name($USER, null, false, true),
$this->title,
$group->name,
),
),
),
),
)
);
)
);
}
}
/**
......
......@@ -828,7 +828,7 @@ class Framework {
return false;
}
$options = self::get_my_assessment_options_for_user($view->get('owner'), $evidence->framework);
$options = self::get_my_assessment_options_for_user($view, $evidence->framework);
if (!$options) {
// not allowed to set the assessment so we just show the current state as html
$choices = self::get_evidence_statuses($collection->get('framework'));
......@@ -942,26 +942,28 @@ class Framework {
/**
* Check to see if a user can set the assessment status for a piece of evidence.
*
* @param string $ownerid The owner of the smart evidence annotation
* @param string $view The view being assessed
* @param string $framework ID of the framework
*
* @return bool
*/
public static function can_assess_user($ownerid, $framework = null) {
return (boolean) static::get_my_assessment_options_for_user($ownerid, $framework);
public static function can_assess_user($view, $framework = null) {
return (boolean) static::get_my_assessment_options_for_user($view, $framework);
}
/**
* Get assessment status options for a piece of evidence.
*
* @param string $ownerid The owner of the smart evidence annotation
* @param string $view The view being assessed
* @param string $framework ID of the framework
* @return array Options for select dropdown
*/
public static function get_my_assessment_options_for_user($ownerid, $framework = null) {
public static function get_my_assessment_options_for_user($view, $framework = null) {
global $USER;
$ownerid = $view->get('owner');
if (empty($ownerid) || !is_numeric($ownerid)) {
return false;
}
......@@ -983,6 +985,11 @@ class Framework {
$isadminofowner = true;
}
}
else if (!empty($USER->get('id')) && group_user_can_assess_submitted_views($view->get('submittedgroup'), $USER->get('id'))) {
if ($USER->get('id') != $owner->get('id')) {
$isadminofowner = true;
}
}
require_once(get_config('libroot') . 'institution.php');
$institution = new Institution($institution);
......
......@@ -97,7 +97,7 @@ else if ($action == 'evidence') {
}
require_once('view.php');
$view = new View($evidence->view);
if (!Framework::can_assess_user($view->get('owner'), $evidence->framework)) {
if (!Framework::can_assess_user($view, $evidence->framework)) {
json_reply(true, get_string('accessdenied', 'error'));
exit;
}
......
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="module/lti/db" VERSION="20180307" COMMENT="XMLDB file for lti related tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="lti_assessment">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" />
<FIELD NAME="oauthserver" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="resourcelinkid" TYPE="text" NOTNULL="true" />
<FIELD NAME="contextid" TYPE="text" NOTNULL="true" />
<FIELD NAME="lisoutcomeserviceurl" TYPE="text" NOTNULL="true" />
<FIELD NAME="contexttitle" TYPE="text" NOTNULL="false" />
<FIELD NAME="resourcelinktitle" TYPE="text" NOTNULL="false" />
<FIELD NAME="group" TYPE="int" NOTNULL="true" />
<FIELD NAME="emailnotifications" TYPE="int" LENGTH="1" DEFAULT="1" NOTNULL="true" />
<FIELD NAME="lock" TYPE="int" LENGTH="1" DEFAULT="1" NOTNULL="true" />
<FIELD NAME="archive" TYPE="int" LENGTH="1" DEFAULT="0" NOTNULL="true" />
<FIELD NAME="timeconfigured" TYPE="datetime" NOTNULL="false" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" />
<KEY NAME="oauthserverregistryfk" TYPE="foreign" FIELDS="oauthserver" REFTABLE="oauth_server_registry" REFFIELDS="id" />
<KEY NAME="groupfk" TYPE="foreign" FIELDS="group" REFTABLE="group" REFFIELDS="id" />
</KEYS>
<INDEXES>
<INDEX NAME="resourcelinkididx" UNIQUE="false" FIELDS="resourcelinkid" />
<INDEX NAME="contextididx" UNIQUE="false" FIELDS="contextid" />
</INDEXES>
</TABLE>
<TABLE NAME="lti_assessment_submission">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" />
<FIELD NAME="usr" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="ltiassessment" TYPE="int" LENGTH="10" NOTNULL="true" />
<FIELD NAME="lisresultsourceid" TYPE="text" NOTNULL="true" />
<FIELD NAME="timesubmitted" TYPE="datetime" NOTNULL="true" />
<FIELD NAME="grade" TYPE="int" LENGTH="4" NOTNULL="false" />
<FIELD NAME="timegraded" TYPE="datetime" NOTNULL="false" />
<FIELD NAME="gradedbyusr" TYPE="int" LENGTH="10" NOTNULL="false" />
<FIELD NAME="collectionid" TYPE="int" LENGTH="10" NOTNULL="false" />
<FIELD NAME="viewid" TYPE="int" LENGTH="10" NOTNULL="false" />
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" />
<KEY NAME="userfk" TYPE="foreign" FIELDS="usr" REFTABLE="usr" REFFIELDS="id" />
<KEY NAME="collectionidfk" TYPE="foreign" FIELDS="collectionid" REFTABLE="collection" REFFIELDS="id" />
<KEY NAME="viewfk" TYPE="foreign" FIELDS="viewid" REFTABLE="view" REFFIELDS="id" />
<KEY NAME="ltiassessmentfk" TYPE="foreign" FIELDS="ltiassessment" REFTABLE="lti_assessment" REFFIELDS="id" />
</KEYS>
<INDEXES>
<INDEX NAME="lisresultsourceididx" UNIQUE="false" FIELDS="lisresultsourceid" />
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
<?php
/**
*
* @package mahara
* @subpackage module-lti
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
function xmldb_module_lti_upgrade($oldversion=0) {
$status = true;
/**
* Ensure that all the Web Services tables have been created - even if we
* are transitioning from artefact/webservice to webservice
*/
if ($oldversion < 2018071009) {
log_debug('Adding "lti_assessment" table');
$table = new XMLDBTable('lti_assessment');
$table->addFieldInfo('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
$table->addFieldInfo('oauthserver', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('resourcelinkid', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL);
$table->addFieldInfo('contextid', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL);
$table->addFieldInfo('lisoutcomeserviceurl', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL);
$table->addFieldInfo('contexttitle', XMLDB_TYPE_TEXT);
$table->addFieldInfo('resourcelinktitle', XMLDB_TYPE_TEXT);
$table->addFieldInfo('group', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('emailnotifications', XMLDB_TYPE_INTEGER, 1, null, XMLDB_NOTNULL, null, null, null, 1);
$table->addFieldInfo('lock', XMLDB_TYPE_INTEGER, 1, null, XMLDB_NOTNULL, null, null, null, 1);
$table->addFieldInfo('archive', XMLDB_TYPE_INTEGER, 1, null, XMLDB_NOTNULL, null, null, null, 0);
$table->addFieldInfo('timeconfigured', XMLDB_TYPE_DATETIME);
$table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->addKeyInfo('oauthserverregistryfk', XMLDB_KEY_FOREIGN, array('oauthserver'), 'oauth_server_registry', array('id'));
$table->addKeyInfo('groupfk', XMLDB_KEY_FOREIGN, array('group'), 'group', array('id'));
$table->addIndexInfo('resourcelinkididx', XMLDB_INDEX_NOTUNIQUE, array('resourcelinkid'));
$table->addIndexInfo('contextididx', XMLDB_INDEX_NOTUNIQUE, array('contextid'));
create_table($table);
log_debug('Adding "lti_assessment_submission" table');
$table = new XMLDBTable('lti_assessment_submission');
$table->addFieldInfo('id', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, XMLDB_SEQUENCE);
$table->addFieldInfo('usr', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('ltiassessment', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL);
$table->addFieldInfo('lisresultsourceid', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL);
$table->addFieldInfo('timesubmitted', XMLDB_TYPE_DATETIME, null, null, XMLDB_NOTNULL);
$table->addFieldInfo('grade', XMLDB_TYPE_INTEGER, 4);
$table->addFieldInfo('timegraded', XMLDB_TYPE_DATETIME);
$table->addFieldInfo('gradedbyusr', XMLDB_TYPE_INTEGER, 10);
$table->addFieldInfo('collectionid', XMLDB_TYPE_INTEGER, 10);
$table->addFieldInfo('viewid', XMLDB_TYPE_INTEGER, 10);
$table->addKeyInfo('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->addKeyInfo('userfk', XMLDB_KEY_FOREIGN, array('usr'), 'usr', array('id'));
$table->addKeyInfo('collectionidfk', XMLDB_KEY_FOREIGN, array('collectionid'), 'collection', array('id'));
$table->addKeyInfo('ltiassessmentfk', XMLDB_KEY_FOREIGN, array('ltiassessment'), 'lti_assessment', array('id'));
$table->addKeyInfo('viewfk', XMLDB_KEY_FOREIGN, array('viewid'), 'view', array('id'));
$table->addIndexInfo('lisresultsourceididx', XMLDB_INDEX_NOTUNIQUE, array('lisresultsourceid'));
create_table($table);
}
return true;
}
\ No newline at end of file
<?php
/**
*
* @package mahara
* @subpackage core
* @author Catalyst IT Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
* @copyright For copyright information on Mahara, please see the README file distributed with this software.
*
*/
define('INTERNAL', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once(get_config('libroot') . 'collection.php');
require_once(get_config('libroot') . 'view.php');
// We relocated this file to view/index.php now that we have merged the pages and collections list.
// Redirect to the new URL.
$collectionid = param_integer('collectionid', 0);
$viewid = param_integer('viewid', 0);
if ($collectionid) {
$collection = new Collection($collectionid);
redirect($collection->get_url());
}
else if ($viewid) {
$view = new View($viewid);
redirect($view->get_url());
}
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>Grade</h3>
<p>Please select a grade from 0 - 100.</p>
......@@ -11,21 +11,52 @@
defined('INTERNAL') || die();
$string['archive'] = 'Archive when graded';
$string['archivedescription'] = 'After a grade has been awarded, a snapshot of the portfolio will be taken.';
$string['autoconfiguredesc'] = 'Automatically enable settings and configurations needed for LTI.';
$string['autoconfiguretitle'] = 'Auto-configure LTI';
$string['autocreateusers'] = 'Auto-create users';
$string['autocreationnotenabled'] = 'Auto-creation of user accounts is not enabled';
$string['configstep'] = 'Conguration item';
$string['portfolioname'] = 'Portfolio title';
$string['configstep'] = 'Configuration item';
$string['configstepstatus'] = 'Status';
$string['configuration'] = 'Assessment settings';
$string['configurationintro'] = 'You can change the submission settings as long as nobody has submitted a portfolio yet. Once there is a submission, the settings are locked in.';
$string['configurationsaved'] = 'Assessment settings saved';
$string['emailtutors'] = 'Email tutors on student submission';
$string['emailtutorsdescription'] = '';
$string['grade_description'] = '';
$string['grade'] = 'Grade';
$string['gradedby'] = 'Graded by';
$string['gradereceived'] = 'You were awarded a grade of %s out of 100 by %s at %s';
$string['gradesubmissions'] = 'Submissions for grading';
$string['gradesubmitted'] = 'Grade successfully submitted';
$string['groupname'] = 'Assessment submissions for "%s" - "%s"';
$string['institutiondenied'] = 'Access to "%s" is denied. Please contact your institution administrator.';
$string['lock'] = 'Unlock portfolio after grading';
$string['lockdescription'] = 'Users make changes to their portfolio after it has been graded.';
$string['ltiserviceexists'] = 'The LTI service group is registered.';
$string['nocollections'] = 'You do not have any portfolios that can be submitted for assessment.';
$string['noticeenabled'] = 'The LTI API is enabled.';
$string['noticenotenabled'] = 'The LTI API is <b>not</b> enabled.';
$string['notconfigured'] = 'Currently, this activity does not allow submissions. Please check back later.';
$string['notreadylabel'] = 'Not ready';
$string['oauthprotocolenabled'] = 'OAuth protocol enabled';
$string['parentauthforlti'] = 'Parent authority';
$string['portfoliosubmitted'] = 'You submitted the portfolio "%s" for assessment on %s.';
$string['readylabel'] = 'Ready';
$string['restprotocolenabled'] = 'REST protocol enabled';
$string['usernameexists2'] = 'Username "%s" is not valid.';
$string['saveandrelease'] = 'Save and allow submissions';
$string['submitportfolio'] = 'Submit a portfolio for assessment';
$string['submitto'] = 'Submit this portfolio for assessment to %s, %s';
$string['submitintro'] = 'Select the page or collection that you want to submit to this activity. If you do not yet have a portfolio, <a href="%sview/index.php" target="_blank">create one</a>. Once you are done, reload the activity from the LMS.';
$string['timegraded'] = 'Time graded';
$string['timesubmitted'] = 'Time submitted';
$string['usernameexists1'] = 'Username "%s" already exists.';
$string['viewsubmittedmessage1'] = '%s has submitted "%s" to %s
Please grade this submission via "%s"';
$string['viewsubmittedsubject1'] = 'Assessment submission to %s';
$string['webserviceauthdisabled'] = 'Web service authentication is not enabled for this institution';
$string['webserviceproviderenabled'] = 'Incoming web service requests allowed';
$string['institutiondenied'] = 'Access to \'%s\' is denied. Please contact your institution administrator.';
$string['notreadylabel'] = 'Not ready';
$string['readylabel'] = 'Ready';
$string['parentauthforlti'] = 'Parent authority';
......@@ -16,6 +16,9 @@ defined('INTERNAL') || die();
*/
class PluginModuleLti extends PluginModule {
public static $grading_roles = array('Instructor');
public static $graded_roles = array('Learner');
private static $default_config = array(
'autocreateusers' => false,
'parentauth' => null,
......@@ -207,4 +210,670 @@ class PluginModuleLti extends PluginModule {
);
return $fields;
}
/**
* Form for submitting collections/pages for lti assessessment
*/
public static function submit_for_grading_form() {
global $USER;
require_once(get_config('libroot') . 'view.php');
list($collections, $views) = View::get_views_and_collections($USER->get('id'));
$viewoptions = $collectionoptions = array();
foreach ($collections as $c) {
if (empty($c['submittedgroup']) && empty($c['submittedhost'])) {
$collectionoptions['c:' . $c['id']] = $c['name'];
}
}
foreach ($views as $v) {
if ($v['type'] != 'profile' && empty($v['submittedgroup']) && empty($v['submittedhost'])) {
$viewoptions['v:' . $v['id']] = $v['name'];
}
}
$options = $optgroups = null;
if (!empty($collectionoptions) && !empty($viewoptions)) {
$optgroups = array(
'collections' => array(
'label' => get_string('Collections', 'collection'),
'options' => $collectionoptions,
),
'views' => array(
'label' => get_string('Views', 'view'),
'options' => $viewoptions,
),
);
}
else if (!empty($collectionoptions)) {
$options = $collectionoptions;
}
else if (!empty($viewoptions)) {
$options = $viewoptions;
}
if (empty($options) && empty($optgroups)) {
return get_string('nocollections', 'module.lti');
}
return pieform(array(
'name' => 'lti_submit_for_grading',
'method' => 'post',
'renderer' => 'div',
'class' => 'form-inline',
'autofocus' => false,
'successcallback' => array('PluginModuleLti', 'submit_for_grading_form_submit'),
'elements' => array(
'inputgroup' => array(
'type' => 'fieldset',
'class' => 'input-group',
'elements' => array(
'options' => array(
'type' => 'select',
'title' => get_string('forassessment1', 'view'),
'collapseifoneoption' => false,
'optgroups' => $optgroups,
'options' => $options,
'class' => 'forassessment text-inline text-small',
),
'submit' => array(
'type' => 'button',
'usebuttontag' => true,
'class' => 'btn-primary input-group-btn ',
'value' => get_string('submit')
),
),
),
),
));
}
/**
* Save grading submission form
*/
public static function submit_for_grading_form_submit(Pieform $form, $values) {
global $USER, $SESSION;
$viewid = $collectionid = null;
if (substr($values['options'], 0, 2) == 'v:') {
$viewid = substr($values['options'], 2);
if (!$view = get_record('view', 'id', $viewid, 'owner', $USER->get('id'))) {
throw new AccessDeniedException("You do not own this view");
}
}
if (substr($values['options'], 0, 2) == 'c:') {
$collectionid = substr($values['options'], 2);
if (!$colleciton = get_record('collection', 'id', $collectionid, 'owner', $USER->get('id'))) {
throw new AccessDeniedException("You do not own this collection");
}
}
if (!$assessment = get_record('lti_assessment', 'id', $SESSION->get('lti.assessment'))) {
throw new MaharaException("Missing assessment record");
}
if (!empty($collectionid)) {
$collection = new Collection($collectionid);
$collection->submit(get_group_by_id($assessment->group), null, null, false);
$submissionname = $collection->get('name');
}
else {
$view = new View($viewid);
$view->submit(get_group_by_id($assessment->group), false);
$submissionname = $view->get('title');
}
$sub = new stdClass();
$sub->usr = $USER->get('id');
$sub->ltiassessment = $SESSION->get('lti.assessment');
$sub->lisresultsourceid = $SESSION->get('lti.lis_result_sourcedid');
$sub->timesubmitted = db_format_timestamp(time());
$sub->collectionid = $collectionid;
$sub->viewid = $viewid;
insert_record('lti_assessment_submission', $sub);
$group = get_record('group', 'id', $assessment->group);
$grouproles = get_column('grouptype_roles', 'role', 'grouptype', 'standard', 'see_submitted_views', 1);
if ($assessment->emailnotifications) {
activity_occurred(
'groupmessage',
array(
'group' => $assessment->group,
'roles' => $grouproles,
'strings' => (object) array(
'subject' => (object) array(
'key' => 'viewsubmittedsubject1',
'section' => 'module.lti',
'args' => array($group->name),
),
'message' => (object) array(
'key' => 'viewsubmittedmessage1',
'section' => 'module.lti',
'args' => array(
display_name($USER, null, false, true),
$submissionname,
$group->name,
$assessment->contexttitle,
),
),
),
)
);
}
redirect('/module/lti/submission.php');
}
public static function submit_from_view_or_collection_form(View $view) {
global $SESSION, $USER;
$sql = "SELECT a.*
FROM {lti_assessment} a
LEFT JOIN {lti_assessment_submission} s ON a.id = s.ltiassessment AND s.usr = ?
WHERE s.id IS NULL AND a.id = ?";
if (!$assessment = get_record_sql($sql, array($USER->get('id'), $SESSION->get('lti.assessment')))) {
return false;
}
if ($collection = $view->get('collection')) {
$value = 'c:' . $collection->get('id');
}
else {
$value = 'v:' . $view->get('id');
}
return pieform(array(
'name' => 'lti_submit_for_grading',
'method' => 'post',
'renderer' => 'div',
'class' => 'form-inline',
'autofocus' => false,
'successcallback' => array('PluginModuleLti', 'submit_for_grading_form_submit'),
'elements' => array(
'text1' => array(
'type' => 'html',
'class' => 'text-inline',
'value' => get_string('submitto', 'module.lti', $assessment->resourcelinktitle, $assessment->contexttitle),
),
'inputgroup' => array(
'type' => 'fieldset',
'class' => 'input-group',
'elements' => array(
'submit' => array(
'type' => 'button',
'usebuttontag' => true,
'class' => 'btn-primary input-group-btn ',
'value' => get_string('submit')
),