/**
* <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.portfolio.ui.structel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.olat.NewControllerFactory;
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.panel.StackedPanel;
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.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.id.context.StateEntry;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.StringHelper;
import org.olat.core.util.resource.OresHelper;
import org.olat.portfolio.EPLoggingAction;
import org.olat.portfolio.EPSecurityCallback;
import org.olat.portfolio.EPSecurityCallbackFactory;
import org.olat.portfolio.PortfolioModule;
import org.olat.portfolio.manager.EPFrontendManager;
import org.olat.portfolio.model.structel.EPStructuredMap;
import org.olat.portfolio.model.structel.EPTargetResource;
import org.olat.portfolio.model.structel.ElementType;
import org.olat.portfolio.model.structel.PortfolioStructure;
import org.olat.portfolio.model.structel.PortfolioStructureMap;
import org.olat.portfolio.ui.EPMapRunViewOption;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Description:<br>
* Present a list of maps. allows:
* - Open a map
* - delete a map
* - copy a map with or without artefacts and open it
*
* <P>
* Initial Date: 04.08.2010 <br>
*
* @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com
*/
public class EPMultipleMapController extends BasicController implements Activateable2 {
private static final String RESTRICT_LINK = "restrictLink";
private static final String VIEW_LINK_PREFIX = "viewLink";
private static final String DELETE_LINK_PREFIX = "deleteLink";
private static final String COPY_LINK_PREFIX = "copyLink";
private static final String SHARE_LINK_PREFIX = "shareLink";
private static final String PAGING_LINK_PREFIX = "pageLink";
private static final int ITEMS_PER_PAGE = 9;
private final VelocityContainer vC;
private DialogBoxController delMapCtrl;
private DialogBoxController copyMapCtrl;
private EPMapViewController mapViewCtrl;
private EPShareListController shareListController;
private CloseableModalController shareBox;
private final StackedPanel myPanel;
private final EPMapRunViewOption option;
private final Identity mapOwner;
private List<PortfolioStructureMap> userMaps;
private boolean restrictShareView = true;
private long start;
@Autowired
private EPFrontendManager ePFMgr;
@Autowired
private PortfolioModule portfolioModule;
// components for paging
private Link forwardLink;
private int currentPageNum = 1;
private int currentPagingFrom = 0;
private int currentPagingTo = ITEMS_PER_PAGE;
private boolean pagingAvailable = false;
public EPMultipleMapController(UserRequest ureq, WindowControl control, EPMapRunViewOption option, Identity mapOwner) {
super(ureq, control);
this.option = option;
this.mapOwner = mapOwner;
vC = createVelocityContainer("multiMaps");
initOrUpdateMaps(ureq);
myPanel = putInitialPanel(vC);
}
/**
* returns a List of PortfolioStructures to display, depending on options (all OLAT-wide shared maps, only shared to me, paging)
*
* @return
*/
private List<PortfolioStructure> getUsersStructsToDisplay(){
pagingAvailable = false;
// get maps for this user
List<PortfolioStructure> allUsersStruct;
switch (option) {
case OTHER_MAPS:// same as OTHERS_MAPS
case OTHERS_MAPS:
vC.remove(vC.getComponent(RESTRICT_LINK));
if (restrictShareView) {
if (portfolioModule.isOfferPublicMapList()) {
LinkFactory.createCustomLink(RESTRICT_LINK, "change", "restrict.show.all", Link.LINK, vC, this);
}
allUsersStruct = ePFMgr.getStructureElementsFromOthersWithoutPublic(getIdentity(), mapOwner, ElementType.STRUCTURED_MAP, ElementType.DEFAULT_MAP);
} else {
if (portfolioModule.isOfferPublicMapList()) {
LinkFactory.createCustomLink(RESTRICT_LINK, "change", "restrict.show.limited", Link.LINK, vC, this);
}
// this query can be quite time consuming, if fetching all structures -> do paging
currentPagingFrom = (currentPageNum-1)*ITEMS_PER_PAGE;
currentPagingTo = currentPagingFrom+ITEMS_PER_PAGE;
allUsersStruct = ePFMgr.getStructureElementsFromOthers(getIdentity(), mapOwner, currentPagingFrom, currentPagingTo ,ElementType.STRUCTURED_MAP, ElementType.DEFAULT_MAP);
pagingAvailable = true;
}
break;
case MY_EXERCISES_MAPS:
allUsersStruct = ePFMgr.getStructureElementsForUser(getIdentity(), ElementType.STRUCTURED_MAP);
break;
default:// MY_DEFAULTS_MAPS
allUsersStruct = ePFMgr.getStructureElementsForUser(getIdentity(), ElementType.DEFAULT_MAP);
}
if (isLogDebugEnabled()) {
logDebug("got all structures to see at: ", String.valueOf(System.currentTimeMillis()));
}
return allUsersStruct;
}
/**
*
*/
private void initOrUpdateMaps(UserRequest ureq) {
if (isLogDebugEnabled()) {
start = System.currentTimeMillis();
logDebug("start loading map overview at : ", String.valueOf(start));
}
List<PortfolioStructure> allUsersStruct = getUsersStructsToDisplay();
userMaps = new ArrayList<PortfolioStructureMap>();
if (allUsersStruct.isEmpty()) {
vC.contextPut("noMaps", true);
return;
} else vC.contextRemove("noMaps");
//remove forward link (maybe it's not needed (last page) )
if(forwardLink != null)
vC.remove(forwardLink);
// now add paging-components if necessary and wanted
int elementCount = ePFMgr.countStructureElementsFromOthers(getIdentity(), mapOwner, ElementType.DEFAULT_MAP);
if(pagingAvailable && elementCount > ITEMS_PER_PAGE){
vC.contextPut("showPaging", true);
int additionalPage = ((elementCount % ITEMS_PER_PAGE) > 0)?1:0;
int pageCount = (elementCount/ITEMS_PER_PAGE) + additionalPage;
List<Component> pagingLinks = new ArrayList<Component>();
for(int i = 1; i < pageCount+1; i++){
Link pageLink = LinkFactory.createCustomLink(PAGING_LINK_PREFIX+i, "switchPage", String.valueOf(i), Link.NONTRANSLATED, vC, this);
pageLink.setUserObject(new Integer(i));
pagingLinks.add(pageLink);
if(i == currentPageNum){
pageLink.setEnabled(false);
}
}
vC.contextPut("pageLinks",pagingLinks);
if(currentPageNum < pageCount){
forwardLink = LinkFactory.createCustomLink("forwardLink", "pagingFWD", "table.forward", Link.LINK, vC, this);
forwardLink.setIconRightCSS("o_icon o_icon_next_page");
}
}
//now display the maps
List<String> artAmount = new ArrayList<String>(userMaps.size());
List<Integer> childAmount = new ArrayList<Integer>(userMaps.size());
List<String> mapStyles = new ArrayList<String>(userMaps.size());
List<Date> deadLines = new ArrayList<Date>(userMaps.size());
List<String> restriStats = new ArrayList<String>(userMaps.size());
List<String> owners = new ArrayList<String>(userMaps.size());
List<String> amounts = new ArrayList<String>(userMaps.size());
int i = 1;
for (PortfolioStructure portfolioStructure : allUsersStruct) {
if (portfolioStructure.getRoot() == null) { //only show maps
PortfolioStructureMap map = (PortfolioStructureMap)portfolioStructure;
EPSecurityCallback secCallback = EPSecurityCallbackFactory.getSecurityCallback(ureq, map, ePFMgr);
userMaps.add(map);
Link vLink = LinkFactory.createCustomLink(VIEW_LINK_PREFIX + i, "viewMap" + map.getResourceableId(), "view.map",
Link.LINK, vC, this);
vLink.setUserObject(map);
vLink.setElementCssClass("o_sel_ep_open_map");
vLink.setIconRightCSS("o_icon o_icon-fw o_icon_start");
//can always try to delete your own map, but exercise only if the course was deleted
vC.remove(vC.getComponent(DELETE_LINK_PREFIX + i)); // remove as update could require hiding it
// can always try to delete your own map, but exercise only if the course was deleted
final boolean myMaps = (option.equals(EPMapRunViewOption.MY_DEFAULTS_MAPS) || option.equals(EPMapRunViewOption.MY_EXERCISES_MAPS));
boolean addDeleteLink = myMaps;
if((map instanceof EPStructuredMap) && (((EPStructuredMap) map).getReturnDate() != null)){
addDeleteLink = false; //it's a portfolio-task that was already handed in, so do not display delete-link
}
if (addDeleteLink) {
final Link dLink = LinkFactory.createCustomLink(DELETE_LINK_PREFIX + i, "delMap" + map.getResourceableId(), "delete.map", Link.LINK, vC, this);
dLink.setIconLeftCSS("o_icon o_icon-fw o_icon_delete_item");
dLink.setUserObject(map);
}
Link cLink = LinkFactory.createCustomLink(COPY_LINK_PREFIX + i, "copyMap" + map.getResourceableId(), "copy.map",
Link.LINK, vC, this);
cLink.setIconLeftCSS("o_icon o_icon_copy");
cLink.setUserObject(map);
// its not allowed to copy maps from a portfolio-task
if (map instanceof EPStructuredMap) {
cLink.setVisible(false);
}
vC.remove(vC.getComponent(SHARE_LINK_PREFIX + i)); // remove as update could require hiding it
if(myMaps && secCallback.canShareMap()) {
Link shareLink = LinkFactory.createCustomLink(SHARE_LINK_PREFIX + i, "shareMap" + map.getResourceableId(), "map.share",
Link.LINK, vC, this);
shareLink.setIconLeftCSS("o_icon o_icon-fw o_icon_share");
shareLink.setUserObject(map);
boolean shared = ePFMgr.isMapShared(map);
if(shared || (map instanceof EPStructuredMap && ((EPStructuredMap)map).getTargetResource() != null)) {
shareLink.setCustomDisplayText(translate("map.share.shared"));
}
}
if (isLogDebugEnabled()) {
logDebug(" in loop : got share state at: ", String.valueOf(System.currentTimeMillis()));
}
// get deadline + link to course
if (map instanceof EPStructuredMap){
EPStructuredMap structMap = (EPStructuredMap)map;
Date deadLine = structMap.getDeadLine();
deadLines.add(deadLine);
EPTargetResource resource = structMap.getTargetResource();
RepositoryEntry repoEntry = RepositoryManager.getInstance().lookupRepositoryEntry(resource.getOLATResourceable(), false);
if(repoEntry != null) {
vC.contextPut("courseName" + i, StringHelper.escapeHtml(repoEntry.getDisplayname()));
String url = Settings.getServerContextPathURI();
url += "/url/RepositoryEntry/" + repoEntry.getKey() + "/CourseNode/" + resource.getSubPath();
vC.contextPut("courseLink" + i, url);
}
if (isLogDebugEnabled()) {
logDebug(" in loop : looked up course at : ", String.valueOf(System.currentTimeMillis()));
}
// get some stats about the restrictions if available
String[] stats = ePFMgr.getRestrictionStatisticsOfMap(structMap);
int toCollect = 0;
if (stats != null){
try {
toCollect = Integer.parseInt(stats[1]) - Integer.parseInt(stats[0]);
} catch (Exception e) {
// do nothing
toCollect = 0;
}
}
if (toCollect != 0) {
restriStats.add(String.valueOf(toCollect));
} else {
restriStats.add(null);
}
if (isLogDebugEnabled()) {
logDebug(" in loop : calculated restriction statistics at : ", String.valueOf(System.currentTimeMillis()));
}
} else {
deadLines.add(null);
restriStats.add(null);
}
// show owner on shared maps
if (!secCallback.isOwner()){
owners.add(ePFMgr.getAllOwnersAsString(map));
} else owners.add(null);
String artCount = String.valueOf(ePFMgr.countArtefactsInMap(map));
artAmount.add(artCount);
Integer childs = ePFMgr.countStructureChildren(map);
childAmount.add(childs);
amounts.add(translate("map.contains", new String[]{childs.toString(), artCount}));
mapStyles.add(ePFMgr.getValidStyleName(map));
if (isLogDebugEnabled()) {
logDebug(" in loop : got map details (artefact-amount, child-struct-amount, style) at : ", String.valueOf(System.currentTimeMillis()));
}
i++;
}
}
vC.contextPut("owners", owners);
vC.contextPut("deadLines", deadLines);
vC.contextPut("restriStats", restriStats);
vC.contextPut("mapStyles", mapStyles);
vC.contextPut("childAmount", childAmount);
vC.contextPut("artefactAmount", artAmount);
vC.contextPut("amounts", amounts);
vC.contextPut("userMaps", userMaps);
if (isLogDebugEnabled()) {
long now = System.currentTimeMillis();
logDebug("finished processing all maps at : ", String.valueOf(now));
logDebug("Total processing time for " + (i-1) + " maps was : ", String.valueOf(now-start));
}
}
@Override
public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
if(entries == null || entries.isEmpty()) return;
Long key = entries.get(0).getOLATResourceable().getResourceableId();
activateMap(ureq, key);
if(mapViewCtrl != null && entries.size() > 1) {
//map successfully activated
List<ContextEntry> subEntries = entries.subList(1, entries.size());
mapViewCtrl.activate(ureq, subEntries, entries.get(0).getTransientState());
}
}
/**
* @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 instanceof Link) {
Link srcLink = (Link) source;
if (srcLink.getUserObject() instanceof PortfolioStructureMap) {
PortfolioStructureMap selMap = (PortfolioStructureMap) srcLink.getUserObject();
if (srcLink.getComponentName().startsWith(VIEW_LINK_PREFIX)) {
activateMap(ureq, selMap);
fireEvent(ureq, new EPMapEvent(EPStructureEvent.SELECT, selMap));
} else if (srcLink.getComponentName().startsWith(DELETE_LINK_PREFIX)) {
deleteMap(ureq, selMap);
} else if (srcLink.getComponentName().startsWith(COPY_LINK_PREFIX)) {
List<String> buttonLabels = new ArrayList<String>();
String introKey = "copy.map.intro";
if (ePFMgr.isMapOwner(getIdentity(), selMap)){
buttonLabels.add(translate("copy.with.artefacts"));
introKey = "copy.map.intro2";
}
buttonLabels.add(translate("copy.without.artefacts"));
buttonLabels.add(translate("copy.cancel"));
String text = translate(introKey, StringHelper.escapeHtml(selMap.getTitle()));
copyMapCtrl = activateGenericDialog(ureq, translate("copy.map.title"), text, buttonLabels , copyMapCtrl);
copyMapCtrl.setUserObject(selMap);
} else if (srcLink.getComponentName().startsWith(SHARE_LINK_PREFIX)) {
popUpShareBox(ureq, selMap);
} else if (srcLink.getComponentName().equals(RESTRICT_LINK)){
restrictShareView = !restrictShareView;
initOrUpdateMaps(ureq);
}
} else{
if( srcLink.equals(forwardLink)){
currentPageNum++;
initOrUpdateMaps(ureq);
} else if (srcLink.getComponentName().startsWith(PAGING_LINK_PREFIX)){
Integer page = (Integer) srcLink.getUserObject();
currentPageNum = page.intValue();
initOrUpdateMaps(ureq);
} else if (srcLink.getComponentName().equals(RESTRICT_LINK)) {
restrictShareView = !restrictShareView;
initOrUpdateMaps(ureq);
}
}
}
}
private void deleteMap(UserRequest ureq, PortfolioStructureMap map) {
String intro = translate("delete.map.intro", StringHelper.escapeHtml(map.getTitle()));
delMapCtrl = activateYesNoDialog(ureq, translate("delete.map.title"), intro, delMapCtrl);
delMapCtrl.setUserObject(map);
}
private void popUpShareBox(UserRequest ureq, PortfolioStructureMap map) {
removeAsListenerAndDispose(shareListController);
removeAsListenerAndDispose(shareBox);
shareListController = new EPShareListController(ureq, getWindowControl(), map);
listenTo(shareListController);
String title = translate("map.share");
shareBox = new CloseableModalController(getWindowControl(), "close", shareListController.getInitialComponent(), true, title);
//shareBox.setInitialWindowSize(800, 600);
listenTo(shareBox);
shareBox.activate();
}
private void activateMap(UserRequest ureq, Long mapKey) {
if(mapKey == null) return;
boolean foundTheMap = false;
// we have a key, find the corresponding map with the current option (restrcited view or not)
for(PortfolioStructureMap map: userMaps) {
if(map.getKey().equals(mapKey) || (map.getResourceableId().equals(mapKey))) {
activateMap(ureq, map);
fireEvent(ureq, new EPMapEvent(EPStructureEvent.SELECT, map));
foundTheMap = true;
break;
}
}
if(!foundTheMap) {
// map not found, switch the option and retry to found the map
restrictShareView = !restrictShareView;
initOrUpdateMaps(ureq);
for(PortfolioStructureMap map: userMaps) {
if(map.getKey().equals(mapKey) || (map.getResourceableId().equals(mapKey))) {
activateMap(ureq, map);
fireEvent(ureq, new EPMapEvent(EPStructureEvent.SELECT, map));
break;
}
}
}
}
public void activateMap(UserRequest ureq, PortfolioStructureMap struct){
if(userMaps != null && !userMaps.contains(struct)) {
initOrUpdateMaps(ureq);
}
if(mapViewCtrl != null) {
removeAsListenerAndDispose(mapViewCtrl);
}
EPSecurityCallback secCallback = EPSecurityCallbackFactory.getSecurityCallback(ureq, struct, ePFMgr);
//release the previous if not correctly released by CLOSE events
WindowControl bwControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance(struct.getClass(), struct.getKey()), null);
mapViewCtrl = new EPMapViewController(ureq, bwControl, struct, true, false, secCallback);
listenTo(mapViewCtrl);
myPanel.pushContent(mapViewCtrl.getInitialComponent());
}
/**
* @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
* org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
*/
@Override
protected void event(UserRequest ureq, Controller source, Event event) {
super.event(ureq, source, event);
if (source == delMapCtrl) {
if (DialogBoxUIFactory.isYesEvent(event)) {
PortfolioStructure mapToDel = (PortfolioStructure) ((DialogBoxController) source).getUserObject();
String title = mapToDel.getTitle();
ePFMgr.deletePortfolioStructure(mapToDel);
showInfo("delete.map.success", title);
ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapPortfolioOres(mapToDel));
ThreadLocalUserActivityLogger.log(EPLoggingAction.EPORTFOLIO_MAP_REMOVED, getClass());
initOrUpdateMaps(ureq);
}
} else if (source == copyMapCtrl) {
if (event.equals(Event.CANCELLED_EVENT)) {
fireEvent(ureq, Event.CANCELLED_EVENT);
return;
}
int pos = DialogBoxUIFactory.getButtonPos(event);
boolean withArtefacts = false;
PortfolioStructure mapToCopy = (PortfolioStructure) ((DialogBoxController) source).getUserObject();
if (!ePFMgr.isMapOwner(getIdentity(), mapToCopy)) pos++; // shift clicked pos, when "with artefacts" was hidden before
if (pos == 2){
// clicked cancel button
fireEvent(ureq, Event.CANCELLED_EVENT);
return;
} else if (pos == 0) withArtefacts = true;
PortfolioStructureMap targetMap = ePFMgr.createAndPersistPortfolioDefaultMap(getIdentity(), translate("map.copy.of", mapToCopy.getTitle()), mapToCopy.getDescription());
ePFMgr.copyStructureRecursively(mapToCopy, targetMap, withArtefacts);
// open the map
String title = targetMap.getTitle();
showInfo("copy.map.success", title);
initOrUpdateMaps(ureq);
String businessPath = "[" + targetMap.getClass().getSimpleName() + ":" + targetMap.getResourceableId() + "]";
NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl());
} else if (source == mapViewCtrl) {
if(EPStructureEvent.CLOSE.equals(event.getCommand())) {
myPanel.popContent();
fireEvent(ureq, event);
removeAsListenerAndDispose(mapViewCtrl);
mapViewCtrl = null;
// refresh on close (back-link) to prevent stale object errors, when map got changed meanwhile
initOrUpdateMaps(ureq);
addToHistory(ureq);
} else if (EPStructureEvent.SUBMIT.equals(event.getCommand()) || event.equals(Event.CHANGED_EVENT)){
// refresh on submission of a map or on any other changes which needs an ui-update
initOrUpdateMaps(ureq);
}
} else if (source == shareListController) {
shareBox.deactivate();
removeAsListenerAndDispose(shareListController);
initOrUpdateMaps(ureq);
}
if (event instanceof EPStructureChangeEvent){
// event from child
String evCmd = event.getCommand();
if (evCmd.equals(EPStructureChangeEvent.ADDED) || evCmd.equals(EPStructureChangeEvent.CHANGED)){
initOrUpdateMaps(ureq);
}
}
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose()
*/
@Override
protected void doDispose() {
//
}
}