/**
* <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.core.commons.modules.glossary;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.olat.NewControllerFactory;
import org.olat.basesecurity.BaseSecurity;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.velocity.VelocityContainer;
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.controller.BasicController;
import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
import org.olat.core.gui.control.generic.dtabs.Activateable2;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.context.BusinessControl;
import org.olat.core.id.context.BusinessControlFactory;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.id.context.StateEntry;
import org.olat.core.logging.activity.CoreLoggingResourceable;
import org.olat.core.logging.activity.LearningResourceLoggingAction;
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.vfs.VFSContainer;
import org.olat.resource.OLATResource;
import org.olat.user.UserInfoMainController;
import org.olat.user.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Description:<br>
* Displays a List of all glossary-entries.
* If the user is author or administrator, he will get Links to add, edit or delete Items.
* The list is sortable by an alphabetical register.
*
* @author Roman Haag, frentix GmbH, roman.haag@frentix.com
*/
public class GlossaryMainController extends BasicController implements Activateable2 {
private VelocityContainer glistVC;
private Link addButton;
private LockResult lockEntry = null;
private final GlossarySecurityCallback glossarySecCallback;
private final boolean eventProfil;
private DialogBoxController deleteDialogCtr;
private Controller glossEditCtrl;
private ArrayList<GlossaryItem> glossaryItemList;
private GlossaryItem currentDeleteItem;
private String filterIndex = "";
private VFSContainer glossaryFolder;
private UserInfoMainController uimc;
private CloseableModalController cmcUserInfo;
private CloseableModalController cmc;
private OLATResourceable resourceable;
private static final String CMD_EDIT = "cmd.edit.";
private static final String CMD_DELETE = "cmd.delete.";
private static final String CMD_AUTHOR = "cmd.author.";
private static final String CMD_MODIFIER = "cmd.modifier.";
private static final String REGISTER_LINK = "register.link.";
private final Formatter formatter;
@Autowired
private UserManager userManager;
@Autowired
private BaseSecurity securityManager;
public GlossaryMainController(WindowControl control, UserRequest ureq, VFSContainer glossaryFolder, OLATResource res,
GlossarySecurityCallback glossarySecCallback, boolean eventProfil) {
super(ureq, control);
this.glossarySecCallback = glossarySecCallback;
this.glossaryFolder = glossaryFolder;
this.eventProfil = eventProfil;
this.resourceable = res;
addLoggingResourceable(CoreLoggingResourceable.wrap(res, OlatResourceableType.genRepoEntry));
ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_OPEN, getClass());
glistVC = createVelocityContainer("glossarylist");
formatter = Formatter.getInstance(getLocale());
glossaryItemList = GlossaryItemManager.getInstance().getGlossaryItemListByVFSItem(glossaryFolder);
Properties glossProps = GlossaryItemManager.getInstance().getGlossaryConfig(glossaryFolder);
Boolean registerEnabled = Boolean.valueOf(glossProps.getProperty(GlossaryItemManager.REGISTER_ONOFF));
glistVC.contextPut("registerEnabled", registerEnabled);
if (!registerEnabled) {
filterIndex = "all";
}
glistVC.contextPut("userAllowToEditEnabled", new Boolean(glossarySecCallback.isUserAllowToEditEnabled()));
addButton = LinkFactory.createButtonSmall("cmd.add", glistVC, this);
addButton.setIconLeftCSS("o_icon o_icon_add o_icon-fw");
initEditView(ureq, glossarySecCallback.canAdd());
updateRegisterAndGlossaryItems();
Link showAllLink = LinkFactory.createCustomLink(REGISTER_LINK + "all", REGISTER_LINK + "all", "glossary.list.showall", Link.LINK,
glistVC, this);
glistVC.contextPut("showAllLink", showAllLink);
putInitialPanel(glistVC);
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose()
*/
@Override
protected void doDispose() {
ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_CLOSE, getClass());
// controllers get disposed itself
// release edit lock
if (lockEntry != null){
CoordinatorManager.getInstance().getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
}
}
/**
* create a List with all Indexes in this Glossary
*
* @param gIList
* @return List containing the Links.
*/
protected List<Link> getIndexLinkList(List<GlossaryItem> gIList) {
Set<String> addedKeys = new HashSet<>();
//get existing indexes
GlossaryItem[] gIArray = gIList.toArray(new GlossaryItem[gIList.size()]);
for (GlossaryItem gi : gIArray) {
String indexChar = gi.getIndex();
if (!addedKeys.contains(indexChar)) {
addedKeys.add(indexChar);
}
}
//build register, first found should be used later on
char alpha;
boolean firstIndexFound = false;
List<Link> indexLinkList = new ArrayList<>(gIList.size());
for (alpha='A'; alpha <= 'Z'; alpha++){
String indexChar = String.valueOf(alpha);
Link indexLink = LinkFactory.createCustomLink(REGISTER_LINK + indexChar, REGISTER_LINK + indexChar, indexChar, Link.NONTRANSLATED, glistVC, this);
if (!addedKeys.contains(indexChar)){
indexLink.setEnabled(false);
} else if (!filterIndex.equals("all") && !firstIndexFound && !addedKeys.contains(filterIndex)){
filterIndex = indexChar;
firstIndexFound = true;
}
indexLinkList.add(indexLink);
}
return indexLinkList;
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
* org.olat.core.gui.components.Component,
* org.olat.core.gui.control.Event)
*/
@Override
protected void event(UserRequest ureq, Component source, Event event) {
if (source == addButton) {
removeAsListenerAndDispose(glossEditCtrl);
glossEditCtrl = new GlossaryItemEditorController(ureq, getWindowControl(), glossaryFolder, glossaryItemList, null, true);
listenTo(glossEditCtrl);
removeAsListenerAndDispose(cmc);
cmc = new CloseableModalController(getWindowControl(), "close", glossEditCtrl.getInitialComponent());
cmc.activate();
listenTo(cmc);
} else if (source instanceof Link) {
Link button = (Link) source;
String cmd = button.getCommand();
if (button.getUserObject() instanceof GlossaryItem){
GlossaryItem currentGlossaryItem = (GlossaryItem) button.getUserObject();
if (cmd.startsWith(CMD_EDIT)) {
removeAsListenerAndDispose(glossEditCtrl);
glossEditCtrl = new GlossaryItemEditorController(ureq, getWindowControl(), glossaryFolder, glossaryItemList, currentGlossaryItem, false);
listenTo(glossEditCtrl);
removeAsListenerAndDispose(cmc);
cmc = new CloseableModalController(getWindowControl(), "close", glossEditCtrl.getInitialComponent());
cmc.activate();
listenTo(cmc);
} else if (button.getCommand().startsWith(CMD_DELETE)) {
currentDeleteItem = currentGlossaryItem;
if (deleteDialogCtr != null) {
deleteDialogCtr.dispose();
}
deleteDialogCtr = activateYesNoDialog(ureq, null, translate("glossary.delete.dialog", StringHelper.escapeHtml(currentGlossaryItem.getGlossTerm())),
deleteDialogCtr);
}
} else if (button.getCommand().startsWith(REGISTER_LINK)) {
filterIndex = cmd.substring(cmd.lastIndexOf(".") + 1);
updateRegisterAndGlossaryItems();
}
} else if (source == glistVC) {
String cmd = event.getCommand();
if(cmd.startsWith(CMD_AUTHOR)) {
String url = event.getCommand().substring(CMD_AUTHOR.length());
openProfil(ureq, url, true);
} else if (cmd.startsWith(CMD_MODIFIER)) {
String url = event.getCommand().substring(CMD_MODIFIER.length());
openProfil(ureq, url, false);
}
}
}
private void openProfil(UserRequest ureq, String pos, boolean author) {
Long identityKey = null;
int id = Integer.parseInt(pos);
@SuppressWarnings("unchecked")
List<GlossaryItemWrapper> wrappers = (List<GlossaryItemWrapper>)glistVC.getContext().get("editAndDelButtonList");
for(GlossaryItemWrapper wrapper:wrappers) {
if(id == wrapper.getId()) {
Revision revision = author ? wrapper.getAuthorRevision() : wrapper.getModifierRevision();
identityKey = revision.getAuthor().extractKey();
break;
}
}
if(identityKey != null) {
if(eventProfil) {
removeAsListenerAndDispose(cmc);
removeAsListenerAndDispose(uimc);
Identity selectedIdentity = securityManager.loadIdentityByKey(identityKey);
if(selectedIdentity != null) {
uimc = new UserInfoMainController(ureq, getWindowControl(), selectedIdentity, false, false);
listenTo(uimc);
cmcUserInfo = new CloseableModalController(getWindowControl(), "c", uimc.getInitialComponent());
listenTo(cmcUserInfo);
cmcUserInfo.activate();
}
} else {
String businessPath = "[Identity:" + identityKey + "]";
NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
}
}
}
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
super.event(ureq, source, event);
if (source == cmc){
// modal dialog closed -> persist changes on glossaryitem
GlossaryItemManager.getInstance().saveGlossaryItemList(glossaryFolder, glossaryItemList);
glossaryItemList = GlossaryItemManager.getInstance().getGlossaryItemListByVFSItem(glossaryFolder);
updateRegisterAndGlossaryItems();
} else if(source == cmcUserInfo) {
removeAsListenerAndDispose(cmcUserInfo);
removeAsListenerAndDispose(uimc);
cmcUserInfo = null;
uimc = null;
} else if (source == deleteDialogCtr) {
if (DialogBoxUIFactory.isYesEvent(event)) {
glossaryItemList.remove(currentDeleteItem);
GlossaryItemManager.getInstance().saveGlossaryItemList(glossaryFolder, glossaryItemList);
// back to glossary view
updateRegisterAndGlossaryItems();
}
}
}
@Override
public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
//
}
private void updateRegisterAndGlossaryItems(){
glistVC.contextPut("registerLinkList", getIndexLinkList(glossaryItemList));
glistVC.contextPut("editAndDelButtonList", updateView(glossaryItemList, filterIndex));
}
/**
*
* @param List with GlossaryItems
* @return a list (same size as GlossaryItems) which contains again lists with
* one editButton and one deleteButton
*/
private List<GlossaryItemWrapper> updateView(ArrayList<GlossaryItem> gIList, String choosenFilterIndex) {
int linkNum = 1;
Set<String> keys = new HashSet<String>();
StringBuilder bufDublicates = new StringBuilder();
List<GlossaryItemWrapper> items = new ArrayList<GlossaryItemWrapper>();
Collections.sort(gIList);
glistVC.contextPut("filterIndex", choosenFilterIndex);
if (!filterIndex.equals("all")) {
// highlight filtered index
Link indexLink = (Link) glistVC.getComponent(REGISTER_LINK + choosenFilterIndex);
if (indexLink!=null){
indexLink.setCustomEnabledLinkCSS("o_glossary_register_active");
}
}
for (GlossaryItem gi : gIList) {
boolean canEdit = glossarySecCallback.canEdit(gi);
if(canEdit) {
Link tmpEditButton = LinkFactory.createCustomLink(CMD_EDIT + linkNum, CMD_EDIT + linkNum, "cmd.edit", Link.BUTTON_XSMALL, glistVC,
this);
tmpEditButton.setIconLeftCSS("o_icon o_icon_edit o_icon-fw");
tmpEditButton.setUserObject(gi);
Link tmpDelButton = LinkFactory.createCustomLink(CMD_DELETE + linkNum, CMD_DELETE + linkNum, "cmd.delete", Link.BUTTON_XSMALL,
glistVC, this);
tmpDelButton.setUserObject(gi);
tmpDelButton.setIconLeftCSS("o_icon o_icon_delete_item o_icon-fw");
}
GlossaryItemWrapper wrapper = new GlossaryItemWrapper(gi, linkNum);
if (keys.contains(gi.getGlossTerm()) && (bufDublicates.indexOf(gi.getGlossTerm()) == -1)) {
bufDublicates.append(gi.getGlossTerm());
bufDublicates.append(" ");
} else {
keys.add(gi.getGlossTerm());
}
items.add(wrapper);
linkNum++;
}
if ((bufDublicates.length() > 0) && glossarySecCallback.canAdd()) {
showWarning("warning.contains.dublicates", bufDublicates.toString());
}
return items;
}
/**
* show edit buttons only if there is not yet a lock on this glossary
*
* @param ureq
* @param allowGlossaryEditing
*/
private void initEditView(UserRequest ureq, boolean allowGlossaryEditing) {
Boolean editModeEnabled = Boolean.valueOf(allowGlossaryEditing);
glistVC.contextPut("editModeEnabled", editModeEnabled);
if (allowGlossaryEditing) {
// try to get lock for this glossary
lockEntry = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(resourceable, ureq.getIdentity(), "GlossaryEdit");
if (!lockEntry.isSuccess()) {
String fullName = userManager.getUserDisplayName(lockEntry.getOwner());
showInfo("glossary.locked", StringHelper.escapeHtml(fullName));
glistVC.contextPut("editModeEnabled", Boolean.FALSE);
}
}
}
public class GlossaryItemWrapper {
private final int id;
private final GlossaryItem delegate;
public GlossaryItemWrapper(GlossaryItem delegate, int id) {
this.delegate = delegate;
this.id = id;
}
public int getId() {
return id;
}
public String getIndex() {
return delegate.getIndex();
}
public boolean hasAuthor() {
Revision authorRev = getAuthorRevision();
return authorRev != null && StringHelper.containsNonWhitespace(authorRev.getAuthor().getLink());
}
public String getAuthorName() {
Revision authorRev = getAuthorRevision();
return authorRev == null ? null : getRevisionAuthorFullName(authorRev);
}
public String getAuthorCmd() {
return CMD_AUTHOR + id;
}
public String getAuthorLink() {
Revision authorRev = getAuthorRevision();
return getLink(authorRev);
}
public boolean hasModifier() {
Revision modifierRev = getModifierRevision();
return modifierRev != null && StringHelper.containsNonWhitespace(modifierRev.getAuthor().getLink());
}
public String getModifierName() {
Revision modifierRev = getModifierRevision();
return modifierRev == null ? null : getRevisionAuthorFullName(modifierRev);
}
public String getModifierCmd() {
return CMD_MODIFIER + id;
}
public String getModifierLink() {
Revision modifierRev = getModifierRevision();
return getLink(modifierRev);
}
public String getCreationDate() {
Revision authorRev = getAuthorRevision();
return getMessageDate(authorRev);
}
public String getLastModificationDate() {
Revision modifierRev = getModifierRevision();
return getMessageDate(modifierRev);
}
private String getMessageDate(Revision rev) {
if(rev == null) return "";
Date date = rev.getJavaDate();
if(date == null) return "";
String dateStr = formatter.formatDate(date);
return translate("glossary.item.at", new String[]{ dateStr });
}
public List<Revision> getRevHistory() {
return delegate.getRevHistory();
}
public ArrayList<String> getGlossFlexions() {
return delegate.getGlossFlexions();
}
public ArrayList<String> getGlossSynonyms() {
return delegate.getGlossSynonyms();
}
public String getGlossDef() {
return delegate.getGlossDef();
}
public ArrayList<URI> getGlossLinks() {
return delegate.getGlossLinks();
}
public ArrayList<GlossaryItem> getGlossSeeAlso() {
return delegate.getGlossSeeAlso();
}
public String getGlossTerm() {
return delegate.getGlossTerm();
}
private String getLink(Revision rev) {
if(rev == null || rev.getAuthor() == null) return null;
String url = rev.getAuthor().getLink();
if(StringHelper.containsNonWhitespace(url) && url.startsWith("[") && url.endsWith("]")) {
int indexUsername = url.indexOf("[Username:");
if(indexUsername > 0) {
url = url.substring(0, indexUsername);
}
BusinessControl bc = BusinessControlFactory.getInstance().createFromString(url);
return BusinessControlFactory.getInstance().getAsURIString(bc, true);
}
return null;
}
public Revision getAuthorRevision() {
List<Revision> revisions = delegate.getRevHistory();
if(revisions == null || revisions.isEmpty()) return null;
Revision revision = revisions.get(0);
if(revision.getAuthor() != null && "added".equals(revision.getRevisionflag())) {
return revision;
}
return null;
}
public Revision getModifierRevision() {
List<Revision> revisions = delegate.getRevHistory();
if(revisions == null || revisions.isEmpty()) return null;
Revision lastRevision = revisions.get(revisions.size() - 1);
if(lastRevision.getAuthor() != null && "changed".equals(lastRevision.getRevisionflag())) {
return lastRevision;
}
return null;
}
private String getRevisionAuthorFullName(Revision revision) {
if(revision == null || revision.getAuthor() == null) return null;
StringBuilder sb = new StringBuilder();
if(StringHelper.containsNonWhitespace(revision.getAuthor().getFirstname())) {
sb.append(revision.getAuthor().getFirstname());
}
if(StringHelper.containsNonWhitespace(revision.getAuthor().getSurname())) {
if(sb.length() > 0) sb.append(' ');
sb.append(revision.getAuthor().getSurname());
}
return sb.toString();
}
}
}