/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.course.nodes.iq;
import java.io.File;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.panel.Panel;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.gui.control.generic.iframe.IFrameDisplayController;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.NotFoundMediaResource;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.UserSession;
import org.olat.core.util.Util;
import org.olat.core.util.event.EventBus;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.mail.MailBundle;
import org.olat.core.util.resource.OresHelper;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.course.CourseModule;
import org.olat.course.DisposedCourseRestartController;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.course.assessment.AssessmentManager;
import org.olat.course.auditing.UserNodeAuditManager;
import org.olat.course.highscore.ui.HighScoreRunController;
import org.olat.course.nodes.AssessableCourseNode;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.IQSELFCourseNode;
import org.olat.course.nodes.IQTESTCourseNode;
import org.olat.course.nodes.QTICourseNode;
import org.olat.course.nodes.SelfAssessableCourseNode;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.run.scoring.ScoreEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.fileresource.DownloadeableMediaResource;
import org.olat.fileresource.FileResourceManager;
import org.olat.ims.qti.process.AssessmentInstance;
import org.olat.ims.qti21.AssessmentTestSession;
import org.olat.ims.qti21.OutcomesListener;
import org.olat.ims.qti21.QTI21AssessmentResultsOptions;
import org.olat.ims.qti21.QTI21DeliveryOptions;
import org.olat.ims.qti21.QTI21LoggingAction;
import org.olat.ims.qti21.QTI21Service;
import org.olat.ims.qti21.model.DigitalSignatureOptions;
import org.olat.ims.qti21.ui.AssessmentResultController;
import org.olat.ims.qti21.ui.AssessmentTestDisplayController;
import org.olat.ims.qti21.ui.QTI21Event;
import org.olat.ims.qti21.ui.QTI21OverrideOptions;
import org.olat.ims.qti21.ui.ResourcesMapper;
import org.olat.instantMessaging.InstantMessagingService;
import org.olat.modules.ModuleConfiguration;
import org.olat.modules.assessment.AssessmentEntry;
import org.olat.modules.assessment.model.AssessmentEntryStatus;
import org.olat.repository.RepositoryEntry;
import org.olat.user.UserManager;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 19.05.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class QTI21AssessmentRunController extends BasicController implements GenericEventListener, OutcomesListener {
private static final OLATResourceable assessmentEventOres = OresHelper.createOLATResourceableType(AssessmentEvent.class);
private static final OLATResourceable assessmentInstanceOres = OresHelper.createOLATResourceableType(AssessmentInstance.class);
private Link startButton, showResultsButton, hideResultsButton, signatureDownloadLink;
private final VelocityContainer mainVC;
private boolean assessmentStopped = true;
private EventBus singleUserEventCenter;
private final boolean anonym;
private final UserSession userSession;
private final ModuleConfiguration config;
private final UserCourseEnvironment userCourseEnv;
private final QTICourseNode courseNode;
private final RepositoryEntry testEntry;
private final QTI21DeliveryOptions deliveryOptions;
private final QTI21OverrideOptions overrideOptions;
// The test is really assessment not a self test or a survey
private final boolean assessmentType = true;
private AssessmentResultController resultCtrl;
private AssessmentTestDisplayController displayCtrl;
private LayoutMain3ColsController displayContainerController;
private AtomicBoolean incrementAttempts = new AtomicBoolean(true);
@Autowired
private QTI21Service qtiService;
@Autowired
private CourseModule courseModule;
public QTI21AssessmentRunController(UserRequest ureq, WindowControl wControl,
UserCourseEnvironment userCourseEnv, QTICourseNode courseNode) {
super(ureq, wControl, Util.createPackageTranslator(CourseNode.class, ureq.getLocale()));
setTranslator(Util.createPackageTranslator(AssessmentTestDisplayController.class, getLocale(), getTranslator()));
this.courseNode = courseNode;
this.userCourseEnv = userCourseEnv;
userSession = ureq.getUserSession();
anonym = userSession.getRoles().isGuestOnly();
config = courseNode.getModuleConfiguration();
testEntry = courseNode.getReferencedRepositoryEntry();
singleUserEventCenter = userSession.getSingleUserEventCenter();
mainVC = createVelocityContainer("assessment_run");
addLoggingResourceable(LoggingResourceable.wrap(courseNode));
mainVC.contextPut("infobox", courseModule.isDisplayInfoBox());
mainVC.contextPut("changelogconfig", courseModule.isDisplayChangeLog());
if(courseNode instanceof IQTESTCourseNode) {
mainVC.contextPut("type", "test");
} else if(courseNode instanceof IQSELFCourseNode) {
mainVC.contextPut("type", "self");
}
deliveryOptions = getDeliveryOptions();
overrideOptions = getOverrideOptions();
init(ureq);
initAssessment(ureq);
putInitialPanel(mainVC);
}
private void init(UserRequest ureq) {
startButton = LinkFactory.createButton("start", mainVC, this);
startButton.setElementCssClass("o_sel_start_qti21assessment");
startButton.setPrimary(true);
startButton.setVisible(!userCourseEnv.isCourseReadOnly());
// fetch disclaimer file
String sDisclaimer = config.getStringValue(IQEditController.CONFIG_KEY_DISCLAIMER);
if (sDisclaimer != null) {
VFSContainer baseContainer = userCourseEnv.getCourseEnvironment().getCourseFolderContainer();
int lastSlash = sDisclaimer.lastIndexOf('/');
if (lastSlash != -1) {
baseContainer = (VFSContainer)baseContainer.resolve(sDisclaimer.substring(0, lastSlash));
sDisclaimer = sDisclaimer.substring(lastSlash);
// first check if disclaimer exists on filesystem
if (baseContainer == null || baseContainer.resolve(sDisclaimer) == null) {
showWarning("disclaimer.file.invalid", sDisclaimer);
} else {
//screenreader do not like iframes, display inline
IFrameDisplayController iFrameCtr = new IFrameDisplayController(ureq, getWindowControl(), baseContainer);
listenTo(iFrameCtr);//dispose automatically
mainVC.put("disc", iFrameCtr.getInitialComponent());
iFrameCtr.setCurrentURI(sDisclaimer);
mainVC.contextPut("hasDisc", Boolean.TRUE);
}
}
}
if (assessmentType) {
checkChats(ureq);
singleUserEventCenter.registerFor(this, getIdentity(), InstantMessagingService.TOWER_EVENT_ORES);
}
}
private void initAssessment(UserRequest ureq) {
// config : show score info
boolean enableScoreInfo= config.getBooleanSafe(IQEditController.CONFIG_KEY_ENABLESCOREINFO);
mainVC.contextPut("enableScoreInfo", new Boolean(enableScoreInfo));
// configuration data
int maxAttempts = deliveryOptions.getMaxAttempts();
if(maxAttempts > 0) {
mainVC.contextPut("attemptsConfig", new Integer(maxAttempts));
} else {
mainVC.contextPut("attemptsConfig", Boolean.FALSE);
}
if (courseNode instanceof AssessableCourseNode) {
AssessableCourseNode assessableCourseNode = (AssessableCourseNode) courseNode;
if (assessableCourseNode.hasScoreConfigured() || userCourseEnv.isCoach()){
HighScoreRunController highScoreCtr = new HighScoreRunController(ureq, getWindowControl(), userCourseEnv, courseNode);
if (highScoreCtr.isViewHighscore()) {
Component highScoreComponent = highScoreCtr.getInitialComponent();
mainVC.put("highScore", highScoreComponent);
}
}
}
// user data
if (courseNode instanceof SelfAssessableCourseNode) {
SelfAssessableCourseNode acn = (SelfAssessableCourseNode)courseNode;
ScoreEvaluation scoreEval = acn.getUserScoreEvaluation(userCourseEnv);
Integer attempts = acn.getUserAttempts(userCourseEnv);
if (scoreEval != null) {
mainVC.contextPut("resultsVisible", Boolean.TRUE);
mainVC.contextPut("hasResults", Boolean.TRUE);
mainVC.contextPut("score", AssessmentHelper.getRoundedScore(scoreEval.getScore()));
mainVC.contextPut("hasPassedValue", (scoreEval.getPassed() == null ? Boolean.FALSE : Boolean.TRUE));
mainVC.contextPut("passed", scoreEval.getPassed());
mainVC.contextPut("attempts", attempts); //at least one attempt
mainVC.contextPut("showChangeLog", Boolean.TRUE && enableScoreInfo);
exposeResults(true);
} else {
exposeResults(false);
}
} else if(courseNode instanceof IQTESTCourseNode) {
IQTESTCourseNode testCourseNode = (IQTESTCourseNode)courseNode;
AssessmentEntry assessmentEntry = testCourseNode.getUserAssessmentEntry(userCourseEnv);
if(assessmentEntry == null) {
mainVC.contextPut("blockAfterSuccess", Boolean.FALSE);
mainVC.contextPut("score", null);
mainVC.contextPut("hasPassedValue", Boolean.FALSE);
mainVC.contextPut("passed", Boolean.FALSE);
mainVC.contextPut("comment", null);
mainVC.contextPut("attempts", 0);
mainVC.contextPut("showChangeLog", Boolean.FALSE);
} else {
Boolean passed = assessmentEntry.getPassed();
//block if test passed (and config set to check it)
Boolean blocked = Boolean.FALSE;
boolean blockAfterSuccess = deliveryOptions.isBlockAfterSuccess();
if(blockAfterSuccess && passed != null && passed.booleanValue()) {
blocked = Boolean.TRUE;
}
mainVC.contextPut("blockAfterSuccess", blocked);
boolean resultsVisible = assessmentEntry.getUserVisibility() == null || assessmentEntry.getUserVisibility().booleanValue();
mainVC.contextPut("resultsVisible", resultsVisible);
mainVC.contextPut("score", AssessmentHelper.getRoundedScore(assessmentEntry.getScore()));
mainVC.contextPut("hasPassedValue", (passed == null ? Boolean.FALSE : Boolean.TRUE));
mainVC.contextPut("passed", passed);
if(resultsVisible) {
StringBuilder comment = Formatter.stripTabsAndReturns(testCourseNode.getUserUserComment(userCourseEnv));
if (comment != null && comment.length() > 0) {
mainVC.contextPut("comment", StringHelper.xssScan(comment));
}
}
Integer attempts = assessmentEntry.getAttempts();
mainVC.contextPut("attempts", attempts == null ? new Integer(0) : attempts);
boolean showChangelog = (!anonym && enableScoreInfo && resultsVisible && isResultVisible(config));
mainVC.contextPut("showChangeLog", showChangelog);
if(deliveryOptions.isDigitalSignature()) {
AssessmentTestSession session = qtiService.getAssessmentTestSession(assessmentEntry.getAssessmentId());
if(session != null) {
File signature = qtiService.getAssessmentResultSignature(session);
if(signature != null && signature.exists()) {
signatureDownloadLink = LinkFactory.createLink("digital.signature.download.link", mainVC, this);
signatureDownloadLink.setIconLeftCSS("o_icon o_icon-fw o_icon_download");
signatureDownloadLink.setTarget("_blank");
Date issueDate = qtiService.getAssessmentResultSignatureIssueDate(session);
if(issueDate != null) {
mainVC.contextPut("signatureIssueDate", Formatter.getInstance(getLocale()).formatDateAndTime(issueDate));
}
}
}
}
exposeResults(resultsVisible);
}
}
}
private void checkChats (UserRequest ureq) {
List<?> allChats = null;
if (ureq != null) {
allChats = ureq.getUserSession().getChats();
}
if (allChats == null || allChats.size() == 0) {
startButton.setEnabled (true);
mainVC.contextPut("hasChatWindowOpen", false);
} else {
startButton.setEnabled (false);
mainVC.contextPut("hasChatWindowOpen", true);
}
}
/**
* WARNING! The variables and are not used
* in the velocity template and the CONFIG_KEY_RESULT_ON_HOME_PAGE is not editable
* in the configuration of the course element for QTI 2.1!!!!
*
* Provides the show results button if results available or a message with the visibility period.
*
* @param ureq
*/
private void exposeResults(boolean resultsVisible) {
//migration: check if old tests have no summary configured
boolean showResultsOnHomePage = config.getBooleanSafe(IQEditController.CONFIG_KEY_RESULT_ON_HOME_PAGE);
QTI21AssessmentResultsOptions showSummary = deliveryOptions.getAssessmentResultsOptions();
if(resultsVisible && !showSummary.none()) {
mainVC.contextPut("showResultsOnHomePage", new Boolean(showResultsOnHomePage));
boolean dateRelatedVisibility = isResultVisible(config);
if(showResultsOnHomePage && dateRelatedVisibility) {
mainVC.contextPut("showResultsVisible",Boolean.TRUE);
showResultsButton = LinkFactory.createLink("command.showResults", "command.showResults", getTranslator(), mainVC, this, Link.LINK | Link.NONTRANSLATED);
showResultsButton.setCustomDisplayText(translate("showResults.title"));
showResultsButton.setElementCssClass("o_qti_show_assessment_results");
showResultsButton.setIconLeftCSS("o_icon o_icon-fw o_icon_open_togglebox");
hideResultsButton = LinkFactory.createLink("command.hideResults", "command.hideResults", getTranslator(), mainVC, this, Link.LINK | Link.NONTRANSLATED);
hideResultsButton.setCustomDisplayText(translate("showResults.title"));
hideResultsButton.setElementCssClass("o_qti_hide_assessment_results");
hideResultsButton.setIconLeftCSS("o_icon o_icon-fw o_icon_close_togglebox");
} else if(showResultsOnHomePage) {
Date startDate = config.getDateValue(IQEditController.CONFIG_KEY_RESULTS_START_DATE);
Date endDate = config.getDateValue(IQEditController.CONFIG_KEY_RESULTS_END_DATE);
String visibilityStartDate = Formatter.getInstance(getLocale()).formatDate(startDate);
String visibilityEndDate = "-";
if(endDate != null) {
visibilityEndDate = Formatter.getInstance(getLocale()).formatDate(endDate);
}
String visibilityPeriod = getTranslator().translate("showResults.visibility", new String[] { visibilityStartDate, visibilityEndDate});
mainVC.contextPut("visibilityPeriod", visibilityPeriod);
mainVC.contextPut("showResultsVisible", Boolean.FALSE);
} else {
mainVC.contextPut("showResultsVisible", Boolean.FALSE);
}
} else {
mainVC.contextPut("showResultsVisible", Boolean.FALSE);
}
if(!anonym && resultsVisible) {
UserNodeAuditManager am = userCourseEnv.getCourseEnvironment().getAuditManager();
mainVC.contextPut("log", am.getUserNodeLog(courseNode, getIdentity()));
}
}
private boolean isResultVisible(ModuleConfiguration modConfig) {
boolean isVisible = false;
boolean showResultsActive = modConfig.getBooleanSafe(IQEditController.CONFIG_KEY_DATE_DEPENDENT_RESULTS);
if(showResultsActive) {
Date startDate = modConfig.getDateValue(IQEditController.CONFIG_KEY_RESULTS_START_DATE);
Date endDate = modConfig.getDateValue(IQEditController.CONFIG_KEY_RESULTS_END_DATE);
if(startDate == null && endDate == null) {
isVisible = true;
} else {
Date currentDate = new Date();
if(startDate != null && currentDate.after(startDate) && (endDate == null || currentDate.before(endDate))) {
isVisible = true;
}
}
} else {
isVisible = true;
}
return isVisible;
}
@Override
protected void doDispose() {
if (assessmentType) {
singleUserEventCenter.deregisterFor(this, assessmentInstanceOres);
singleUserEventCenter.deregisterFor(this, InstantMessagingService.TOWER_EVENT_ORES);
if (!assessmentStopped) {
AssessmentEvent assessmentStoppedEvent = new AssessmentEvent(AssessmentEvent.TYPE.STOPPED, userSession);
singleUserEventCenter.fireEventToListenersOf(assessmentStoppedEvent, assessmentEventOres);
}
}
}
@Override
public void event(Event event) {
if (assessmentType) {
if (event.getCommand().startsWith("ChatWindow")) {
checkChats(null);
}
}
}
@Override
protected void event(UserRequest ureq, Component source, Event event) {
if(startButton == source && startButton.isEnabled() && startButton.isVisible()) {
doStart(ureq);
}else if(source == showResultsButton) {
doShowResults(ureq);
} else if (source == hideResultsButton) {
doHideResults();
} else if (source == signatureDownloadLink) {
doDownloadSignature(ureq);
}
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if (source == displayCtrl) {
if(event == Event.CANCELLED_EVENT) {
doExitAssessment(ureq, event, false);
initAssessment(ureq);
showInfo("assessment.test.cancelled");
} else if("suspend".equals(event.getCommand())) {
doExitAssessment(ureq, event, false);
initAssessment(ureq);
showInfo("assessment.test.suspended");
} else if(event instanceof QTI21Event) {
QTI21Event qe = (QTI21Event)event;
if(QTI21Event.EXIT.equals(qe.getCommand())) {
if(!displayCtrl.isResultsVisible()) {
doExitAssessment(ureq, event, true);
initAssessment(ureq);
}
} else if(QTI21Event.CLOSE_RESULTS.equals(qe.getCommand())) {
doExitAssessment(ureq, event, true);
initAssessment(ureq);
}
}
}
super.event(ureq, source, event);
}
private void doShowResults(UserRequest ureq) {
removeAsListenerAndDispose(resultCtrl);
AssessmentTestSession session = null;
if(courseNode instanceof SelfAssessableCourseNode) {
RepositoryEntry courseEntry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
session = qtiService.getLastAssessmentTestSessions(courseEntry, courseNode.getIdent(), testEntry, getIdentity());
} else {
AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
AssessmentEntry assessmentEntry = am.getAssessmentEntry(courseNode, getIdentity());
if(assessmentEntry.getAssessmentId() != null) {
session = qtiService.getAssessmentTestSession(assessmentEntry.getAssessmentId());
} else {
RepositoryEntry courseEntry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
session = qtiService.getLastAssessmentTestSessions(courseEntry, courseNode.getIdent(), testEntry, getIdentity());
}
}
if(session == null) {
mainVC.contextPut("showResults", Boolean.FALSE);
} else {
FileResourceManager frm = FileResourceManager.getInstance();
File fUnzippedDirRoot = frm.unzipFileResource(session.getTestEntry().getOlatResource());
URI assessmentObjectUri = qtiService.createAssessmentTestUri(fUnzippedDirRoot);
File submissionDir = qtiService.getAssessmentResultFile(session);
String mapperUri = registerCacheableMapper(ureq, "QTI21CNResults::" + session.getTestEntry().getKey(),
new ResourcesMapper(assessmentObjectUri, submissionDir));
resultCtrl = new AssessmentResultController(ureq, getWindowControl(), getIdentity(), true, session,
fUnzippedDirRoot, mapperUri, null, getDeliveryOptions().getAssessmentResultsOptions(), false, false);
listenTo(resultCtrl);
mainVC.put("resultReport", resultCtrl.getInitialComponent());
mainVC.contextPut("showResults", Boolean.TRUE);
}
}
private void doHideResults() {
mainVC.contextPut("showResults", Boolean.FALSE);
}
private void doDownloadSignature(UserRequest ureq) {
MediaResource resource = null;
if(courseNode instanceof IQTESTCourseNode) {
IQTESTCourseNode testCourseNode = (IQTESTCourseNode)courseNode;
AssessmentEntry assessmentEntry = testCourseNode.getUserAssessmentEntry(userCourseEnv);
AssessmentTestSession session = qtiService.getAssessmentTestSession(assessmentEntry.getAssessmentId());
File signature = qtiService.getAssessmentResultSignature(session);
if(signature.exists()) {
resource = new DownloadeableMediaResource(signature);
}
}
if(resource == null) {
resource = new NotFoundMediaResource("");
}
ureq.getDispatchResult().setResultingMediaResource(resource);
}
private void doStart(UserRequest ureq) {
removeAsListenerAndDispose(displayCtrl);
OLATResourceable ores = OresHelper.createOLATResourceableTypeWithoutCheck("test");
ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ores));
WindowControl bwControl = addToHistory(ureq, ores, null);
RepositoryEntry courseRe = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
displayCtrl = new AssessmentTestDisplayController(ureq, bwControl, this, testEntry, courseRe, courseNode.getIdent(),
deliveryOptions, overrideOptions, true, false, false);
listenTo(displayCtrl);
if(displayCtrl.isTerminated()) {
//do nothing
} else {
// in case displayController was unable to initialize, a message was set by displayController
// this is the case if no more attempts or security check was unsuccessfull
displayContainerController = new LayoutMain3ColsController(ureq, getWindowControl(), displayCtrl);
listenTo(displayContainerController); // autodispose
Panel empty = new Panel("empty");//empty panel set as "menu" and "tool"
Controller courseCloser = new DisposedCourseRestartController(ureq, getWindowControl(), courseRe);
Controller disposedRestartController = new LayoutMain3ColsController(ureq, getWindowControl(), empty, courseCloser.getInitialComponent(), "disposed");
displayContainerController.setDisposedMessageController(disposedRestartController);
boolean fullWindow = deliveryOptions.isHideLms();
if(fullWindow) {
displayContainerController.setAsFullscreen(ureq);
}
displayContainerController.activate();
assessmentStopped = false;
singleUserEventCenter.registerFor(this, getIdentity(), assessmentInstanceOres);
singleUserEventCenter.fireEventToListenersOf(new AssessmentEvent(AssessmentEvent.TYPE.STARTED, ureq.getUserSession()), assessmentEventOres);
ThreadLocalUserActivityLogger.log(QTI21LoggingAction.QTI_START_IN_COURSE, getClass());
}
}
private QTI21DeliveryOptions getDeliveryOptions() {
QTI21DeliveryOptions testOptions = qtiService.getDeliveryOptions(testEntry);
QTI21DeliveryOptions finalOptions = testOptions.clone();
boolean configRef = config.getBooleanSafe(IQEditController.CONFIG_KEY_CONFIG_REF, false);
if(!configRef) {
finalOptions.setMaxAttempts(config.getIntegerSafe(IQEditController.CONFIG_KEY_ATTEMPTS, testOptions.getMaxAttempts()));
finalOptions.setBlockAfterSuccess(config.getBooleanSafe(IQEditController.CONFIG_KEY_BLOCK_AFTER_SUCCESS, testOptions.isBlockAfterSuccess()));
finalOptions.setHideLms(config.getBooleanSafe(IQEditController.CONFIG_FULLWINDOW, testOptions.isHideLms()));
finalOptions.setShowTitles(config.getBooleanSafe(IQEditController.CONFIG_KEY_QUESTIONTITLE, testOptions.isShowTitles()));
finalOptions.setPersonalNotes(config.getBooleanSafe(IQEditController.CONFIG_KEY_MEMO, testOptions.isPersonalNotes()));
finalOptions.setEnableCancel(config.getBooleanSafe(IQEditController.CONFIG_KEY_ENABLECANCEL, testOptions.isEnableCancel()));
finalOptions.setEnableSuspend(config.getBooleanSafe(IQEditController.CONFIG_KEY_ENABLESUSPEND, testOptions.isEnableSuspend()));
finalOptions.setDisplayQuestionProgress(config.getBooleanSafe(IQEditController.CONFIG_KEY_QUESTIONPROGRESS, testOptions.isDisplayQuestionProgress()));
finalOptions.setDisplayScoreProgress(config.getBooleanSafe(IQEditController.CONFIG_KEY_SCOREPROGRESS, testOptions.isDisplayScoreProgress()));
finalOptions.setAssessmentResultsOptions(QTI21AssessmentResultsOptions.parseString(config.getStringValue(IQEditController.CONFIG_KEY_SUMMARY, AssessmentInstance.QMD_ENTRY_SUMMARY_COMPACT)));
finalOptions.setShowMenu(config.getBooleanSafe(IQEditController.CONFIG_KEY_ENABLEMENU, testOptions.isShowMenu()));
finalOptions.setAllowAnonym(config.getBooleanSafe(IQEditController.CONFIG_ALLOW_ANONYM, testOptions.isAllowAnonym()));
finalOptions.setDigitalSignature(config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE, testOptions.isDigitalSignature()));
finalOptions.setDigitalSignatureMail(config.getBooleanSafe(IQEditController.CONFIG_DIGITAL_SIGNATURE_SEND_MAIL, testOptions.isDigitalSignatureMail()));
}
return finalOptions;
}
private QTI21OverrideOptions getOverrideOptions() {
QTI21OverrideOptions finalOptions = QTI21OverrideOptions.nothingOverriden();
boolean configRef = config.getBooleanSafe(IQEditController.CONFIG_KEY_CONFIG_REF, false);
if(!configRef) {
Long maxTimeLimit = null;
int timeLimit = config.getIntegerSafe(IQEditController.CONFIG_KEY_TIME_LIMIT, -1);
if(timeLimit > 0) {
maxTimeLimit = new Long(timeLimit);
}
finalOptions = new QTI21OverrideOptions(maxTimeLimit);
}
return finalOptions;
}
/**
* Remove the runtime from the GUI stack only.
* @param ureq
* @param event
* @param testEnded true if the test was ended and not suspended or cancelled (use to control increment of attempts)
*/
private void doExitAssessment(UserRequest ureq, Event event, boolean testEnded) {
if(displayContainerController != null) {
displayContainerController.deactivate(ureq);
} else {
getWindowControl().pop();
}
removeHistory(ureq);
OLATResourceable ores = OresHelper.createOLATResourceableInstance("test", -1l);
addToHistory(ureq, ores, null);
if (!assessmentStopped) {
assessmentStopped = true;
singleUserEventCenter.deregisterFor(this, assessmentInstanceOres);
AssessmentEvent assessmentStoppedEvent = new AssessmentEvent(AssessmentEvent.TYPE.STOPPED, userSession);
singleUserEventCenter.fireEventToListenersOf(assessmentStoppedEvent, assessmentEventOres);
}
if(testEnded) {
incrementAttempts.set(true);
}
fireEvent(ureq, event);
}
@Override
public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Date timestamp, Locale locale) {
decorateCourseConfirmation(candidateSession, options, userCourseEnv.getCourseEnvironment(), courseNode, testEntry, timestamp, locale);
}
public static void decorateCourseConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options,
CourseEnvironment courseEnv, CourseNode courseNode, RepositoryEntry testEntry, Date timestamp, Locale locale) {
MailBundle bundle = new MailBundle();
bundle.setToId(candidateSession.getIdentity());
String fullname = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(candidateSession.getIdentity());
Date assessedDate = candidateSession.getFinishTime() == null ? timestamp : candidateSession.getFinishTime();
String[] args = new String[] {
courseEnv.getCourseTitle(), // {0}
courseEnv.getCourseResourceableId().toString(), // {1}
courseNode.getShortTitle(), // {2}
courseNode.getIdent(), // {3}
testEntry.getDisplayname(), // {4}
fullname, // {5}
Formatter.getInstance(locale)
.formatDateAndTime(assessedDate) // {6}
};
Translator translator = Util.createPackageTranslator(QTI21AssessmentRunController.class, locale);
String subject = translator.translate("digital.signature.mail.subject", args);
String body = translator.translate("digital.signature.mail.body", args);
bundle.setContent(subject, body);
options.setMailBundle(bundle);
options.setSubIdentName(courseNode.getShortTitle());
}
@Override
public void updateOutcomes(Float score, Boolean pass) {
//ScoreEvaluation sceval = new ScoreEvaluation(score, pass, Boolean.FALSE);
//courseNode.updateUserScoreEvaluation(sceval, userCourseEnv, getIdentity(), false);
}
@Override
public void submit(Float score, Boolean pass, Long assessmentId) {
if(anonym) return;
if(courseNode instanceof IQTESTCourseNode) {
Boolean visibility;
AssessmentEntryStatus assessmentStatus;
if(IQEditController.CORRECTION_MANUAL.equals(courseNode.getModuleConfiguration().getStringValue(IQEditController.CONFIG_CORRECTION_MODE))) {
assessmentStatus = AssessmentEntryStatus.inReview;
visibility = Boolean.FALSE;
} else {
assessmentStatus = AssessmentEntryStatus.done;
visibility = Boolean.TRUE;
}
ScoreEvaluation sceval = new ScoreEvaluation(score, pass, assessmentStatus, visibility, Boolean.TRUE, assessmentId);
boolean increment = incrementAttempts.getAndSet(false);
((IQTESTCourseNode)courseNode).updateUserScoreEvaluation(sceval, userCourseEnv, getIdentity(), increment);
if(increment) {
ThreadLocalUserActivityLogger.log(QTI21LoggingAction.QTI_CLOSE_IN_COURSE, getClass());
}
} else if(courseNode instanceof SelfAssessableCourseNode) {
boolean increment = incrementAttempts.getAndSet(false);
if(increment) {
((SelfAssessableCourseNode)courseNode).incrementUserAttempts(userCourseEnv);
}
}
}
}