/**
* <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.ims.qti21.ui.components;
import java.util.Date;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.render.RenderResult;
import org.olat.core.gui.render.Renderer;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.gui.render.URLBuilder;
import org.olat.core.gui.translator.Translator;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.ims.qti21.AssessmentTestSession;
import org.olat.ims.qti21.model.audit.CandidateEvent;
import org.olat.ims.qti21.model.audit.CandidateItemEventType;
import org.olat.ims.qti21.ui.CandidateSessionContext;
import uk.ac.ed.ph.jqtiplus.node.content.variable.PrintedVariable;
import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem;
import uk.ac.ed.ph.jqtiplus.node.item.template.declaration.TemplateDeclaration;
import uk.ac.ed.ph.jqtiplus.node.outcome.declaration.OutcomeDeclaration;
import uk.ac.ed.ph.jqtiplus.node.result.SessionStatus;
import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem;
import uk.ac.ed.ph.jqtiplus.running.ItemSessionController;
import uk.ac.ed.ph.jqtiplus.state.ItemSessionState;
import uk.ac.ed.ph.jqtiplus.types.Identifier;
import uk.ac.ed.ph.jqtiplus.value.Value;
/**
*
* Initial date: 10.12.2014<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class AssessmentItemComponentRenderer extends AssessmentObjectComponentRenderer {
private static final OLog log = Tracing.createLoggerFor(AssessmentItemComponentRenderer.class);
@Override
public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu,
Translator translator, RenderResult renderResult, String[] args) {
AssessmentItemComponent cmp = (AssessmentItemComponent)source;
sb.append("<div class='qtiworks o_assessmentitem'>");
ItemSessionController itemSessionController = cmp.getItemSessionController();
CandidateSessionContext candidateSessionContext = cmp.getCandidateSessionContext();
/* Create appropriate options that link back to this controller */
final AssessmentTestSession candidateSession = candidateSessionContext.getCandidateSession();
if (candidateSession != null && candidateSession.isExploded()) {
renderExploded(sb, translator);
} else if (candidateSessionContext.isTerminated()) {
renderTerminated(sb, translator);
} else {
/* Look up most recent event */
final CandidateEvent latestEvent = candidateSessionContext.getLastEvent();// assertSessionEntered(candidateSession);
/* Load the ItemSessionState */
final ItemSessionState itemSessionState = cmp.getItemSessionController().getItemSessionState();// candidateDataService.loadItemSessionState(latestEvent);
/* Touch the session's duration state if appropriate */
if (itemSessionState.isEntered() && !itemSessionState.isEnded() && !itemSessionState.isSuspended()) {
final Date timestamp = candidateSessionContext.getCurrentRequestTimestamp();
itemSessionController.touchDuration(timestamp);
}
/* Render event */
AssessmentRenderer renderHints = new AssessmentRenderer(renderer);
renderItemEvent(renderHints, sb, cmp, latestEvent, itemSessionState, ubu, translator);
}
sb.append("</div>");
}
private void renderItemEvent(AssessmentRenderer renderer, StringOutput sb, AssessmentItemComponent component,
CandidateEvent candidateEvent, ItemSessionState itemSessionState, URLBuilder ubu, Translator translator) {
final CandidateItemEventType itemEventType = candidateEvent.getItemEventType();
/* Create and partially configure rendering request */
//renderingRequest.setPrompt("" /* itemDeliverySettings.getPrompt() */);
/* If session has terminated, render appropriate state and exit */
if (itemSessionState.isExited()) {
renderTerminated(sb, translator);
return;
}
/* Detect "modal" events. These will cause a particular rendering state to be
* displayed, which candidate will then leave.
*/
if (itemEventType==CandidateItemEventType.SOLUTION) {
renderer.setSolutionMode(true);
}
/* Now set candidate action permissions depending on state of session */
if (itemEventType==CandidateItemEventType.SOLUTION || itemSessionState.isEnded()) {
/* Item session is ended (closed) */
renderer.setEndAllowed(false);
renderer.setHardResetAllowed(false /* itemDeliverySettings.isAllowHardResetWhenEnded() */);
renderer.setSoftResetAllowed(false /* itemDeliverySettings.isAllowSoftResetWhenEnded() */);
renderer.setSolutionAllowed(true /* itemDeliverySettings.isAllowSolutionWhenEnded() */);
renderer.setCandidateCommentAllowed(false);
} else if (itemSessionState.isOpen()) {
/* Item session is open (interacting) */
renderer.setEndAllowed(true /* itemDeliverySettings.isAllowEnd() */);
renderer.setHardResetAllowed(false /* itemDeliverySettings.isAllowHardResetWhenOpen() */);
renderer.setSoftResetAllowed(false /* itemDeliverySettings.isAllowSoftResetWhenOpen() */);
renderer.setSolutionAllowed(true /* itemDeliverySettings.isAllowSolutionWhenOpen() */);
renderer.setCandidateCommentAllowed(false /* itemDeliverySettings.isAllowCandidateComment() */);
} else {
throw new OLATRuntimeException("Item has not been entered yet. We do not currently support rendering of this state.", null);
}
/* Finally pass to rendering layer */
// candidateAuditLogger.logItemRendering(candidateEvent);
//final List<CandidateEventNotification> notifications = candidateEvent.getNotifications();
try {
renderTestItemBody(renderer, sb, component, itemSessionState, ubu, translator);
} catch (final RuntimeException e) {
/* Rendering is complex and may trigger an unexpected Exception (due to a bug in the XSLT).
* In this case, the best we can do for the candidate is to 'explode' the session.
* See bug #49.
*/
log.error("", e);
renderExploded(sb, translator);
}
}
private void renderTestItemBody(AssessmentRenderer renderer, StringOutput sb, AssessmentItemComponent component, ItemSessionState itemSessionState,
URLBuilder ubu, Translator translator) {
final AssessmentItem assessmentItem = component.getAssessmentItem();
final ResolvedAssessmentItem resolvedAssessmentItem = component.getResolvedAssessmentItem();
sb.append("<div class='o_assessmentitem_wrapper'>");
//title + status
sb.append("<h4 class='itemTitle'>");
renderItemStatus(renderer, sb, itemSessionState, translator);
sb.append(StringHelper.escapeHtml(assessmentItem.getTitle())).append("</h4>")
.append("<div id='itemBody' class='clearfix'>");
//TODO prompt
//render itemBody
assessmentItem.getItemBody().getBlocks().forEach((block)
-> renderBlock(renderer, sb, component, resolvedAssessmentItem, itemSessionState, block, ubu, translator));
//comment
renderComment(renderer, sb, component, itemSessionState, translator);
//end body
sb.append("</div>");
// Display active modal feedback (only after responseProcessing)
if(itemSessionState.getSessionStatus() == SessionStatus.FINAL) {
renderTestItemModalFeedback(renderer, sb, component, resolvedAssessmentItem, itemSessionState, ubu, translator);
}
//controls
sb.append("<div class='o_button_group o_assessmentitem_controls'>");
//submit button
if(component.isItemSessionOpen(itemSessionState, renderer.isSolutionMode())) {
Component submit = component.getQtiItem().getSubmitButton().getComponent();
submit.getHTMLRendererSingleton().render(renderer.getRenderer(), sb, submit, ubu, translator, new RenderResult(), null);
}
sb.append("</div>");
sb.append("</div>"); // end wrapper
}
private void renderItemStatus(AssessmentRenderer renderer, StringOutput sb, ItemSessionState itemSessionState, Translator translator) {
if(renderer.isSolutionMode()) {
sb.append("<span class='o_assessmentitem_status review'>").append(translator.translate("assessment.item.status.modelSolution")).append("</span>");
} else {
super.renderItemStatus(sb, itemSessionState, null, translator);
}
}
@Override
protected void renderPrintedVariable(AssessmentRenderer renderer, StringOutput sb, AssessmentObjectComponent component, ResolvedAssessmentItem resolvedAssessmentItem,
ItemSessionState itemSessionState, PrintedVariable printedVar) {
Identifier identifier = printedVar.getIdentifier();
Value templateValue = itemSessionState.getTemplateValues().get(identifier);
Value outcomeValue = itemSessionState.getOutcomeValues().get(identifier);
sb.append("<span class='printedVariable'>");
if(outcomeValue != null) {
OutcomeDeclaration outcomeDeclaration = resolvedAssessmentItem.getRootNodeLookup()
.extractIfSuccessful().getOutcomeDeclaration(identifier);
renderPrintedVariable(renderer, sb, printedVar, outcomeDeclaration, outcomeValue);
} else if(templateValue != null) {
TemplateDeclaration templateDeclaration = resolvedAssessmentItem.getRootNodeLookup()
.extractIfSuccessful().getTemplateDeclaration(identifier);
renderPrintedVariable(renderer, sb, printedVar, templateDeclaration, templateValue);
} else {
sb.append("(variable ").append(identifier.toString()).append(" was not found)");
}
sb.append("</span>");
}
}