/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/portal/trunk/portal-impl/impl/src/java/org/sakaiproject/portal/charon/SkinnableCharonPortal.java $
* $Id: SkinnableCharonPortal.java 132937 2013-12-29 16:42:57Z csev@umich.edu $
***********************************************************************************
*
* Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.portal.charon;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.entity.api.ResourcePropertiesEdit;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.IdUsedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.portal.api.Editor;
import org.sakaiproject.portal.api.PageFilter;
import org.sakaiproject.portal.api.Portal;
import org.sakaiproject.portal.api.PortalService;
import org.sakaiproject.portal.api.PortalChatPermittedHelper;
import org.sakaiproject.portal.api.PortalHandler;
import org.sakaiproject.portal.api.PortalRenderContext;
import org.sakaiproject.portal.api.PortalRenderEngine;
import org.sakaiproject.portal.api.PortalService;
import org.sakaiproject.portal.api.PortalSiteHelper;
import org.sakaiproject.portal.api.SiteNeighbourhoodService;
import org.sakaiproject.portal.api.SiteView;
import org.sakaiproject.portal.api.StoredState;
import org.sakaiproject.portal.charon.handlers.AtomHandler;
import org.sakaiproject.portal.charon.handlers.DirectToolHandler;
import org.sakaiproject.portal.charon.handlers.ErrorDoneHandler;
import org.sakaiproject.portal.charon.handlers.ErrorReportHandler;
import org.sakaiproject.portal.charon.handlers.HelpHandler;
import org.sakaiproject.portal.charon.handlers.JoinHandler;
import org.sakaiproject.portal.charon.handlers.LoginHandler;
import org.sakaiproject.portal.charon.handlers.LogoutHandler;
import org.sakaiproject.portal.charon.handlers.NavLoginHandler;
import org.sakaiproject.portal.charon.handlers.OpmlHandler;
import org.sakaiproject.portal.charon.handlers.PDAHandler;
import org.sakaiproject.portal.charon.handlers.PageHandler;
import org.sakaiproject.portal.charon.handlers.PresenceHandler;
import org.sakaiproject.portal.charon.handlers.ReLoginHandler;
import org.sakaiproject.portal.charon.handlers.RoleSwitchHandler;
import org.sakaiproject.portal.charon.handlers.RoleSwitchOutHandler;
import org.sakaiproject.portal.charon.handlers.RssHandler;
import org.sakaiproject.portal.charon.handlers.SiteHandler;
import org.sakaiproject.portal.charon.handlers.SiteResetHandler;
import org.sakaiproject.portal.charon.handlers.StaticScriptsHandler;
import org.sakaiproject.portal.charon.handlers.StaticStylesHandler;
import org.sakaiproject.portal.charon.handlers.TimeoutDialogHandler;
import org.sakaiproject.portal.charon.handlers.ToolHandler;
import org.sakaiproject.portal.charon.handlers.ToolResetHandler;
import org.sakaiproject.portal.charon.handlers.WorksiteHandler;
import org.sakaiproject.portal.charon.handlers.WorksiteResetHandler;
import org.sakaiproject.portal.charon.handlers.XLoginHandler;
import org.sakaiproject.portal.charon.site.PortalSiteHelperImpl;
import org.sakaiproject.portal.render.api.RenderResult;
import org.sakaiproject.portal.render.cover.ToolRenderService;
import org.sakaiproject.portal.util.ErrorReporter;
import org.sakaiproject.portal.util.ToolURLManagerImpl;
import org.sakaiproject.portal.util.URLUtils;
import org.sakaiproject.portal.util.CSSUtils;
import org.sakaiproject.portal.util.ToolUtils;
import org.sakaiproject.portal.util.PortalUtils;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SitePage;
import org.sakaiproject.site.api.ToolConfiguration;
import org.sakaiproject.site.cover.SiteService;
import org.sakaiproject.thread_local.cover.ThreadLocalManager;
import org.sakaiproject.time.api.TimeService;
import org.sakaiproject.tool.api.ActiveTool;
import org.sakaiproject.tool.api.Placement;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.tool.api.ToolException;
import org.sakaiproject.tool.api.ToolSession;
import org.sakaiproject.tool.api.ToolURL;
import org.sakaiproject.tool.cover.ActiveToolManager;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.user.api.Preferences;
import org.sakaiproject.user.api.PreferencesEdit;
import org.sakaiproject.user.api.PreferencesService;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.user.cover.UserDirectoryService;
import org.sakaiproject.util.BasicAuth;
import org.sakaiproject.util.EditorConfiguration;
import org.sakaiproject.util.ResourceLoader;
import org.sakaiproject.util.Validator;
import org.sakaiproject.util.Web;
import au.com.flyingkite.mobiledetect.UAgentInfo;
/**
* <p/> Charon is the Sakai Site based portal.
* </p>
*
* @since Sakai 2.4
* @version $Rev: 132937 $
*
*/
@SuppressWarnings("deprecation")
public class SkinnableCharonPortal extends HttpServlet implements Portal
{
/**
*
*/
private static final long serialVersionUID = 2645929710236293089L;
/**
* Our log (commons).
*/
private static Log M_log = LogFactory.getLog(SkinnableCharonPortal.class);
/**
* messages.
*/
private static ResourceLoader rloader = new ResourceLoader("sitenav");
/**
* Parameter value to indicate to look up a tool ID within a site
*/
protected static final String PARAM_SAKAI_SITE = "sakai.site";
private BasicAuth basicAuth = null;
private boolean enableDirect = false;
private PortalService portalService;
private SecurityService securityService = null;
/**
* Keyword to look for in sakai.properties copyright message to replace
* for the server's time's year for auto-update of Copyright end date
*/
private static final String SERVER_COPYRIGHT_CURRENT_YEAR_KEYWORD = "currentYearFromServer";
/**
* Chat helper.
*/
private PortalChatPermittedHelper chatHelper;
private static final String PADDING = " ";
private static final String INCLUDE_BOTTOM = "include-bottom";
private static final String INCLUDE_LOGIN = "include-login";
private static final String INCLUDE_TITLE = "include-title";
private PortalSiteHelper siteHelper = null;
// private HashMap<String, PortalHandler> handlerMap = new HashMap<String,
// PortalHandler>();
private String gatewaySiteUrl;
private String gatewayPdaSiteUrl;
private WorksiteHandler worksiteHandler;
private SiteHandler siteHandler;
private String portalContext;
private String PROP_PARENT_ID = SiteService.PROP_PARENT_ID;
// 2.3 back port
// public String String PROP_PARENT_ID = "sakai:parent-id";
private String PROP_SHOW_SUBSITES = SiteService.PROP_SHOW_SUBSITES ;
private final String PROP_PDA_HTML_INCLUDE = "sakai:pdaHtmlInclude";
// 2.3 back port
// public String PROP_SHOW_SUBSITES = "sakai:show-subsites";
private boolean isMobileDevice = false;
private boolean forceContainer = false;
private boolean sakaiTutorialEnabled = true;
private String handlerPrefix;
private PageFilter pageFilter = new PageFilter() {
public List filter(List newPages, Site site)
{
return newPages;
}
public List<Map> filterPlacements(List<Map> l, Site site)
{
return l;
}
};
// define string that identifies this as the logged in users' my workspace
private String myWorkspaceSiteId = "~";
public String getPortalContext()
{
return portalContext;
}
/**
* Shutdown the servlet.
*/
public void destroy()
{
M_log.info("destroy()");
portalService.removePortal(this);
super.destroy();
}
public void doError(HttpServletRequest req, HttpServletResponse res, Session session,
int mode) throws ToolException, IOException
{
if (ThreadLocalManager.get(ATTR_ERROR) == null)
{
ThreadLocalManager.set(ATTR_ERROR, ATTR_ERROR);
// send to the error site
switch (mode)
{
case ERROR_SITE:
{
siteHandler.doSite(req, res, session, "!error", null, null, null, null,
req.getContextPath() + req.getServletPath());
break;
}
case ERROR_WORKSITE:
{
worksiteHandler.doWorksite(req, res, session, "!error", null, req
.getContextPath()
+ req.getServletPath());
break;
}
}
return;
}
// error and we cannot use the error site...
// form a context sensitive title
String title = ServerConfigurationService.getString("ui.service","Sakai") + " : Portal";
// start the response
PortalRenderContext rcontext = startPageContext("", title, null, req);
showSession(rcontext, true);
showSnoop(rcontext, true, getServletConfig(), req);
sendResponse(rcontext, res, "error", null);
}
private void showSnoop(PortalRenderContext rcontext, boolean b,
ServletConfig servletConfig, HttpServletRequest req)
{
Enumeration e = null;
rcontext.put("snoopRequest", req.toString());
if (servletConfig != null)
{
Map<String, Object> m = new HashMap<String, Object>();
e = servletConfig.getInitParameterNames();
if (e != null)
{
while (e.hasMoreElements())
{
String param = (String) e.nextElement();
m.put(param, servletConfig.getInitParameter(param));
}
}
rcontext.put("snoopServletConfigParams", m);
}
rcontext.put("snoopRequest", req);
e = req.getHeaderNames();
if (e.hasMoreElements())
{
Map<String, Object> m = new HashMap<String, Object>();
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
m.put(name, req.getHeader(name));
}
rcontext.put("snoopRequestHeaders", m);
}
e = req.getParameterNames();
if (e.hasMoreElements())
{
Map<String, Object> m = new HashMap<String, Object>();
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
m.put(name, req.getParameter(name));
}
rcontext.put("snoopRequestParamsSingle", m);
}
e = req.getParameterNames();
if (e.hasMoreElements())
{
Map<String, Object> m = new HashMap<String, Object>();
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
String[] vals = (String[]) req.getParameterValues(name);
StringBuilder sb = new StringBuilder();
if (vals != null)
{
sb.append(vals[0]);
for (int i = 1; i < vals.length; i++)
sb.append(" ").append(vals[i]);
}
m.put(name, sb.toString());
}
rcontext.put("snoopRequestParamsMulti", m);
}
e = req.getAttributeNames();
if (e.hasMoreElements())
{
Map<String, Object> m = new HashMap<String, Object>();
while (e.hasMoreElements())
{
String name = (String) e.nextElement();
m.put(name, req.getAttribute(name));
}
rcontext.put("snoopRequestAttr", m);
}
}
protected void doThrowableError(HttpServletRequest req, HttpServletResponse res,
Throwable t)
{
ErrorReporter err = new ErrorReporter();
err.report(req, res, t);
}
/*
*
*
* Include the children of a site
*/
// TODO: Extract to a provider
public void includeSubSites(PortalRenderContext rcontext, HttpServletRequest req,
Session session, String siteId, String toolContextPath,
String prefix, boolean resetTools)
// throws ToolException, IOException
{
if ( siteId == null || rcontext == null ) return;
// Check the setting as to whether we are to do this
String pref = ServerConfigurationService.getString("portal.includesubsites");
if ( "never".equals(pref) ) return;
Site site = null;
try
{
site = siteHelper.getSiteVisit(siteId);
}
catch (Exception e)
{
return;
}
if ( site == null ) return;
ResourceProperties rp = site.getProperties();
String showSub = rp.getProperty(PROP_SHOW_SUBSITES);
// System.out.println("Checking subsite pref:"+site.getTitle()+" pref="+pref+" show="+showSub);
if ( "false".equals(showSub) ) return;
if ( "false".equals(pref) )
{
if ( ! "true".equals(showSub) ) return;
}
SiteView siteView = siteHelper.getSitesView(SiteView.View.SUB_SITES_VIEW,req, session, siteId);
if ( siteView.isEmpty() ) return;
siteView.setPrefix(prefix);
siteView.setToolContextPath(toolContextPath);
siteView.setResetTools(resetTools);
if( !siteView.isEmpty() ) {
rcontext.put("subSites", siteView.getRenderContextObject());
boolean showSubsitesAsFlyout = ServerConfigurationService.getBoolean("portal.showSubsitesAsFlyout",false);
rcontext.put("showSubsitesAsFlyout", showSubsitesAsFlyout);
}
}
/*
* Produce a portlet like view with the navigation all at the top with
* implicit reset
*/
public PortalRenderContext includePortal(HttpServletRequest req,
HttpServletResponse res, Session session, String siteId, String toolId,
String toolContextPath, String prefix, boolean doPages, boolean resetTools,
boolean includeSummary, boolean expandSite) throws ToolException, IOException
{
String errorMessage = null;
String sitePdaHtmlInclude = null;
// find the site, for visiting
Site site = null;
try
{
site = siteHelper.getSiteVisit(siteId);
sitePdaHtmlInclude = site.getProperties().getProperty(PROP_PDA_HTML_INCLUDE);
}
catch (IdUnusedException e)
{
errorMessage = "Unable to find site: " + siteId;
siteId = null;
toolId = null;
}
catch (PermissionException e)
{
if (session.getUserId() == null)
{
errorMessage = "No permission for anonymous user to view site: " + siteId;
}
else
{
errorMessage = "No permission to view site: " + siteId;
}
siteId = null;
toolId = null; // Tool needs the site and needs it to be visitable
}
// Get the Tool Placement
ToolConfiguration placement = null;
if (site != null && toolId != null)
{
placement = SiteService.findTool(toolId);
if (placement == null)
{
errorMessage = "Unable to find tool placement " + toolId;
toolId = null;
}
boolean thisTool = siteHelper.allowTool(site, placement);
if (!thisTool)
{
errorMessage = "No permission to view tool placement " + toolId;
toolId = null;
placement = null;
}
}
// form a context sensitive title
String title = ServerConfigurationService.getString("ui.service","Sakai");
if (site != null)
{
title = title + ":" + site.getTitle();
if (placement != null) title = title + " : " + placement.getTitle();
}
// start the response
String siteType = null;
String siteSkin = null;
if (site != null)
{
siteType = calcSiteType(siteId);
siteSkin = site.getSkin();
}
PortalRenderContext rcontext = startPageContext(siteType, title, siteSkin, req);
if (sitePdaHtmlInclude != null) rcontext.put("sitePdaHtmlInclude", sitePdaHtmlInclude);
// Make the top Url where the "top" url is
String portalTopUrl = Web.serverUrl(req)
+ ServerConfigurationService.getString("portalPath") + "/";
if (prefix != null) portalTopUrl = portalTopUrl + prefix + "/";
rcontext.put("portalTopUrl", portalTopUrl);
rcontext.put("loggedIn", Boolean.valueOf(session.getUserId() != null));
rcontext.put("siteId", siteId);
if (placement != null)
{
Map m = includeTool(res, req, placement);
if (m != null) rcontext.put("currentPlacement", m);
}
if (site != null)
{
SiteView siteView = siteHelper.getSitesView(SiteView.View.CURRENT_SITE_VIEW, req, session, siteId );
siteView.setPrefix(prefix);
siteView.setResetTools(resetTools);
siteView.setToolContextPath(toolContextPath);
siteView.setIncludeSummary(includeSummary);
siteView.setDoPages(doPages);
if ( !siteView.isEmpty() ) {
rcontext.put("currentSite", siteView.getRenderContextObject());
}
}
//List l = siteHelper.convertSitesToMaps(req, mySites, prefix, siteId, myWorkspaceSiteId,
// includeSummary, expandSite, resetTools, doPages, toolContextPath,
// loggedIn);
SiteView siteView = siteHelper.getSitesView(SiteView.View.ALL_SITES_VIEW, req, session, siteId );
siteView.setPrefix(prefix);
siteView.setResetTools(resetTools);
siteView.setToolContextPath(toolContextPath);
siteView.setIncludeSummary(includeSummary);
siteView.setDoPages(doPages);
siteView.setExpandSite(expandSite);
rcontext.put("allSites", siteView.getRenderContextObject());
includeLogin(rcontext, req, session);
includeBottom(rcontext);
return rcontext;
}
public boolean isPortletPlacement(Placement placement)
{
return ToolUtils.isPortletPlacement(placement);
}
public Map includeTool(HttpServletResponse res, HttpServletRequest req,
ToolConfiguration placement) throws IOException
{
// find the tool registered for this
ActiveTool tool = ActiveToolManager.getActiveTool(placement.getToolId());
if (tool == null)
{
// doError(req, res, session);
return null;
}
// Get the Site - we could change the API call in the future to
// pass site in, but that would break portals that extend Charon
// so for now we simply look this up here.
String siteId = placement.getSiteId();
Site site = null;
try
{
site = SiteService.getSiteVisit(siteId);
}
catch (IdUnusedException e)
{
site = null;
}
catch (PermissionException e)
{
site = null;
}
// emit title information
String titleString = Web.escapeHtml(placement.getTitle());
String toolId = Web.escapeHtml(placement.getToolId());
// for the reset button
String toolUrl = ServerConfigurationService.getToolUrl() + "/"
+ Web.escapeUrl(placement.getId()) + "/";
// Reset is different (and awesome) when inlining
boolean toolInline = ToolUtils.isInlineRequest(req);
if ( toolInline ) {
String newUrl = ToolUtils.getPageUrlForTool(req, site, placement);
if ( newUrl != null ) toolUrl = newUrl;
}
// Reset the tool state if requested
// Resets of inline tools have already been handled earlier in the request
// (See PDAHandler.java and SiteJHandler.java)
if (!toolInline && portalService.isResetRequested(req))
{
Session s = SessionManager.getCurrentSession();
ToolSession ts = s.getToolSession(placement.getId());
ts.clearAttributes();
}
boolean showResetButton = !"false".equals(placement.getConfig().getProperty(
Portal.TOOLCONFIG_SHOW_RESET_BUTTON));
String resetActionUrl = PortalStringUtil.replaceFirst(toolUrl, "/tool/", "/tool-reset/");
String sakaiPanel = req.getParameter("panel");
if ( sakaiPanel != null && sakaiPanel.matches(".*[\"'<>].*" ) ) sakaiPanel=null;
if ( sakaiPanel == null ) sakaiPanel="Main";
resetActionUrl = URLUtils.addParameter(resetActionUrl, "panel", sakaiPanel);
// SAK-20462 - Pass through the sakai_action parameter
String sakaiAction = req.getParameter("sakai_action");
if ( sakaiAction != null && sakaiAction.matches(".*[\"'<>].*" ) ) sakaiAction=null;
if ( sakaiAction != null ) resetActionUrl = URLUtils.addParameter(resetActionUrl, "sakai_action", sakaiAction);
// Reset is different for Portlets
if (isPortletPlacement(placement))
{
resetActionUrl = Web.serverUrl(req)
+ ServerConfigurationService.getString("portalPath")
+ URLUtils.getSafePathInfo(req) + "?sakai.state.reset=true";
}
// for the help button
// get the help document ID from the tool config (tool registration
// usually).
// The help document ID defaults to the tool ID
boolean helpEnabledGlobally = ServerConfigurationService.getBoolean(
"display.help.icon", true);
boolean helpEnabledInTool = !"false".equals(placement.getConfig().getProperty(
Portal.TOOLCONFIG_SHOW_HELP_BUTTON));
boolean showHelpButton = helpEnabledGlobally && helpEnabledInTool;
String helpActionUrl = "";
if (showHelpButton)
{
String helpDocUrl = placement.getConfig().getProperty(
Portal.TOOLCONFIG_HELP_DOCUMENT_URL);
String helpDocId = placement.getConfig().getProperty(
Portal.TOOLCONFIG_HELP_DOCUMENT_ID);
if (helpDocUrl != null && helpDocUrl.length() > 0)
{
helpActionUrl = helpDocUrl;
}
else
{
if (helpDocId == null || helpDocId.length() == 0)
{
helpDocId = tool.getId();
}
helpActionUrl = ServerConfigurationService.getHelpUrl(helpDocId);
}
}
Map<String, Object> toolMap = new HashMap<String, Object>();
RenderResult result = ToolRenderService.render(this,placement, req, res,
getServletContext());
if (result.getJSR168HelpUrl() != null)
{
toolMap.put("toolJSR168Help", Web.serverUrl(req) + result.getJSR168HelpUrl());
}
// Must have site.upd to see the Edit button
if (result.getJSR168EditUrl() != null && site != null)
{
if (securityService.unlock(SiteService.SECURE_UPDATE_SITE, site
.getReference()))
{
String editUrl = Web.serverUrl(req) + result.getJSR168EditUrl();
toolMap.put("toolJSR168Edit", editUrl);
toolMap.put("toolJSR168EditEncode", URLUtils.encodeUrl(editUrl));
}
}
toolMap.put("toolRenderResult", result);
toolMap.put("hasRenderResult", Boolean.valueOf(true));
toolMap.put("toolUrl", toolUrl);
boolean allowNeo = ServerConfigurationService.getBoolean("portal.allow.neo.portlet", true);
Session s = SessionManager.getCurrentSession();
ToolSession ts = s.getToolSession(placement.getId());
ts.removeAttribute(SAKAI_PORTAL_BREADCRUMBS);
ts.removeAttribute(SAKAI_PORTAL_SUPPRESSTITLE);
if ( allowNeo ) {
ts.setAttribute(SAKAI_PORTAL_ALLOW_NEO,"true");
ts.setAttribute(SAKAI_PORTAL_HELP_ACTION,helpActionUrl);
ts.setAttribute(SAKAI_PORTAL_RESET_ACTION,resetActionUrl);
}
if (isPortletPlacement(placement))
{
// If the tool has requested it, pre-fetch render output.
String doPreFetch = placement.getConfig().getProperty(Portal.JSR_168_PRE_RENDER);
if ( ! "false".equals(doPreFetch) )
{
try {
result.getContent();
} catch (Throwable t) {
ErrorReporter err = new ErrorReporter();
String str = err.reportFragment(req, res, t);
result.setContent(str);
}
if ( allowNeo ) {
Object bread = ts.getAttribute(SAKAI_PORTAL_BREADCRUMBS);
if ( bread != null ) toolMap.put("breadcrumbs", bread);
Object suppressTitle = ts.getAttribute(SAKAI_PORTAL_SUPPRESSTITLE);
if ( "true".equals(suppressTitle) ) toolMap.put("suppressTitle", Boolean.TRUE);
}
}
toolMap.put("toolPlacementIDJS", "_self");
toolMap.put("isPortletPlacement", Boolean.TRUE);
}
else
{
String suppressTitleLegacy = placement.getConfig().getProperty(SAKAI_PORTAL_SUPPRESSTITLE);
if ( "true".equals(suppressTitleLegacy) ) toolMap.put("suppressTitle", Boolean.TRUE);
toolMap.put("toolPlacementIDJS", Web.escapeJavascript("Main"
+ placement.getId()));
}
toolMap.put("toolResetActionUrl", resetActionUrl);
toolMap.put("toolResetActionUrlEncode", URLUtils.encodeUrl(resetActionUrl));
toolMap.put("toolTitle", titleString);
toolMap.put("toolTitleEncode", URLUtils.encodeUrl(titleString));
toolMap.put("toolShowResetButton", Boolean.valueOf(showResetButton));
toolMap.put("toolShowHelpButton", Boolean.valueOf(showHelpButton));
toolMap.put("toolHelpActionUrl", helpActionUrl);
toolMap.put("toolId", toolId);
toolMap.put("toolInline", Boolean.valueOf(toolInline));
String directToolUrl = ServerConfigurationService.getPortalUrl() + "/" + DirectToolHandler.URL_FRAGMENT +"/" + Web.escapeUrl(placement.getId()) + "/";
toolMap.put("directToolUrl", directToolUrl);
//props to enable/disable the display on a per tool/placement basis
//will be displayed if not explicitly disabled in the tool/placement properties
boolean showDirectToolUrl = !"false".equals(placement.getConfig().getProperty(Portal.TOOL_DIRECTURL_ENABLED_PROP));
toolMap.put("showDirectToolUrl", showDirectToolUrl);
return toolMap;
}
private boolean usePdaHandler(HttpServletRequest req)
{
if ( req == null) return false;
checkMobile(req);
//check sakai.properties to see if auto redirect is enabled
//defaults to true - if set to false, skip the PDA check
if(!ServerConfigurationService.getBoolean("portal.pda.autoredirect", true)){
if(M_log.isDebugEnabled()) {
M_log.debug("Auto redirect for mobile devices is disabled, classic view will be used preferentially.");
}
return false;
}
//check if we have a cookie to force classic view, skip the PDA check
Cookie c = findCookie(req, Portal.PORTAL_MODE_COOKIE_NAME);
if ((c != null) && (c.getValue().equals(Portal.FORCE_CLASSIC_COOKIE_VALUE))) {
if(M_log.isDebugEnabled()) {
M_log.debug("Cookie found, classic view will be used preferentially.");
}
return false;
}
return isMobileDevice;
}
/**
* Respond to navigation / access requests.
*
* @param req
* The servlet request.
* @param res
* The servlet response.
* @throws javax.servlet.ServletException.
* @throws java.io.IOException.
*/
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
int stat = PortalHandler.NEXT;
try
{
basicAuth.doLogin(req);
if (!ToolRenderService.preprocess(this,req, res, getServletContext()))
{
return;
}
// Check to see if the pre-process step has redirected us - if so,
// our work is done here - we will likely come back again to finish
// our
// work.
if (res.isCommitted())
{
return;
}
// get the Sakai session
Session session = SessionManager.getCurrentSession();
// recognize what to do from the path
String option = URLUtils.getSafePathInfo(req);
//FindBugs thinks this is not used but is passed to the portal handler
String[] parts = {};
if (option == null || "/".equals(option))
{
// Use the default handler prefix
parts = new String[]{"", handlerPrefix};
}
else
{
//get the parts (the first will be "")
parts = option.split("/");
}
Map<String, PortalHandler> handlerMap = portalService.getHandlerMap(this);
PortalHandler ph;
boolean pdaHandler = usePdaHandler(req);
if(M_log.isDebugEnabled()){
M_log.debug("Using pdaHandler: " + pdaHandler);
}
// begin SAK-19089
// if not logged in and accessing "/" and not from PDA, redirect to gatewaySiteUrl
if ((gatewaySiteUrl != null) && (option == null || "/".equals(option) || "/pda".equals(option))
/* && (!pdaHandler) */ && (session.getUserId() == null))
{
// redirect to gatewaySiteURL
res.sendRedirect(gatewaySiteUrl);
return;
}
// end SAK-19089
// begin SAK-22991
// if not logged in and from PDA, redirect to gatewayPdaSiteUrl
if ((gatewayPdaSiteUrl != null) && (option == null || "/pda".equals(option)) && (session.getUserId() == null))
{
// redirect to gatewaySiteURL
res.sendRedirect(gatewayPdaSiteUrl);
return;
}
// end SAK-19089
// SAK-22633 - Only forward site urls to PDAHandler
if (pdaHandler && parts.length > 1 && "site".equals(parts[1])){
//Mobile access
ph = handlerMap.get("pda");
parts[1] = "pda";
} else{
ph = handlerMap.get(parts[1]);
}
if (ph != null)
{
stat = ph.doGet(parts, req, res, session);
if (res.isCommitted())
{
if (stat != PortalHandler.RESET_DONE)
{
portalService.setResetState(null);
}
return;
}
}
if (stat == PortalHandler.NEXT)
{
for (Iterator<PortalHandler> i = handlerMap.values().iterator(); i.hasNext();)
{
ph = i.next();
stat = ph.doGet(parts, req, res, session);
if (res.isCommitted())
{
if (stat != PortalHandler.RESET_DONE)
{
portalService.setResetState(null);
}
return;
}
// this should be
if (stat != PortalHandler.NEXT)
{
break;
}
}
}
if (stat == PortalHandler.NEXT)
{
doError(req, res, session, Portal.ERROR_SITE);
}
}
catch (Throwable t)
{
doThrowableError(req, res, t);
}
// Make sure to clear any reset State at the end of the request unless
// we *just* set it
if (stat != PortalHandler.RESET_DONE)
{
portalService.setResetState(null);
}
}
public void doLogin(HttpServletRequest req, HttpServletResponse res, Session session,
String returnPath, boolean skipContainer) throws ToolException
{
try
{
if (basicAuth.doAuth(req, res))
{
// System.err.println("BASIC Auth Request Sent to the Browser
// ");
return;
}
}
catch (IOException ioex)
{
throw new ToolException(ioex);
}
// setup for the helper if needed (Note: in session, not tool session,
// special for Login helper)
// Note: always set this if we are passed in a return path... a blank
// return path is valid... to clean up from
// possible abandened previous login attempt -ggolden
if (returnPath != null)
{
// where to go after
String returnUrl = Web.returnUrl(req, returnPath);
if (req.getQueryString() != null )
returnUrl += "?"+req.getQueryString();
session.setAttribute(Tool.HELPER_DONE_URL, returnUrl);
}
ActiveTool tool = ActiveToolManager.getActiveTool("sakai.login");
// to skip container auth for this one, forcing things to be handled
// internaly, set the "extreme" login path
String loginPath = (!forceContainer && skipContainer ? "/xlogin" : "/relogin");
String context = req.getContextPath() + req.getServletPath() + loginPath;
if ("/pda".equals(returnPath)) {
context = req.getContextPath() + req.getServletPath() + returnPath + loginPath;
}
tool.help(req, res, context, loginPath);
}
/**
* Process a logout
*
* @param req
* Request object
* @param res
* Response object
* @param session
* Current session
* @param returnPath
* if not null, the path to use for the end-user browser redirect
* after the logout is complete. Leave null to use the configured
* logged out URL.
* @throws IOException
*/
public void doLogout(HttpServletRequest req, HttpServletResponse res,
Session session, String returnPath) throws ToolException
{
// SAK-16370 to allow multiple logout urls
String loggedOutUrl = null;
String userType = UserDirectoryService.getCurrentUser().getType();
if(userType == null) {
loggedOutUrl = ServerConfigurationService.getLoggedOutUrl();
} else {
loggedOutUrl = ServerConfigurationService.getString("loggedOutUrl." + userType, ServerConfigurationService.getLoggedOutUrl());
}
if ( returnPath != null )
{
loggedOutUrl = loggedOutUrl + returnPath;
}
session.setAttribute(Tool.HELPER_DONE_URL, loggedOutUrl);
ActiveTool tool = ActiveToolManager.getActiveTool("sakai.login");
String context = req.getContextPath() + req.getServletPath() + "/logout";
tool.help(req, res, context, "/logout");
}
/**
* Check if we are on a mobile device. Only does this once per session.
* @param req HttpServletRequest
*/
public void checkMobile(HttpServletRequest req) {
//check session param and return if already set. We don't care what the value actually is, just that we have already processed it
Session session = SessionManager.getCurrentSession();
Boolean mobileDeviceParam = (Boolean)session.getAttribute("is_mobile_device");
if(M_log.isDebugEnabled()){
M_log.debug("session id: " + session.getId());
M_log.debug("is_mobile_device session param: " + mobileDeviceParam);
}
if(mobileDeviceParam != null) {
//SAK-22484 before returning, ensure the value is set to the value to the session param
isMobileDevice = mobileDeviceParam;
return;
}
//get user agent, return if null as we cannot check
String userAgent = req.getHeader("User-Agent");
if (StringUtils.isBlank(userAgent)) {
//SAK-22484 cannot determine, set false
isMobileDevice = false;
return;
}
UAgentInfo agentInfo = new UAgentInfo(userAgent, null);
//check mobile
isMobileDevice = agentInfo.detectMobileQuick();
//check tablet if needed
boolean tabletsUseMobileView = ServerConfigurationService.getBoolean("portal.tablets.use.mobile", false);
if(tabletsUseMobileView && !isMobileDevice) {
isMobileDevice = agentInfo.detectTierTablet();
}
//set session param so we don't need to do this again
session.setAttribute("is_mobile_device", Boolean.valueOf(isMobileDevice));
if(M_log.isDebugEnabled()){
M_log.debug("User-Agent: " + userAgent);
M_log.debug("Mobile device: " + isMobileDevice);
M_log.debug("portal.tablets.use.mobile: " + tabletsUseMobileView);
}
}
/**
* Check for mobile via checkMobile(req) and setup some context params
*/
public void setupMobileDevice(HttpServletRequest req, PortalRenderContext rcontext)
{
if ( req == null) return;
checkMobile(req);
// Not a mobile device
if (!isMobileDevice) {
return;
}
rcontext.put("mobileDevice", Boolean.TRUE);
//for now just always assume we have a small display.
rcontext.put("mobileSmallDisplay",Boolean.TRUE);
// Old WURFL code left here for reference in case people want to flesh out the detection
// Check to see if we have too few columns of text
/*
String columns = device.getCapability("columns");
{
int icol = -1;
try { icol = Integer.parseInt(columns); } catch (Exception t) { icol = -1; }
if ( icol > 1 && icol < 50 )
{
rcontext.put("wurflSmallDisplay",Boolean.TRUE);
return;
}
}
// Check if we have too few pixels
String width = device.getCapability("resolution_width");
if ( width != null && width.length() > 1 )
{
int iwidth = -1;
try { iwidth = Integer.parseInt(width); } catch (Throwable t) { iwidth = -1; }
if ( iwidth > 1 && iwidth < 400 )
{
rcontext.put("wurflSmallDisplay",Boolean.TRUE);
return;
}
}
*/
}
public PortalRenderContext startPageContext(String siteType, String title,
String skin, HttpServletRequest request)
{
PortalRenderEngine rengine = portalService
.getRenderEngine(portalContext, request);
PortalRenderContext rcontext = rengine.newRenderContext(request);
skin = getSkin(skin);
String skinRepo = ServerConfigurationService.getString("skin.repo");
rcontext.put("pageSkinRepo", skinRepo);
rcontext.put("pageSkin", skin);
rcontext.put("pageTitle", Web.escapeHtml(title));
rcontext.put("pageScriptPath", PortalUtils.getScriptPath());
rcontext.put("portalCDNPath", PortalUtils.getCDNPath());
rcontext.put("portalCDNQuery", PortalUtils.getCDNQuery());
rcontext.put("pageTop", Boolean.valueOf(true));
rcontext.put("rloader", rloader);
//rcontext.put("browser", new BrowserDetector(request));
Session s = SessionManager.getCurrentSession();
rcontext.put("loggedIn", Boolean.valueOf(s.getUserId() != null));
rcontext.put("userId", s.getUserId());
rcontext.put("userEid", s.getUserEid());
rcontext.put("loggedOutUrl",ServerConfigurationService.getLoggedOutUrl());
rcontext.put("portalPath",ServerConfigurationService.getPortalUrl());
rcontext.put("timeoutDialogEnabled",Boolean.valueOf(ServerConfigurationService.getBoolean("timeoutDialogEnabled", true)));
rcontext.put("timeoutDialogWarningSeconds", Integer.valueOf(ServerConfigurationService.getInt("timeoutDialogWarningSeconds", 600)));
// rcontext.put("sitHelp", Web.escapeHtml(rb.getString("sit_help")));
// rcontext.put("sitReset", Web.escapeHtml(rb.getString("sit_reset")));
if (siteType != null && siteType.length() > 0)
{
siteType = "class=\"" + siteType + "\"";
}
else
{
siteType = "";
}
rcontext.put("pageSiteType", siteType);
rcontext.put("toolParamResetState", portalService.getResetStateParam());
// Get the tool header properties
Properties props = toolHeaderProperties(skin);
for(Object okey : props.keySet() )
{
String key = (String) okey;
String keyund = key.replace('.','_');
rcontext.put(keyund,props.getProperty(key));
}
// Copy the minimization preferences to the context
String minStr = ServerConfigurationService.getString("portal.allow.minimize.tools","true");
rcontext.put("portal_allow_minimize_tools",Boolean.valueOf( "true".equals(minStr) ) ) ;
minStr = ServerConfigurationService.getString("portal.allow.minimize.navigation","false");
rcontext.put("portal_allow_minimize_navigation",Boolean.valueOf( "true".equals(minStr) ) ) ;
minStr = ServerConfigurationService.getString("portal.allow.auto.minimize","true");
rcontext.put("portal_allow_auto_minimize",Boolean.valueOf( "true".equals(minStr) ) ) ;
// copy the add link to /mobile to the content
String addMLnk = ServerConfigurationService.getString("portal.add.mobile.link","false");
// how many tools to show in portal pull downs
rcontext.put("maxToolsInt", Integer.valueOf(ServerConfigurationService.getInt("portal.tool.menu.max", 10)));
// show the mobile link or not
if (s.getAttribute("is_mobile_device") == null && request != null){
//determine if we are on a mobile device - sets up the params we need
checkMobile(request);
}
boolean isMobileDevice = s.getAttribute("is_mobile_device") != null ? ((Boolean) s.getAttribute("is_mobile_device")).booleanValue():false;
rcontext.put("portal_add_mobile_link",Boolean.valueOf( "true".equals(addMLnk) && isMobileDevice ) ) ;
rcontext.put("toolDirectUrlEnabled", ServerConfigurationService.getBoolean("portal.tool.direct.url.enabled", false));
rcontext.put("toolShortUrlEnabled", ServerConfigurationService.getBoolean("shortenedurl.portal.tool.enabled", true));
return rcontext;
}
/**
* Respond to data posting requests.
*
* @param req
* The servlet request.
* @param res
* The servlet response.
* @throws ServletException
* @throws IOException
*/
protected void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
int stat = PortalHandler.NEXT;
try
{
basicAuth.doLogin(req);
if (!ToolRenderService.preprocess(this,req, res, getServletContext()))
{
// System.err.println("POST FAILED, REDIRECT ?");
return;
}
// Check to see if the pre-process step has redirected us - if so,
// our work is done here - we will likely come back again to finish
// our
// work. T
if (res.isCommitted())
{
return;
}
// get the Sakai session
Session session = SessionManager.getCurrentSession();
// recognize what to do from the path
String option = URLUtils.getSafePathInfo(req);
// if missing, we have a stray post
if ((option == null) || ("/".equals(option)))
{
doError(req, res, session, ERROR_SITE);
return;
}
// get the parts (the first will be "")
String[] parts = option.split("/");
Map<String, PortalHandler> handlerMap = portalService.getHandlerMap(this);
PortalHandler ph;
boolean pdaHandler = usePdaHandler(req);
if(M_log.isDebugEnabled()){
M_log.debug("Using pdaHandler: " + pdaHandler);
}
// SAK-22633 - Only forward site urls to PDAHandler
if (pdaHandler && parts.length > 1 && "site".equals(parts[1])){
//Mobile access
ph = handlerMap.get("pda");
parts[1] = "pda";
}else{
ph = handlerMap.get(parts[1]);
}
if (ph != null)
{
stat = ph.doPost(parts, req, res, session);
if (res.isCommitted())
{
return;
}
}
if (stat == PortalHandler.NEXT)
{
List<PortalHandler> urlHandlers;
for (Iterator<PortalHandler> i = handlerMap.values().iterator(); i.hasNext();)
{
ph = i.next();
stat = ph.doPost(parts, req, res, session);
if (res.isCommitted())
{
return;
}
// this should be
if (stat != PortalHandler.NEXT)
{
break;
}
}
}
if (stat == PortalHandler.NEXT)
{
doError(req, res, session, Portal.ERROR_SITE);
}
}
catch (Throwable t)
{
doThrowableError(req, res, t);
}
}
/*
* Checks to see which form of tool or page placement we have. The normal
* placement is a GUID. However when the parameter sakai.site is added to
* the request, the placement can be of the form sakai.resources. This
* routine determines which form of the placement id, and if this is the
* second type, performs the lookup and returns the GUID of the placement.
* If we cannot resolve the placement, we simply return the passed in
* placement ID. If we cannot visit the site, we send the user to login
* processing and return null to the caller.
*
* If the reference is to the magical, indexical MyWorkspace site ('~')
* then replace ~ by their My Workspace. Give them a chance to login
* if necessary.
*/
public String getPlacement(HttpServletRequest req, HttpServletResponse res,
Session session, String placementId, boolean doPage) throws ToolException
{
String siteId = req.getParameter(PARAM_SAKAI_SITE);
if (siteId == null) return placementId; // Standard placement
// Try to resolve the indexical MyWorkspace reference
if (myWorkspaceSiteId.equals(siteId)) {
// If not logged in then allow login. You can't go to your workspace if
// you aren't known to the system.
if (session.getUserId() == null)
{
doLogin(req, res, session, URLUtils.getSafePathInfo(req), false);
}
// If the login was successful lookup the myworkworkspace site.
if (session.getUserId() != null) {
siteId=getUserEidBasedSiteId(session.getUserEid());
}
}
// find the site, for visiting
// Sites like the !gateway site allow visits by anonymous
Site site = null;
try
{
site = getSiteHelper().getSiteVisit(siteId);
}
catch (IdUnusedException e)
{
return placementId; // cannot resolve placement
}
catch (PermissionException e)
{
// If we are not logged in, try again after we log in, otherwise
// punt
if (session.getUserId() == null)
{
doLogin(req, res, session, URLUtils.getSafePathInfo(req), false);
return null;
}
return placementId; // cannot resolve placement
}
if (site == null) return placementId;
ToolConfiguration toolConfig = site.getToolForCommonId(placementId);
if (toolConfig == null) return placementId;
if (doPage)
{
return toolConfig.getPageId();
}
else
{
return toolConfig.getId();
}
}
// NOTE: This code is duplicated in ToolPortal.java - make sure to change
// both places
public Properties toolHeaderProperties(String skin)
{
return toolHeaderProperties(skin, null);
}
public Properties toolHeaderProperties(String skin, Placement placement)
{
Properties retval = new Properties();
// setup html information that the tool might need (skin, body on load,
// js includes, etc).
String templates = ServerConfigurationService.getString("portal.templates", "neoskin");
String skinRepo = ServerConfigurationService.getString("skin.repo");
// Adjust skin name if we are in the neo Portal
String headCssToolBase = "<link href=\""
+ PortalUtils.getCDNPath()
+ CSSUtils.getCssToolBase()
+ PortalUtils.getCDNQuery()
+ "\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n";
String headCssToolSkin = "<link href=\""
+ PortalUtils.getCDNPath()
+ CSSUtils.getCssToolSkin(skin)
+ PortalUtils.getCDNQuery()
+ "\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />\n";
String headCss = headCssToolBase + headCssToolSkin;
Editor editor = portalService.getActiveEditor(placement);
String preloadScript = editor.getPreloadScript() == null ? ""
: "<script type=\"text/javascript\">"
+ editor.getPreloadScript()
+ "</script>\n";
String editorScript = editor.getEditorUrl() == null ? ""
: "<script type=\"text/javascript\" src=\""
+ PortalUtils.getCDNPath()
+ editor.getEditorUrl()
+ PortalUtils.getCDNQuery()
+ "\"></script>\n";
String launchScript = editor.getLaunchUrl() == null ? ""
: "<script type=\"text/javascript\" src=\""
+ PortalUtils.getCDNPath()
+ editor.getLaunchUrl()
+ PortalUtils.getCDNQuery()
+ "\"></script>\n";
StringBuilder headJs = new StringBuilder();
headJs.append("<script type=\"text/javascript\" src=\"");
headJs.append(PortalUtils.getCDNPath());
headJs.append("/library/js/headscripts.js");
headJs.append(PortalUtils.getCDNQuery());
headJs.append("\"></script>\n");
headJs.append("<script type=\"text/javascript\">var sakai = sakai || {}; sakai.editor = sakai.editor || {}; sakai.locale = sakai.locale || {};\n");
headJs.append("sakai.locale.userCountry = '" + rloader.getLocale().getCountry() + "';\n");
headJs.append("sakai.locale.userLanguage = '" + rloader.getLocale().getLanguage() + "';\n");
headJs.append("sakai.locale.userLocale = '" + rloader.getLocale().toString() + "';\n");
headJs.append("sakai.editor.collectionId = '" + portalService.getBrowserCollectionId(placement) + "';\n");
headJs.append("sakai.editor.enableResourceSearch = " + EditorConfiguration.enableResourceSearch() + ";</script>\n");
headJs.append(preloadScript);
headJs.append(editorScript);
headJs.append(launchScript);
Session s = SessionManager.getCurrentSession();
String userWarning = (String) s.getAttribute("userWarning");
if (StringUtils.isNotEmpty(userWarning)) {
headJs.append("<script type=\"text/javascript\">window.parent.jQuery.pnotify({pnotify_title: '");
headJs.append(rloader.getString("pnotify_notice"));
headJs.append("', pnotify_text: '");
headJs.append(userWarning);
headJs.append("', type: 'error' });</script>");
s.removeAttribute("userWarning");
}
// TODO: Should we include jquery here? See includeStandardHead.vm
String head = headCss + headJs.toString();
retval.setProperty("sakai.html.head", head);
retval.setProperty("sakai.html.head.css", headCss);
retval.setProperty("sakai.html.head.css.base", SessionManager.getCurrentSession().getAttribute(PortalService.SAKAI_CONTROLLING_PORTAL) == "pda" ? "" : headCssToolBase);
retval.setProperty("sakai.html.head.css.skin", SessionManager.getCurrentSession().getAttribute(PortalService.SAKAI_CONTROLLING_PORTAL) == "pda" ? "" : headCssToolSkin);
retval.setProperty("sakai.html.head.js", headJs.toString());
return retval;
}
public void setupForward(HttpServletRequest req, HttpServletResponse res,
Placement p, String skin) throws ToolException
{
// Get the tool header properties
Properties props = toolHeaderProperties(skin, p);
for(Object okey : props.keySet() )
{
String key = (String) okey;
req.setAttribute(key,props.getProperty(key));
}
StringBuilder bodyonload = new StringBuilder();
if (p != null)
{
String element = Web.escapeJavascript("Main" + p.getId());
bodyonload.append("setMainFrameHeight('" + element + "');");
}
bodyonload.append("setFocus(focus_path);");
req.setAttribute("sakai.html.body.onload", bodyonload.toString());
portalService.getRenderEngine(portalContext, req).setupForward(req, res, p, skin);
}
/**
* Forward to the tool - but first setup JavaScript/CSS etc that the tool
* will render
*/
public void forwardTool(ActiveTool tool, HttpServletRequest req,
HttpServletResponse res, Placement p, String skin, String toolContextPath,
String toolPathInfo) throws ToolException
{
// if there is a stored request state, and path, extract that from the
// session and reinstance it
// let the tool do the the work (forward)
if (enableDirect)
{
StoredState ss = portalService.getStoredState();
if (ss == null || !toolContextPath.equals(ss.getToolContextPath()))
{
setupForward(req, res, p, skin);
req.setAttribute(ToolURL.MANAGER, new ToolURLManagerImpl(res));
tool.forward(req, res, p, toolContextPath, toolPathInfo);
}
else
{
M_log.debug("Restoring StoredState [" + ss + "]");
HttpServletRequest sreq = ss.getRequest(req);
Placement splacement = ss.getPlacement();
String stoolContext = ss.getToolContextPath();
String stoolPathInfo = ss.getToolPathInfo();
ActiveTool stool = ActiveToolManager.getActiveTool(p.getToolId());
String sskin = ss.getSkin();
setupForward(sreq, res, splacement, sskin);
req.setAttribute(ToolURL.MANAGER, new ToolURLManagerImpl(res));
stool.forward(sreq, res, splacement, stoolContext, stoolPathInfo);
// this is correct as we have checked the context path of the
// tool
portalService.setStoredState(null);
}
}
else
{
setupForward(req, res, p, skin);
req.setAttribute(ToolURL.MANAGER, new ToolURLManagerImpl(res));
tool.forward(req, res, p, toolContextPath, toolPathInfo);
}
}
public void forwardPortal(ActiveTool tool, HttpServletRequest req,
HttpServletResponse res, ToolConfiguration p, String skin,
String toolContextPath, String toolPathInfo) throws ToolException,
IOException
{
String portalPath = ServerConfigurationService.getString("portalPath", "/portal");
// if there is a stored request state, and path, extract that from the
// session and reinstance it
// generate the forward to the tool page placement
String portalPlacementUrl = portalPath + getPortalPageUrl(p);
res.sendRedirect(portalPlacementUrl);
return;
}
public String getPortalPageUrl(ToolConfiguration p)
{
SitePage sitePage = p.getContainingPage();
String page = getSiteHelper().lookupPageToAlias(p.getSiteId(), sitePage);
if (page == null)
{
// Fall back to default of using the page Id.
page = p.getPageId();
}
return "/site/" + p.getSiteId() + "/page/" + page;
}
/**
* Access the Servlet's information display.
*
* @return servlet information.
*/
public String getServletInfo()
{
return "Sakai Charon Portal";
}
public void includeBottom(PortalRenderContext rcontext)
{
if (rcontext.uses(INCLUDE_BOTTOM))
{
String thisUser = SessionManager.getCurrentSessionUserId();
//Get user preferences
PreferencesService preferencesService = (PreferencesService) ComponentManager.get(PreferencesService.class);
Preferences prefs = preferencesService.getPreferences(thisUser);
boolean showServerTime = ServerConfigurationService.getBoolean("portal.show.time", true);
if (showServerTime) {
rcontext.put("showServerTime","true");
Calendar now = Calendar.getInstance();
Date nowDate = new Date(now.getTimeInMillis());
//first set server date and time
TimeZone serverTz = TimeZone.getDefault();
now.setTimeZone(serverTz);
rcontext.put("serverTzDisplay",
serverTz.getDisplayName(
serverTz.inDaylightTime(nowDate),
TimeZone.SHORT
)
);
rcontext.put("serverTzGMTOffset",
String.valueOf(
now.getTimeInMillis() + now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET)
)
);
//provide the user's preferred timezone information if it is different
//Get the Properties object that holds user's TimeZone preferences
ResourceProperties tzprops = prefs.getProperties(TimeService.APPLICATION_ID);
//Get the ID of the timezone using the timezone key.
//Default to 'localTimeZone' (server timezone?)
String preferredTzId = (String) tzprops.get(TimeService.TIMEZONE_KEY);
if (preferredTzId != null && !preferredTzId.equals(serverTz.getID())) {
TimeZone preferredTz = TimeZone.getTimeZone(preferredTzId);
now.setTimeZone(preferredTz);
rcontext.put("showPreferredTzTime", "true");
//now set up the portal information
rcontext.put("preferredTzDisplay",
preferredTz.getDisplayName(
preferredTz.inDaylightTime(nowDate),
TimeZone.SHORT
)
);
rcontext.put("preferredTzGMTOffset",
String.valueOf(
now.getTimeInMillis() + now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET)
)
);
} else {
rcontext.put("showPreferredTzTime", "false");
}
}
rcontext.put("pagepopup", false);
String copyright = ServerConfigurationService
.getString("bottom.copyrighttext");
/**
* Replace keyword in copyright message from sakai.properties
* with the server's current year to auto-update of Copyright end date
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy");
String currentServerYear = simpleDateFormat.format(new Date());
copyright = copyright.replaceAll(SERVER_COPYRIGHT_CURRENT_YEAR_KEYWORD, currentServerYear);
String service = ServerConfigurationService.getString("ui.service", "Sakai");
String serviceVersion = ServerConfigurationService.getString(
"version.service", "?");
String sakaiVersion = ServerConfigurationService.getString("version.sakai",
"?");
String kernelVersion = ServerConfigurationService.getString("version.kernel",
"?");
String server = ServerConfigurationService.getServerId();
String[] bottomNav = ServerConfigurationService.getStrings("bottomnav");
String[] poweredByUrl = ServerConfigurationService.getStrings("powered.url");
String[] poweredByImage = ServerConfigurationService
.getStrings("powered.img");
String[] poweredByAltText = ServerConfigurationService
.getStrings("powered.alt");
{
List<Object> l = new ArrayList<Object>();
if ((bottomNav != null) && (bottomNav.length > 0))
{
for (int i = 0; i < bottomNav.length; i++)
{
l.add(bottomNav[i]);
}
}
rcontext.put("bottomNav", l);
}
boolean neoChatAvailable
= ServerConfigurationService.getBoolean("portal.neochat", true)
&& chatHelper.checkChatPermitted(thisUser);
rcontext.put("neoChat", neoChatAvailable);
rcontext.put("portalChatPollInterval",
ServerConfigurationService.getInt("portal.chat.pollInterval", 5000));
rcontext.put("neoAvatar",
ServerConfigurationService.getBoolean("portal.neoavatar", true));
if(sakaiTutorialEnabled && thisUser != null) {
if (!("1".equals(prefs.getProperties().getProperty("sakaiTutorialFlag")))) {
rcontext.put("tutorial", true);
//now save this in the user's prefefences so we don't show it again
PreferencesEdit preferences = null;
SecurityAdvisor secAdv = null;
try {
secAdv = new SecurityAdvisor(){
@Override
public SecurityAdvice isAllowed(String userId, String function,
String reference) {
if("prefs.add".equals(function) || "prefs.upd".equals(function)){
return SecurityAdvice.ALLOWED;
}
return null;
}
};
securityService.pushAdvisor(secAdv);
try {
preferences = preferencesService.edit(thisUser);
} catch (IdUnusedException ex1 ) {
try {
preferences = preferencesService.add( thisUser );
} catch (IdUsedException ex2) {
M_log.error(ex2);
} catch( PermissionException ex3) {
M_log.error(ex3);
}
}
if (preferences != null) {
ResourcePropertiesEdit props = preferences.getPropertiesEdit();
props.addProperty("sakaiTutorialFlag", "1");
preferencesService.commit(preferences);
}
} catch (Exception e1) {
M_log.error(e1);
}finally{
if(secAdv != null){
securityService.popAdvisor(secAdv);
}
}
}
}
// rcontext.put("bottomNavSitNewWindow",
// Web.escapeHtml(rb.getString("site_newwindow")));
if ((poweredByUrl != null) && (poweredByImage != null)
&& (poweredByAltText != null)
&& (poweredByUrl.length == poweredByImage.length)
&& (poweredByUrl.length == poweredByAltText.length))
{
{
List<Object> l = new ArrayList<Object>();
for (int i = 0; i < poweredByUrl.length; i++)
{
Map<String, Object> m = new HashMap<String, Object>();
m.put("poweredByUrl", poweredByUrl[i]);
m.put("poweredByImage", poweredByImage[i]);
m.put("poweredByAltText", poweredByAltText[i]);
l.add(m);
}
rcontext.put("bottomNavPoweredBy", l);
}
}
else
{
List<Object> l = new ArrayList<Object>();
Map<String, Object> m = new HashMap<String, Object>();
m.put("poweredByUrl", "http://sakaiproject.org");
m.put("poweredByImage", "/library/image/sakai_powered.gif");
m.put("poweredByAltText", "Powered by Sakai");
l.add(m);
rcontext.put("bottomNavPoweredBy", l);
}
rcontext.put("bottomNavService", service);
rcontext.put("bottomNavCopyright", copyright);
rcontext.put("bottomNavServiceVersion", serviceVersion);
rcontext.put("bottomNavSakaiVersion", sakaiVersion);
rcontext.put("bottomNavKernelVersion", kernelVersion);
rcontext.put("bottomNavServer", server);
}
}
public void includeLogin(PortalRenderContext rcontext, HttpServletRequest req,
Session session)
{
if (rcontext.uses(INCLUDE_LOGIN))
{
// for the main login/out link
String logInOutUrl = Web.serverUrl(req);
String message = null;
String image1 = null;
// for a possible second link
String logInOutUrl2 = null;
String logInOutUrl2Pda = null;
String message2 = null;
String image2 = null;
// for showing user display name and id next to logout (SAK-10492)
String loginUserDispName = null;
String loginUserDispId = null;
boolean displayUserloginInfo = ServerConfigurationService.
getBoolean("display.userlogin.info", true);
// check for the top.login (where the login fields are present
// instead
// of a login link, but ignore it if container.login is set
boolean topLogin = ServerConfigurationService.getBoolean("top.login", true);
boolean containerLogin = ServerConfigurationService.getBoolean("container.login", false);
if (containerLogin) topLogin = false;
// if not logged in they get login
if (session.getUserId() == null)
{
// we don't need any of this if we are doing top login
if (!topLogin)
{
logInOutUrl += ServerConfigurationService.getString("portalPath")
+ "/login";
// let the login url be overridden by configuration
String overrideLoginUrl = StringUtils
.trimToNull(ServerConfigurationService.getString("login.url"));
if (overrideLoginUrl != null) logInOutUrl = overrideLoginUrl;
// check for a login text override
message = StringUtils.trimToNull(ServerConfigurationService
.getString("login.text"));
if (message == null) message = rloader.getString("log.login");
// check for an image for the login
image1 = StringUtils.trimToNull(ServerConfigurationService
.getString("login.icon"));
// check for a possible second, xlogin link
if (Boolean.TRUE.toString().equalsIgnoreCase(
ServerConfigurationService.getString("xlogin.enabled")))
{
// get the text and image as configured
message2 = StringUtils.trimToNull(ServerConfigurationService
.getString("xlogin.text"));
if (message2 == null) message2 = rloader.getString("log.xlogin");
image2 = StringUtils.trimToNull(ServerConfigurationService
.getString("xlogin.icon"));
logInOutUrl2 = ServerConfigurationService.getString("portalPath")
+ "/xlogin";
logInOutUrl2Pda = ServerConfigurationService.getString("portalPath")
+ "/pda/xlogin";
}
}
}
// if logged in they get logout
else
{
logInOutUrl += ServerConfigurationService.getString("portalPath")
+ "/logout";
// get current user display id and name
if (displayUserloginInfo)
{
User thisUser = UserDirectoryService.getCurrentUser();
loginUserDispId = Validator.escapeHtml(thisUser.getDisplayId());
loginUserDispName = Validator.escapeHtml(thisUser.getDisplayName());
}
// check for a logout text override
message = StringUtils.trimToNull(ServerConfigurationService
.getString("logout.text"));
if (message == null) message = rloader.getString("sit_log");
// check for an image for the logout
image1 = StringUtils.trimToNull(ServerConfigurationService
.getString("logout.icon"));
// since we are doing logout, cancel top.login
topLogin = false;
}
rcontext.put("loginTopLogin", Boolean.valueOf(topLogin));
// display portal links - SAK-22983
String portalLinks = portalService.getPortalLinks();
if (portalLinks != null) {
rcontext.put("portalLinks",portalLinks);
}
if (!topLogin)
{
rcontext.put("loginLogInOutUrl", logInOutUrl);
rcontext.put("loginMessage", message);
rcontext.put("loginImage1", image1);
rcontext.put("loginHasImage1", Boolean.valueOf(image1 != null));
rcontext.put("loginLogInOutUrl2", logInOutUrl2);
rcontext.put("loginLogInOutUrl2Pda", logInOutUrl2Pda);
rcontext.put("loginHasLogInOutUrl2", Boolean
.valueOf(logInOutUrl2 != null));
rcontext.put("loginMessage2", message2);
rcontext.put("loginImage2", image2);
rcontext.put("loginHasImage2", Boolean.valueOf(image2 != null));
// put out the links version
// else put out the fields that will send to the login interface
}
else
{
String eidWording = null;
String pwWording = null;
eidWording = StringUtils.trimToNull(rloader.getString("log.userid"));
pwWording = StringUtils.trimToNull(rloader.getString("log.pass"));
if (eidWording == null) eidWording = "eid";
if (pwWording == null) pwWording = "pw";
String loginWording = rloader.getString("log.login");
rcontext.put("loginPortalPath", ServerConfigurationService
.getString("portalPath"));
rcontext.put("loginEidWording", eidWording);
rcontext.put("loginPwWording", pwWording);
rcontext.put("loginWording", loginWording);
// setup for the redirect after login
session.setAttribute(Tool.HELPER_DONE_URL, ServerConfigurationService
.getPortalUrl());
}
if (displayUserloginInfo)
{
rcontext.put("loginUserDispName", loginUserDispName);
rcontext.put("loginUserDispId", loginUserDispId);
}
rcontext.put("displayUserloginInfo", displayUserloginInfo && loginUserDispId != null);
}
}
/**
* @param rcontext
* @param res
* @param req
* @param session
* @param site
* @param page
* @param toolContextPath
* @param portalPrefix
* @return
* @throws IOException
*/
public void includeWorksite(PortalRenderContext rcontext, HttpServletResponse res,
HttpServletRequest req, Session session, Site site, SitePage page,
String toolContextPath, String portalPrefix) throws IOException
{
worksiteHandler.includeWorksite(rcontext, res, req, session, site, page,
toolContextPath, portalPrefix);
}
/**
* Initialize the servlet.
*
* @param config
* The servlet config.
* @throws ServletException
*/
public void init(ServletConfig config) throws ServletException
{
super.init(config);
portalContext = config.getInitParameter("portal.context");
if (portalContext == null || portalContext.length() == 0)
{
portalContext = DEFAULT_PORTAL_CONTEXT;
}
boolean findPageAliases = ServerConfigurationService.getBoolean("portal.use.page.aliases", false);
siteHelper = new PortalSiteHelperImpl(this, findPageAliases);
portalService = org.sakaiproject.portal.api.cover.PortalService.getInstance();
securityService = (SecurityService) ComponentManager.get("org.sakaiproject.authz.api.SecurityService");
chatHelper = org.sakaiproject.portal.api.cover.PortalChatPermittedHelper.getInstance();
M_log.info("init()");
forceContainer = ServerConfigurationService.getBoolean("login.use.xlogin.to.relogin", true);
handlerPrefix = ServerConfigurationService.getString("portal.handler.default", "site");
gatewaySiteUrl = ServerConfigurationService.getString("gatewaySiteUrl", null);
gatewayPdaSiteUrl = ServerConfigurationService.getString("gatewayPdaSiteUrl", null);
sakaiTutorialEnabled = ServerConfigurationService.getBoolean("portal.use.tutorial", true);
basicAuth = new BasicAuth();
basicAuth.init();
enableDirect = portalService.isEnableDirect();
// do this before adding handlers to prevent handlers registering 2
// times.
// if the handlers were already there they will be re-registered,
// but when they are added again, they will be replaced.
// warning messages will appear, but the end state will be the same.
portalService.addPortal(this);
worksiteHandler = new WorksiteHandler();
siteHandler = new SiteHandler();
addHandler(siteHandler);
addHandler(new SiteResetHandler());
addHandler(new ToolHandler());
addHandler(new ToolResetHandler());
addHandler(new PageHandler());
addHandler(worksiteHandler);
addHandler(new WorksiteResetHandler());
addHandler(new RssHandler());
addHandler(new PDAHandler());
addHandler(new AtomHandler());
addHandler(new OpmlHandler());
addHandler(new NavLoginHandler());
addHandler(new PresenceHandler());
addHandler(new HelpHandler());
addHandler(new ReLoginHandler());
addHandler(new LoginHandler());
addHandler(new XLoginHandler());
addHandler(new LogoutHandler());
addHandler(new ErrorDoneHandler());
addHandler(new ErrorReportHandler());
addHandler(new StaticStylesHandler());
addHandler(new StaticScriptsHandler());
addHandler(new DirectToolHandler());
addHandler(new RoleSwitchHandler());
addHandler(new RoleSwitchOutHandler());
addHandler(new TimeoutDialogHandler());
addHandler(new JoinHandler());
}
/**
* Register a handler for a URL stub
*
* @param handler
*/
private void addHandler(PortalHandler handler)
{
portalService.addHandler(this, handler);
}
private void removeHandler(String urlFragment)
{
portalService.removeHandler(this, urlFragment);
}
/**
* Send the POST request to login
*
* @param req
* @param res
* @param session
* @throws IOException
*/
protected void postLogin(HttpServletRequest req, HttpServletResponse res,
Session session, String loginPath) throws ToolException
{
ActiveTool tool = ActiveToolManager.getActiveTool("sakai.login");
String context = req.getContextPath() + req.getServletPath() + "/" + loginPath;
tool.help(req, res, context, "/" + loginPath);
}
/**
* Output some session information
*
* @param rcontext
* The print writer
* @param html
* If true, output in HTML, else in text.
*/
protected void showSession(PortalRenderContext rcontext, boolean html)
{
// get the current user session information
Session s = SessionManager.getCurrentSession();
rcontext.put("sessionSession", s);
ToolSession ts = SessionManager.getCurrentToolSession();
rcontext.put("sessionToolSession", ts);
}
public void sendResponse(PortalRenderContext rcontext, HttpServletResponse res,
String template, String contentType) throws IOException
{
// headers
if (contentType == null)
{
res.setContentType("text/html; charset=UTF-8");
}
else
{
res.setContentType(contentType);
}
res.addDateHeader("Expires", System.currentTimeMillis()
- (1000L * 60L * 60L * 24L * 365L));
res.addDateHeader("Last-Modified", System.currentTimeMillis());
res
.addHeader("Cache-Control",
"no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
res.addHeader("Pragma", "no-cache");
// get the writer
PrintWriter out = res.getWriter();
try
{
PortalRenderEngine rengine = rcontext.getRenderEngine();
rengine.render(template, rcontext, out);
}
catch (Exception e)
{
throw new RuntimeException("Failed to render template ", e);
}
}
/**
* Returns the type ("course", "project", "workspace", "mySpecialSiteType",
* etc) of the given site; special handling of returning "workspace" for
* user workspace sites. This method is tightly coupled to site skinning.
*/
public String calcSiteType(String siteId)
{
String siteType = null;
if (siteId != null && siteId.length() != 0)
{
if (SiteService.isUserSite(siteId))
{
siteType = "workspace";
}
else
{
try
{
siteType = SiteService.getSite(siteId).getType();
}
catch (IdUnusedException ex)
{
// ignore, the site wasn't found
}
}
}
if (siteType != null && siteType.trim().length() == 0) siteType = null;
return siteType;
}
private void logXEntry()
{
Exception e = new Exception();
StackTraceElement se = e.getStackTrace()[1];
M_log.info("Log marker " + se.getMethodName() + ":" + se.getFileName() + ":"
+ se.getLineNumber());
}
/**
* Check for any just expired sessions and redirect
*
* @return true if we redirected, false if not
*/
public boolean redirectIfLoggedOut(HttpServletResponse res) throws IOException
{
// if we are in a newly created session where we had an invalid
// (presumed timed out) session in the request,
// send script to cause a sakai top level redirect
if (ThreadLocalManager.get(SessionManager.CURRENT_INVALID_SESSION) != null)
{
String loggedOutUrl = ServerConfigurationService.getLoggedOutUrl();
sendPortalRedirect(res, loggedOutUrl);
return true;
}
return false;
}
/**
* Send a redirect so our Portal window ends up at the url, via javascript.
*
* @param url
* The redirect url
*/
protected void sendPortalRedirect(HttpServletResponse res, String url)
throws IOException
{
PortalRenderContext rcontext = startPageContext("", null, null, null);
rcontext.put("redirectUrl", url);
sendResponse(rcontext, res, "portal-redirect", null);
}
/**
* Compute the string that will identify the user site for this user - use
* the EID if possible
*
* @param userId
* The user id
* @return The site "ID" but based on the user EID
*/
public String getUserEidBasedSiteId(String userId)
{
try
{
// use the user EID
String eid = UserDirectoryService.getUserEid(userId);
return SiteService.getUserSiteId(eid);
}
catch (UserNotDefinedException e)
{
M_log.warn("getUserEidBasedSiteId: user id not found for eid: " + userId);
return SiteService.getUserSiteId(userId);
}
}
/* (non-Javadoc)
* @see org.sakaiproject.portal.api.Portal#getPageFilter()
*/
public PageFilter getPageFilter()
{
return pageFilter;
}
/* (non-Javadoc)
* @see org.sakaiproject.portal.api.Portal#setPageFilter(org.sakaiproject.portal.api.PageFilter)
*/
public void setPageFilter(PageFilter pageFilter)
{
this.pageFilter = pageFilter;
}
/* (non-Javadoc)
* @see org.sakaiproject.portal.api.Portal#getSiteHelper()
*/
public PortalSiteHelper getSiteHelper()
{
return this.siteHelper;
}
/* (non-Javadoc)
* @see org.sakaiproject.portal.api.Portal#getSiteNeighbourhoodService()
*/
public SiteNeighbourhoodService getSiteNeighbourhoodService()
{
return portalService.getSiteNeighbourhoodService();
}
/**
* Find a cookie by this name from the request
*
* @param req
* The servlet request.
* @param name
* The cookie name
* @return The cookie of this name in the request, or null if not found.
*/
public Cookie findCookie(HttpServletRequest req, String name)
{
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(name)) {
return cookies[i];
}
}
}
return null;
}
/**
* Do the getSiteSkin, adjusting for the overall skin/templates for the portal.
*
* @return The skin
*/
protected String getSiteSkin(String siteId)
{
String skin = SiteService.getSiteSkin(siteId);
return getSkin(skin);
}
/**
* Do the getSkin, adjusting for the overall skin/templates for the portal.
*
* @return The skin
*/
protected String getSkin(String skin)
{
return CSSUtils.adjustCssSkinFolder(skin);
}
}