/**
* 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) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.core.gui.control.generic.iframe;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.olat.core.commons.chiefcontrollers.BaseChiefController;
import org.olat.core.commons.fullWebApp.BaseFullWebappController;
import org.olat.core.defaults.dispatcher.ClassPathStaticDispatcher;
import org.olat.core.defaults.dispatcher.StaticMediaDispatcher;
import org.olat.core.dispatcher.mapper.Mapper;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.Window;
import org.olat.core.gui.components.htmlheader.jscss.CustomCSS;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.panel.Panel;
import org.olat.core.gui.components.velocity.VelocityContainer;
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.textmarker.GlossaryMarkupItemController;
import org.olat.core.gui.control.generic.textmarker.TextMarkerManagerImpl;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.NotFoundMediaResource;
import org.olat.core.gui.media.StringMediaResource;
import org.olat.core.gui.render.StringOutput;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.FileUtils;
import org.olat.core.util.SimpleHtmlParser;
import org.olat.core.util.StringHelper;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.event.MultiUserEvent;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSMediaResource;
/**
* Class that loads a resource (html) in an Iframe and tries to adjust the size of the Iframe to hide the scrollbars.
* This is done by injecting some javascript into the head part of the loaded html file which then resizes the iframe itself.
* See package documentation for details.
* Initial Date: Dec 9, 2004
*
* @author Felix Jost<br>
*
* @author guido
*/
public class IFrameDisplayController extends BasicController implements GenericEventListener {
private static final String DEFAULT_ENCODING = "iso-8859-1";
private static final String UNICODE_ENCODING = "unicode";
private static final Pattern PATTERN_ENCTYPE = Pattern.compile("<meta.*charset=([^\"\']*)[\"\']", Pattern.CASE_INSENSITIVE);
private static final String NEW_URI_EVENT = "newUriEvent";
private static final String FILE_SUFFIX_HTM = "htm";
private static final String TAG_FRAMESET = "<frameset";
private static final String TAG_FRAMESET_UPPERC = "<FRAMESET";
private static final String FILE_SUFFIX_JS = ".js";
private static final String MIMETYPE = "text/html;charset=";
private static final String COMMAND_DOWNLOAD = "command.download";
private VelocityContainer myContent = createVelocityContainer("index");
private VelocityContainer eventVC = createVelocityContainer("event");
private Panel newUriEventPanel;
private Panel main;
private VFSItem rootDir;
// the latest encoding is saved since .js files loaded by the browser are
// assumed to have the same encoding as the html page
private String g_encoding;
private OLog log = Tracing.createLoggerFor(this.getClass());
private String frameId;
// When textMarking is enabled we include some additional javaScript stuff to show toolTips as well in iframeContent.
// This is only needed in course context
private boolean enableTextmarking;
private Mapper contentMapper;
private String staticFilesPath;
private String baseURI; // base uri of contentMapper
private String currentUri; // relative uri of currently loaded page in iframe
private boolean checkForInlineEvent; // false when a new currentUri is set
private boolean adjusteightAutomatically;
private String customCssURL;
// users theme
private String themeBaseUri;
private long suppressEndlessReload;
private String customHeaderContent;
//download link
private Link downloadLink;
private boolean allowDownload = false;
/**
*
* @param ureq
* @param wControl
* @param fileRoot File that points to the root directory of the resource
*/
public IFrameDisplayController(UserRequest ureq, WindowControl wControl, File fileRoot) {
this(ureq, wControl, new LocalFolderImpl(fileRoot), null);
}
/**
*
* @param ureq
* @param wControl
* @param fileRoot
* @param ores - send an OLATresourcable of the context (e.g. course) where the iframe runs and it will be checked if the user has textmarking (glossar) enabled in this course
*/
public IFrameDisplayController(UserRequest ureq, WindowControl wControl, File fileRoot, OLATResourceable ores) {
this(ureq, wControl, new LocalFolderImpl(fileRoot), null, ores);
}
/**
*
* @param ureq
* @param wControl
* @param rootDir VFSItem that points to the root folder of the resource
*/
public IFrameDisplayController(UserRequest ureq, WindowControl wControl, VFSItem rootDir) {
this(ureq, wControl, rootDir, null, null);
}
/**
*
* @param ureq
* @param wControl
* @param rootDir
* @param ores - send an OLATresourcable of the context (e.g. course) where the iframe runs and it will be checked if the user has textmarking (glossar) enabled in this course
*/
public IFrameDisplayController(UserRequest ureq, WindowControl wControl, VFSItem rootDir, OLATResourceable ores) {
this(ureq, wControl, rootDir, null, ores);
}
/**
*
* @param ureq
* @param wControl
* @param rootDir
* @param frameId if you need access to the iframe html id, provide it here
* @param enableTextmarking to enable textmakring of the content in the iframe enable it here
*/
public IFrameDisplayController(UserRequest ureq, WindowControl wControl, VFSItem rootDir, String frameId, OLATResourceable contextRecourcable) {
// FIXME:fj:c performance: use a line iterator which finds the charset
// statement and can switch encoding while reading it into a stringbuffer
// FIXME:fj:b add a link to open the iframe in a new window
super(ureq, wControl);
this.enableTextmarking = TextMarkerManagerImpl.getInstance().isTextmarkingEnabled(ureq, contextRecourcable);
//register this object for textMarking on/off events
//TODO:gs how to unregister and where? unregister need ureq so dispose does not work
if (contextRecourcable != null) {
ureq.getUserSession().getSingleUserEventCenter().registerFor(this, getIdentity(), contextRecourcable);
}
// Set correct user content theme
themeBaseUri = wControl.getWindowBackOffice().getWindow().getGuiTheme().getBaseURI();
// Deliver js files via class path static dispatcher to enable browser caching
staticFilesPath = ClassPathStaticDispatcher.getInstance().getMapperBasePath(this.getClass());
//Delivers content files via local mapper to enable session based browser caching for at least this instance
//FIXME:FG: implement named mapper concept based on business path to allow browser caching and distributed media server
//TODO:gs may use the same contentMapper if users clicks again on the same singlePage, now each time a new Mapper gets created and
//therefore the browser can not reuse the cached elements
contentMapper = new Mapper() {
public MediaResource handle(String relPath, HttpServletRequest request) {
String isPopUpParam = request.getParameter("olatraw");
boolean isPopUp = false;
if (isPopUpParam != null && isPopUpParam.equals("true")) isPopUp = true;
return deliverFile(relPath, isPopUp);
}
};
baseURI = registerMapper(contentMapper);
myContent.contextPut("baseURI", baseURI);
newUriEventPanel = new Panel("newUriEvent");
newUriEventPanel.setContent(eventVC);
this.rootDir = rootDir;
this.frameId = frameId;
main = new Panel("iframemain");
if (frameId == null) this.frameId = "ifdc" + hashCode();
main.setContent(myContent);
myContent.contextPut("frameId", this.frameId);
myContent.put("newUriEvent", newUriEventPanel);
// add default iframe height
myContent.contextPut("iframeHeight", 600); // used as fallback
adjusteightAutomatically = true; // default
myContent.contextPut("adjustAutoHeight", Boolean.TRUE);
// Add us as cycle listener to be notified when current dispatch cycle is
// finished. we then need to add the css which is not yet defined at this
// point
getWindowControl().getWindowBackOffice().addCycleListener(this);
//
putInitialPanel(main);
}
/**
* Sets the start page, may be null, a relative or an absolute URI
*
* @param newCurrentURI The currentURI to set
*/
public void setCurrentURI(String newCurrentURI) {
// set new uri and the content dirty to redraw on screen
changeCurrentURI(newCurrentURI, true);
}
/**
* Allow download for all types but html
* @param allow
*/
public void setAllowDownload(boolean allow) {
this.allowDownload = allow;
setPageDownload(isPageDownloadAllowed(currentUri));
}
/**
* Configuration method to use an explicit height for the iframe instead of
* the default automatic sizeing code. If you don't call this method, OLAT
* will try to size the iframe so that no scrollbars appear. In most cases
* this works. If it does not work, use this method to set an explicit height.
* <br />
* Set 0 to reset to automatic behaviour.
*
* @param height
*/
public void setHeightPX(int height) {
if (height == 0) {
myContent.contextPut("iframeHeight", 600);
adjusteightAutomatically = true;
myContent.contextPut("adjustAutoHeight", Boolean.TRUE);
} else {
myContent.contextPut("iframeHeight", height);
adjusteightAutomatically = false;
myContent.contextPut("adjustAutoHeight", Boolean.FALSE);
}
}
/**
* Add a custom HTML header element. This string will be added into the HTML
* HEAD part of the HTML page. This could be CSS, JS or other header elements.
*
* @param customHeaderContent A custom HEAD element or NULL
*/
public void setCustomHeaderContent(String customHeaderContent) {
this.customHeaderContent = customHeaderContent;
}
/**
* Change the start page, may be null
*
* @param currentURI
* The currentURI to set
* @param forceLoading
* true: force rendering of iframe velocity container, will
* redraw everything;
* false: set new uri without redrawing
* iframe. This implies that the content in the iframe has
* already been loaded (e.g. by an in line user click)
*/
private void changeCurrentURI(String uri, boolean forceLoading) {
if (uri == null) {
uri = "";
myContent.contextPut("isAbsoluteURI", Boolean.FALSE);
} else if (uri.startsWith("http")) {
// http and https urls are absolute urls that do not need be fetched from
// the OLAT server, they retrieve their content from an external content server
//TODO:gs a if an absolut uri is loaded the iframe should size to the default site, see functions.js stuff
myContent.contextPut("isAbsoluteURI", Boolean.TRUE);
} else {
// Check for problematic URI that start with '/' (would lead to a logout (login screen)).
if (uri.startsWith("/")) uri = uri.substring(1);
myContent.contextPut("isAbsoluteURI", Boolean.FALSE);
}
// set new current uri and push to velocity
this.currentUri = uri;
myContent.contextPut("currentURI", this.currentUri);
if (forceLoading) {
// Serve new URI as currentURI, no need to check for inline events
this.checkForInlineEvent = false;
} else {
// Don't redraw iframe
myContent.setDirty(false);
this.checkForInlineEvent = true;
}
setPageDownload(isPageDownloadAllowed(currentUri));
}
private boolean isPageDownloadAllowed(String uri) {
if(!allowDownload || !StringHelper.containsNonWhitespace(uri)) {
return false;
}
String uriLc = uri.toLowerCase();
if(uriLc.endsWith(".html") || uriLc.endsWith(".htm") || uriLc.endsWith(".xhtml")) {
return false;
}
return true;
}
private void setPageDownload(boolean allow) {
if(allow) {
if(downloadLink == null) {
downloadLink = LinkFactory.createCustomLink(COMMAND_DOWNLOAD, COMMAND_DOWNLOAD, "", Link.NONTRANSLATED, myContent, this);
downloadLink.setCustomEnabledLinkCSS("b_content_download");
downloadLink.setTooltip(getTranslator().translate(COMMAND_DOWNLOAD), false);
} else if (!downloadLink.isVisible()) {
downloadLink.setVisible(true);
}
//update always the name
downloadLink.setCustomDisplayText(currentUri);
} else {
if(downloadLink != null) {
downloadLink.setVisible(false);
}
}
}
/**
* @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)
*/
public void event(UserRequest ureq, Component source, Event event) {
if (source == eventVC) {
if (event.getCommand().equals(NEW_URI_EVENT)) {
// This event gets triggered from the iframe content by calling a js function outside
// Get new uri from JS method and fire to parents
String newUri = ureq.getModuleURI();
int baseUriPos = newUri.indexOf(baseURI);
if (baseUriPos != -1) {
int newUriPos = baseUriPos + baseURI.length();
if (newUri.length() > newUriPos) {
newUri = newUri.substring(newUriPos);
String hash = ureq.getHttpReq().getParameter("hash");
if (StringHelper.containsNonWhitespace(hash)) {
// force iframe reload to fix truncated page problem
changeCurrentURI(newUri + '#' + hash, true);
fireEvent(ureq, new NewIframeUriEvent(newUri));
}
if (newUri.startsWith("/")) {
// clean newUri to make equals check work
newUri = newUri.substring(1);
}
if (! newUri.equals(this.currentUri)) {
changeCurrentURI(newUri, false);
fireEvent(ureq, new NewIframeUriEvent(currentUri));
}
// else probably a reload, no need to propagate new uri event
}
// else ?? don't do anything
}
// else ?? don't do anything
//
// don't mark as dirty to prevent re-rendering in AJAX mode - event was a
// background event only
eventVC.setDirty(false);
}
} else if (source == downloadLink) {
MediaResource mediaResource = deliverFile(currentUri, false);
if(mediaResource instanceof VFSMediaResource) {
((VFSMediaResource)mediaResource).setDownloadable(true);
}
ureq.getDispatchResult().setResultingMediaResource(mediaResource);
}
}
/**
* deliver the file (html, css, js) as MediaResource
* @param path
* @return
*/
/**
* TODO: firefox 2.0 has an strange error when the iframe is loaded the browser requests the first file always twice
* @param isPopUp
*/
protected MediaResource deliverFile(String path, boolean isPopUp) {
MediaResource mr;
VFSLeaf vfsLeaf = null;
VFSItem vfsItem = null;
//if directory gets renamed root becomes null
if (rootDir == null) {
return new NotFoundMediaResource("directory not found"+path);
} else {
vfsItem = rootDir.resolve(path);
}
//only files are allowed, but somehow it happened that folders showed up here
if (vfsItem instanceof VFSLeaf) {
vfsLeaf = (VFSLeaf) rootDir.resolve(path);
} else {
mr = new NotFoundMediaResource(path);
}
if (vfsLeaf == null) {
mr = new NotFoundMediaResource(path);
} else {
// check if path ends with .html, .htm or .xhtml. We do this by searching for "htm"
// and accept positions of this string at length-3 or length-4
if (path.toLowerCase().lastIndexOf(FILE_SUFFIX_HTM) >= (path.length()-4)) {
// set the http content-type and the encoding
// try to load in iso-8859-1
String page = FileUtils.load(vfsLeaf.getInputStream(), DEFAULT_ENCODING);
// search for the <meta content="text/html; charset=utf-8"
// http-equiv="Content-Type" /> tag
// if none found, assume iso-8859-1
String enc = DEFAULT_ENCODING;
boolean useLoadedPageString = false;
// <meta.*charset=([^"]*)"
//extract only the charset attribute without the overhead of creating an htmlparser
Matcher m = PATTERN_ENCTYPE.matcher(page);
boolean found = m.find();
if (found) {
// use found char set
String htmlcharset = m.group(1);
//if longer than 50 the regexp did fail
if (htmlcharset.length() < 50) enc
= htmlcharset;
// reuse already loaded page when page uses the default encoding
if (htmlcharset.equalsIgnoreCase(DEFAULT_ENCODING) || htmlcharset.contains(DEFAULT_ENCODING) || htmlcharset.toLowerCase().contains(DEFAULT_ENCODING)) {
useLoadedPageString = true;
}
} else {
// try it with unicode character set as a fallback
String pageUnicode = FileUtils.load(vfsLeaf.getInputStream(), UNICODE_ENCODING);
m = PATTERN_ENCTYPE.matcher(pageUnicode);
found = m.find();
if (found) {
page = pageUnicode; // reuse already loaded page
useLoadedPageString = true;
} else {
// fallback, use the default encoding and reuse the already loaded page
useLoadedPageString = true;
}
}
//test the encoding first, sometime the regexp delivers fancy stuff. If it fails use fallback to default
try {
Charset.isSupported(enc);
} catch (IllegalCharsetNameException e) {
//TODO: see OLAT-5407
log.warn("IllegalCharsetNameException in:"+path+", enc="+enc+", overwriting to "+DEFAULT_ENCODING);
enc = DEFAULT_ENCODING;
}
// set the new encoding to remember for any following .js file loads
g_encoding = enc;
if (useLoadedPageString) {
mr = prepareMediaResource(page, enc, isPopUp);
} else {
// found a new charset other than iso-8859-1, load string with proper encoding
page = FileUtils.load(vfsLeaf.getInputStream(), enc);
mr = prepareMediaResource(page, enc, isPopUp);
}
} else if (path.endsWith(FILE_SUFFIX_JS)) { // a javascript library
VFSMediaResource vmr = new VFSMediaResource(vfsLeaf);
// set the encoding; could be null if this page starts with .js file
// (not very common...).
// if we set no header here, apache sends the default encoding
// together with the mime-type, which is wrong.
// so we assume the .js file has the same encoding as the html file
// that loads the .js file
if (g_encoding != null) vmr.setEncoding(g_encoding);
mr = vmr;
} else {
// binary data: not .html, not .htm, not .js -> treated as is
VFSMediaResource vmr = new VFSMediaResource(vfsLeaf);
mr = vmr;
}
}
return mr;
}
private StringMediaResource prepareMediaResource(String page, String enc, boolean isPopUp) {
StringMediaResource smr = new StringMediaResource();
String mimetype = MIMETYPE + StringHelper.check4xMacRoman(enc);
smr.setContentType(mimetype);
smr.setEncoding(enc);
//inject some javascript code to size iframe to proper height, but only when not a page with framesets
if (page.indexOf(TAG_FRAMESET) != -1 || page.indexOf(TAG_FRAMESET_UPPERC) != -1 || isPopUp) {
//is frameset -> deliver unparsed
smr.setData(page);
} else {
smr.setData(injectJavaScript(page, checkForInlineEvent));
// When loading next page, check if it was an inline user click
this.checkForInlineEvent = true;
}
return smr;
}
/**
* it would be possible to access the iframe.document but there is no event
* sended when the content changes. Like this is is easier to inject the js
* code and resize the iframe like this.
*
* @param page
* @param addCheckForInlineEvents
* true: check if page is rendered in iframe, if yes send event
* to framework; false: don't do this check
* @return
*/
/**
* TODO:gs make more stable by only adding some js stuff to the end of the page. First check if document.height is ready
* when puttings js to the end or menachism like ext.onReady is needed
*/
private String injectJavaScript(String page, boolean addCheckForInlineEvents) {
StringOutput sb = new StringOutput();
//do not use parser and just check for css and script stuff myself and append just before body and head
SimpleHtmlParser parser = new SimpleHtmlParser(page);
if (!parser.isValidHtml()) {
return page;
}
String docType = parser.getHtmlDocType();
if (docType != null) sb.append(docType).append("\n");
if (parser.getXhtmlNamespaces() == null) sb.append("<html><head>");
else {
sb.append(parser.getXhtmlNamespaces());
sb.append("<head><meta http-equiv=\"Content-Script-Type\" content=\"text/javascript\">");//neded to allow body onload attribute
}
//<meta http-equiv="content-type" content="text/html; charset=utf-8" />
sb.append("<meta http-equiv=\"content-type\" content=\"text/html; charset=");
if (parser.getCharsetName() != null) sb.append(parser.getCharsetName());
else sb.append(DEFAULT_ENCODING);
sb.append("\"");
if (docType != null && docType.indexOf("XHTML") > 0) sb.append("/"); // close tag only when xhtml to validate
sb.append(">");
if (!parser.hasOwnCss()) {
// add olat content css as used in html editor
sb.append("<link href=\"").append(themeBaseUri).append("all/content.css\" rel=\"stylesheet\" type=\"text/css\" ");
if (docType != null && docType.indexOf("XHTML") > 0) sb.append("/"); // close tag only when xhtml to validate
sb.append(">\n");
if (customCssURL != null) {
// add the custom CSS, e.g. the course css that overrides the standard content css
sb.append("<link href=\"").append(customCssURL).append("\" rel=\"stylesheet\" type=\"text/css\" ");
if (docType != null && docType.indexOf("XHTML") > 0) sb.append("/"); // close tag only when xhtml to validate
sb.append(">\n");
}
}
//TODO:gs:a do not include if it is a scorm packge!! may results in problems
if (this.enableTextmarking) {
if (log.isDebug()) log.debug("Textmarking is enabled, including tooltips js files into iframe source...");
sb.append("\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/prototype/prototype.js");
sb.append("\"></script>");
sb.append("\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/ext/adapter/prototype/ext-prototype-adapter.js");
sb.append("\"></script>");
sb.append("\n<link rel=\"stylesheet\" type=\"text/css\" href=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/ext/resources/css/ext-all.css");
sb.append("\"");
if (docType != null && docType.indexOf("XHTML") > 0) sb.append("/"); // close tag only when xhtml to validate
// Loading ExtJS minimalisic, only what's needed for the quick tips
sb.append(">\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/ext-foundation.js");
sb.append("\"></script>");
sb.append("\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/cmp-foundation.js");
sb.append("\"></script>");
sb.append("\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/ext-dd.js");
sb.append("\"></script>");
sb.append("\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/ext/pkgs/pkg-tips.js");
sb.append("\"></script>");
// Load glossary code now
sb.append("\n<script type=\"text/javascript\" id=\"textmarkerLib\" src=\"");
sb.append( getWindowControl().getWindowBackOffice().getWindowManager().getMapPathFor(GlossaryMarkupItemController.class) ) ;
sb.append("/js/glossaryhighlighter.js");
sb.append("\"></script>");
sb.append("\n<link rel=\"stylesheet\" type=\"text/css\" id=\"textmarkercss\" href=\"");
sb.append( getWindowControl().getWindowBackOffice().getWindowManager().getMapPathFor(GlossaryMarkupItemController.class) ) ;
sb.append("/css/textmarker.css");
sb.append("\">\n");
}
// Load some iframe.js helper code
sb.append("\n<script type=\"text/javascript\">\n/* <![CDATA[ */\n");
// Set the iframe id, used by the resize function. Important to set before iframe.js is loaded
sb.append("b_iframeid=\"").append(this.frameId).append("\";");
sb.append("b_isInlineUri=").append(Boolean.valueOf(addCheckForInlineEvents).toString()).append(";");
sb.append("\n/* ]]> */\n</script>");
sb.append("<script type=\"text/javascript\" src=\"").append(staticFilesPath).append("/js/iframe.js\"></script>\n");
// Resize frame to fit height of html page.
// Do this only when there is some content available. This can be false when
// the content is written all dynamically via javascript. In this cases, the
// resizeing is meaningless anyway.
if (parser.getHtmlContent().length() > 0) {
sb.append("\n<script type=\"text/javascript\">\n/* <![CDATA[ */\n");
// register the resize code to be executed on document load and click events
if (adjusteightAutomatically) {
sb.append("b_addOnloadEvent(b_sizeIframe);");
sb.append("b_addOnclickEvent(b_sizeIframe);");
}
// register the tooltips enabling on document load event
sb.append("b_addOnloadEvent(b_enableTooltips);");
sb.append("b_addOnloadEvent(b_hideExtMessageBox);");
if (addCheckForInlineEvents) {
// Refresh dirty menu tree by triggering client side request to component which fires events
// which is not possible by mappers. The method will first check if the page is loaded in our
// iframe and ignore all other requests (files in framesets, sub-iframes, AJAX calls etc)
if ((System.currentTimeMillis() - this.suppressEndlessReload) > 2000) sb.append("b_addOnloadEvent(b_sendNewUriEventToParent);");
this.suppressEndlessReload = System.currentTimeMillis();
}
sb.append("b_addOnloadEvent(b_changeLinkTargets);");
if (this.enableTextmarking){
sb.append("b_addOnloadEvent(b_glossaryHighlight);");
}
sb.append("\n/* ]]> */\n</script>");
}
String origHTMLHead = parser.getHtmlHead();
// jsMath brute force approach to render latex formulas: add library if
// a jsmath class is found in the code and the library is not already in
// the header of the page
if (BaseChiefController.isJsMathEnabled()) {
if ((page.indexOf("class=\"math\"") != -1 || page.indexOf("class='math'") != -1) && (origHTMLHead == null || origHTMLHead.indexOf("jsMath/easy/load.js") == -1)) {
sb.append("\n<script type=\"text/javascript\" src=\"");
StaticMediaDispatcher.renderStaticURI(sb, "js/jsMath/easy/load.js");
sb.append("\"></script>");
// don't show jsmath info box, aready visible in parent window
sb.append("<style type='text/css'>#jsMath_button {display:none}</style>");
}
}
// add some custom header things like js code or css
if (customHeaderContent != null) {
sb.append(customHeaderContent);
}
// Add HTML header stuff from original page: css, javascript, title etc.
if (origHTMLHead != null) sb.append(origHTMLHead);
sb.append("</head>");
// use the original body tag, may include all kind of attributes (class, style, onload, on...)
sb.append(parser.getBodyTag());
// finally add content and finish page
sb.append(parser.getHtmlContent());
sb.append("</body></html>");
return sb.toString();
}
@Override
protected void doDispose() {
//contentMapper get's unregistered automatically with basic controller
// remove us as listener if not already done
getWindowControl().getWindowBackOffice().removeCycleListener(this);
}
/**
* this event gets fired from the TextMarkerController when the user swiches on/off textmarking
* @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
*/
public void event(Event event) {
if (event instanceof MultiUserEvent) {
if (event.getCommand().equals("glossaryOn")) {
this.enableTextmarking = true;
} else if (event.getCommand().equals("glossaryOff")) {
this.enableTextmarking = false;
}
}
else if (event.equals(Window.BEFORE_INLINE_RENDERING)){
// Set the custom CSS URL that is used by the current tab or site if
// available. The reason why we do this here and not in the constructor is
// that during the constructing phase this property is not yet set on the
// window.
Window myWindow = getWindowControl().getWindowBackOffice().getWindow();
CustomCSS currentCustomCSS = (CustomCSS) myWindow.getAttribute(BaseFullWebappController.CURRENT_CUSTOM_CSS_KEY);
if (currentCustomCSS != null) customCssURL = currentCustomCSS.getCSSURL();
// done, remove us as listener
getWindowControl().getWindowBackOffice().removeCycleListener(this);
}
}
}