/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.core.portal.rendering.portalfunctions;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import com.enonic.cms.framework.util.URLUtils;
import com.enonic.cms.framework.util.UrlPathEncoder;
import com.enonic.cms.core.Path;
import com.enonic.cms.core.SiteURLResolver;
import com.enonic.cms.core.captcha.CaptchaService;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentKey;
import com.enonic.cms.core.content.binary.AttachmentNativeLinkKeyWithBinaryKey;
import com.enonic.cms.core.content.binary.BinaryDataKey;
import com.enonic.cms.core.content.binary.ContentBinaryDataEntity;
import com.enonic.cms.core.image.ImageRequest;
import com.enonic.cms.core.image.ImageRequestParser;
import com.enonic.cms.core.localization.LocalizationResourceBundleUtils;
import com.enonic.cms.core.localization.LocalizationService;
import com.enonic.cms.core.portal.ReservedLocalPaths;
import com.enonic.cms.core.portal.image.ImageService;
import com.enonic.cms.core.portal.instruction.CreateAttachmentUrlInstruction;
import com.enonic.cms.core.portal.instruction.CreateContentUrlInstruction;
import com.enonic.cms.core.portal.instruction.CreateImageUrlInstruction;
import com.enonic.cms.core.portal.instruction.CreateResourceUrlInstruction;
import com.enonic.cms.core.portal.instruction.PostProcessInstruction;
import com.enonic.cms.core.portal.instruction.PostProcessInstructionSerializer;
import com.enonic.cms.core.portal.instruction.RenderWindowInstruction;
import com.enonic.cms.core.portal.livetrace.LivePortalTraceService;
import com.enonic.cms.core.portal.livetrace.ViewFunctionTrace;
import com.enonic.cms.core.portal.livetrace.ViewFunctionTracer;
import com.enonic.cms.core.portal.rendering.tracing.RenderTrace;
import com.enonic.cms.core.portal.ticket.TicketConstants;
import com.enonic.cms.core.resolver.ResolverContext;
import com.enonic.cms.core.resolver.locale.LocaleResolverService;
import com.enonic.cms.core.resource.ResourceKey;
import com.enonic.cms.core.security.SecurityService;
import com.enonic.cms.core.structure.SiteEntity;
import com.enonic.cms.core.structure.SiteKey;
import com.enonic.cms.core.structure.SitePath;
import com.enonic.cms.core.structure.SitePropertiesService;
import com.enonic.cms.core.structure.SitePropertyNames;
import com.enonic.cms.core.structure.menuitem.MenuItemEntity;
import com.enonic.cms.core.structure.menuitem.MenuItemKey;
import com.enonic.cms.core.structure.page.WindowKey;
import com.enonic.cms.store.dao.ContentBinaryDataDao;
import com.enonic.cms.store.dao.ContentDao;
import com.enonic.cms.store.dao.MenuItemDao;
import com.enonic.cms.store.dao.PortletDao;
public class PortalFunctions
{
private static final Pattern IMAGE_KEY_FORMAT = Pattern.compile( "(\\d+(/[-\\w]+){0,2}?)|(^user/.+$)" );
private static final String CAPTCHA_RELATIVE_URL = "_captcha";
private static final String RESOURCE_PATH_PUBLIC_ROOT = "/" + ReservedLocalPaths.PATH_RESOURCE;
private boolean encodeURIs;
private HttpServletRequest request;
private SiteURLResolver siteURLResolver;
private PortalFunctionsContext context;
private CaptchaService captchaService;
private LocalizationService localizeService;
private LocaleResolverService localeResolverService;
private ContentDao contentDao;
private MenuItemDao menuItemDao;
private PortletDao portletDao;
private ContentBinaryDataDao contentBinaryDataDao;
private ImageService imageService;
private SecurityService securityService;
private CreateAttachmentUrlFunction createAttachmentUrlFunction;
private IsWindowEmptyFunction isWindowEmptyFunction;
private SitePropertiesService sitePropertiesService;
private LivePortalTraceService livePortalTraceService;
public String getInstanceKey()
{
return context.getPortalInstanceKey().toString();
}
public String getWindowKey()
{
if ( !context.getPortalInstanceKey().isWindow() )
{
throw new PortalFunctionException( "Not in a context of a window" );
}
return context.getPortalInstanceKey().getWindowKey().asString();
}
public Boolean isWindowInline()
{
if ( !context.getPortalInstanceKey().isWindow() )
{
throw new PortalFunctionException( "Not in a context of a window" );
}
return context.isRenderedInline();
}
public Boolean isWindowEmpty( final String portletWindowKey, String[] params )
{
ViewFunctionTrace trace = ViewFunctionTracer.startTracing( "isWindowEmpty", livePortalTraceService );
try
{
ViewFunctionTracer.traceFunctionArgument( "portletWindowKey", portletWindowKey, trace );
ViewFunctionTracer.traceFunctionArgument( "params", params, trace );
return isWindowEmptyFunction.isWindowEmpty( new WindowKey( portletWindowKey ), params );
}
finally
{
ViewFunctionTracer.stopTracing( trace, livePortalTraceService );
}
}
public String createUrl( String local, String[] params )
{
SitePath sitePath = new SitePath( context.getSite().getKey(), new Path( local != null ? local : "" ) );
addParamsToSitePath( params, sitePath );
return siteURLResolver.createUrl( request, sitePath, true );
}
public String createWindowUrl()
{
if ( !context.getPortalInstanceKey().isWindow() )
{
throw new PortalFunctionException( "Not in a context of a window" );
}
final CreateWindowUrlFunction function = new CreateWindowUrlFunction( menuItemDao, portletDao, context );
function.useCurrentLocation();
final SitePath windowUrl = function.createWindowUrl();
return siteURLResolver.createUrl( request, windowUrl, true );
}
public String createWindowUrl( String[] params )
{
if ( !context.getPortalInstanceKey().isWindow() )
{
throw new PortalFunctionException( "Not in a context of a window" );
}
final CreateWindowUrlFunction function = new CreateWindowUrlFunction( menuItemDao, portletDao, context );
function.useCurrentLocation();
final SitePath windowUrl = function.createWindowUrl();
addParamsToSitePath( params, windowUrl );
return siteURLResolver.createUrl( request, windowUrl, true );
}
public String createWindowUrl( WindowKey windowKey, String[] params )
{
final CreateWindowUrlFunction function = new CreateWindowUrlFunction( menuItemDao, portletDao, context );
function.useWindowKey( windowKey );
final SitePath windowUrl = function.createWindowUrl();
addParamsToSitePath( params, windowUrl );
return siteURLResolver.createUrl( request, windowUrl, true );
}
public String createWindowUrl( WindowKey windowKey, String[] params, String outputFormat )
{
final CreateWindowUrlFunction function = new CreateWindowUrlFunction( menuItemDao, portletDao, context );
function.useWindowKey( windowKey );
function.outputFormat( outputFormat );
final SitePath windowUrl = function.createWindowUrl();
addParamsToSitePath( params, windowUrl );
return siteURLResolver.createUrl( request, windowUrl, true );
}
public String createPageUrl( String[] params )
{
MenuItemKey menuItemKey = context.getMenuItem().getKey();
if ( encodeURIs )
{
return encodeURI( "page", menuItemKey != null ? menuItemKey.toString() : "NA", params );
}
if ( menuItemKey == null )
{
final String failureReason = "createPageUrl must be called in context of a menuitem or window";
throw new PortalFunctionException( failureReason );
}
if ( params != null && params.length % 2 == 1 )
{
String paramsStr = "";
for ( String param : params )
{
paramsStr += paramsStr.length() > 0 ? ", " + param : param;
}
final String failureReason = "Illegal parameter. Illegal params size: " + params.length + ": " + paramsStr;
throw new PortalFunctionException( failureReason );
}
SitePath originalSitePath = context.getOriginalSitePath().removeWindowReference();
Path requestedLocalPath = originalSitePath.getLocalPath();
if ( requestedLocalPath != null && requestedLocalPath.startsWith( "/page" ) && originalSitePath.getParam( "id" ) != null )
{
return createPageUrl( menuItemKey, params );
}
SitePath pageSitePath = originalSitePath.createNewInSameSite( requestedLocalPath, new HashMap<String, String[]>() );
addParamsToSitePath( params, pageSitePath );
return siteURLResolver.createUrl( request, pageSitePath, true );
}
public String createPageUrl( MenuItemKey menuItemKey, String[] params )
{
if ( encodeURIs )
{
return encodeURI( "page", String.valueOf( menuItemKey ), params );
}
MenuItemEntity menuItem = menuItemDao.findByKey( menuItemKey );
if ( menuItem == null )
{
final String failureReason = "menuitem does not exist: " + menuItemKey;
throw new PortalFunctionException( failureReason );
}
String path = menuItem.getPathAsString();
SiteEntity currentSite = context.getSite();
SiteEntity menuItemSite = menuItem.getSite();
final boolean menuItemIsOnOtherSite = !currentSite.equals( menuItemSite );
final boolean debugMode = RenderTrace.isTraceOn();
final boolean createUrlOnOtherSiteUsingSiteUrl = !debugMode && menuItemIsOnOtherSite;
if ( createUrlOnOtherSiteUsingSiteUrl )
{
return createUrlOnSiteUsingSiteURL( params, path, menuItemSite );
}
SitePath pageSitePath = new SitePath( menuItemSite.getKey(), new Path( path ) );
addParamsToSitePath( params, pageSitePath );
return siteURLResolver.createUrl( request, pageSitePath, true );
}
private String createUrlOnSiteUsingSiteURL( String[] params, String path, SiteEntity siteEntity )
{
final String siteUrl = sitePropertiesService.getSiteProperty( siteEntity.getKey(), SitePropertyNames.SITE_URL );
if ( !URLUtils.verifyValidURL( siteUrl ) )
{
throw new PortalFunctionException(
"Not able to create link to site " + siteEntity.getKey().toInt() + ": No valid cms.site.url defined in site-" +
siteEntity.getKey().toInt() + ".properties: " + siteUrl );
}
SitePath pageSitePath = new SitePath( siteEntity.getKey(), new Path( path ) );
addParamsToSitePath( params, pageSitePath );
return siteURLResolver.createUrlWithBasePathOverride( request, pageSitePath, true, siteUrl );
}
public String createContentUrl( ContentKey contentKey, String[] params )
{
if ( encodeURIs )
{
return encodeURI( "content", contentKey.toString(), params );
}
if ( contentKey == null )
{
throw new PortalFunctionException( "ContentKey is null" );
}
ContentEntity content = contentDao.findByKey( contentKey );
if ( content == null )
{
throw new PortalFunctionException( "Content with key : " + contentKey + " not found" );
}
CreateContentUrlInstruction instruction = new CreateContentUrlInstruction();
instruction.setContentKey( contentKey.toString() );
instruction.setParams( params );
return serializePostProcessInstruction( instruction );
}
public String createPermalink( ContentKey contentKey, String[] params )
{
if ( encodeURIs )
{
return encodeURI( "content", contentKey.toString(), params );
}
if ( contentKey == null )
{
throw new PortalFunctionException( "ContentKey is null" );
}
ContentEntity content = contentDao.findByKey( contentKey );
if ( content == null )
{
throw new PortalFunctionException( "Content with key : " + contentKey + " not found" );
}
CreateContentUrlInstruction instruction = new CreateContentUrlInstruction();
instruction.setContentKey( contentKey.toString() );
instruction.setParams( params );
instruction.setCreateAsPermalink( true );
return serializePostProcessInstruction( instruction );
}
public String createServicesUrl( String handler, String operation, String redirect, String[] params )
{
if ( context.getPortalInstanceKey().getMenuItemKey() == null )
{
final String failureReason = "createServicesUrl must be called in context of a menuitem or window";
throw new PortalFunctionException( failureReason );
}
SitePath sitePath = new SitePath( context.getSite().getKey(), ReservedLocalPaths.PATH_USERSERVICES );
StringBuilder handlerAndOperationPath = new StringBuilder();
handlerAndOperationPath.append( handler );
handlerAndOperationPath.append( "/" ).append( operation );
sitePath = sitePath.appendPath( new Path( handlerAndOperationPath.toString() ) );
sitePath.addParam( "_instanceKey", UrlPathEncoder.encode( context.getPortalInstanceKey().toString() ) );
if ( StringUtils.isNotEmpty( redirect ) )
{
sitePath.addParam( "_redirect", UrlPathEncoder.encode( redirect ) );
//addRedirectParameter( redirect, sitePath );
}
sitePath.addParam( TicketConstants.PARAMETER_NAME, TicketConstants.PLACEHOLDER );
addParamsToSitePath( params, sitePath );
return siteURLResolver.createUrl( request, sitePath, true );
}
public String createBinaryUrl( final BinaryDataKey binaryDataKey, final String[] params )
{
if ( encodeURIs )
{
return encodeURI( "binary", binaryDataKey.toString(), params );
}
ContentBinaryDataEntity contentBinaryData = contentBinaryDataDao.findByBinaryKey( binaryDataKey.toInt() );
if ( contentBinaryData == null )
{
final String failureReason = "content binary data for binary does not exist: " + binaryDataKey;
throw new PortalFunctionException( failureReason );
}
final ContentKey contentKey = contentBinaryData.getContentVersion().getContent().getKey();
final AttachmentNativeLinkKeyWithBinaryKey nativeLinkKey = new AttachmentNativeLinkKeyWithBinaryKey( contentKey, binaryDataKey );
SitePath sitePath = new SitePath( context.getSite().getKey(),
new Path( ReservedLocalPaths.PATH_ATTACHMENT + "/" + nativeLinkKey.asUrlRepresentation() ) );
addParamsToSitePath( params, sitePath );
return siteURLResolver.createUrl( request, sitePath, true );
}
public String createAttachmentUrl( String nativeLinkKey, String[] params )
{
if ( encodeURIs )
{
return encodeURI( "attachment", nativeLinkKey, params );
}
MenuItemKey requestedMenuItemKey = getRequestedMenuItemKey();
CreateAttachmentUrlInstruction instruction =
createAttachmentUrlFunction.createAttachmentUrl( nativeLinkKey, params, requestedMenuItemKey );
return serializePostProcessInstruction( instruction );
}
private MenuItemKey getRequestedMenuItemKey()
{
MenuItemKey menuItemKey = null;
MenuItemEntity currentMenuItem = context.getMenuItem();
if ( currentMenuItem != null )
{
menuItemKey = currentMenuItem.getKey();
}
return menuItemKey;
}
public String createImageUrl( final String key, final String filter, final String background, final String format,
final String quality )
{
return doCreateImageUrl( key, filter, background, format, quality );
}
private String doCreateImageUrl( final String key, final String filter, final String background, final String format,
final String quality )
{
verifyImageKey( key );
CreateImageUrlInstruction instruction = new CreateImageUrlInstruction();
instruction.setKey( key );
instruction.setFilter( filter );
instruction.setFormat( format );
instruction.setQuality( quality );
instruction.setBackground( background );
MenuItemKey requestedMenuItemKey = getRequestedMenuItemKey();
if ( requestedMenuItemKey != null )
{
instruction.setRequestedMenuItemKey( requestedMenuItemKey.toString() );
}
return serializePostProcessInstruction( instruction );
}
protected void verifyImageKey( String imageKey )
{
if ( StringUtils.isBlank( imageKey ) )
{
throw new PortalFunctionException( "Imagekey can not be empty" );
}
Matcher m = IMAGE_KEY_FORMAT.matcher( imageKey );
if ( !m.matches() )
{
throw new PortalFunctionException( "Invalid imagekey: " + imageKey );
}
}
public String createResourceUrl( String pathAsString, String[] params )
{
if ( encodeURIs )
{
return encodeURI( "resource", pathAsString, params );
}
if ( StringUtils.isBlank( pathAsString ) )
{
throw new PortalFunctionException( "Path is blank" );
}
String homeDir = resolvePathToPublicHome();
boolean isTildePath = pathAsString.startsWith( "~" );
if ( isTildePath && homeDir == null )
{
final String failureReason = "Cannot use ~ paths when no public home dir set for site: " + pathAsString;
throw new PortalFunctionException( failureReason );
}
if ( isTildePath && !homeDir.startsWith( RESOURCE_PATH_PUBLIC_ROOT ) )
{
final String failureReason = "Public home does not start with " + RESOURCE_PATH_PUBLIC_ROOT + ": " + homeDir;
throw new PortalFunctionException( failureReason );
}
String resolvedPath = pathAsString;
if ( isTildePath )
{
resolvedPath = pathAsString.replaceFirst( "~", homeDir );
}
if ( !resolvedPath.startsWith( RESOURCE_PATH_PUBLIC_ROOT ) )
{
final String failureReason = "Path does not start with " + RESOURCE_PATH_PUBLIC_ROOT + ": " + pathAsString;
throw new PortalFunctionException( failureReason );
}
CreateResourceUrlInstruction instruction = new CreateResourceUrlInstruction();
instruction.setResolvedPath( resolvedPath );
instruction.setParams( params );
return serializePostProcessInstruction( instruction );
}
private String serializePostProcessInstruction( PostProcessInstruction instruction )
{
String serializedInstruction;
try
{
serializedInstruction = PostProcessInstructionSerializer.serialize( instruction );
}
catch ( IOException e )
{
String failureReason = "Creation of post-process-instruction failed";
throw new PortalFunctionException( failureReason, e );
}
return serializedInstruction;
}
public String createCaptchaImageUrl()
{
SitePath captchaSitePath = new SitePath( context.getSite().getKey(), new Path( CAPTCHA_RELATIVE_URL ) );
return siteURLResolver.createUrl( request, captchaSitePath, true );
}
public String createCaptchaFormInputName()
{
return CaptchaService.FORM_VARIABLE_CAPTCHA_RESPONSE;
}
public boolean isCaptchaEnabled( String handler, String operation )
{
SiteKey siteKey = context.getSite().getKey();
return captchaService.hasCaptchaCheck( siteKey, handler, operation );
}
public String localize( String phrase )
{
return localizeService.getLocalizedPhrase( context.getSite(), phrase, context.getLocale() );
}
public String localize( String phrase, String[] params )
{
return localizeService.getLocalizedPhrase( context.getSite(), phrase, params, context.getLocale() );
}
public String localize( String phrase, String[] params, String locale )
{
Locale parsedLocale = LocalizationResourceBundleUtils.parseLocaleString( locale );
return localizeService.getLocalizedPhrase( context.getSite(), phrase, params, parsedLocale );
}
public String getLocale()
{
ResolverContext resolverContext = new ResolverContext( request, context.getSite(), context.getMenuItem(), null );
Locale locale = localeResolverService.getLocale( resolverContext );
if ( locale == null )
{
return null;
}
return locale.toString();
}
public String getPageKey()
{
MenuItemKey menuItemKey = context.getPortalInstanceKey().getMenuItemKey();
if ( menuItemKey == null )
{
final String failureReason = "pageKey is not available on site context";
throw new PortalFunctionException( failureReason );
}
return menuItemKey.toString();
}
public String createWindowPlaceholder( final String portletWindowKey, final String[] params )
{
RenderWindowInstruction instruction = new RenderWindowInstruction();
instruction.setPortletWindowKey( portletWindowKey );
instruction.setParams( params );
return serializePostProcessInstruction( instruction );
}
public Boolean imageExists( String key )
{
ImageRequestParser parser = new ImageRequestParser();
ImageRequest request = parser.parse( "_image/" + key, null, false );
request.setRequester( securityService.getLoggedInPortalUserAsEntity() );
request.setRequestDateTime( new DateTime() );
return imageService.accessibleInPortal( request );
}
private void addParamsToSitePath( String[] params, SitePath sitePath )
{
if ( params != null )
{
for ( int i = 0; i < params.length / 2; i++ )
{
String name = params[i * 2];
String value = params[i * 2 + 1];
sitePath.addParam( UrlPathEncoder.encode( name ), UrlPathEncoder.encode( value == null ? "" : value ) );
}
}
}
/**
* This is used to create a parseable "native" url format. These URLs are interpreted by remote clients. Typically "Enonic web clipping
* porlet (JSR 168)"
*/
private String encodeURI( String type, String key, String[] params )
{
StringBuilder encodedURI = new StringBuilder( "{cmsurl:" );
encodedURI.append( type );
encodedURI.append( "-" );
encodedURI.append( key );
if ( params != null )
{
encodedURI.append( "?" );
for ( int i = 0; i < params.length / 2; i++ )
{
String name = params[i * 2];
String value = params[i * 2 + 1];
encodedURI.append( name );
encodedURI.append( "=" );
encodedURI.append( value );
if ( i < ( params.length / 2 ) - 1 )
{
encodedURI.append( "&" );
}
}
}
encodedURI.append( "}" );
return encodedURI.toString();
}
private String resolvePathToPublicHome()
{
ResourceKey publicPath = context.getSite().getPathToPublicResources();
if ( publicPath != null )
{
String asString = publicPath.toString().trim();
if ( asString.length() == 0 )
{
return null;
}
return asString;
}
return null;
}
public void setEncodeURIs( boolean value )
{
this.encodeURIs = value;
}
public void setSiteURLResolver( SiteURLResolver value )
{
this.siteURLResolver = value;
}
public void setRequest( HttpServletRequest value )
{
this.request = value;
}
public void setCaptchaService( CaptchaService captchaService )
{
this.captchaService = captchaService;
}
public void setLocalizeService( LocalizationService localizeService )
{
this.localizeService = localizeService;
}
public void setLocaleResolvingService( LocaleResolverService localeResolverService )
{
this.localeResolverService = localeResolverService;
}
public void setContentDao( ContentDao value )
{
this.contentDao = value;
}
public void setMenuItemDao( MenuItemDao value )
{
this.menuItemDao = value;
}
public void setPortletDao( PortletDao portletDao )
{
this.portletDao = portletDao;
}
public void setContext( PortalFunctionsContext context )
{
this.context = context;
}
public void setContentBinaryDataDao( ContentBinaryDataDao contentBinaryDataDao )
{
this.contentBinaryDataDao = contentBinaryDataDao;
}
public void setImageService( ImageService imageService )
{
this.imageService = imageService;
}
public void setSecurityService( SecurityService securityService )
{
this.securityService = securityService;
}
public void setCreateAttachmentUrlFunction( CreateAttachmentUrlFunction value )
{
this.createAttachmentUrlFunction = value;
}
public void setSitePropertiesService( SitePropertiesService sitePropertiesService )
{
this.sitePropertiesService = sitePropertiesService;
}
public void setIsWindowEmptyFunction( IsWindowEmptyFunction windowEmptyFunction )
{
isWindowEmptyFunction = windowEmptyFunction;
}
public void setLivePortalTraceService( LivePortalTraceService livePortalTraceService )
{
this.livePortalTraceService = livePortalTraceService;
}
}