001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.ui; 020 021import org.apache.logging.log4j.LogManager; 022import org.apache.wiki.api.core.Context; 023import org.apache.wiki.i18n.InternationalizationManager; 024import org.apache.wiki.modules.ModuleManager; 025import org.apache.wiki.preferences.Preferences; 026import org.apache.wiki.util.ClassUtil; 027 028import javax.servlet.jsp.PageContext; 029import javax.servlet.jsp.jstl.fmt.LocaleSupport; 030import java.util.Enumeration; 031import java.util.HashMap; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Locale; 035import java.util.Map; 036import java.util.ResourceBundle; 037import java.util.Set; 038import java.util.TimeZone; 039import java.util.Vector; 040 041 042/** 043 * This class takes care of managing JSPWiki templates. This class also provides the ResourceRequest mechanism. 044 * 045 * @since 2.1.62 046 */ 047public interface TemplateManager extends ModuleManager { 048 049 String SKIN_DIRECTORY = "skins"; 050 051 /** Requests a JavaScript function to be called during window.onload. Value is {@value}. */ 052 String RESOURCE_JSFUNCTION = "jsfunction"; 053 054 /** Requests a JavaScript associative array with all localized strings. */ 055 String RESOURCE_JSLOCALIZEDSTRINGS = "jslocalizedstrings"; 056 057 /** Requests a stylesheet to be inserted. Value is {@value}. */ 058 String RESOURCE_STYLESHEET = "stylesheet"; 059 060 /** Requests a script to be loaded. Value is {@value}. */ 061 String RESOURCE_SCRIPT = "script"; 062 063 /** Requests inlined CSS. Value is {@value}. */ 064 String RESOURCE_INLINECSS = "inlinecss"; 065 066 /** The default directory for the properties. Value is {@value}. */ 067 String DIRECTORY = "templates"; 068 069 /** The name of the default template. Value is {@value}. */ 070 String DEFAULT_TEMPLATE = "default"; 071 072 /** Name of the file that contains the properties. */ 073 String PROPERTYFILE = "template.properties"; 074 075 /** Location of I18N Resource bundles, and path prefix and suffixes */ 076 String I18NRESOURCE_PREFIX = "templates/default_"; 077 078 String I18NRESOURCE_SUFFIX = ".properties"; 079 080 /** The default (en) RESOURCE name and id. */ 081 String I18NRESOURCE_EN = "templates/default.properties"; 082 String I18NRESOURCE_EN_ID = "en"; 083 084 /** I18N string to mark the default locale */ 085 String I18NDEFAULT_LOCALE = "prefs.user.language.default"; 086 087 /** I18N string to mark the server timezone */ 088 String I18NSERVER_TIMEZONE = "prefs.user.timezone.server"; 089 090 /** Prefix of the default timeformat properties. */ 091 String TIMEFORMATPROPERTIES = "jspwiki.defaultprefs.timeformat."; 092 093 /** The name under which the resource includes map is stored in the WikiContext. */ 094 String RESOURCE_INCLUDES = "jspwiki.resourceincludes"; 095 096 /** Requests a HTTP header. Value is {@value}. */ 097 String RESOURCE_HTTPHEADER = "httpheader"; 098 099 /** 100 * Check the existence of a template. 101 */ 102 boolean templateExists( String templateName ); 103 104 /** 105 * An utility method for finding a JSP page. It searches only under either current context or by the absolute name. 106 * 107 * @param pageContext the JSP PageContext 108 * @param name The name of the JSP page to look for (e.g "Wiki.jsp") 109 * @return The context path to the resource 110 */ 111 String findJSP( PageContext pageContext, String name ); 112 113 /** 114 * Attempts to locate a resource under the given template. If that template does not exist, or the page does not exist under that 115 * template, will attempt to locate a similarly named file under the default template. 116 * <p> 117 * Even though the name suggests only JSP files can be located, but in fact this method can find also other resources than JSP files. 118 * 119 * @param pageContext The JSP PageContext 120 * @param template From which template we should seek initially? 121 * @param name Which resource are we looking for (e.g. "ViewTemplate.jsp") 122 * @return path to the JSP page; null, if it was not found. 123 */ 124 String findJSP( PageContext pageContext, String template, String name ); 125 126 /** 127 * Attempts to locate a resource under the given template. This matches the functionality findJSP(), but uses the WikiContext as 128 * the argument. If there is no servlet context (i.e. this is embedded), will just simply return a best-guess. 129 * <p> 130 * This method is typically used to locate any resource, including JSP pages, images, scripts, etc. 131 * 132 * @since 2.6 133 * @param ctx the wiki context 134 * @param template the name of the template to use 135 * @param name the name of the resource to fine 136 * @return the path to the resource 137 */ 138 String findResource( Context ctx, String template, String name ); 139 140 /** 141 * Lists the skins available under this template. Returns an empty Set, if there are no extra skins available. Note that 142 * this method does not check whether there is anything actually in the directories, it just lists them. This may change 143 * in the future. 144 * 145 * @param pageContext the JSP PageContext 146 * @param template The template to search 147 * @return Set of Strings with the skin names. 148 * @since 2.3.26 149 */ 150 Set< String > listSkins( PageContext pageContext, String template ); 151 152 /** 153 * List all installed i18n language properties by classpath searching for files like : 154 * templates/default_*.properties 155 * templates/default.properties 156 * 157 * @param pageContext page context 158 * @return map of installed Languages 159 * @since 2.7.x 160 */ 161 default Map< String, String > listLanguages( final PageContext pageContext ) { 162 final Map< String, String > resultMap = new LinkedHashMap<>(); 163 final String clientLanguage = pageContext.getRequest().getLocale().toString(); 164 final List< String > entries = ClassUtil.classpathEntriesUnder( DIRECTORY ); 165 for( String name : entries ) { 166 if ( name.equals( I18NRESOURCE_EN ) || (name.startsWith( I18NRESOURCE_PREFIX ) && name.endsWith( I18NRESOURCE_SUFFIX ) ) ) { 167 if( name.equals( I18NRESOURCE_EN ) ) { 168 name = I18NRESOURCE_EN_ID; 169 } else { 170 name = name.substring( I18NRESOURCE_PREFIX.length(), name.lastIndexOf( I18NRESOURCE_SUFFIX ) ); 171 } 172 final Locale locale = new Locale( name.substring( 0, 2 ), !name.contains( "_" ) ? "" : name.substring( 3, 5 ) ); 173 String defaultLanguage = ""; 174 if( clientLanguage.startsWith( name ) ) { 175 defaultLanguage = LocaleSupport.getLocalizedMessage( pageContext, I18NDEFAULT_LOCALE ); 176 } 177 resultMap.put( name, locale.getDisplayName( locale ) + " " + defaultLanguage ); 178 } 179 } 180 181 return resultMap; 182 } 183 184 185 /** 186 * List all available timeformats, read from the jspwiki.properties 187 * 188 * @param pageContext page context 189 * @return map of TimeFormats 190 * @since 2.7.x 191 */ 192 Map< String, String > listTimeFormats( final PageContext pageContext ); 193 194 /** 195 * List all timezones, with special marker for server timezone 196 * 197 * @param pageContext page context 198 * @return map of TimeZones 199 * @since 2.7.x 200 */ 201 default Map< String, String > listTimeZones( final PageContext pageContext ) { 202 final Map< String, String > resultMap = new LinkedHashMap<>(); 203 final String[][] tzs = { 204 { "GMT-12", "Enitwetok, Kwajalien" }, 205 { "GMT-11", "Nome, Midway Island, Samoa" }, 206 { "GMT-10", "Hawaii" }, 207 { "GMT-9", "Alaska" }, 208 { "GMT-8", "Pacific Time" }, 209 { "GMT-7", "Mountain Time" }, 210 { "GMT-6", "Central Time, Mexico City" }, 211 { "GMT-5", "Eastern Time, Bogota, Lima, Quito" }, 212 { "GMT-4", "Atlantic Time, Caracas, La Paz" }, 213 { "GMT-3:30", "Newfoundland" }, 214 { "GMT-3", "Brazil, Buenos Aires, Georgetown, Falkland Is." }, 215 { "GMT-2", "Mid-Atlantic, Ascention Is., St Helena" }, 216 { "GMT-1", "Azores, Cape Verde Islands" }, 217 { "GMT", "Casablanca, Dublin, Edinburgh, London, Lisbon, Monrovia" }, 218 { "GMT+1", "Berlin, Brussels, Copenhagen, Madrid, Paris, Rome" }, 219 { "GMT+2", "Helsinki, Athens, Kaliningrad, South Africa, Warsaw" }, 220 { "GMT+3", "Baghdad, Riyadh, Moscow, Nairobi" }, 221 { "GMT+3:30", "Tehran" }, 222 { "GMT+4", "Adu Dhabi, Baku, Muscat, Tbilisi" }, 223 { "GMT+4:30", "Kabul" }, 224 { "GMT+5", "Islamabad, Karachi, Tashkent" }, 225 { "GMT+5:30", "Bombay, Calcutta, Madras, New Delhi" }, 226 { "GMT+6", "Almaty, Colomba, Dhakra" }, 227 { "GMT+7", "Bangkok, Hanoi, Jakarta" }, 228 { "GMT+8", "Beijing, Hong Kong, Perth, Singapore, Taipei" }, 229 { "GMT+9", "Osaka, Sapporo, Seoul, Tokyo, Yakutsk" }, 230 { "GMT+9:30", "Adelaide, Darwin" }, 231 { "GMT+10", "Melbourne, Papua New Guinea, Sydney, Vladivostok" }, 232 { "GMT+11", "Magadan, New Caledonia, Solomon Islands" }, 233 { "GMT+12", "Auckland, Wellington, Fiji, Marshall Island" } }; 234 235 final TimeZone servertz = TimeZone.getDefault(); 236 for( final String[] strings : tzs ) { 237 String tzID = strings[ 0 ]; 238 final TimeZone tz = TimeZone.getTimeZone( tzID ); 239 String serverTimeZone = ""; 240 if( servertz.getRawOffset() == tz.getRawOffset() ) { 241 serverTimeZone = LocaleSupport.getLocalizedMessage( pageContext, I18NSERVER_TIMEZONE ); 242 tzID = servertz.getID(); 243 } 244 245 resultMap.put( tzID, "(" + strings[ 0 ] + ") " + strings[ 1 ] + " " + serverTimeZone ); 246 } 247 248 return resultMap; 249 } 250 251 /** 252 * Returns the include resources marker for a given type. This is in a 253 * HTML or Javascript comment format. 254 * 255 * @param context the wiki context 256 * @param type the marker 257 * @return the generated marker comment 258 */ 259 static String getMarker( final Context context, final String type ) { 260 if( type.equals( RESOURCE_JSLOCALIZEDSTRINGS ) ) { 261 return getJSLocalizedStrings( context ); 262 } else if( type.equals( RESOURCE_JSFUNCTION ) ) { 263 return "/* INCLUDERESOURCES ("+type+") */"; 264 } 265 return "<!-- INCLUDERESOURCES ("+type+") -->"; 266 } 267 268 /** 269 * Extract all i18n strings in the javascript domain. (javascript.*) Returns a javascript snippet which defines the LocalizedStings array. 270 * 271 * @param context the {@link Context} 272 * @return Javascript snippet which defines the LocalizedStrings array 273 * @since 2.5.108 274 */ 275 static String getJSLocalizedStrings( final Context context ) { 276 final StringBuilder sb = new StringBuilder(); 277 sb.append( "var LocalizedStrings = {\n"); 278 final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.DEF_TEMPLATE ); 279 boolean first = true; 280 281 for( final Enumeration< String > en = rb.getKeys(); en.hasMoreElements(); ) { 282 final String key = en.nextElement(); 283 if( key.startsWith("javascript") ) { 284 if( first ) { 285 first = false; 286 } else { 287 sb.append( ",\n" ); 288 } 289 sb.append( "\"" ).append( key ).append( "\":\"" ).append( rb.getString( key ) ).append( "\"" ); 290 } 291 } 292 sb.append("\n};\n"); 293 294 return( sb.toString() ); 295 } 296 297 /** 298 * Adds a resource request to the current request context. The content will be added at the resource-type marker 299 * (see IncludeResourcesTag) in WikiJSPFilter. 300 * <p> 301 * The resources can be of different types. For RESOURCE_SCRIPT and RESOURCE_STYLESHEET this is an URI path to the resource 302 * (a script file or an external stylesheet) that needs to be included. For RESOURCE_INLINECSS the resource should be something 303 * that can be added between <style></style> in the header file (commonheader.jsp). For RESOURCE_JSFUNCTION it is the name 304 * of the Javascript function that should be run at page load. 305 * <p> 306 * The IncludeResourceTag inserts code in the template files, which is then filled by the WikiFilter after the request has been 307 * rendered but not yet sent to the recipient. 308 * <p> 309 * Note that ALL resource requests get rendered, so this method does not check if the request already exists in the resources. 310 * Therefore, if you have a plugin which makes a new resource request every time, you'll end up with multiple resource requests 311 * rendered. It's thus a good idea to make this request only once during the page life cycle. 312 * 313 * @param ctx The current wiki context 314 * @param type What kind of a request should be added? 315 * @param resource The resource to add. 316 */ 317 static void addResourceRequest( final Context ctx, final String type, final String resource ) { 318 HashMap< String, Vector< String > > resourcemap = ctx.getVariable( RESOURCE_INCLUDES ); 319 if( resourcemap == null ) { 320 resourcemap = new HashMap<>(); 321 } 322 323 Vector< String > resources = resourcemap.get( type ); 324 if( resources == null ) { 325 resources = new Vector<>(); 326 } 327 328 String resourceString = null; 329 switch( type ) { 330 case RESOURCE_SCRIPT: 331 resourceString = "<script type='text/javascript' src='" + resource + "'></script>"; 332 break; 333 case RESOURCE_STYLESHEET: 334 resourceString = "<link rel='stylesheet' type='text/css' href='" + resource + "' />"; 335 break; 336 case RESOURCE_INLINECSS: 337 resourceString = "<style type='text/css'>\n" + resource + "\n</style>\n"; 338 break; 339 case RESOURCE_JSFUNCTION: 340 case RESOURCE_HTTPHEADER: 341 resourceString = resource; 342 break; 343 } 344 345 if( resourceString != null ) { 346 resources.add( resourceString ); 347 } 348 349 LogManager.getLogger( TemplateManager.class ).debug( "Request to add a resource: " + resourceString ); 350 351 resourcemap.put( type, resources ); 352 ctx.setVariable( RESOURCE_INCLUDES, resourcemap ); 353 } 354 355 /** 356 * Returns resource requests for a particular type. If there are no resources, returns an empty array. 357 * 358 * @param ctx WikiContext 359 * @param type The resource request type 360 * @return a String array for the resource requests 361 */ 362 static String[] getResourceRequests( final Context ctx, final String type ) { 363 final HashMap< String, Vector< String > > hm = ctx.getVariable( RESOURCE_INCLUDES ); 364 if( hm == null ) { 365 return new String[0]; 366 } 367 368 final Vector<String> resources = hm.get( type ); 369 if( resources == null ){ 370 return new String[0]; 371 } 372 373 final String[] res = new String[resources.size()]; 374 return resources.toArray( res ); 375 } 376 377 /** 378 * Returns all those types that have been requested so far. 379 * 380 * @param ctx the wiki context 381 * @return the array of types requested 382 */ 383 static String[] getResourceTypes( final Context ctx ) { 384 String[] res = new String[0]; 385 if( ctx != null ) { 386 final HashMap< String, String > hm = ctx.getVariable( RESOURCE_INCLUDES ); 387 if( hm != null ) { 388 final Set< String > keys = hm.keySet(); 389 res = keys.toArray( res ); 390 } 391 } 392 393 return res; 394 } 395 396}