/**
* <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.cl.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.DownloadLink;
import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.ControllerEventListener;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.dtabs.Activateable2;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.id.context.StateEntry;
import org.olat.core.logging.activity.CourseLoggingAction;
import org.olat.core.logging.activity.StringResourceableType;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.course.auditing.UserNodeAuditManager;
import org.olat.course.highscore.ui.HighScoreRunController;
import org.olat.course.nodes.CheckListCourseNode;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.MSCourseNode;
import org.olat.course.nodes.cl.CheckboxManager;
import org.olat.course.nodes.cl.model.Checkbox;
import org.olat.course.nodes.cl.model.CheckboxList;
import org.olat.course.nodes.cl.model.DBCheck;
import org.olat.course.nodes.cl.model.DBCheckbox;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.modules.ModuleConfiguration;
import org.olat.modules.assessment.AssessmentEntry;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 04.02.2014<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class CheckListRunController extends FormBasicController implements ControllerEventListener, Activateable2 {
private final Date dueDate;
private final boolean withScore, withPassed;
private final Boolean closeAfterDueDate;
private final CheckboxList checkboxList;
private static final String[] onKeys = new String[]{ "on" };
private final ModuleConfiguration config;
private final CheckListCourseNode courseNode;
private final OLATResourceable courseOres;
private final UserCourseEnvironment userCourseEnv;
@Autowired
private CheckboxManager checkboxManager;
/**
* Use this constructor to launch the checklist.
*
* @param ureq
* @param wControl
* @param courseNode
*/
public CheckListRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
OLATResourceable courseOres, CheckListCourseNode courseNode) {
super(ureq, wControl, "run", Util.createPackageTranslator(CourseNode.class, ureq.getLocale()));
this.courseNode = courseNode;
this.courseOres = courseOres;
this.userCourseEnv = userCourseEnv;
config = courseNode.getModuleConfiguration();
CheckboxList configCheckboxList = (CheckboxList)config.get(CheckListCourseNode.CONFIG_KEY_CHECKBOX);
if(configCheckboxList == null) {
checkboxList = new CheckboxList();
checkboxList.setList(Collections.<Checkbox>emptyList());
} else {
checkboxList = configCheckboxList;
}
closeAfterDueDate = (Boolean)config.get(CheckListCourseNode.CONFIG_KEY_CLOSE_AFTER_DUE_DATE);
if(closeAfterDueDate != null && closeAfterDueDate.booleanValue()) {
dueDate = (Date)config.get(CheckListCourseNode.CONFIG_KEY_DUE_DATE);
} else {
dueDate = null;
}
Boolean hasScore = (Boolean)config.get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD);
withScore = (hasScore == null || hasScore.booleanValue());
Boolean hasPassed = (Boolean)config.get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD);
withPassed = (hasPassed == null || hasPassed.booleanValue());
initForm(ureq);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
boolean readOnly = isReadOnly();
if(formLayout instanceof FormLayoutContainer) {
FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
layoutCont.contextPut("readOnly", new Boolean(readOnly));
if(dueDate != null) {
layoutCont.contextPut("dueDate", dueDate);
if(dueDate.compareTo(new Date()) < 0) {
layoutCont.contextPut("afterDueDate", Boolean.TRUE);
}
}
layoutCont.contextPut("withScore", new Boolean(withScore));
if (courseNode.getModuleConfiguration().getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD,false)){
HighScoreRunController highScoreCtr = new HighScoreRunController(ureq, getWindowControl(),
userCourseEnv, courseNode, this.mainForm);
if (highScoreCtr.isViewHighscore()) {
Component highScoreComponent = highScoreCtr.getInitialComponent();
layoutCont.put("highScore", highScoreComponent);
}
}
List<DBCheck> checks = checkboxManager.loadCheck(getIdentity(), courseOres, courseNode.getIdent());
Map<String, DBCheck> uuidToCheckMap = new HashMap<>();
for(DBCheck check:checks) {
uuidToCheckMap.put(check.getCheckbox().getCheckboxId(), check);
}
List<Checkbox> list = checkboxList.getList();
List<CheckboxWrapper> wrappers = new ArrayList<>(list.size());
for(Checkbox checkbox:list) {
DBCheck check = uuidToCheckMap.get(checkbox.getCheckboxId());
CheckboxWrapper wrapper = forgeCheckboxWrapper(checkbox, check, readOnly, formLayout);
layoutCont.add(wrapper.getCheckboxEl());
wrappers.add(wrapper);
}
layoutCont.contextPut("checkboxList", wrappers);
if(withScore || withPassed) {
layoutCont.contextPut("enableScoreInfo", Boolean.TRUE);
exposeConfigToVC(layoutCont);
exposeUserDataToVC(layoutCont);
} else {
layoutCont.contextPut("enableScoreInfo", Boolean.FALSE);
}
}
}
private void exposeConfigToVC(FormLayoutContainer layoutCont) {
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD));
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD));
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD));
String infoTextUser = (String) config.get(MSCourseNode.CONFIG_KEY_INFOTEXT_USER);
if(StringHelper.containsNonWhitespace(infoTextUser)) {
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_INFOTEXT_USER, infoTextUser);
}
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE)));
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MIN, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN)));
layoutCont.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MAX, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX)));
}
private void exposeUserDataToVC(FormLayoutContainer layoutCont) {
AssessmentEntry scoreEval = courseNode.getUserAssessmentEntry(userCourseEnv);
if(scoreEval == null) {
layoutCont.contextPut("score", null);
layoutCont.contextPut("hasPassedValue", Boolean.FALSE);
layoutCont.contextPut("passed", null);
layoutCont.contextPut("comment", null);
} else {
boolean resultsVisible = scoreEval.getUserVisibility() == null || scoreEval.getUserVisibility().booleanValue();
layoutCont.contextPut("resultsVisible", resultsVisible);
layoutCont.contextPut("score", AssessmentHelper.getRoundedScore(scoreEval.getScore()));
layoutCont.contextPut("hasPassedValue", (scoreEval.getPassed() == null ? Boolean.FALSE : Boolean.TRUE));
layoutCont.contextPut("passed", scoreEval.getPassed());
if(resultsVisible) {
StringBuilder comment = Formatter.stripTabsAndReturns(scoreEval.getComment());
layoutCont.contextPut("comment", StringHelper.xssScan(comment));
}
}
UserNodeAuditManager am = userCourseEnv.getCourseEnvironment().getAuditManager();
layoutCont.contextPut("log", am.getUserNodeLog(courseNode, userCourseEnv.getIdentityEnvironment().getIdentity()));
}
private CheckboxWrapper forgeCheckboxWrapper(Checkbox checkbox, DBCheck check, boolean readOnly, FormItemContainer formLayout) {
String[] values = new String[]{ translate(checkbox.getLabel().i18nKey()) };
boolean canCheck = CheckboxReleaseEnum.userAndCoach.equals(checkbox.getRelease());
String boxId = "box_" + checkbox.getCheckboxId();
MultipleSelectionElement el = uifactory
.addCheckboxesHorizontal(boxId, null, formLayout, onKeys, values);
el.setEnabled(canCheck && !readOnly && !userCourseEnv.isCourseReadOnly());
el.addActionListener(FormEvent.ONCHANGE);
DownloadLink downloadLink = null;
if(StringHelper.containsNonWhitespace(checkbox.getFilename())) {
VFSContainer container = checkboxManager.getFileContainer(userCourseEnv.getCourseEnvironment(), courseNode);
VFSItem item = container.resolve(checkbox.getFilename());
if(item instanceof VFSLeaf) {
String name = "file_" + checkbox.getCheckboxId();
downloadLink = uifactory.addDownloadLink(name, checkbox.getFilename(), null, (VFSLeaf)item, formLayout);
}
}
CheckboxWrapper wrapper = new CheckboxWrapper(checkbox, downloadLink, el);
el.setUserObject(wrapper);
if(check != null && check.getChecked() != null && check.getChecked().booleanValue()) {
el.select(onKeys[0], true);
wrapper.setDbCheckbox(check.getCheckbox());
}
if(downloadLink != null) {
downloadLink.setUserObject(wrapper);
}
return wrapper;
}
private boolean isReadOnly() {
return (closeAfterDueDate != null && closeAfterDueDate.booleanValue()
&& dueDate != null && dueDate.before(new Date()));
}
@Override
protected void doDispose() {
//
}
@Override
protected void formOK(UserRequest ureq) {
//
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source instanceof MultipleSelectionElement) {
MultipleSelectionElement boxEl = (MultipleSelectionElement)source;
CheckboxWrapper wrapper = (CheckboxWrapper)boxEl.getUserObject();
if(wrapper != null) {
boolean checked = boxEl.isAtLeastSelected(1);
doCheck(wrapper, checked);
}
}
super.formInnerEvent(ureq, source, event);
}
private void doCheck(CheckboxWrapper wrapper, boolean checked) {
DBCheckbox theOne;
if(wrapper.getDbCheckbox() == null) {
String uuid = wrapper.getCheckbox().getCheckboxId();
theOne = checkboxManager.loadCheckbox(courseOres, courseNode.getIdent(), uuid);
} else {
theOne = wrapper.getDbCheckbox();
}
if(theOne == null) {
//only warning because this happen in course preview
logWarn("A checkbox is missing: " + courseOres + " / " + courseNode.getIdent(), null);
} else {
Float score;
if(checked) {
score = wrapper.getCheckbox().getPoints();
} else {
score = 0f;
}
checkboxManager.check(theOne, getIdentity(), score, new Boolean(checked));
//make sure all results is on the database before calculating some scores
//manager commit already DBFactory.getInstance().commit();
courseNode.updateScoreEvaluation(getIdentity(), userCourseEnv, getIdentity());
Checkbox checkbox = wrapper.getCheckbox();
logUpdateCheck(checkbox.getCheckboxId(), checkbox.getTitle());
}
exposeUserDataToVC(flc);
}
private void logUpdateCheck(String checkboxId, String boxTitle) {
ThreadLocalUserActivityLogger.log(CourseLoggingAction.CHECKLIST_CHECK_UPDATED, getClass(),
LoggingResourceable.wrapNonOlatResource(StringResourceableType.checkbox, checkboxId, boxTitle));
}
@Override
public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
//nothin to do
}
public static class CheckboxWrapper {
private final Checkbox checkbox;
private final DownloadLink downloadLink;
private final MultipleSelectionElement checkboxEl;
private DBCheckbox dbCheckbox;
public CheckboxWrapper(Checkbox checkbox, DownloadLink downloadLink, MultipleSelectionElement checkboxEl) {
this.checkboxEl = checkboxEl;
this.downloadLink = downloadLink;
this.checkbox = checkbox;
}
public Checkbox getCheckbox() {
return checkbox;
}
/**
* This value is lazy loaded and can be null!
* @return
*/
public DBCheckbox getDbCheckbox() {
return dbCheckbox;
}
public void setDbCheckbox(DBCheckbox dbCheckbox) {
this.dbCheckbox = dbCheckbox;
}
public String getTitle() {
return checkbox.getTitle();
}
public boolean isPointsAvailable() {
return checkbox.getPoints() != null;
}
public String getPoints() {
return AssessmentHelper.getRoundedScore(checkbox.getPoints());
}
public String getDescription() {
return checkbox.getDescription();
}
public MultipleSelectionElement getCheckboxEl() {
return checkboxEl;
}
public String getCheckboxElName() {
return checkboxEl.getName();
}
public boolean hasDownload() {
return StringHelper.containsNonWhitespace(checkbox.getFilename()) && downloadLink != null;
}
public String getDownloadName() {
return downloadLink.getComponent().getComponentName();
}
}
}