/**
* <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.memberlist.ui;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.GroupRoles;
import org.olat.core.commons.modules.bc.FileUploadController;
import org.olat.core.gui.UserRequest;
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.FileElement;
import org.olat.core.gui.components.form.flexible.elements.FormLink;
import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
import org.olat.core.gui.components.form.flexible.elements.RichTextElement;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
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.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.translator.Translator;
import org.olat.core.gui.util.CSSHelper;
import org.olat.core.id.Identity;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.FileUtils;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.mail.ContactList;
import org.olat.core.util.mail.MailBundle;
import org.olat.core.util.mail.MailContext;
import org.olat.core.util.mail.MailContextImpl;
import org.olat.core.util.mail.MailHelper;
import org.olat.core.util.mail.MailLoggingAction;
import org.olat.core.util.mail.MailManager;
import org.olat.core.util.mail.MailModule;
import org.olat.core.util.mail.MailerResult;
import org.olat.core.util.mail.ui.EMailIdentity;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.nodes.members.Member;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryService;
import org.olat.user.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 21.12.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public class MembersMailController extends FormBasicController {
private static final String[] keys = new String[]{ "on" };
private RichTextElement bodyEl;
private FileElement attachmentEl;
private FormLink addMemberButton;
private TextElement subjectEl, externalAddressesEl;
private MultipleSelectionElement ownerEl, coachEl, participantEl, waitingEl, individualEl, externalEl, copyFromEl;
private FormLayoutContainer uploadCont, individualMemberCont;
private CloseableModalController cmc;
private SelectMembersController selectMemberCtrl;
private int counter = 0;
private long attachmentSize = 0l;
private File attachementTempDir;
private final CourseEnvironment courseEnv;
private final int contactAttachmentMaxSizeInMb;
private final List<Member> selectedMembers = new ArrayList<>();
private final List<Attachment> attachments = new ArrayList<>();
private final List<Member> ownerList, coachList, participantList, waitingList;
@Autowired
private UserManager userManager;
@Autowired
private MailManager mailService;
@Autowired
private MailModule mailModule;
@Autowired
private BaseSecurity securityManager;
@Autowired
private RepositoryService repositoryService;
public MembersMailController(UserRequest ureq, WindowControl wControl, Translator translator, CourseEnvironment courseEnv,
List<Member> ownerList, List<Member> coachList, List<Member> participantList, List<Member> waitingList, String bodyTemplate) {
super(ureq, wControl);
setTranslator(Util.createPackageTranslator(translator, MailHelper.class, ureq.getLocale()));
this.courseEnv = courseEnv;
this.ownerList = ownerList;
this.coachList = coachList;
this.participantList = participantList;
this.waitingList = waitingList;
this.contactAttachmentMaxSizeInMb = mailModule.getMaxSizeForAttachement();
initForm(ureq);
// preset body template if set
if (StringHelper.containsNonWhitespace(bodyTemplate)) {
bodyEl.setValue(bodyTemplate);
}
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
String fullName = userManager.getUserDisplayName(getIdentity());
if(StringHelper.containsNonWhitespace(fullName)) {
fullName = "[" + fullName + "]";
}
TextElement fromEl = uifactory.addTextElement("from", "email.from", 255, fullName, formLayout);
fromEl.setEnabled(false);
uifactory.addSpacerElement("space-1", formLayout, false);
String to = "send.mail.to";
if(ownerList != null && ownerList.size() > 0) {
String[] values = new String[] { translate("contact.all.owners") };
ownerEl = uifactory.addCheckboxesHorizontal("contact.all.owners", to, formLayout, keys, values);
to = null;
}
if(coachList != null && coachList.size() > 0) {
String[] values = new String[] { translate("contact.all.coaches") };
coachEl = uifactory.addCheckboxesHorizontal("contact.all.coaches", to, formLayout, keys, values);
to = null;
}
if(participantList != null && participantList.size() > 0) {
String[] values = new String[] { translate("contact.all.participants") };
participantEl = uifactory.addCheckboxesHorizontal("contact.all.participants", to, formLayout, keys, values);
to = null;
}
if(waitingList != null && waitingList.size() > 0) {
String[] values = new String[] { translate("contact.all.waiting") };
waitingEl = uifactory.addCheckboxesHorizontal("contact.all.waiting", to, formLayout, keys, values);
to = null;
}
if((ownerList != null && ownerList.size() > 0)
|| (coachList != null && coachList.size() > 0)
|| (participantList != null && participantList.size() > 0)
|| (waitingList != null && waitingList.size() > 0)) {
String[] values = new String[] { translate("contact.individual") };
individualEl = uifactory.addCheckboxesHorizontal("contact.individual", to, formLayout, keys, values);
individualEl.addActionListener(FormEvent.ONCHANGE);
to = null;
String attachmentPage = velocity_root + "/individual_members.html";
individualMemberCont = FormLayoutContainer.createCustomFormLayout("contact.individual.list", getTranslator(), attachmentPage);
formLayout.add(individualMemberCont);
individualMemberCont.setRootForm(mainForm);
individualMemberCont.setVisible(false);
individualMemberCont.contextPut("selectedMembers", selectedMembers);
addMemberButton = uifactory.addFormLink("add.member", "add", "", "", individualMemberCont, Link.NONTRANSLATED);
addMemberButton.setIconLeftCSS("o_icon o_icon-lg o_icon_table_large");
addMemberButton.setDomReplacementWrapperRequired(false);
addMemberButton.getComponent().setSuppressDirtyFormWarning(true);
}
String[] extValues = new String[] { translate("contact.external") };
externalEl = uifactory.addCheckboxesHorizontal("contact.external", to, formLayout, keys, extValues);
externalEl.addActionListener(FormEvent.ONCHANGE);
externalAddressesEl = uifactory.addTextAreaElement("contact.external.list", null, 4096, 3, 60, false, "", formLayout);
externalAddressesEl.setExampleKey("contact.external.list.example", null);
externalAddressesEl.setVisible(false);
uifactory.addSpacerElement("space-2", formLayout, false);
subjectEl = uifactory.addTextElement("subject", "mail.subject", 255, "", formLayout);
subjectEl.setDisplaySize(255);
subjectEl.setMandatory(true);
bodyEl = uifactory.addRichTextElementForStringDataMinimalistic("body", "mail.body", "", 15, 8, formLayout, getWindowControl());
bodyEl.setMandatory(true);
attachmentEl = uifactory.addFileElement(getWindowControl(), "file_upload_1", "contact.attachment", formLayout);
attachmentEl.addActionListener(FormEvent.ONCHANGE);
attachmentEl.setExampleKey("contact.attachment.maxsize", new String[]{ Integer.toString(contactAttachmentMaxSizeInMb) });
String attachmentPage = velocity_root + "/attachments.html";
uploadCont = FormLayoutContainer.createCustomFormLayout("file_upload_inner", getTranslator(), attachmentPage);
uploadCont.setRootForm(mainForm);
uploadCont.setVisible(false);
uploadCont.contextPut("attachments", attachments);
formLayout.add(uploadCont);
String[] copyValues = new String[] { "" };
copyFromEl = uifactory.addCheckboxesHorizontal("copy.from", "contact.cp.from", formLayout, keys, copyValues);
FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
formLayout.add(buttonGroupLayout);
uifactory.addFormSubmitButton("email.send", buttonGroupLayout);
uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
}
@Override
protected void doDispose() {
if(attachementTempDir != null && attachementTempDir.exists()) {
FileUtils.deleteDirsAndFiles(attachementTempDir, true, true);
}
}
@Override
protected boolean validateFormLogic(UserRequest ureq) {
boolean allOk = true;
subjectEl.clearError();
if(!StringHelper.containsNonWhitespace(subjectEl.getValue())) {
subjectEl.setErrorKey("form.legende.mandatory", null);
allOk &= false;
}
bodyEl.clearError();
if(!StringHelper.containsNonWhitespace(bodyEl.getValue())) {
bodyEl.setErrorKey("form.legende.mandatory", null);
allOk &= false;
}
externalAddressesEl.clearError();
if(externalEl != null && externalEl.isAtLeastSelected(1)) {
String value = externalAddressesEl.getValue();
StringBuilder errors = new StringBuilder();
if(StringHelper.containsNonWhitespace(value)) {
for(StringTokenizer tokenizer= new StringTokenizer(value, ",\r\n", false); tokenizer.hasMoreTokens(); ) {
String email = tokenizer.nextToken().trim();
if(!MailHelper.isValidEmailAddress(email)) {
if(errors.length() > 0) errors.append(", ");
errors.append(email);
}
}
}
if(errors.length() > 0) {
externalAddressesEl.setErrorKey("mailhelper.error.addressinvalid", new String[]{ errors.toString() });
allOk &= false;
}
}
return allOk & super.validateFormLogic(ureq);
}
private File[] getAttachments() {
File[] atttachmentArr = new File[attachments.size()];
for(int i=attachments.size(); i-->0; ) {
atttachmentArr[i] = attachments.get(i).getFile();
}
return atttachmentArr;
}
@Override
protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source == externalEl) {
externalAddressesEl.setVisible(externalEl.isAtLeastSelected(1));
} else if(source == individualEl) {
individualMemberCont.setVisible(individualEl.isAtLeastSelected(1));
flc.setDirty(true);
} else if(source == attachmentEl) {
doUploadAttachement();
} else if(source == addMemberButton) {
doChooseMember(ureq);
} else if(source instanceof FormLink) {
FormLink link = (FormLink)source;
String cmd = link.getCmd();
if("delete".equals(cmd)) {
Attachment attachment = (Attachment)link.getUserObject();
doDeleteAttachment(attachment);
} else if("remove".equals(cmd)) {
Member member = (Member)link.getUserObject();
doRemoveIndividualMember(member);
}
}
super.formInnerEvent(ureq, source, event);
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
if(source == selectMemberCtrl) {
if(event == Event.DONE_EVENT) {
List<Member> moreSelectedMembers = selectMemberCtrl.getSelectedMembers();
doAddSelectedMembers(moreSelectedMembers);
}
cmc.deactivate();
cleanUp();
} else if(source == cmc) {
cleanUp();
}
super.event(ureq, source, event);
}
private void cleanUp() {
removeAsListenerAndDispose(selectMemberCtrl);
removeAsListenerAndDispose(cmc);
selectMemberCtrl = null;
cmc = null;
}
private void doAddSelectedMembers(List<Member> moreSelectedMembers) {
if(moreSelectedMembers == null || moreSelectedMembers.isEmpty()) return;
for(Member selectedMember:selectedMembers) {
if(selectedMember.getRemoveLink() != null) {
individualMemberCont.remove(selectedMember.getRemoveLink());
}
}
selectedMembers.clear();
for(Member member:moreSelectedMembers) {
if(selectedMembers.contains(member)) continue;
String removeLinkName = "remove_" + (++counter);
FormLink removeLink = uifactory.addFormLink(removeLinkName, "remove", "", null, individualMemberCont, Link.NONTRANSLATED);
removeLink.setUserObject(member);
removeLink.setIconLeftCSS("o_icon o_icon_remove");
individualMemberCont.add(removeLink);
member.setRemoveLink(removeLink);
selectedMembers.add(member);
}
}
private void doRemoveIndividualMember(Member member) {
selectedMembers.remove(member);
if(member.getRemoveLink() != null) {
individualMemberCont.remove(member.getRemoveLink());
}
individualMemberCont.setDirty(true);
}
private void doChooseMember(UserRequest ureq) {
if(selectMemberCtrl != null || cmc != null) return;
List<Member> owners = ownerList;
List<Member> coaches = coachList;
List<Member> participants = participantList;
List<Member> waiting = waitingList;
if(ownerEl != null && ownerEl.isAtLeastSelected(1)) {
owners = null;
}
if(coachEl != null && coachEl.isAtLeastSelected(1)) {
coaches = null;
}
if(participantEl != null && participantEl.isAtLeastSelected(1)) {
participants = null;
}
if(waitingEl != null && waitingEl.isAtLeastSelected(1)) {
waiting = null;
}
if(owners == null && coaches == null && participants == null) {
showWarning("already.all.selected");
} else {
selectMemberCtrl = new SelectMembersController(ureq, getWindowControl(), getTranslator(),
selectedMembers, owners, coaches, participants, waiting);
listenTo(selectMemberCtrl);
String title = translate("select.members");
cmc = new CloseableModalController(getWindowControl(), translate("close"), selectMemberCtrl.getInitialComponent(), true, title);
cmc.suppressDirtyFormWarningOnClose();
listenTo(cmc);
cmc.activate();
}
}
private void doDeleteAttachment(Attachment attachment) {
attachmentSize -= attachment.getFile().length();
attachment.getFile().delete();
attachments.remove(attachment);
uploadCont.setVisible(attachments.size() > 0);
uploadCont.setDirty(true);
}
private void doUploadAttachement() {
if(attachementTempDir == null) {
attachementTempDir = FileUtils.createTempDir("attachements", null, null);
}
long size = attachmentEl.getUploadSize();
String filename = attachmentEl.getUploadFileName();
if(size + attachmentSize > (contactAttachmentMaxSizeInMb * 1024 * 1024)) {
showWarning("contact.attachment,maxsize", Integer.toString(contactAttachmentMaxSizeInMb));
attachmentEl.reset();
} else {
File attachment = attachmentEl.moveUploadFileTo(attachementTempDir);
attachmentEl.reset();
if(attachment == null) {
logError("Could not move contact-form attachment to " + attachementTempDir.getAbsolutePath(), null);
setTranslator(Util.createPackageTranslator(FileUploadController.class, getLocale(), getTranslator()));
showError("FileMoveCopyFailed","");
} else {
attachmentSize += size;
FormLink removeFile = uifactory.addFormLink("delete_" + (++counter), "delete", "delete", null, uploadCont, Link.LINK);
removeFile.setIconLeftCSS("o_icon o_icon-fw o_icon_delete");
String css = CSSHelper.createFiletypeIconCssClassFor(filename);
Attachment wrapper = new Attachment(attachment, attachment.getName(), css, removeFile);
removeFile.setUserObject(wrapper);
attachments.add(wrapper);
uploadCont.setVisible(true);
}
}
}
private void doSend(UserRequest ureq) {
ContactList contactList = new ContactList("");
if (courseEnv == null) {
if(coachEl != null && coachEl.isAtLeastSelected(1)) {
List<Long> identityKeys = new ArrayList<>(coachList.size());
for(Member coach:coachList) {
identityKeys.add(coach.getKey());
}
List<Identity> coaches = securityManager.loadIdentityByKeys(identityKeys);
contactList.addAllIdentites(coaches);
}
if(participantEl != null && participantEl.isAtLeastSelected(1)) {
List<Long> identityKeys = new ArrayList<>(participantList.size());
for(Member participant:participantList) {
identityKeys.add(participant.getKey());
}
List<Identity> participants = securityManager.loadIdentityByKeys(identityKeys);
contactList.addAllIdentites(participants);
}
if(waitingEl != null && waitingEl.isAtLeastSelected(1)) {
List<Long> identityKeys = new ArrayList<>(waitingList.size());
for(Member waiter:waitingList) {
identityKeys.add(waiter.getKey());
}
List<Identity> waiters = securityManager.loadIdentityByKeys(identityKeys);
contactList.addAllIdentites(waiters);
}
} else {
if(ownerEl != null && ownerEl.isAtLeastSelected(1)) {
RepositoryEntry courseRepositoryEntry = courseEnv.getCourseGroupManager().getCourseEntry();
List<Identity> owners = repositoryService.getMembers(courseRepositoryEntry, GroupRoles.owner.name());
contactList.addAllIdentites(owners);
}
if(coachEl != null && coachEl.isAtLeastSelected(1)) {
Set<Long> sendToWhatYouSee = new HashSet<>();
for(Member coach:coachList) {
sendToWhatYouSee.add(coach.getKey());
}
CourseGroupManager cgm = courseEnv.getCourseGroupManager();
avoidInvisibleMember(cgm.getCoachesFromBusinessGroups(), contactList, sendToWhatYouSee);
avoidInvisibleMember(cgm.getCoaches(), contactList, sendToWhatYouSee);
}
if(participantEl != null && participantEl.isAtLeastSelected(1)) {
Set<Long> sendToWhatYouSee = new HashSet<>();
for(Member participant:participantList) {
sendToWhatYouSee.add(participant.getKey());
}
CourseGroupManager cgm = courseEnv.getCourseGroupManager();
avoidInvisibleMember(cgm.getParticipantsFromBusinessGroups(), contactList, sendToWhatYouSee);
avoidInvisibleMember(cgm.getParticipants(), contactList, sendToWhatYouSee);
}
}
if(individualEl != null && individualEl.isAtLeastSelected(1)
&& selectedMembers != null && selectedMembers.size() > 0) {
List<Long> identityKeys = new ArrayList<>(selectedMembers.size());
for(Member member:selectedMembers) {
identityKeys.add(member.getKey());
}
List<Identity> selectedIdentities = securityManager.loadIdentityByKeys(identityKeys);
contactList.addAllIdentites(selectedIdentities);
}
if(externalEl != null && externalEl.isAtLeastSelected(1)) {
String value = externalAddressesEl.getValue();
if(StringHelper.containsNonWhitespace(value)) {
for(StringTokenizer tokenizer= new StringTokenizer(value, ",\r\n", false); tokenizer.hasMoreTokens(); ) {
String email = tokenizer.nextToken().trim();
contactList.add(new EMailIdentity(email, getLocale()));
}
}
}
doSendEmailToMember(ureq, contactList);
}
private void doSendEmailToMember(UserRequest ureq, ContactList contactList) {
boolean success = false;
try {
File[] attachmentArr = getAttachments();
MailContext context = new MailContextImpl(getWindowControl().getBusinessControl().getAsString());
MailBundle bundle = new MailBundle();
bundle.setContext(context);
bundle.setFromId(getIdentity());
bundle.setContactLists(Collections.singletonList(contactList));
bundle.setContent(subjectEl.getValue(), bodyEl.getValue(), attachmentArr);
MailerResult result = mailService.sendMessage(bundle);
if(copyFromEl.isAtLeastSelected(1)) {
MailBundle ccBundle = new MailBundle();
ccBundle.setContext(context);
ccBundle.setFromId(getIdentity());
ccBundle.setCc(getIdentity());
ccBundle.setContent(subjectEl.getValue(), bodyEl.getValue(), attachmentArr);
MailerResult ccResult = mailService.sendMessage(ccBundle);
result.append(ccResult);
}
success = result.isSuccessful();
} catch (Exception e) {
//error in recipient email address(es)
handleAddressException(success);
}
if (success) {
showInfo("msg.send.ok");
// do logging
ThreadLocalUserActivityLogger.log(MailLoggingAction.MAIL_SENT, getClass());
fireEvent(ureq, Event.DONE_EVENT);
} else {
showInfo("error.msg.send.nok");
fireEvent(ureq, Event.FAILED_EVENT);
}
}
private void handleAddressException(boolean success) {
StringBuilder error = new StringBuilder();
if (success) {
error.append(translate("error.msg.send.partially.nok"))
.append("<br />")
.append(translate("error.msg.send.invalid.rcps"));
} else {
error.append(translate("error.msg.send.nok"))
.append("<br />")
.append(translate("error.msg.send.553"));
}
getWindowControl().setError(error.toString());
}
private void avoidInvisibleMember(List<Identity> members, ContactList contactList, Set<Long> sendToWhatYouSee) {
for(Identity member:members) {
if(sendToWhatYouSee.contains(member.getKey())) {
contactList.add(member);
}
}
}
@Override
protected void formOK(UserRequest ureq) {
doSend(ureq);
fireEvent(ureq, Event.DONE_EVENT);
}
@Override
protected void formCancelled(UserRequest ureq) {
fireEvent(ureq, Event.CANCELLED_EVENT);
}
public static class Attachment {
private final File file;
private final String filename;
private final String cssClass;
private final FormLink deleteLink;
public Attachment(File file, String filename, String cssClass, FormLink deleteLink) {
this.file = file;
this.filename = filename;
this.cssClass = cssClass;
this.deleteLink = deleteLink;
}
public File getFile() {
return file;
}
public String getCssClass() {
return cssClass;
}
public String getFilename() {
return filename;
}
public FormLink getDeleteLink() {
return deleteLink;
}
public String getDeleteComponentName() {
return deleteLink.getComponent().getComponentName();
}
}
}