/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <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> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.commons.fullWebApp; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import org.olat.NewControllerFactory; import org.olat.admin.landingpages.LandingPagesModule; import org.olat.admin.layout.LayoutModule; import org.olat.admin.layout.LogoInformations; import org.olat.basesecurity.BaseSecurityModule; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.chiefcontrollers.BaseChiefController; import org.olat.core.commons.chiefcontrollers.ChiefControllerMessageEvent; import org.olat.core.commons.chiefcontrollers.LanguageChangedEvent; import org.olat.core.commons.controllers.resume.ResumeSessionController; import org.olat.core.commons.fullWebApp.util.GlobalStickyMessage; import org.olat.core.dispatcher.Dispatcher; import org.olat.core.gui.GUIMessage; import org.olat.core.gui.UserRequest; import org.olat.core.gui.WindowManager; import org.olat.core.gui.WindowSettings; import org.olat.core.gui.Windows; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.ComponentCollection; import org.olat.core.gui.components.Window; import org.olat.core.gui.components.countdown.CountDownComponent; import org.olat.core.gui.components.htmlheader.jscss.CustomCSS; import org.olat.core.gui.components.htmlheader.jscss.CustomJSComponent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.panel.OncePanel; import org.olat.core.gui.components.panel.Panel; import org.olat.core.gui.components.panel.StackedPanel; import org.olat.core.gui.components.text.TextFactory; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.ChiefController; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.ScreenMode; import org.olat.core.gui.control.ScreenMode.Mode; import org.olat.core.gui.control.VetoableCloseController; import org.olat.core.gui.control.WindowBackOffice; 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.dtabs.DTab; import org.olat.core.gui.control.generic.dtabs.DTabImpl; import org.olat.core.gui.control.generic.dtabs.DTabs; import org.olat.core.gui.control.guistack.GuiStack; import org.olat.core.gui.control.navigation.BornSiteInstance; import org.olat.core.gui.control.navigation.NavElement; import org.olat.core.gui.control.navigation.SiteInstance; import org.olat.core.gui.control.util.ZIndexWrapper; import org.olat.core.gui.control.winmgr.JSCommand; import org.olat.core.gui.themes.Theme; import org.olat.core.gui.translator.Translator; import org.olat.core.helpers.Settings; import org.olat.core.id.IdentityEnvironment; 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.HistoryPoint; import org.olat.core.id.context.HistoryPointImpl; import org.olat.core.id.context.StateEntry; import org.olat.core.id.context.StateSite; import org.olat.core.logging.AssertException; import org.olat.core.logging.JavaScriptTracingController; import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; import org.olat.core.util.prefs.Preferences; import org.olat.core.util.resource.OresHelper; import org.olat.course.assessment.AssessmentMode.Status; import org.olat.course.assessment.AssessmentModeNotificationEvent; import org.olat.course.assessment.model.TransientAssessmentMode; import org.olat.course.assessment.ui.mode.AssessmentModeGuardController; import org.olat.course.assessment.ui.mode.ChooseAssessmentModeEvent; import org.olat.gui.control.UserToolsMenuController; import org.olat.home.HomeSite; /** * Description:<br> * The BaseFullWebappController defines the outer most part of the main layout * <P> * Initial Date: 20.07.2007 <br> * * @author patrickb, Felix Jost, Florian Gnägi */ public class BaseFullWebappController extends BasicController implements DTabs, ChiefController, GenericEventListener { private static final String PRESENTED_AFTER_LOGIN_WORKFLOW = "presentedAfterLoginWorkflow"; //Base chief private Panel contentPanel; private Controller jsServerC; private Controller debugC; private Controller inlineTranslationC; private Controller developmentC; private Controller jsLoggerC; private List<String> bodyCssClasses = new ArrayList<>(3); private Boolean reload; private final ScreenMode screenMode = new ScreenMode(); private WindowBackOffice wbo; // STARTED private GuiStack currentGuiStack; private Panel main, modalPanel; private GUIMessage guiMessage; private OncePanel guimsgPanel; private Panel cssHolder, guimsgHolder, currentMsgHolder; private VelocityContainer guimsgVc, mainVc, navSitesVc, navTabsVc; private StickyMessageComponent stickyMessageCmp; private LockStatus lockStatus; private OLATResourceable lockResource; private TransientAssessmentMode lockMode; // NEW FROM FullChiefController private LockableController topnavCtr; private LockableController footerCtr; private UserToolsMenuController userToolsMenuCtrl; private SiteInstance curSite; private DTab curDTab; private final List<TabState> siteAndTabs = new ArrayList<TabState>(); // the dynamic tabs list private List<DTab> dtabs; private List<Integer> dtabsLinkNames; private List<Controller> dtabsControllers; private Map<DTab,HistoryPoint> dtabToBusinessPath = new HashMap<DTab,HistoryPoint>(); // used as link id which is load url safe (e.g. replayable private int dtabCreateCounter = 0; // the sites list private SiteInstance userTools; private List<SiteInstance> sites; private Map<SiteInstance, BornSiteInstance> siteToBornSite = new HashMap<SiteInstance, BornSiteInstance>(); private Map<SiteInstance,HistoryPoint> siteToBusinessPath = new HashMap<SiteInstance,HistoryPoint>(); private BaseFullWebappControllerParts baseFullWebappControllerParts; protected Controller contentCtrl; private ResumeSessionController resumeSessionCtrl; private AssessmentModeGuardController assessmentGuardCtrl; private StackedPanel initialPanel; private WindowSettings wSettings; private final boolean isAdmin; private final int maxTabs = 20; public BaseFullWebappController(UserRequest ureq, BaseFullWebappControllerParts baseFullWebappControllerParts) { // only-use-in-super-call, since we define our own super(ureq, null); setLoggingUserRequest(ureq); this.baseFullWebappControllerParts = baseFullWebappControllerParts; guiMessage = new GUIMessage(); guimsgPanel = new OncePanel("guimsgPanel"); UserSession usess = ureq.getUserSession(); WindowManager winman = Windows.getWindows(ureq).getWindowManager(); String windowSettings = (String)usess.removeEntryFromNonClearedStore(Dispatcher.WINDOW_SETTINGS); WindowSettings settings = WindowSettings.parse(windowSettings); wbo = winman.createWindowBackOffice("basechiefwindow", this, settings); IdentityEnvironment identityEnv = usess.getIdentityEnvironment(); if(identityEnv != null && identityEnv.getRoles() != null) { isAdmin = identityEnv.getRoles().isOLATAdmin(); } else { isAdmin = false; } // define the new windowcontrol WindowControl myWControl = new BaseFullWebappWindowControl(this, wbo); overrideWindowControl(myWControl); Window myWindow = myWControl.getWindowBackOffice().getWindow(); myWindow.setDTabs(this); //REVIEW:PB remove if back support is desired myWindow.addListener(this);//to be able to report BACK / FORWARD / RELOAD /* * does all initialisation, moved to method because of possibility to react * on LanguageChangeEvents -> resets and rebuilds footer, header, topnav, sites, content etc. */ initialize(ureq); mainVc.setDomReplaceable(false); initialPanel = putInitialPanel(mainVc); initialPanel.setDomReplaceable(false); // ------ all the frame preparation is finished ---- initializeBase(ureq, winman, initialPanel); if(usess.isAuthenticated() && !isAdmin && usess.getAssessmentModes() != null && usess.getAssessmentModes().size() > 0) { assessmentGuardCtrl = new AssessmentModeGuardController(ureq, getWindowControl(), usess.getAssessmentModes(), false); listenTo(assessmentGuardCtrl); assessmentGuardCtrl.getInitialComponent(); lockStatus = LockStatus.popup; //as security remove all removeRedirects(usess); //lock the gui lockGUI(); } else { // present an overlay with configured afterlogin-controllers or nothing if none configured. // presented only once per session. Boolean alreadySeen = ((Boolean)usess.getEntry(PRESENTED_AFTER_LOGIN_WORKFLOW)); if (usess.isAuthenticated() && alreadySeen == null) { resumeSessionCtrl = new ResumeSessionController(ureq, getWindowControl()); listenTo(resumeSessionCtrl); resumeSessionCtrl.getInitialComponent(); ureq.getUserSession().putEntry(PRESENTED_AFTER_LOGIN_WORKFLOW, Boolean.TRUE); } } if(assessmentGuardCtrl == null && (resumeSessionCtrl == null || (!resumeSessionCtrl.redirect() && !resumeSessionCtrl.userInteractionNeeded())) && usess.getEntry("AuthDispatcher:businessPath") == null) { String bc = initializeDefaultSite(ureq); if(StringHelper.containsNonWhitespace(bc) && usess.getEntry("redirect-bc") == null) { usess.putEntry("redirect-bc", bc); } } Object fullScreen = Windows.getWindows(ureq).getFullScreen(); if(Boolean.TRUE.equals(fullScreen)) { Windows.getWindows(ureq).setFullScreen(null); screenMode.setMode(Mode.full); } // register for cycle event to be able to adjust the guimessage place getWindowControl().getWindowBackOffice().addCycleListener(this); // register for locale change events -> //move to a i18nModule? languageManger? languageChooserController? OLATResourceable wrappedLocale = OresHelper.createOLATResourceableType(Locale.class); usess.getSingleUserEventCenter().registerFor(this, getIdentity(), wrappedLocale); //register for assessment mode CoordinatorManager.getInstance().getCoordinator().getEventBus() .registerFor(this, getIdentity(), AssessmentModeNotificationEvent.ASSESSMENT_MODE_NOTIFICATION); // register for global sticky message changed events GlobalStickyMessage.registerForGlobalStickyMessage(this, getIdentity()); } @Override public boolean isLoginInterceptionInProgress() { return resumeSessionCtrl != null && resumeSessionCtrl.userInteractionNeeded(); } /** * Remove all possible redirect commands in session. * * @param usess */ private void removeRedirects(UserSession usess) { usess.removeEntry("AuthDispatcher:businessPath"); usess.removeEntry("redirect-bc"); usess.removeEntryFromNonClearedStore("AuthDispatcher:businessPath"); usess.removeEntryFromNonClearedStore("redirect-bc"); } private void initializeBase(UserRequest ureq, WindowManager winman, ComponentCollection mainPanel) { // component-id of mainPanel for the window id mainVc.contextPut("o_winid", mainPanel.getDispatchID()); BaseSecurityModule securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class); mainVc.contextPut("enforceTopFrame", securityModule.isForceTopFrame()); // add optional css classes mainVc.contextPut("bodyCssClasses", bodyCssClasses); Window w = wbo.getWindow(); mainVc.put("jsCssRawHtmlHeader", w.getJsCssRawHtmlHeader()); // control part for ajax-communication. returns an empty panel if ajax // is not enabled, so that ajax can be turned on on the fly for // development mode jsServerC = wbo.createAJAXController(ureq); mainVc.put("jsServer", jsServerC.getInitialComponent()); // init with no bookmark (=empty bc) mainVc.contextPut("o_bc", ""); mainVc.contextPut("o_serverUri", Settings.createServerURI()); // the current language; used e.g. by screenreaders mainVc.contextPut("lang", ureq.getLocale().toString()); // the current GUI theme and the global settings that contains the // font-size. both are pushed as objects so that window.dirty always reads // out the correct value mainVc.contextPut("theme", w.getGuiTheme()); mainVc.contextPut("globalSettings", winman.getGlobalSettings()); // also add the optional theme javascript addThemeJS(); // content panel contentPanel = new Panel("olatContentPanel"); mainVc.put("olatContentPanel", contentPanel); mainVc.contextPut("o_winid", w.getDispatchID()); mainVc.contextPut("buildversion", Settings.getVersion()); if (wbo.isDebuging()) { debugC = wbo.createDebugDispatcherController(ureq, getWindowControl()); mainVc.put("guidebug", debugC.getInitialComponent()); } // Inline translation interceptor. when the translation tool is enabled it // will start the translation tool in translation mode, if the overlay // feature is enabled it will start in customizing mode // fxdiff: allow user-managers to use the inline translation also. if (ureq.getUserSession().isAuthenticated() && (ureq.getUserSession().getRoles().isOLATAdmin() || ureq.getUserSession().getRoles().isUserManager()) && (I18nModule.isTransToolEnabled() || I18nModule.isOverlayEnabled())) { inlineTranslationC = wbo.createInlineTranslationDispatcherController(ureq, getWindowControl()); Preferences guiPrefs = ureq.getUserSession().getGuiPreferences(); Boolean isInlineTranslationEnabled = (Boolean) guiPrefs.get(I18nModule.class, I18nModule.GUI_PREFS_INLINE_TRANSLATION_ENABLED, Boolean.FALSE); I18nManager.getInstance().setMarkLocalizedStringsEnabled(ureq.getUserSession(), isInlineTranslationEnabled); mainVc.put("inlineTranslation", inlineTranslationC.getInitialComponent()); } // debug info if debugging if (wbo.isDebuging()) { developmentC = wbo.createDevelopmentController(ureq, getWindowControl()); mainVc.put("development", developmentC.getInitialComponent()); } // attach AJAX javascript console jsLoggerC = new JavaScriptTracingController(ureq, getWindowControl()); // the js logger provides only a header element, nevertheless we need to // put it into the main velocity container. mainVc.put("jsLoggerC", jsLoggerC.getInitialComponent()); // put the global js translator mapper path into the main window mainVc.contextPut("jsTranslationMapperPath", BaseChiefController.jsTranslationMapperPath); // master window //w.addListener(this); // to be able to report "browser reload" to the user w.setContentPane(mainPanel); } @Override public Window getWindow() { return wbo.getWindow(); } @Override public WindowControl getWindowControl() { return super.getWindowControl(); } private void initialize(UserRequest ureq) { mainVc = createVelocityContainer("fullwebapplayout"); mainVc.contextPut("screenMode", screenMode); LayoutModule layoutModule = CoreSpringFactory.getImpl(LayoutModule.class); LandingPagesModule landingPagesModule = CoreSpringFactory.getImpl(LandingPagesModule.class); LogoInformations logoInfos = new LogoInformations(ureq, layoutModule, landingPagesModule); mainVc.contextPut("logoInfos", logoInfos); // use separate container for navigation to prevent full page refresh in ajax mode on site change // nav is not a controller part because it is a fundamental part of the BaseFullWebAppConroller. navSitesVc = createVelocityContainer("nav_sites"); navSitesVc.setDomReplacementWrapperRequired(false); navSitesVc.contextPut("visible", Boolean.TRUE); mainVc.put("sitesComponent", navSitesVc); navTabsVc = createVelocityContainer("nav_tabs"); navTabsVc.setDomReplacementWrapperRequired(false); mainVc.put("tabsComponent", navTabsVc); // GUI messages guimsgVc = createVelocityContainer("guimsg"); guimsgVc.contextPut("guiMessage", guiMessage); guimsgHolder = new Panel("guimsgholder"); guimsgHolder.setContent(guimsgPanel); currentMsgHolder = guimsgHolder; mainVc.put("guimessage", guimsgHolder); // CSS panel cssHolder = new Panel("customCss"); mainVc.put("customCssHolder", cssHolder); // sticky maintenance message stickyMessageCmp = new StickyMessageComponent("stickymsg", screenMode); mainVc.put("stickymsg", stickyMessageCmp); updateStickyMessage(); dtabs = new ArrayList<>(); dtabsLinkNames = new ArrayList<>(); dtabsControllers = new ArrayList<>(); // -- sites -- by definition the first site is activated at the beginning userTools = new HomeSite(null); sites = baseFullWebappControllerParts.getSiteInstances(ureq, getWindowControl()); if (sites != null && sites.size() == 0) { sites = null; } List<String> siteLinks = new ArrayList<>(); // either sites is null or contains at least one SiteInstance. if (sites != null) { // create the links for the sites for (Iterator<SiteInstance> iterator = sites.iterator(); iterator.hasNext();) { SiteInstance si = iterator.next(); NavElement navEl = si.getNavElement(); if(navEl != null) { String linkName = "t" + CodeHelper.getRAMUniqueID(); siteLinks.add(linkName); Link link = LinkFactory.createCustomLink(linkName, "t", "", Link.NONTRANSLATED, navSitesVc, this); link.setCustomDisplayText(navEl.getTitle()); link.setTitle(navEl.getDescription()); link.setUserObject(si); Character accessKey = navEl.getAccessKey(); if (accessKey != null && StringHelper.containsNonWhitespace(accessKey.toString())) { link.setAccessKey(accessKey.toString()); } } } } navSitesVc.contextPut("sites", siteLinks); navSitesVc.contextPut("tabhelper", this); navTabsVc.contextPut("dtabs", dtabs); navTabsVc.contextPut("dtabsLinkNames", dtabsLinkNames); navTabsVc.contextPut("tabhelper", this); // header, optional (e.g. for logo, advertising ) Controller headerCtr = baseFullWebappControllerParts.createHeaderController(ureq, getWindowControl()); if (headerCtr != null) { listenTo(headerCtr); // cleanup on dispose Component headerCmp = headerCtr.getInitialComponent(); mainVc.put("headerComponent", headerCmp); } // topnav, optional (e.g. for imprint, logout) topnavCtr = baseFullWebappControllerParts.createTopNavController(ureq, getWindowControl()); if (topnavCtr != null) { listenTo(topnavCtr); // cleanup on dispose mainVc.put("topnavComponent", topnavCtr.getInitialComponent()); userToolsMenuCtrl = new UserToolsMenuController(ureq, getWindowControl()); listenTo(userToolsMenuCtrl); mainVc.put("menuComponent", userToolsMenuCtrl.getInitialComponent()); } // panel for modal overlays, placed right after the olat-header-div modalPanel = new Panel("ccmodalpanel"); mainVc.put("modalpanel", modalPanel); // main, mandatory (e.g. a LayoutMain3ColsController) main = new Panel("mainContent"); mainVc.put("main", main); // footer, optional (e.g. for copyright, powered by) footerCtr = baseFullWebappControllerParts.createFooterController(ureq, getWindowControl()); if (footerCtr != null) { listenTo(footerCtr); // cleanup on dispose Component footerCmp = footerCtr.getInitialComponent(); mainVc.put("footerComponent", footerCmp); } contentCtrl = baseFullWebappControllerParts.getContentController(ureq, getWindowControl()); if (contentCtrl != null) { listenTo(contentCtrl); GuiStack gs = getWindowControl().getWindowBackOffice().createGuiStack(contentCtrl.getInitialComponent()); setGuiStack(gs); main.setContent(contentCtrl.getInitialComponent()); } else { main.setContent(TextFactory.createTextComponentFromString("empty", "", null, false, null)); //set a guistack for the after login interceptor GuiStack gs = getWindowControl().getWindowBackOffice().createGuiStack(new Panel("dummy")); setGuiStack(gs); } setWindowSettings(getWindowControl().getWindowBackOffice().getWindowSettings()); } private void updateStickyMessage() { stickyMessageCmp.setText(GlobalStickyMessage.getGlobalStickyMessage()); } /** * @param ureq * @return The current business path if a site is initialized or null */ private String initializeDefaultSite(UserRequest ureq) { String businessPath = null; if (sites != null && sites.size() > 0 && curSite == null && curDTab == null && contentCtrl == null && lockResource == null) { SiteInstance s = sites.get(0); //activate site only if no content was set -> allow content before activation of default site. activateSite(s, ureq, null, false); businessPath = updateBusinessPath(ureq, s); } return businessPath; } protected GUIMessage getGUIMessage() { return guiMessage; } protected OncePanel getGUIMsgPanel() { return guimsgPanel; } protected GuiStack getCurrentGuiStack() { return currentGuiStack; } protected VelocityContainer getGUIMsgVc() { return guimsgVc; } private void setWindowSettings(WindowSettings wSettings) { if((this.wSettings == null && wSettings != null) || (this.wSettings != null && !this.wSettings.equals(wSettings))) { this.wSettings = wSettings; boolean navVisible = wSettings == null || !wSettings.isHideNavigation(); navSitesVc.setVisible(navVisible); navTabsVc.setVisible(navVisible); if (topnavCtr != null) { topnavCtr.getInitialComponent().setVisible(wSettings == null || !wSettings.isHideHeader()); } if (footerCtr != null) { footerCtr.getInitialComponent().setVisible(wSettings == null || !wSettings.isHideFooter()); } mainVc.setDirty(true); } } /** * adds the theme custom js-code to the mainVc */ private void addThemeJS() { Theme currentTheme = getWindowControl().getWindowBackOffice().getWindow().getGuiTheme(); if (currentTheme.hasCustomJS()) { String relPath = currentTheme.getRelPathToCustomJS(); CustomJSComponent customJS = new CustomJSComponent("customThemejs", new String[] { relPath }); if (isLogDebugEnabled()) { logDebug("injecting custom javascript from current OLAT-Theme", relPath); } // no need to wrap in panel, will never change mainVc.put(customJS.getComponentName(), customJS); } } @Override protected void event(UserRequest ureq, Component source, Event event) { if (source instanceof Link) { Link link = (Link) source; String mC = link.getCommand().substring(0, 1); if (mC.equals("t")) { // activate normal tab SiteInstance s = (SiteInstance) link.getUserObject(); //fix the state of the last tab/site updateBusinessPath(ureq); HistoryPoint point = null; if(siteToBusinessPath.containsKey(s)) { point = siteToBusinessPath.get(s); } activateSite(s, ureq, null, true); if(point != null) { BusinessControlFactory.getInstance().addToHistory(ureq, point); } updateBusinessPath(ureq, s); } else if (mC.equals("a")) { // activate dyntab DTab dt = (DTab) link.getUserObject(); //fix the state of the last tab/site updateBusinessPath(ureq); HistoryPoint point = null; if(dtabToBusinessPath.containsKey(dt)) { point = dtabToBusinessPath.get(dt); } doActivateDTab(dt); if(dt.getController() instanceof Activateable2) { ((Activateable2)dt.getController()).activate(ureq, null, new ReloadEvent()); } if(point != null) { BusinessControlFactory.getInstance().addToHistory(ureq, point); } } else if (mC.equals("c")) { // close dyntab DTab dt = (DTab) link.getUserObject(); requestCloseTab(ureq, dt); } } else if (source == getWindowControl().getWindowBackOffice().getWindow()) { if (event == Window.OLDTIMESTAMPCALL) { getLogger().info("RELOAD"); HistoryPoint point = ureq.getUserSession().popLastHistoryEntry(); if(point != null) { back(ureq, point); } } } } protected void back(UserRequest ureq, HistoryPoint cstate) { List<ContextEntry> entries = cstate.getEntries(); if(entries.isEmpty()) return; entries = new ArrayList<ContextEntry>(entries); ContextEntry state = entries.remove(0); if(state == null) return;//no red screen for this OLATResourceable ores = state.getOLATResourceable(); if(ores != null && "HomeSite".equals(ores.getResourceableTypeName())) { activateSite(userTools, ureq, entries, false); } else { DTab dt = getDTab(ores); if(dt != null) { doActivateDTab(dt); if(dt.getController() instanceof Activateable2) { ((Activateable2)dt.getController()).activate(ureq, entries, null); } updateBusinessPath(ureq, dt); } else { StateEntry s = state.getTransientState(); if(s instanceof StateSite && ((StateSite)s).getSite() != null && sites != null) { SiteInstance site = ((StateSite)s).getSite(); for(SiteInstance savedSite:sites) { if(savedSite != null && site.getClass().equals(savedSite.getClass())) { activateSite(savedSite, ureq, entries, false); } } } } } } @Override protected void event(UserRequest ureq, Controller source, Event event) { if(resumeSessionCtrl == source) { resumeSessionCtrl.redirect(); resumeSessionCtrl = null; initializeDefaultSite(ureq); } else if(assessmentGuardCtrl == source) { if(event instanceof ChooseAssessmentModeEvent) { ChooseAssessmentModeEvent came = (ChooseAssessmentModeEvent)event; lockMode = came.getAssessmentMode(); lockStatus = LockStatus.locked; removeAsListenerAndDispose(assessmentGuardCtrl); assessmentGuardCtrl = null; } else if("continue".equals(event.getCommand())) { //unlock session ureq.getUserSession().unlockResource(); unlockResource(); initializeDefaultSite(ureq); removeAsListenerAndDispose(assessmentGuardCtrl); assessmentGuardCtrl = null; lockStatus = null; lockMode = null; } } else { int tabIndex = dtabsControllers.indexOf(source); if (tabIndex > -1) { // Event comes from a controller in a dtab. Check if the controller is // finished and close the tab. Cancel and failed is interpreted as // finished. if (event == Event.DONE_EVENT || event == Event.CANCELLED_EVENT || event == Event.FAILED_EVENT) { DTab tab = dtabs.get(tabIndex); removeDTab(ureq, tab);//disposes also tab and controllers } } } } @Override protected void doDispose() { // deregister for chief global sticky messages events GlobalStickyMessage.deregisterForGlobalStickyMessage(this); // dispose sites and tabs if (dtabs != null) { for (DTab tab : dtabs) { tab.dispose(); } for (BornSiteInstance bornSite : siteToBornSite.values()) { bornSite.dispose(); } dtabs = null; dtabsControllers = null; sites = null; siteToBornSite = null; siteToBusinessPath = null; dtabToBusinessPath = null; } //clear the DTabs Service WindowBackOffice wbackOffice = getWindowControl().getWindowBackOffice(); wbackOffice.getWindow().setDTabs(null); wbackOffice.removeCycleListener(this); if (jsServerC != null) { jsServerC.dispose(); jsServerC = null; } if (debugC != null) { debugC.dispose(); debugC = null; } if (inlineTranslationC != null) { inlineTranslationC.dispose(); inlineTranslationC = null; } if (developmentC != null) { developmentC.dispose(); developmentC = null; } if (jsLoggerC != null) { jsLoggerC.dispose(); jsLoggerC = null; } //deregister for assessment mode CoordinatorManager.getInstance().getCoordinator().getEventBus() .deregisterFor(this, AssessmentModeNotificationEvent.ASSESSMENT_MODE_NOTIFICATION); } private void setGuiStack(GuiStack guiStack) { currentGuiStack = guiStack; StackedPanel guiStackPanel = currentGuiStack.getPanel(); main.setContent(guiStackPanel); // place for modal dialogs, which are overlayd over the normal layout (using // css alpha blending) // maybe null if no current modal dialog -> clears the panel StackedPanel modalStackP = currentGuiStack.getModalPanel(); modalPanel.setContent(modalStackP); } /** * Activate a site if not locked * * @param s * @param ureq * @param entries * @param forceReload */ private void activateSite(SiteInstance s, UserRequest ureq, List<ContextEntry> entries, boolean forceReload) { if(lockResource != null) return; BornSiteInstance bs = siteToBornSite.get(s); GuiStack gs; Controller resC; //PB//WindowControl site_wControl; if (bs != null && s != curSite) { // single - click -> fetch guistack from cache gs = bs.getGuiStackHandle(); resC = bs.getController(); } else if (bs != null && s == curSite && !forceReload) { //via activate, don't force the reload gs = bs.getGuiStackHandle(); resC = bs.getController(); } else { // bs == null (not yet in cache) || s == curSite // double click or not yet in cache. // dispose old controller if (bs != null) { // already in cache -> dispose old bs.getController().dispose(); } // reset site and create new controller s.reset(); resC = s.createController(ureq, getWindowControl()); gs = getWindowControl().getWindowBackOffice().createGuiStack(resC.getInitialComponent()); //PB//site_wControl = bwControl; //PB//siteToBornSite.put(s, new BornSiteInstance(gs, resC, bwControl)); siteToBornSite.put(s, new BornSiteInstance(gs, resC)); } doActivateSite(s, gs); if(resC instanceof Activateable2) { ((Activateable2)resC).activate(ureq, entries, null); } //perhaps has activation changed the gui stack and it need to be updated setGuiStack(gs); } private void doActivateSite(SiteInstance s, GuiStack gs) { removeCurrentCustomCSSFromView(); // set curSite setCurrent(s, null); setGuiStack(gs); NavElement navEl = s.getNavElement(); if(navEl != null) { setWindowTitle(navEl.getTitle()); setBodyDataResource("site", s.getClass().getSimpleName(), null); } // update marking of active site/tab navSitesVc.setDirty(true); navTabsVc.setDirty(true); // add css for this site BornSiteInstance bs = siteToBornSite.get(s); if (bs != null) { addCurrentCustomCSSToView(bs.getCustomCSS()); } } private void doActivateDTab(DTab dtabi) { removeCurrentCustomCSSFromView(); //set curDTab setCurrent(null, dtabi); setGuiStack(dtabi.getGuiStackHandle()); // set description as page title, getTitel() might contain trucated values setWindowTitle(dtabi.getNavElement().getDescription()); // set data-* values on body for css and javascript customizations OLATResourceable ores = dtabi.getOLATResourceable(); String restype = (ores == null ? null : ores.getResourceableTypeName()); String resid = (ores == null ? null : ores.getResourceableId() + ""); OLATResourceable initialOres = dtabi.getInitialOLATResourceable(); String repoid = (initialOres == null ? null : initialOres.getResourceableId() + ""); setBodyDataResource(restype, resid, repoid); // update marking of active site/tab navSitesVc.setDirty(true); navTabsVc.setDirty(true); // add css for this tab addCurrentCustomCSSToView(dtabi.getCustomCSS()); } private void setWindowTitle(String newTitle) { StringBuilder sb = new StringBuilder(); sb.append("document.title = \""); sb.append(Formatter.escapeDoubleQuotes(translate("page.appname") + " - " + newTitle)); sb.append("\";"); JSCommand jsc = new JSCommand(sb.toString()); WindowControl wControl = getWindowControl(); if (wControl != null && wControl.getWindowBackOffice() != null) { wControl.getWindowBackOffice().sendCommandTo(jsc); } } /** * Helper method to set data-" attributes to the body element in the DOM. * Using the data attributes it is possible to implement css styles specific * to certain areas (sites, groups, courses) of for specific course id's. * The data attributes are removed if null * * @param restype The resource type or NULL if n.a. * @param resid The resource ID that matchtes the restype or NULL if n.a. * @param repoentryid the repository entry ID if available or NULL if n.a. */ private void setBodyDataResource(String restype, String resid, String repoentryid) { StringBuilder sb = new StringBuilder(); sb.append("try {var oobody = jQuery('body');"); // The source type info: for sites value is 'site', for courses // 'CourseModule' and groups 'BusinessGroup' if (restype == null) { sb.append("oobody.removeAttr('data-restype');"); } else { sb.append("oobody.attr('data-restype','"); sb.append(Formatter.escapeDoubleQuotes(restype)); sb.append("');"); } // The resource id: for sites this is the name of the site (e.g. // MyCoursesSite") for courses it is the numeric resource id (not the // course/repo entry id) if (resid == null) { sb.append("oobody.removeAttr('data-resid');"); } else { sb.append("oobody.attr('data-resid','"); sb.append(Formatter.escapeDoubleQuotes(resid)); sb.append("');"); } // The repository id, aka course-id. Normally only available for courses // (not for sites or groups) if (repoentryid == null) { sb.append("oobody.removeAttr('data-repoid');"); } else { sb.append("oobody.attr('data-repoid','"); sb.append(Formatter.escapeDoubleQuotes(repoentryid)); sb.append("');"); } sb.append("oobody=null;}catch(e){}"); JSCommand jsc = new JSCommand(sb.toString()); WindowControl wControl = getWindowControl(); if (wControl != null && wControl.getWindowBackOffice() != null) { wControl.getWindowBackOffice().sendCommandTo(jsc); } } /** * Remove the current custom css from the view */ @Override public void removeCurrentCustomCSSFromView() { Window myWindow = getWindowControl().getWindowBackOffice().getWindow(); CustomCSS currentCustomCSS = myWindow.getCustomCSS(); if (currentCustomCSS != null) { // remove css and js from view cssHolder.setContent(null); myWindow.setCustomCSS(null); } } /** * Add a custom css to the view and mark it as the current custom CSS. * * @param customCSS */ @Override public void addCurrentCustomCSSToView(CustomCSS customCSS) { if (customCSS == null) return; // The current CSS is stored as a window attribute so that is can be // accessed by the IFrameDisplayController Window myWindow = getWindowControl().getWindowBackOffice().getWindow(); myWindow.setCustomCSS(customCSS); // add css component to view cssHolder.setContent(customCSS.getJSAndCSSComponent()); } @Override public boolean wishReload(UserRequest ureq, boolean erase) { boolean screen = getScreenMode().wishScreenModeSwitch(erase); boolean r = (reload == null ? false : reload.booleanValue()); if(erase && reload != null) { reload = null; } boolean l = checkAssessmentGuard(ureq, lockMode); return l || r || screen; } @Override public boolean wishAsyncReload(UserRequest ureq, boolean erase) { boolean screen = getScreenMode().wishScreenModeSwitch(erase); boolean l = checkAssessmentGuard(ureq, lockMode); return screen || l; } @Override public ScreenMode getScreenMode() { return screenMode; } /** * adds a css-Classname to the OLAT body-tag * * @param cssClass * the name of a css-Class */ @Override public void addBodyCssClass(String cssClass) { // sets class for full page refreshes bodyCssClasses.add(cssClass); // only relevant in AJAX mode JSCommand jsc = new JSCommand("try { jQuery('#o_body').addClass('" + cssClass + "'); } catch(e){if(window.console) console.log(e) }"); getWindowControl().getWindowBackOffice().sendCommandTo(jsc); } /** * removes the given css-Classname from the OLAT body-tag * * @param cssClass * the name of a css-Class */ @Override public void removeBodyCssClass(String cssClass) { // sets class for full page refreshes bodyCssClasses.remove(cssClass); //only relevant in AJAX mode JSCommand jsc = new JSCommand("try { jQuery('#o_body').removeClass('" + cssClass + "'); } catch(e){if(window.console) console.log(e) }"); getWindowControl().getWindowBackOffice().sendCommandTo(jsc); } /** * @param pos * @return the dtab at pos pos */ public DTab getDTabAt(int pos) { synchronized (dtabs) { return dtabs.get(pos); } } public void removeDTab(UserRequest ureq, DTab delt) { // remove from tab list and mapper table synchronized (dtabs) {//o_clusterOK dtabs are per user session only - user session is always in the same vm // make dtabs and dtabsControllers access synchronized int dtabIndex = dtabs.indexOf(delt); if(dtabIndex == -1){ // OLAT-3343 :: although one session only is implemented, a user can // open multiple "main windows" in different _browser tabs_. // closing one dtab in _browser tab one_ and then closing the same dtab // once again in the _browser tab two_ leads to the case where dtabIndex // is -1, e.g. not found causing the redscreen described in the issue. // // NOTHING TO REMOVE, return return; } // Remove tab itself dtabs.remove(delt); dtabToBusinessPath.remove(delt); Integer tabId = dtabsLinkNames.remove(dtabIndex); Controller tabCtr = dtabsControllers.get(dtabIndex); dtabsControllers.remove(tabCtr); for(Iterator<TabState> it=siteAndTabs.iterator(); it.hasNext(); ) { if(it.next().getDtab() == delt) { it.remove(); } } navTabsVc.setDirty(true); // remove created links for dtab out of container navTabsVc.remove(navTabsVc.getComponent("a" + tabId)); navTabsVc.remove(navTabsVc.getComponent("c" + tabId)); navTabsVc.remove(navTabsVc.getComponent("ca" + tabId)); navTabsVc.remove(navTabsVc.getComponent("cp" + tabId)); if (delt == curDTab && ureq != null) { // if we close the current tab -> return to the previous popTheTabState(ureq); } // else just remove the dtabs delt.dispose();//dispose tab and controllers in tab } } private void popTheTabState(UserRequest ureq) { if(siteAndTabs.isEmpty() && sites != null) { SiteInstance firstSite = sites.get(0); BornSiteInstance bs = siteToBornSite.get(firstSite); if(bs == null) { activateSite(firstSite, ureq, null, false); } else { doActivateSite(firstSite, bs.getGuiStackHandle()); } } else if(!siteAndTabs.isEmpty()) { TabState state = siteAndTabs.remove(siteAndTabs.size() - 1); if(state.getSite() != null) { // latest selected static tab // activate previous chosen static site -> this site has already been // constructed and is thus in the cache SiteInstance si = state.getSite(); BornSiteInstance bs = siteToBornSite.get(si); // bs != null since clicked previously GuiStack gsh = bs.getGuiStackHandle(); doActivateSite(si, gsh); if(siteToBusinessPath.containsValue(si)) { ureq.getUserSession().addToHistory(ureq, siteToBusinessPath.get(si)); } } else if (state.getDtab() != null && !state.getDtab().getController().isDisposed()) { DTab tab = state.getDtab(); doActivateDTab(tab); if(dtabToBusinessPath.containsKey(tab)) { ureq.getUserSession().addToHistory(ureq, dtabToBusinessPath.get(tab)); } } else { popTheTabState(ureq); } } } /** * @param dt */ private void requestCloseTab(UserRequest ureq, DTab delt) { Controller c = delt.getController(); if (c instanceof VetoableCloseController) { VetoableCloseController vcc = (VetoableCloseController) c; // rembember current dtab, and swap to the temporary one DTab reTab = curDTab; doActivateDTab(delt); boolean immediateClose = vcc.requestForClose(ureq); if (!immediateClose) { return; } else { if (reTab != null) { doActivateDTab(reTab); } removeDTab(ureq, delt); } } else { removeDTab(ureq, delt); } } /** * @see org.olat.core.gui.control.generic.dtabs.DTabs#getDTab(org.olat.core.id.OLATResourceable */ @Override public DTab getDTab(OLATResourceable ores) { synchronized (dtabs) { for (Iterator<DTab> it_dts = dtabs.iterator(); it_dts.hasNext();) { DTab dtab = it_dts.next(); if (OresHelper.equals(dtab.getOLATResourceable(), ores)) { return dtab; } if (OresHelper.equals(dtab.getInitialOLATResourceable(), ores)) { return dtab; } } return null; } } @Override public DTab createDTab(OLATResourceable ores, OLATResourceable repoOres, Controller rootController, String title) { final DTabImpl dt; if (dtabs.size() >= maxTabs) { getWindowControl().setError(translate("warn.tabsfull")); dt = null; } else if(lockResource != null && ( !lockResource.getResourceableId().equals(ores.getResourceableId()) || !lockResource.getResourceableTypeName().equals(ores.getResourceableTypeName()))) { dt = null; } else { dt = new DTabImpl(ores, repoOres, title, rootController, getWindowControl()); } return dt; } @Override public boolean addDTab(UserRequest ureq, DTab dt) { if(isDisposed()) { return false; } DTab old = getDTab(dt.getOLATResourceable()); if (old != null) { return true; } // add to tabs list synchronized (dtabs) { // make dtabs and dtabsControllers access synchronized dtabs.add(dt); dtabsLinkNames.add(dtabCreateCounter); Link link = LinkFactory.createCustomLink("a" + dtabCreateCounter, "a" + dtabCreateCounter, "", Link.NONTRANSLATED, navTabsVc, this); link.setCustomDisplayText(StringHelper.escapeHtml(dt.getNavElement().getTitle())); link.setIconLeftCSS("o_icon o_icon-fw " + dt.getNavElement().getIconCSSClass()); link.setTitle(dt.getTitle()); link.setUserObject(dt); // Set accessibility access key using the 's' key. You can loop through all opened tabs by // pressing s repetitively (works only in IE/FF which is normally used by blind people) link.setAccessKey("s"); // add close links Link calink = LinkFactory.createCustomLink("c" + dtabCreateCounter, "c" + dtabCreateCounter, "", Link.NONTRANSLATED, navTabsVc, this); calink.setCustomEnabledLinkCSS("o_navbar_tab_close"); calink.setIconLeftCSS("o_icon o_icon_close_tab"); calink.setTitle(translate("close")); calink.setUserObject(dt); Controller dtabCtr = dt.getController(); dtabCtr.addControllerListener(this); updateBusinessPath(ureq, dt); // add to tabs controller lookup table for later event dispatching dtabsControllers.add(dtabCtr); // increase DTab added counter. dtabCreateCounter++; } return true; } /** * Activating a tab is like focusing a new window - we need to adjust the * guipath since e.g. the button triggering the activation is not * part of the guipath, but rather the new tab in its initial state. * in all other cases the "focus of interest" (where the calculation of the * guipath is started) matches the controller which listens to the * event caused by a user interaction. * this is the starting point. */ @Override public void activate(UserRequest ureq, DTab dTab, List<ContextEntry> entries) { UserSession usess = ureq.getUserSession(); if((lockStatus != null || usess.isInAssessmentModeProcess()) && !usess.matchLockResource(dTab.getOLATResourceable())) { return; } //update window settings if needed setWindowSettings(getWindowControl().getWindowBackOffice().getWindowSettings()); // init view (e.g. kurs in run mode, repo-detail-edit...) // jump here via external link or just open a new tab from e.g. repository if(dTab == null && contentCtrl instanceof Activateable2) { ((Activateable2)contentCtrl).activate(ureq, entries, null); } else { DTabImpl dtabi = (DTabImpl) dTab; Controller c = dtabi.getController(); if (c == null) { throw new AssertException("no controller set yet! " + dTab); } doActivateDTab(dtabi); if(c instanceof Activateable2) { final Activateable2 activateable = ((Activateable2) c); activateable.activate(ureq, entries, null); } updateBusinessPath(ureq, dtabi); //update the panels after activation setGuiStack(dtabi.getGuiStackHandle()); } } @Override public void activateStatic(UserRequest ureq, String className, List<ContextEntry> entries) { if(className != null && className.endsWith("HomeSite")) { activateSite(userTools, ureq, entries, false); } else { for (Iterator<SiteInstance> it_sites = sites.iterator(); it_sites.hasNext();) { SiteInstance site = it_sites.next(); String cName = site.getClass().getName(); if (cName.equals(className)) { activateSite(site, ureq, entries, false); return; } } } } @Override public void closeDTab(UserRequest ureq, OLATResourceable ores, HistoryPoint launchedFromPoint) { // Now try to go back to place that is attached to (optional) root back business path if (launchedFromPoint != null && StringHelper.containsNonWhitespace(launchedFromPoint.getBusinessPath()) && launchedFromPoint.getEntries() != null && launchedFromPoint.getEntries().size() > 0) { BusinessControl bc = BusinessControlFactory.getInstance().createFromPoint(launchedFromPoint); if(bc.hasContextEntry()) { WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl()); try { //make the resume secure. If something fail, don't generate a red screen NewControllerFactory.getInstance().launch(ureq, bwControl); } catch (Exception e) { logError("Error while resuming with root level back business path::" + launchedFromPoint.getBusinessPath(), e); } } } // Navigate beyond the stack, our own layout has been popped - close this tab DTabs tabs = getWindowControl().getWindowBackOffice().getWindow().getDTabs(); if (tabs != null) { DTab tab = tabs.getDTab(ores); if (tab != null) { tabs.removeDTab(ureq, tab); } } } @Override public void event(Event event) { if (event == Window.AFTER_VALIDATING) { // now update the guimessage List<ZIndexWrapper> places = getWindowControl().getWindowBackOffice().getGuiMessages(); Panel winnerP = null; int maxZ = -1; if (places != null) { // we have places where we can put the gui message for (Iterator<ZIndexWrapper> it_places = places.iterator(); it_places.hasNext();) { ZIndexWrapper ziw = it_places.next(); int cind = ziw.getZindex(); if (cind > maxZ) { maxZ = cind; winnerP = ziw.getPanel(); } } } else { winnerP = guimsgHolder; } if (winnerP != null && winnerP != currentMsgHolder) { currentMsgHolder.setContent(null); winnerP.setContent(guimsgPanel); currentMsgHolder = winnerP; } else { currentMsgHolder = guimsgHolder; currentMsgHolder.setContent(guimsgPanel); currentMsgHolder.setDirty(guimsgPanel.isDirty()); } } else if(event instanceof LanguageChangedEvent){ LanguageChangedEvent lce = (LanguageChangedEvent)event; UserRequest ureq = lce.getCurrentUreq(); getTranslator().setLocale(lce.getNewLocale()); initialize(ureq); WindowManager winman = Windows.getWindows(ureq).getWindowManager(); initializeBase(ureq, winman, initialPanel); initialPanel.setContent(mainVc); reload = Boolean.TRUE; } else if (event instanceof ChiefControllerMessageEvent) { // msg can be set to show only on one node or on all nodes updateStickyMessage(); } else if (event instanceof AssessmentModeNotificationEvent) { try { processAssessmentModeNotificationEvent((AssessmentModeNotificationEvent)event); } catch (Exception e) { logError("", e); } } } private void processAssessmentModeNotificationEvent(AssessmentModeNotificationEvent event) { if(getIdentity() == null) return; String cmd = event.getCommand(); if(AssessmentModeNotificationEvent.STOP_WARNING.equals(cmd)) { lockResourceMessage(event.getAssessementMode()); } else if(event.getAssessedIdentityKeys() != null && event.getAssessedIdentityKeys().contains(getIdentity().getKey())) { switch(cmd) { case AssessmentModeNotificationEvent.BEFORE: if(asyncUnlockResource(event.getAssessementMode())) { stickyMessageCmp.setDelegateComponent(null); } break; case AssessmentModeNotificationEvent.LEADTIME: if(asyncLockResource(event.getAssessementMode())) { stickyMessageCmp.setDelegateComponent(null); } break; case AssessmentModeNotificationEvent.START_ASSESSMENT: asyncLockResource(event.getAssessementMode()); break; case AssessmentModeNotificationEvent.STOP_ASSESSMENT: if(asyncLockResource(event.getAssessementMode())) { stickyMessageCmp.setDelegateComponent(null); } break; case AssessmentModeNotificationEvent.END: if(asyncUnlockResource(event.getAssessementMode())) { stickyMessageCmp.setDelegateComponent(null); } break; } } } @Override public boolean hasStaticSite(Class<? extends SiteInstance> type) { boolean hasSite = false; if(sites != null && sites.size() > 0) { for(SiteInstance site:sites) { if(site.getClass().equals(type)) { hasSite = true; } } } return hasSite; } @Override public OLATResourceable getLockResource() { return lockResource; } @Override public void lockResource(OLATResourceable resource) { this.lockResource = resource; lockGUI(); } private void lockGUI() { if(topnavCtr != null) { topnavCtr.lock(); } if(footerCtr != null) { footerCtr.lock(); } if(userToolsMenuCtrl != null) { userToolsMenuCtrl.lock(); } if(dtabsControllers != null) { for(int i=dtabsControllers.size(); i-->0; ) { DTab tab = dtabs.get(i); if(lockResource == null || !lockResource.getResourceableId().equals(tab.getOLATResourceable().getResourceableId())) { removeDTab(null, tab); } else if (lockResource != null && lockResource.getResourceableId().equals(tab.getOLATResourceable().getResourceableId()) && lockStatus != LockStatus.locked) { removeDTab(null, tab); } } } navSitesVc.contextPut("visible", Boolean.FALSE); navSitesVc.setDirty(true); navTabsVc.setDirty(true); main.setContent(new Panel("empty-mode")); } private void unlockResource() { this.lockResource = null; if(topnavCtr != null) { topnavCtr.unlock(); } if(footerCtr != null) { footerCtr.unlock(); } if(userToolsMenuCtrl != null) { userToolsMenuCtrl.unlock(); } navSitesVc.contextPut("visible", Boolean.TRUE); navSitesVc.setDirty(true); navTabsVc.setDirty(true); } private boolean asyncLockResource(TransientAssessmentMode mode) { boolean lock; if(isAdmin) { lock = false; } else if(lockResource == null) { logAudit("Async lock resource for user: " + getIdentity().getName() + " (" + mode.getResource() + ")", null); lockResource(mode.getResource()); lock = true; lockMode = mode; lockStatus = LockStatus.need; } else if(lockResource != null && lockResource.getResourceableId().equals(mode.getResource().getResourceableId())) { if(mode.getStatus() == Status.leadtime || mode.getStatus() == Status.followup) { if(assessmentGuardCtrl == null) { lockStatus = LockStatus.need; } lockMode = mode; } lock = true; } else { lock = false; } return lock; } private boolean asyncUnlockResource(TransientAssessmentMode mode) { boolean unlock; if(lockResource != null && lockResource.getResourceableId().equals(mode.getResource().getResourceableId())) { logAudit("Async unlock resource for user: " + getIdentity().getName() + " (" + mode.getResource() + ")", null); unlockResource(); if(lockMode != null) { //check if there is a locked resource first lockStatus = LockStatus.need; } else { lockStatus = null; } lockMode = null; unlock = true; } else { unlock = false; } return unlock; } private void lockResourceMessage(TransientAssessmentMode mode) { if(lockResource != null && lockResource.getResourceableId().equals(mode.getResource().getResourceableId())) { Translator trans = Util.createPackageTranslator(AssessmentModeGuardController.class, getLocale()); if(stickyMessageCmp.getDelegateComponent() instanceof CountDownComponent) { CountDownComponent cmp = (CountDownComponent)stickyMessageCmp.getDelegateComponent(); cmp.setDate(mode.getEnd()); } else { CountDownComponent cmp = new CountDownComponent("stickcountdown", mode.getEnd(), trans); cmp.setI18nKey("assessment.countdown"); stickyMessageCmp.setDelegateComponent(cmp); } } } private boolean checkAssessmentGuard(UserRequest ureq, TransientAssessmentMode mode) { boolean needUpdate; if(assessmentGuardCtrl == null) { if(lockStatus == LockStatus.need) { List<TransientAssessmentMode> modes = mode == null ? Collections.<TransientAssessmentMode>emptyList() : Collections.singletonList(mode); assessmentGuardCtrl = new AssessmentModeGuardController(ureq, getWindowControl(), modes , true); listenTo(assessmentGuardCtrl); assessmentGuardCtrl.getInitialComponent(); lockStatus = LockStatus.popup; lockGUI(); needUpdate = true; } else { needUpdate = false; } } else { needUpdate = assessmentGuardCtrl.updateAssessmentMode(ureq); } return needUpdate; } /** * [used by velocity] helper for velocity * */ public boolean isSiteActive(SiteInstance si) { return curSite != null && si == curSite; } /** * * [used by velocity] * * @return */ public boolean isDTabActive(DTab dtab) { return curDTab != null && dtab == curDTab; } /** * Invitee have only one dynamic tab. They are not allowed * to close it. * [used by velocity] * * @return */ public boolean isCanCloseDTab(DTab dtab) { boolean canClose = true; if(lockResource != null && lockResource.getResourceableId().equals(dtab.getOLATResourceable().getResourceableId()) && lockResource.getResourceableTypeName().equals(dtab.getOLATResourceable().getResourceableTypeName())) { canClose = false; } else { canClose = (sites != null && sites.size() > 0); if(!canClose && dtabs != null) { synchronized (dtabs) { canClose = (dtabs != null && dtabs.size() > 1); } } } return canClose; } @Override public String getWindowTitle() { String title = translate("page.appname"); if(siteAndTabs.size() > 0) { TabState state = siteAndTabs.get(siteAndTabs.size() - 1); if(state != null) { String tabTitle = state.getTitle(); if(StringHelper.containsNonWhitespace(tabTitle)) { title += " - " + tabTitle; } } } return title; } private void setCurrent(SiteInstance site, DTab tab) { curSite = site; curDTab = tab; siteAndTabs.add(new TabState(tab, site)); //limite the size if(siteAndTabs.size() > 30) { while(siteAndTabs.size() > 30) { siteAndTabs.remove(0); } } } private void updateBusinessPath(UserRequest ureq) { if(siteAndTabs.isEmpty()) return; TabState tabState = siteAndTabs.get(siteAndTabs.size() - 1); if(tabState.getSite() != null) { updateBusinessPath(ureq, tabState.getSite()); } else if (tabState.getDtab() != null) { updateBusinessPath(ureq, tabState.getDtab()); } } private String updateBusinessPath(UserRequest ureq, SiteInstance site) { if(site == null) return null; try { String businessPath = siteToBornSite.get(site).getController().getWindowControlForDebug().getBusinessControl().getAsString(); HistoryPoint point = ureq.getUserSession().getLastHistoryPoint(); int index = businessPath.indexOf(':'); if(index > 0 && point != null && point.getBusinessPath() != null) { String start = businessPath.substring(0, index); if(!point.getBusinessPath().startsWith(start)) { //if a controller has not set its business path, don't pollute the mapping List<ContextEntry> entries = siteToBornSite.get(site).getController().getWindowControlForDebug().getBusinessControl().getEntries(); siteToBusinessPath.put(site, new HistoryPointImpl(ureq.getUuid(), businessPath, entries)); return BusinessControlFactory.getInstance().getAsRestPart(entries, true); } List<ContextEntry> entries = siteToBornSite.get(site).getController().getWindowControlForDebug().getBusinessControl().getEntries(); businessPath = BusinessControlFactory.getInstance().getAsRestPart(entries, true); } siteToBusinessPath.put(site, point); return businessPath; } catch (Exception e) { logError("", e); return null; } } private void updateBusinessPath(UserRequest ureq, DTab tab) { //dtabToBusinessPath is null if the controller is disposed if(tab == null || dtabToBusinessPath == null) return; try { String businessPath = tab.getController().getWindowControlForDebug().getBusinessControl().getAsString(); HistoryPoint point = ureq.getUserSession().getLastHistoryPoint(); int index = businessPath.indexOf(']'); if(index > 0 && point != null && point.getBusinessPath() != null) { String start = businessPath.substring(0, index); if(!point.getBusinessPath().startsWith(start)) { //if a controller has not set its business path, don't pollute the mapping List<ContextEntry> entries = tab.getController().getWindowControlForDebug().getBusinessControl().getEntries(); dtabToBusinessPath.put(tab, new HistoryPointImpl(ureq.getUuid(), businessPath, entries)); return; } } dtabToBusinessPath.put(tab, point); } catch (Exception e) { logError("", e); } } private static class TabState { private final DTab dtab; private final SiteInstance site; private TabState(DTab dtab, SiteInstance site) { this.dtab = dtab; this.site = site; } public DTab getDtab() { return dtab; } public SiteInstance getSite() { return site; } public String getTitle() { if(site != null && site.getNavElement() != null) { return site.getNavElement().getTitle(); } else if(dtab != null) { return dtab.getTitle(); } return null; } } private enum LockStatus { need, popup, locked } }