/**
* <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.List;
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.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.OLATResourceable;
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.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.LockResult;
import org.olat.course.CourseFactory;
import org.olat.course.ICourse;
import org.olat.course.nodes.CourseNode;
import org.olat.portfolio.EPLoggingAction;
import org.olat.portfolio.EPSecurityCallback;
import org.olat.portfolio.EPSecurityCallbackFactory;
import org.olat.portfolio.manager.EPFrontendManager;
import org.olat.portfolio.model.structel.EPPage;
import org.olat.portfolio.model.structel.EPStructuredMap;
import org.olat.portfolio.model.structel.EPStructuredMapTemplate;
import org.olat.portfolio.model.structel.EPTargetResource;
import org.olat.portfolio.model.structel.PortfolioStructure;
import org.olat.portfolio.model.structel.PortfolioStructureMap;
import org.olat.portfolio.model.structel.StructureStatusEnum;
import org.olat.portfolio.ui.structel.edit.EPStructureTreeAndDetailsEditController;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.user.DisplayPortraitController;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Description:<br>
* shows a map itself with containing pages, structures, etc.
*
* <P>
* Initial Date: 04.08.2010 <br>
*
* @author Roman Haag, roman.haag@frentix.com, http://www.frentix.com
*/
public class EPMapViewController extends BasicController implements Activateable2 {
private Link editButton, backLink, submitAssessLink;
private final VelocityContainer mainVc;
private EPMultiplePageController pageCtrl;
private EPStructureTreeAndDetailsEditController editCtrl;
private DialogBoxController confirmationSubmissionCtr;
private final boolean back;
private EditMode editMode = EditMode.view;
private PortfolioStructureMap map;
private EPSecurityCallback secCallback;
private LockResult lockEntry;
@Autowired
private EPFrontendManager ePFMgr;
@Autowired
private RepositoryManager repositoryManager;
@Autowired
private CoordinatorManager coordinatorManager;
public EPMapViewController(UserRequest ureq, WindowControl control, PortfolioStructureMap initialMap, boolean back,
boolean preview, EPSecurityCallback secCallback) {
super(ureq, control);
this.map = initialMap;
this.back = back;
this.secCallback = secCallback;
mainVc = createVelocityContainer("mapview");
// if this is a structured map (assigned from a template) do a sync first
if (map instanceof EPStructuredMap && (map.getStatus() == null || !map.getStatus().equals(StructureStatusEnum.CLOSED) )){
map = (PortfolioStructureMap) ePFMgr.loadPortfolioStructureByKey(map.getKey());
boolean syncOk = ePFMgr.synchronizeStructuredMapToUserCopy(map);
if (syncOk) {
showInfo("synced.map.success");
} else if(map == null) {
showWarning("synced.map.deleted");
putInitialPanel(createVelocityContainer("map_deleted"));
return;
} else {
showError("synced.map.error");
}
}
if(EPSecurityCallbackFactory.isLockNeeded(secCallback)) {
lockEntry = coordinatorManager.getCoordinator().getLocker().acquireLock(initialMap, ureq.getIdentity(), "mmp");
if(!lockEntry.isSuccess()) {
this.secCallback = EPSecurityCallbackFactory.updateAfterFailedLock(secCallback);
showWarning("map.already.edited");
}
}
//don't show the message for preview
if(initialMap instanceof EPStructuredMapTemplate && !preview) {
boolean inUse = ePFMgr.isTemplateInUse(initialMap, null, null, null);
if(inUse) {
showWarning("template.alreadyInUse");
}
}
initForm(ureq);
putInitialPanel(mainVc);
}
public boolean canEditStructure() {
return secCallback.canEditStructure();
}
protected void initForm(UserRequest ureq) {
Identity ownerIdentity = ePFMgr.getFirstOwnerIdentity(map);
if(ownerIdentity != null) {
DisplayPortraitController portraitCtr = new DisplayPortraitController(ureq, getWindowControl(), ownerIdentity, false,true,true,false);
mainVc.put("ownerportrait", portraitCtr.getInitialComponent());
}
mainVc.contextPut("map", map);
mainVc.contextPut("style", ePFMgr.getValidStyleName(map));
mainVc.remove(mainVc.getComponent("map.editButton"));
if(secCallback.canEditStructure()) {
editButton = LinkFactory.createButton("map.editButton", mainVc, this);
editButton.setElementCssClass("o_sel_ep_edit_map");
editButton.setIconLeftCSS("o_icon o_icon-fw o_icon_edit");
if(editMode == EditMode.view) {
editButton.setCustomDisplayText(translate("map.editButton.on"));
} else {
editButton.setCustomDisplayText(translate("map.editButton.off"));
}
}
if(back) {
backLink = LinkFactory.createLinkBack(mainVc, this);
}
mainVc.remove(mainVc.getComponent("map.submit.assess"));
if(secCallback.canSubmitAssess() && !StructureStatusEnum.CLOSED.equals(map.getStatus())) {
submitAssessLink = LinkFactory.createButtonSmall("map.submit.assess", mainVc, this);
}
if(map instanceof EPStructuredMap) {
EPTargetResource resource = ((EPStructuredMap)map).getTargetResource();
RepositoryEntry repoEntry = repositoryManager.lookupRepositoryEntry(resource.getOLATResourceable(), false);
if(repoEntry != null) {
mainVc.contextPut("courseName", StringHelper.escapeHtml(repoEntry.getDisplayname()));
String url = Settings.getServerContextPathURI();
url += "/url/RepositoryEntry/" + repoEntry.getKey() + "/CourseNode/" + resource.getSubPath();
mainVc.contextPut("courseLink", url);
}
}
mainVc.remove(mainVc.getComponent("addButton"));
if(secCallback.canAddPage() && !StructureStatusEnum.CLOSED.equals(map.getStatus())) {
EPAddElementsController addButton = new EPAddElementsController(ureq, getWindowControl(), map);
if(secCallback.canAddPage()) {
addButton.setShowLink(EPAddElementsController.ADD_PAGE);
}
mainVc.put("addButton", addButton.getInitialComponent());
listenTo(addButton);
}
mainVc.contextPut("closed", Boolean.valueOf((StructureStatusEnum.CLOSED.equals(map.getStatus()))));
List<PortfolioStructure> pageList = ePFMgr.loadStructureChildren(map);
if (pageList!=null && pageList.size() != 0) {
// prepare to paint pages also
removeAsListenerAndDispose(pageCtrl);
pageCtrl = new EPMultiplePageController(ureq, getWindowControl(), pageList, secCallback);
mainVc.put("pagesCtrl", pageCtrl.getInitialComponent());
listenTo(pageCtrl);
} else if (mainVc.getComponent("pagesCtrl")!=null){
mainVc.remove(mainVc.getComponent("pagesCtrl"));
}
}
/**
* @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest, org.olat.core.gui.components.form.flexible.FormItem, org.olat.core.gui.components.form.flexible.impl.FormEvent)
*/
@Override
public void event(UserRequest ureq, Component source, Event event) {
if (source == editButton){
toogleEditMode(ureq);
} else if(source == backLink) {
fireEvent(ureq, new EPMapEvent(EPStructureEvent.CLOSE, map));
} else if(source == submitAssessLink) {
if (preCheckMapSubmit()){
doConfirmSubmitAssess(ureq);
} else {
showWarning("map.cannot.submit.nomore.coursenode");
}
}
}
private void toogleEditMode(UserRequest ureq) {
removeAsListenerAndDispose(editCtrl);
if (editMode == EditMode.view){
edit(ureq);
} else {
view(ureq);
}
}
public void view(UserRequest ureq) {
PortfolioStructure currentEditedStructure = null;
if(editCtrl != null) {
removeAsListenerAndDispose(editCtrl);
mainVc.remove(editCtrl.getInitialComponent());
currentEditedStructure = editCtrl.getSelectedStructure();
}
initForm(ureq);
editMode = EditMode.view;
if(editButton != null) {
editButton.setCustomDisplayText(translate("map.editButton.on"));
}
if(currentEditedStructure != null && pageCtrl != null) {
EPPage page = getSelectedPage(currentEditedStructure);
if(page != null) {
pageCtrl.selectPage(ureq, page);
addToHistory(ureq, page, null);
}
}
}
public void edit(UserRequest ureq) {
if(canEditStructure()) {
removeAsListenerAndDispose(editCtrl);
PortfolioStructure selectedPage = null;
if(pageCtrl != null) {
selectedPage = pageCtrl.getSelectedPage();
}
initOrUpdateEditMode(ureq, selectedPage);
editMode = EditMode.editor;
}
}
@Override
public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
if(entries == null || entries.isEmpty()) return;
pageCtrl.activate(ureq, entries, state);
}
public PortfolioStructureMap getMap() {
return map;
}
private EPPage getSelectedPage(PortfolioStructure structure) {
PortfolioStructure current = structure;
do {
if(current instanceof EPPage) {
return (EPPage)current;
}
current = current.getRoot();
} while (current != null);
return null;
}
private boolean preCheckMapSubmit(){
EPStructuredMap submittedMap = (EPStructuredMap) map;
try {
EPTargetResource resource = submittedMap.getTargetResource();
OLATResourceable courseOres = resource.getOLATResourceable();
ICourse course = CourseFactory.loadCourse(courseOres);
CourseNode courseNode = course.getRunStructure().getNode(resource.getSubPath());
if (courseNode==null) return false;
} catch (Exception e) {
return false;
}
return true;
}
private void doConfirmSubmitAssess(UserRequest ureq) {
if(ePFMgr.checkCollectRestrictionOfMap(map)) {
String title = translate("map.submit.assess.title");
String text = translate("map.submit.assess.description");
confirmationSubmissionCtr = activateYesNoDialog(ureq, title, text, confirmationSubmissionCtr);
} else {
String title = translate("map.submit.assess.restriction.error.title");
String[] stats = ePFMgr.getRestrictionStatisticsOfMap(map);
String text = translate("map.submit.assess.restriction.error.description") + "<br/>" + translate("map.submit.assess.restriction.error.hint", stats);
confirmationSubmissionCtr = activateYesNoDialog(ureq, title, text, confirmationSubmissionCtr);
confirmationSubmissionCtr.setCssClass("o_icon_warn");
}
}
private void doSubmitAssess(UserRequest ureq) {
ePFMgr.submitMap(map);
secCallback = EPSecurityCallbackFactory.getSecurityCallback(ureq, map, ePFMgr);
fireEvent(ureq, new EPMapEvent(EPStructureEvent.SUBMIT, map));
mainVc.remove(mainVc.getComponent("editor")); // switch back to non-edit mode
ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapPortfolioOres(map));
ThreadLocalUserActivityLogger.log(EPLoggingAction.EPORTFOLIO_TASK_FINISHED, getClass());
reloadMapAndRefreshUI(ureq);
}
/**
* @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 (event instanceof EPStructureChangeEvent && event.getCommand().equals(EPStructureChangeEvent.ADDED)){
EPStructureChangeEvent changeEvent = (EPStructureChangeEvent)event;
PortfolioStructure structure = changeEvent.getPortfolioStructure();
// don't do reloadMapAndRefreshUI(ureq) here; no db-commit yet! no refresh -> stale object!
if(structure instanceof EPPage) {
//jump to the edit mode for new pages
initOrUpdateEditMode(ureq, structure);
}
} else if(event instanceof EPStructureEvent && event.getCommand().equals(EPStructureEvent.CHANGE)) {
// reload map
reloadMapAndRefreshUI(ureq);
} else if (source == editCtrl && event.equals(Event.CHANGED_EVENT)) {
// refresh view on changes in TOC or style
reloadMapAndRefreshUI(ureq);
PortfolioStructure selectedPage = editCtrl.getSelectedStructure();
initOrUpdateEditMode(ureq, selectedPage);
} else if (source == confirmationSubmissionCtr) {
if (DialogBoxUIFactory.isYesEvent(event)) {
doSubmitAssess(ureq);
}
}
fireEvent(ureq, event); // fire to multiple maps controller, so it can refresh itself!
}
private void initOrUpdateEditMode(UserRequest ureq, PortfolioStructure startStruct) {
removeAsListenerAndDispose(editCtrl);
editCtrl = new EPStructureTreeAndDetailsEditController(ureq, getWindowControl(), startStruct, map, secCallback);
mainVc.put("editor", editCtrl.getInitialComponent());
listenTo(editCtrl);
editButton.setUserObject(Boolean.TRUE);
editButton.setCustomDisplayText(translate("map.editButton.off"));
}
private void reloadMapAndRefreshUI(UserRequest ureq){
this.map = (PortfolioStructureMap) ePFMgr.loadPortfolioStructureByKey(map.getKey());
initForm(ureq);
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose()
*/
@Override
protected void doDispose() {
if(lockEntry != null) {
coordinatorManager.getCoordinator().getLocker().releaseLock(lockEntry);
lockEntry = null;
}
}
private enum EditMode {
view,
editor
}
}