/** * 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.modules.singlepage; import java.util.List; import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel; import org.olat.core.commons.editor.htmleditor.WysiwygFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.htmlsite.OlatCmdEvent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.panel.SimpleStackedPanel; 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.clone.CloneableController; import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.iframe.DeliveryOptions; import org.olat.core.gui.control.generic.iframe.IFrameDisplayController; import org.olat.core.gui.control.generic.iframe.NewIframeUriEvent; import org.olat.core.id.OLATResourceable; import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.CoreLoggingResourceable; import org.olat.core.logging.activity.CourseLoggingAction; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.Formatter; import org.olat.core.util.vfs.VFSContainer; /** * Description:<BR> * Wrapper controller that shows local html pages from the given folder / filename * <P/> * Initial Date: Dec 16, 2004 * * EVENTS: to listening controllers: * - OlatCmdEvent (which has to be accepted by calling accept() on the event) * - NewInlineUriEvent if the user changed the page by clicking on a link * * @author gnaegi */ public class SinglePageController extends BasicController implements CloneableController, Activateable2 { private static final OLog log = Tracing.createLoggerFor(SinglePageController.class); private static final String GOTO_NID = "GOTO_NID: "; private static final String COMMAND_EDIT = "command.edit"; private Link editLink; private final StackedPanel mainPanel; private Controller htmlEditorController; private final IFrameDisplayController idc; private final VelocityContainer myContent; private CustomLinkTreeModel customLinkTreeModel; private final String frameId; private final boolean randomizeMapper; private final DeliveryOptions deliveryOptions; private String g_curURI; // save constructor args to remember if we open a site in a new window private String g_fileName; private boolean g_allowRelativeLinks; private VFSContainer g_rootContainer; private VFSContainer g_new_rootContainer; /** * * @param ureq * @param wControl * @param inIframe * @param rootContainer * @param fileName * @param currentUri * @param allowRelativeLinks * @param showHomeLink */ public SinglePageController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, String fileName, boolean allowRelativeLinks) { //default behavior is to show the home link in a single page this(ureq, wControl, rootContainer, fileName, allowRelativeLinks, null, null, null, false); } /** * Constructor for the generic single page controller. * * displays the html page (or any file if in iframe mode) and, if not on the first page and not in iframe mode, * offers a button to return to the start page. * (useful for a "home" button) * <p> * You can call the allowPageEditing after this construtor to allow users to edit the page * * @param folderPath The course folder which contains the single page html file * @param inIframe if true, the contents are rendered within an iframe * @param fileName the relative filePath in the material folder starting with a slash, e.g. /welcome.html or /docu/info.html * @param rootContainer the root from which to resolve the files (like "the htdocs directory") * @param currentUri if not null, the start page is set to this uri (instead of the fileName arg). relative to the -corrected- rootcontainer if !allowRelativeLinks * * @param allowRelativeLinks if true, an initial uri of /folder/a.html allows navigating till "/", if false, only * navigating in /folder/ and subfolders of this folder is allowed * @param showHomeLink true enables the home link icon and link which is added to the SP, false removes icon and link. * * */ public SinglePageController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, String fileName, boolean allowRelativeLinks, String frameId, OLATResourceable contextResourcable, DeliveryOptions config, boolean randomizeMapper) { super(ureq, wControl); SimpleStackedPanel mainP = new SimpleStackedPanel("iframemain"); myContent = createVelocityContainer("index"); // remember values in case of later cloning // g_fileName : initial file name given (no root correction), e.g. bla.html or f/g/blu.html // always use non-iframe mode for screenreaders this.deliveryOptions = config; this.g_allowRelativeLinks = allowRelativeLinks; this.g_fileName = fileName; this.g_rootContainer = rootContainer; this.frameId = frameId; this.randomizeMapper = randomizeMapper; boolean jumpIn = false; // strip beginning slash String startURI = ( (fileName.charAt(0) == '/')? fileName.substring(1) : fileName); // jump (e.g. from search) to the path if the business-launch-path says so. BusinessControl bc = getWindowControl().getBusinessControl(); ContextEntry ce = bc.popLauncherContextEntry(); if ( ce != null ) { // a context path is left for me log.debug("businesscontrol (for further jumps) would be:"+bc); OLATResourceable ores = ce.getOLATResourceable(); log.debug("OLATResourceable=" + ores); String typeName = ores.getResourceableTypeName(); // typeName format: 'path=/test1/test2/readme.txt' // First remove prefix 'path=' String path = typeName.substring("path=".length()); if (path.length() > 0) { log.debug("direct navigation to container-path=" + path); jumpIn = true; startURI = path; } } // adjust root folder if security does not allow using ../.. etc. // *** IF YOU CHANGE THIS LOGIC, do also change it in SPCourseNodeIndexer! *** if (!allowRelativeLinks && !jumpIn) { // start uri is filename without relative path. // the relative path of the file is added to the vfs rootcontainer int sla = startURI.lastIndexOf('/'); if (sla != -1) { String root = startURI.substring(0,sla); startURI = startURI.substring(sla+1); VFSContainer newroot = (VFSContainer)rootContainer.resolve(root); g_new_rootContainer = newroot; } else { g_new_rootContainer = rootContainer; } } else { g_new_rootContainer = rootContainer; } setCurURI(startURI); // startURI and g_new_rootContainer set // g_curURI : the current uri (relative to the (evt. corrected) rootcontainer) // g_new_rootContainer : the given rootcontainer or adjusted in case when relativelinks are not allowed // Display in iframe when idc = new IFrameDisplayController(ureq, getWindowControl(), g_new_rootContainer, frameId, contextResourcable, deliveryOptions, false, randomizeMapper); listenTo(idc); idc.setCurrentURI(startURI); myContent.put("content", idc.getInitialComponent()); mainP.setContent(myContent); mainPanel = putInitialPanel(mainP); } /** * When you call this method the edit mode will be enabled. By default no edit * is possible, you have to call this method after construction time explicitly */ public void allowPageEditing() { editLink = LinkFactory.createCustomLink(COMMAND_EDIT, COMMAND_EDIT, "", Link.NONTRANSLATED, myContent, this); editLink.setElementCssClass("o_edit"); editLink.setIconLeftCSS("o_icon o_icon_edit_file o_icon-lg"); editLink.setTitle(translate(COMMAND_EDIT)); } public void setAllowDownload(boolean allow) { if (idc != null) { // can be null because the boolean "inIframe" in the constructor does not // always use the iframe. When in braille mode the system renders the page inline in any case. idc.setAllowDownload(allow); } } /** * @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 public void event(UserRequest ureq, Controller source, Event event) { if (source == idc) { if (event instanceof OlatCmdEvent) { OlatCmdEvent oce = (OlatCmdEvent) event; String nodeId = oce.getSubcommand(); ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_BROWSE_GOTO_NODE, getClass(), CoreLoggingResourceable.wrapSpUri(GOTO_NID+nodeId)); // refire to listening controllers fireEvent(ureq, event); } else if (event instanceof NewIframeUriEvent) { NewIframeUriEvent iframeEvent = (NewIframeUriEvent) event; String newUri = iframeEvent.getNewUri(); setCurURI(newUri); // log this uri change ThreadLocalUserActivityLogger.log(CourseLoggingAction.NODE_SINGLEPAGE_GET_FILE, getClass(), CoreLoggingResourceable.wrapSpUri(newUri)); } } else if (source == htmlEditorController) { idc.setCurrentURI(g_curURI); mainPanel.setContent(myContent); } } /** * @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 public void event(UserRequest ureq, Component source, Event event) { if (source == editLink && event.getCommand().equals(COMMAND_EDIT)) { removeAsListenerAndDispose(htmlEditorController); if (g_curURI == null || g_new_rootContainer == null || g_new_rootContainer.resolve(g_curURI) == null) { showError("error.pagenotfound"); } else { if (customLinkTreeModel == null) { htmlEditorController = WysiwygFactory.createWysiwygController(ureq, getWindowControl(), g_new_rootContainer, g_curURI, true, true); } else { htmlEditorController = WysiwygFactory.createWysiwygControllerWithInternalLink(ureq, getWindowControl(), g_new_rootContainer, g_curURI, true, customLinkTreeModel); } listenTo(htmlEditorController); mainPanel.setContent(htmlEditorController.getInitialComponent()); } } } private void setCurURI(String uri) { this.g_curURI = uri; } /** * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) */ @Override protected void doDispose() { // NOTE: do not deregister this mapper here: the url pointing to this mapper is opened in a new browser window // and the user will expect to be able to browse beyond the lifetime of this originating controller here. //if (mapper != null) {mr.deregister(mapper);} } /** * Set the internal link tree model that should be used in the HTML editor to * display links * * @param customLinkTreeModel */ public void setInternalLinkTreeModel(CustomLinkTreeModel customLinkTreeModel) { this.customLinkTreeModel = customLinkTreeModel; } /** * @see org.olat.core.gui.control.generic.clone.CloneableController#cloneController(org.olat.core.gui.UserRequest, org.olat.core.gui.control.WindowControl) */ @Override public Controller cloneController(UserRequest ureq, WindowControl control) { return new SinglePageController(ureq, control, g_rootContainer, g_fileName, g_allowRelativeLinks, frameId, null, deliveryOptions, randomizeMapper); } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { if(entries == null || entries.isEmpty() || idc == null) return; // delegate to iframe controller idc.activate(ureq, entries, state); } /** * Set a scale factor to enlarge / shrink the entire page. This is handy when * a preview of a page should be displayed. * E.g: 2 will scale the page by factor 2, 0.5 will shrink the page by factor 2 * * @param scaleFactor > 0 or 1: don't scale; < 1: shrink page; > 1 enlarge page. * @param displayHeight > 0: size to fit; > 0: fixed size in pixel * @param hideOverflow true: don't show scroll-bars; false: default behavior with scroll-bars */ public void setScaleFactorAndHeight(float scaleFactor, int displayHeight, boolean hideOverflow) { if ((scaleFactor > 0 && scaleFactor != 1) || hideOverflow || displayHeight > 0) { StringBuilder cssRule = new StringBuilder(128); StringBuilder ieCssRule = new StringBuilder(32); // add rule for scaling if (scaleFactor > 0 && scaleFactor != 1) { String formattedScaleFactor = Formatter.roundToString(scaleFactor, 2); cssRule.append("transform: scale(").append(formattedScaleFactor).append("); transform-origin: top;") .append("--webkit-transform: scale(").append(formattedScaleFactor).append("); --webkit-transform-origin: top;") .append("--moz-transform: scale(").append(formattedScaleFactor).append("); --moz-transform-origin: top;"); ieCssRule.append("zoom: ").append(formattedScaleFactor).append(";"); } // add rule to set fix height if (displayHeight > 0) { if (idc != null) { idc.setHeightPX(displayHeight); } else { // add to rule for html component, so such feature available cssRule.append("height: ").append(displayHeight).append("px;"); } } // add overflow rule if (hideOverflow) { cssRule.append("overflow: hidden;"); } StringBuilder header = new StringBuilder(256); header.append("<style type='text/css'>body {").append(cssRule).append("}</style>"); header.append("<!--[if lt IE 10]><style type='text/css'>body {").append(ieCssRule).append("}</style><![endif]-->"); idc.setCustomHeaderContent(header.toString()); } else { idc.setCustomHeaderContent(null); } } }