/** * <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.assessment.ui.mode; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import org.olat.NewControllerFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.Windows; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.countdown.CountDownComponent; 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.ChiefController; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.ScreenMode.Mode; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.id.OLATResourceable; import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.AssessmentModeManager; import org.olat.course.assessment.AssessmentModeNotificationEvent; import org.olat.course.assessment.model.TransientAssessmentMode; import org.springframework.beans.factory.annotation.Autowired; /** * * Initial date: 18.12.2014<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class AssessmentModeGuardController extends BasicController implements GenericEventListener { private final Link mainContinueButton; private final VelocityContainer mainVC; private final CloseableModalController cmc; private final String address; private boolean pushUpdate = false; private List<TransientAssessmentMode> modes; private final ResourceGuards guards = new ResourceGuards(); @Autowired private AssessmentModeManager assessmentModeMgr; /** * * @param ureq * @param wControl * @param modes List of assessments * @param forcePush Async popup need forcePush=true */ public AssessmentModeGuardController(UserRequest ureq, WindowControl wControl, List<TransientAssessmentMode> modes, boolean forcePush) { super(ureq, wControl); putInitialPanel(new Panel("assessment-mode-chooser")); this.modes = modes; this.pushUpdate = forcePush; address = ureq.getHttpReq().getRemoteAddr(); mainVC = createVelocityContainer("choose_mode"); mainVC.contextPut("guards", guards); mainContinueButton = LinkFactory.createCustomLink("continue-main", "continue-main", "current.mode.continue", Link.BUTTON, mainVC, this); mainContinueButton.setElementCssClass("o_sel_assessment_continue"); mainContinueButton.setCustomEnabledLinkCSS("btn btn-primary"); mainContinueButton.setCustomDisabledLinkCSS("o_disabled btn btn-default"); mainContinueButton.setVisible(false); mainVC.put("continue-main", mainContinueButton); syncAssessmentModes(ureq); cmc = new CloseableModalController(getWindowControl(), translate("close"), mainVC, true, translate("current.mode"), false); cmc.activate(); listenTo(cmc); //register for assessment mode CoordinatorManager.getInstance().getCoordinator().getEventBus() .registerFor(this, getIdentity(), AssessmentModeNotificationEvent.ASSESSMENT_MODE_NOTIFICATION); } public void deactivate() { try { cmc.deactivate(); removeAsListenerAndDispose(cmc); } catch (Exception e) { logWarn("", e); } } @Override protected void doDispose() { CoordinatorManager.getInstance().getCoordinator().getEventBus() .deregisterFor(this, AssessmentModeNotificationEvent.ASSESSMENT_MODE_NOTIFICATION); } public boolean updateAssessmentMode(UserRequest ureq) { boolean f; if(pushUpdate) { syncAssessmentModes(ureq); f = true; pushUpdate = false; } else { f = false; } return f; } private void syncAssessmentModes(UserRequest ureq) { List<ResourceGuard> modeWrappers = new ArrayList<ResourceGuard>(); for(TransientAssessmentMode mode:modes) { if(mode != null) { ResourceGuard wrapper = syncAssessmentMode(ureq, mode); if(wrapper != null) { modeWrappers.add(wrapper); } } } guards.setList(modeWrappers); mainContinueButton.setVisible(modeWrappers.isEmpty()); mainVC.setDirty(true); } private ResourceGuard syncAssessmentMode(UserRequest ureq, TransientAssessmentMode mode) { Date now = new Date(); Date beginWithLeadTime = mode.getBeginWithLeadTime(); Date endWithFollowupTime = mode.getEndWithFollowupTime(); //check if the mode must not be guarded anymore if(mode.isManual() && (Status.end.equals(mode.getStatus()) || Status.none.equals(mode.getStatus()))) { return null; } else if(!mode.isManual() && (beginWithLeadTime.after(now) || now.after(endWithFollowupTime))) { return null; } ResourceGuard guard = guards.getGuardFor(mode); if(guard == null) { guard = createGuard(mode); } StringBuilder sb = new StringBuilder(); boolean allowed = true; if(mode.getIpList() != null) { boolean ipInRange = assessmentModeMgr.isIpAllowed(mode.getIpList(), address); if(!ipInRange) { sb.append("<h4><i class='o_icon o_icon_warn o_icon-fw'> </i>"); sb.append(translate("error.ip.range")); sb.append("</h4>"); sb.append(translate("error.ip.range.desc", address)); } allowed &= ipInRange; } if(mode.getSafeExamBrowserKey() != null) { boolean safeExamCheck = assessmentModeMgr.isSafelyAllowed(ureq.getHttpReq(), mode.getSafeExamBrowserKey()); if(!safeExamCheck) { sb.append("<h4><i class='o_icon o_icon_warn o_icon-fw'> </i>"); sb.append(translate("error.safe.exam")); sb.append("</h4>"); sb.append(translate("error.safe.exam.desc")); } allowed &= safeExamCheck; } guard.getCountDown().setDate(mode.getBegin()); String state; if(allowed) { Link go = guard.getGo(); Link cont = guard.getContinue(); state = updateButtons(mode, now, go, cont); } else { state = "error"; } guard.sync(state, sb.toString(), mode, getLocale()); return guard; } private String updateButtons(TransientAssessmentMode mode, Date now, Link go, Link cont) { String state; if(mode.isManual()) { if(Status.leadtime == mode.getStatus()) { state = Status.leadtime.name(); go.setEnabled(false); go.setVisible(true); cont.setEnabled(false); cont.setVisible(false); } else if(Status.assessment == mode.getStatus()) { state = Status.assessment.name(); go.setEnabled(true); go.setVisible(true); cont.setEnabled(false); cont.setVisible(false); } else if(Status.followup == mode.getStatus()) { state = Status.followup.name(); go.setEnabled(false); go.setVisible(false); cont.setEnabled(false); cont.setVisible(false); } else if(Status.end == mode.getStatus()) { state = Status.end.name(); go.setEnabled(false); go.setVisible(false); cont.setEnabled(true); cont.setVisible(true); } else { state = "error"; go.setEnabled(false); go.setVisible(false); cont.setEnabled(false); cont.setVisible(false); } } else { Date begin = mode.getBegin(); Date beginWithLeadTime = mode.getBeginWithLeadTime(); Date end = mode.getEnd(); Date endWithLeadTime = mode.getEndWithFollowupTime(); if(beginWithLeadTime.compareTo(now) <= 0 && begin.compareTo(now) > 0) { state = Status.leadtime.name(); go.setEnabled(false); go.setVisible(true); cont.setEnabled(false); cont.setVisible(false); } else if(begin.compareTo(now) <= 0 && end.compareTo(now) > 0) { state = Status.assessment.name(); go.setEnabled(true); go.setVisible(true); cont.setEnabled(false); cont.setVisible(false); } else if(end.compareTo(now) <= 0 && endWithLeadTime.compareTo(now) > 0) { state = Status.followup.name(); go.setEnabled(false); go.setVisible(false); cont.setEnabled(false); cont.setVisible(false); } else if(endWithLeadTime.compareTo(now) <= 0 || Status.end == mode.getStatus()) { state = Status.end.name(); go.setEnabled(false); go.setVisible(false); cont.setEnabled(true); cont.setVisible(true); } else { state = "error"; go.setEnabled(false); go.setVisible(false); cont.setEnabled(false); cont.setVisible(false); } } return state; } private ResourceGuard createGuard(TransientAssessmentMode mode) { String id = Long.toString(CodeHelper.getRAMUniqueID()); Link goButton = LinkFactory.createCustomLink("go-" + id, "go", "current.mode.start", Link.BUTTON, mainVC, this); goButton.setElementCssClass("o_sel_assessment_start"); goButton.setCustomEnabledLinkCSS("btn btn-primary"); goButton.setCustomDisabledLinkCSS("o_disabled btn btn-default"); Link continueButton = LinkFactory.createCustomLink("continue-" + id, "continue", "current.mode.continue", Link.BUTTON, mainVC, this); continueButton.setCustomEnabledLinkCSS("btn btn-primary"); continueButton.setCustomDisabledLinkCSS("o_disabled btn btn-default"); CountDownComponent countDown = new CountDownComponent("count-" + id, mode.getBegin(), getTranslator()); countDown.setI18nKey("current.mode.in"); ResourceGuard guard = new ResourceGuard(mode.getModeKey(), goButton, continueButton, countDown); mainVC.put(goButton.getComponentName(), goButton); mainVC.put(continueButton.getComponentName(), continueButton); mainVC.put(countDown.getComponentName(), countDown); goButton.setUserObject(guard); continueButton.setUserObject(guard); return guard; } @Override public void event(Event event) { if (event instanceof AssessmentModeNotificationEvent) { try { processAssessmentModeNotificationEvent((AssessmentModeNotificationEvent)event); } catch (Exception e) { logError("", e); } } } private void processAssessmentModeNotificationEvent(AssessmentModeNotificationEvent event) { if(getIdentity() != null && event.getAssessedIdentityKeys() != null && event.getAssessedIdentityKeys().contains(getIdentity().getKey())) { boolean update = false; TransientAssessmentMode mode = event.getAssessementMode(); List<TransientAssessmentMode> updatedModes = new ArrayList<TransientAssessmentMode>(); for(TransientAssessmentMode currentMode:modes) { if(currentMode.getModeKey().equals(mode.getModeKey())) { updatedModes.add(mode); update |= (currentMode.getStatus() != mode.getStatus()); } else { updatedModes.add(currentMode); update |= true; } } modes = updatedModes; pushUpdate |= update; } } @Override protected void event(UserRequest ureq, Component source, Event event) { if(source instanceof Link) { Link link = (Link)source; if("go".equals(link.getCommand())) { ResourceGuard guard = (ResourceGuard)link.getUserObject(); launchAssessmentMode(ureq, guard.getReference()); } else if("continue".equals(link.getCommand()) || "continue-main".equals(link.getCommand())) { ResourceGuard guard = (ResourceGuard)link.getUserObject(); continueAfterAssessmentMode(ureq, guard); } } } private void continueAfterAssessmentMode(UserRequest ureq, ResourceGuard selectedGuard) { List<ResourceGuard> lastGuards = new ArrayList<ResourceGuard>(); for(ResourceGuard currentGuard:guards.getList()) { if(currentGuard != selectedGuard) { lastGuards.add(currentGuard); } } guards.setList(lastGuards); boolean canContinue = guards.getSize() == 0; if(canContinue) { cmc.deactivate(); //make sure to see the navigation bar ChiefController cc = Windows.getWindows(ureq).getChiefController(); cc.getScreenMode().setMode(Mode.standard); fireEvent(ureq, new Event("continue")); String businessPath = "[MyCoursesSite:0]"; NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); } else { mainVC.setDirty(true); } } /** * Remove the list of assessment modes and lock the chief controller. * * * @param ureq * @param mode */ private void launchAssessmentMode(UserRequest ureq, TransientAssessmentMode mode) { cmc.deactivate(); ureq.getUserSession().setAssessmentModes(null); OLATResourceable resource = mode.getResource(); ureq.getUserSession().setLockResource(resource, mode); getWindowControl().getWindowBackOffice().getChiefController().lockResource(resource); fireEvent(ureq, new ChooseAssessmentModeEvent(mode)); String businessPath = "[RepositoryEntry:" + mode.getRepositoryEntryKey() + "]"; if(StringHelper.containsNonWhitespace(mode.getStartElementKey())) { businessPath += "[CourseNode:" + mode.getStartElementKey() + "]"; } NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); } public static final class ResourceGuard { private String status; private String errors; private final Link goButton, continueButton; private final Long modeKey; private String name; private String displayName; private String description; private String safeExamBrowserHint; private String begin; private String end; private String leadTime; private String followupTime; private TransientAssessmentMode reference; private CountDownComponent countDown; public ResourceGuard(Long modeKey, Link goButton, Link continueButton, CountDownComponent countDown) { this.modeKey = modeKey; this.goButton = goButton; this.countDown = countDown; this.continueButton = continueButton; } public void sync(String newStatus, String newErrors, TransientAssessmentMode mode, Locale locale) { errors = newErrors; status = newStatus; reference = mode; name = mode.getName(); displayName = mode.getDisplayName(); description = mode.getDescription(); safeExamBrowserHint = mode.getSafeExamBrowserHint(); Formatter f = Formatter.getInstance(locale); begin = f.formatDateAndTime(mode.getBegin()); end = f.formatDateAndTime(mode.getEnd()); if(mode.getFollowupTime() > 0) { followupTime = Integer.toString(mode.getFollowupTime()); } else { followupTime = null; } if(mode.getLeadTime() > 0) { leadTime = Integer.toString(mode.getLeadTime()); } else { leadTime = null; } } public Long getModeKey() { return modeKey; } public TransientAssessmentMode getReference() { return reference; } public String getName() { return name; } public String getDescription() { return description; } public String getSafeExamBrowserHint() { return safeExamBrowserHint; } public String getDisplayName() { return displayName; } public String getBegin() { return begin; } public String getEnd() { return end; } public String getLeadTime() { return leadTime; } public String getFollowupTime() { return followupTime; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getErrors() { return errors; } public void setErrors(String errors) { this.errors = errors; } public Link getGo() { return goButton; } public Link getContinue() { return continueButton; } public CountDownComponent getCountDown() { return countDown; } } public static class ResourceGuards { private List<ResourceGuard> guards = new ArrayList<>(); public int getSize() { return guards.size(); } public ResourceGuard getGuardFor(TransientAssessmentMode mode) { ResourceGuard guard = null; for(ResourceGuard g:getList()) { if(g.getModeKey().equals(mode.getModeKey())) { guard = g; } } return guard; } public List<ResourceGuard> getList() { return guards; } public void setList(List<ResourceGuard> guards) { this.guards = guards; } } }