/**
* <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.commons.info.ui;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.olat.commons.info.manager.InfoMessageFrontendManager;
import org.olat.commons.info.manager.MailFormatter;
import org.olat.commons.info.model.InfoMessage;
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.date.DateComponentFactory;
import org.olat.core.gui.components.date.DateElement;
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.FormLink;
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.link.Link;
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.generic.closablewrapper.CloseableModalController;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.gui.control.generic.wizard.Step;
import org.olat.core.gui.control.generic.wizard.StepRunnerCallback;
import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
import org.olat.core.gui.control.generic.wizard.StepsRunContext;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.User;
import org.olat.core.id.UserConstants;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.logging.activity.CourseLoggingAction;
import org.olat.core.logging.activity.OlatResourceableType;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.LockResult;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.nodes.info.InfoCourseNodeConfiguration;
import org.olat.group.BusinessGroup;
import org.olat.modules.ModuleConfiguration;
import org.olat.user.UserManager;
import org.olat.util.logging.activity.LoggingResourceable;
/**
*
* Description:<br>
* Controller which display the info messages from an OLATResourceable
*
* <P>
* Initial Date: 26 jul. 2010 <br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*/
public class InfoDisplayController extends FormBasicController {
private Step start;
private FormLink newInfoLink;
private FormLink oldMsgsLink;
private FormLink newMsgsLink;
private final List<FormLink> editLinks = new ArrayList<FormLink>();
private final List<FormLink> deleteLinks = new ArrayList<FormLink>();
private StepsMainRunController newInfoWizard;
private DialogBoxController confirmDelete;
private InfoEditController editController;
private CloseableModalController editDialogBox;
private final List<Long> previousDisplayKeys = new ArrayList<Long>();
private final InfoSecurityCallback secCallback;
private final OLATResourceable ores;
private final String resSubPath;
private final String businessPath;
private int maxResults = 0;
private int maxResultsConfig = 0;
private int duration = -1;
private Date after = null;
private Date afterConfig = null;
private final UserManager userManager;
private final InfoMessageFrontendManager infoMessageManager;
private LockResult lockEntry;
private MailFormatter sendMailFormatter;
private List<SendMailOption> sendMailOptions = new ArrayList<SendMailOption>();
public InfoDisplayController(UserRequest ureq, WindowControl wControl, InfoSecurityCallback secCallback,
BusinessGroup businessGroup, String resSubPath, String businessPath) {
super(ureq, wControl, "display");
userManager = CoreSpringFactory.getImpl(UserManager.class);
infoMessageManager = CoreSpringFactory.getImpl(InfoMessageFrontendManager.class);
this.secCallback = secCallback;
this.ores = businessGroup.getResource();
this.resSubPath = resSubPath;
this.businessPath = businessPath;
// default show 10 messages for groups
maxResults = maxResultsConfig = 10;
initForm(ureq);
// now load with configuration
loadMessages(ureq);
}
public InfoDisplayController(UserRequest ureq, WindowControl wControl, ModuleConfiguration config,
InfoSecurityCallback secCallback, OLATResourceable ores, String resSubPath, String businessPath) {
super(ureq, wControl, "display");
this.secCallback = secCallback;
this.ores = ores;
this.resSubPath = resSubPath;
this.businessPath = businessPath;
userManager = CoreSpringFactory.getImpl(UserManager.class);
infoMessageManager = CoreSpringFactory.getImpl(InfoMessageFrontendManager.class);
maxResults = maxResultsConfig = getConfigValue(config, InfoCourseNodeConfiguration.CONFIG_LENGTH, 10);
duration = getConfigValue(config, InfoCourseNodeConfiguration.CONFIG_DURATION, 90);
if(duration > 0) {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.DATE, -duration);
after = afterConfig = cal.getTime();
}
initForm(ureq);
// OLAT-6302 when a specific message is shown display the page that
// contains the message. Jump in e.g. from portlet
ContextEntry ce = wControl.getBusinessControl().popLauncherContextEntry();
if (ce != null) { // a context path is left for me
OLATResourceable businessPathResource = ce.getOLATResourceable();
String typeName = businessPathResource.getResourceableTypeName();
if ("InfoMessage".equals(typeName)) {
Long messageId = businessPathResource.getResourceableId();
if (messageId != null && messageId.longValue() > 0) {
// currently no pageing is implemented, just page with all entries
maxResults = -1;
after = null;
}
}
}
// now load with configuration
loadMessages(ureq);
}
private int getConfigValue(ModuleConfiguration config, String key, int def) {
String durationStr = (String)config.get(key);
if("\u221E".equals(durationStr)) {
return -1;
} else if(StringHelper.containsNonWhitespace(durationStr)) {
try {
return Integer.parseInt(durationStr);
} catch(NumberFormatException e) { /* fallback to default */ }
}
return def;
}
public List<SendMailOption> getSendMailOptions() {
return this.sendMailOptions;
}
public void addSendMailOptions(SendMailOption sendMailOption) {
sendMailOptions.add(sendMailOption);
}
public MailFormatter getSendMailFormatter() {
return sendMailFormatter;
}
public void setSendMailFormatter(MailFormatter sendMailFormatter) {
this.sendMailFormatter = sendMailFormatter;
}
/**
* This is the main method which push the messages in the layout container,
* and clean-up old links.
*/
protected void loadMessages(UserRequest ureq) {
//first clear the current message if any
for(Long key:previousDisplayKeys) {
flc.contextRemove("info.date." + key);
if(flc.getComponent("info.delete." + key) != null) {
flc.remove("info.delete." + key);
}
if(flc.getComponent("info.edit." + key) != null) {
flc.remove("info.edit." + key);
}
}
previousDisplayKeys.clear();
deleteLinks.clear();
List<InfoMessage> msgs = infoMessageManager.loadInfoMessageByResource(ores, resSubPath, businessPath, after, null, 0, maxResults);
List<InfoMessageForDisplay> infoDisplays = new ArrayList<InfoMessageForDisplay>(msgs.size());
for(InfoMessage info:msgs) {
previousDisplayKeys.add(info.getKey());
infoDisplays.add(createInfoMessageForDisplay(info));
String dateCmpName = "info.date." + info.getKey();
DateElement dateEl = DateComponentFactory.createDateElementWithYear(dateCmpName, info.getCreationDate());
flc.add(dateCmpName, dateEl);
if(secCallback.canEdit(info)) {
String editName = "info.edit." + info.getKey();
FormLink link = uifactory.addFormLink(editName, "edit", "edit", flc, Link.BUTTON_SMALL);
link.setElementCssClass("o_sel_info_edit_msg");
link.setUserObject(info);
editLinks.add(link);
flc.add(link);
}
if(secCallback.canDelete()) {
String delName = "info.delete." + info.getKey();
FormLink link = uifactory.addFormLink(delName, "delete", "delete", flc, Link.BUTTON_SMALL);
link.setElementCssClass("o_sel_info_delete_msg");
link.setUserObject(info);
deleteLinks.add(link);
flc.add(link);
}
}
flc.contextPut("infos", infoDisplays);
int numOfInfos = infoMessageManager.countInfoMessageByResource(ores, resSubPath, businessPath, null, null);
oldMsgsLink.setVisible((msgs.size() < numOfInfos));
newMsgsLink.setVisible((msgs.size() == numOfInfos) && (numOfInfos > maxResultsConfig) && (maxResultsConfig > 0));
}
private InfoMessageForDisplay createInfoMessageForDisplay(InfoMessage info) {
String message = info.getMessage();
boolean html = StringHelper.isHtml(message);
if(html) {
message = message.toString();
} else if(StringHelper.containsNonWhitespace(message)) {
message = Formatter.escWithBR(info.getMessage()).toString();
message = Formatter.formatURLsAsLinks(message);
}
Formatter formatter = Formatter.getInstance(getLocale());
String modifier = null;
if(info.getModifier() != null) {
String formattedName = userManager.getUserDisplayName(info.getModifier());
String creationDate = formatter.formatDateAndTime(info.getModificationDate());
modifier = translate("display.modifier", new String[]{StringHelper.escapeHtml(formattedName), creationDate});
}
String authorName = userManager.getUserDisplayName(info.getAuthor());
String creationDate = formatter.formatDateAndTime(info.getCreationDate());
String infos;
if (authorName.isEmpty()) {
infos = translate("display.info.noauthor", new String[]{creationDate});
} else {
infos = translate("display.info", new String[]{StringHelper.escapeHtml(authorName), creationDate});
}
return new InfoMessageForDisplay(info.getKey(), info.getTitle(), message, infos, modifier);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
if(secCallback.canAdd()) {
newInfoLink = uifactory.addFormLink("new_message", "new_message", "new_message", formLayout, Link.BUTTON);
newInfoLink.setElementCssClass("o_sel_course_info_create_msg");
}
oldMsgsLink = uifactory.addFormLink("display.old_messages", "display.old_messages", "display.old_messages", formLayout, Link.BUTTON);
oldMsgsLink.setElementCssClass("o_sel_course_info_old_msgs");
newMsgsLink = uifactory.addFormLink("display.new_messages", "display.new_messages", "display.new_messages", formLayout, Link.BUTTON);
newMsgsLink.setElementCssClass("o_sel_course_info_new_msgs");
}
@Override
protected void doDispose() {
if(lockEntry != null) {
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
}
}
@Override
protected void formOK(UserRequest ureq) {
//
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if(source == newInfoWizard) {
if (event == Event.CANCELLED_EVENT) {
getWindowControl().pop();
} else if (event == Event.CHANGED_EVENT) {
getWindowControl().pop();
loadMessages(ureq);
flc.setDirty(true);//update the view
} else if (event == Event.DONE_EVENT){
showError("failed");
}
} else if(source == confirmDelete) {
if(DialogBoxUIFactory.isYesEvent(event)) {
InfoMessage msgToDelete = (InfoMessage)confirmDelete.getUserObject();
ThreadLocalUserActivityLogger.log(CourseLoggingAction.INFO_MESSAGE_DELETED, getClass(),
LoggingResourceable.wrap(msgToDelete.getOLATResourceable(), OlatResourceableType.infoMessage));
infoMessageManager.deleteInfoMessage(msgToDelete);
loadMessages(ureq);
}
confirmDelete.setUserObject(null);
//release lock
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
} else if (source == editController) {
if(event == Event.DONE_EVENT) {
loadMessages(ureq);
}
editDialogBox.deactivate();
removeAsListenerAndDispose(editController);
editDialogBox = null;
editController = null;
//release lock
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
} else if (source == editDialogBox) {
//release lock if the dialog is closed
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
} else {
super.event(ureq, source, event);
}
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source == newInfoLink) {
start = new CreateInfoStep(ureq, sendMailOptions);
newInfoWizard = new StepsMainRunController(ureq, getWindowControl(), start, new FinishedCallback(),
new CancelCallback(), translate("create_message"), "o_sel_info_messages_create_wizard");
listenTo(newInfoWizard);
getWindowControl().pushAsModalDialog(newInfoWizard.getInitialComponent());
} else if(deleteLinks.contains(source)) {
InfoMessage msg = (InfoMessage)source.getUserObject();
popupDelete(ureq, msg);
} else if(editLinks.contains(source)) {
InfoMessage msg = (InfoMessage)source.getUserObject();
popupEdit(ureq, msg);
} else if(source == oldMsgsLink) {
maxResults = -1;
after = null;
loadMessages(ureq);
} else if(source == newMsgsLink) {
maxResults = maxResultsConfig;
after = afterConfig;
loadMessages(ureq);
} else {
super.formInnerEvent(ureq, source, event);
}
}
protected void popupDelete(UserRequest ureq, InfoMessage msg) {
OLATResourceable mres = OresHelper.createOLATResourceableInstance(InfoMessage.class, msg.getKey());
lockEntry = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(mres, ureq.getIdentity(), "");
if(lockEntry.isSuccess()) {
//locked -> reload the message
msg = infoMessageManager.loadInfoMessage(msg.getKey());
if(msg == null) {
showWarning("already.deleted");
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
loadMessages(ureq);
} else {
String title = StringHelper.escapeHtml(msg.getTitle());
String confirmDeleteText = translate("edit.confirm_delete", new String[]{ title });
confirmDelete = activateYesNoDialog(ureq, null, confirmDeleteText, confirmDelete);
confirmDelete.setUserObject(msg);
}
} else {
User user = lockEntry.getOwner().getUser();
String name = user.getProperty(UserConstants.FIRSTNAME, null) + " " + user.getProperty(UserConstants.LASTNAME, null);
showWarning("already.edited", name);
}
}
protected void popupEdit(UserRequest ureq, InfoMessage msg) {
OLATResourceable mres = OresHelper.createOLATResourceableInstance(InfoMessage.class, msg.getKey());
lockEntry = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(mres, ureq.getIdentity(), "");
if(lockEntry.isSuccess()) {
msg = infoMessageManager.loadInfoMessage(msg.getKey());
if(msg == null) {
showWarning("already.deleted");
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
loadMessages(ureq);
} else {
removeAsListenerAndDispose(editController);
removeAsListenerAndDispose(editDialogBox);
editController = new InfoEditController(ureq, getWindowControl(), msg);
listenTo(editController);
editDialogBox = new CloseableModalController(getWindowControl(), translate("edit"),
editController.getInitialComponent(), true, translate("edit.title"), true);
editDialogBox.activate();
listenTo(editDialogBox);
}
} else {
User user = lockEntry.getOwner().getUser();
String name = user.getProperty(UserConstants.FIRSTNAME, null) + " " + user.getProperty(UserConstants.LASTNAME, null);
showWarning("already.edited", name);
}
}
protected class FinishedCallback implements StepRunnerCallback {
@Override
public Step execute(UserRequest ureq, WindowControl wControl, StepsRunContext runContext) {
String title = (String)runContext.get(WizardConstants.MSG_TITLE);
String message = (String)runContext.get(WizardConstants.MSG_MESSAGE);
@SuppressWarnings("unchecked")
Set<String> selectedOptions = (Set<String>)runContext.get(WizardConstants.SEND_MAIL);
InfoMessage msg = infoMessageManager.createInfoMessage(ores, resSubPath, businessPath, ureq.getIdentity());
msg.setTitle(title);
msg.setMessage(message);
List<Identity> identities = new ArrayList<Identity>();
for(SendMailOption option:sendMailOptions) {
if(selectedOptions != null && selectedOptions.contains(option.getOptionKey())) {
identities.addAll(option.getSelectedIdentities());
}
}
infoMessageManager.sendInfoMessage(msg, sendMailFormatter, ureq.getLocale(), ureq.getIdentity(), identities);
ThreadLocalUserActivityLogger.log(CourseLoggingAction.INFO_MESSAGE_CREATED, getClass(),
LoggingResourceable.wrap(msg.getOLATResourceable(), OlatResourceableType.infoMessage));
return StepsMainRunController.DONE_MODIFIED;
}
}
protected class CancelCallback implements StepRunnerCallback {
@Override
public Step execute(UserRequest ureq, WindowControl wControl, StepsRunContext runContext) {
return Step.NOSTEP;
}
}
}