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 */ 019 package org.apache.wiki.plugin; 020 021 import java.text.DateFormat; 022 import java.text.SimpleDateFormat; 023 import java.util.Calendar; 024 import java.util.Collection; 025 import java.util.Date; 026 import java.util.GregorianCalendar; 027 import java.util.Iterator; 028 import java.util.Map; 029 030 import org.apache.log4j.Logger; 031 import org.apache.wiki.WikiContext; 032 import org.apache.wiki.WikiEngine; 033 import org.apache.wiki.WikiPage; 034 import org.apache.wiki.api.exceptions.PluginException; 035 import org.apache.wiki.api.plugin.WikiPlugin; 036 import org.apache.wiki.attachment.Attachment; 037 import org.apache.wiki.i18n.InternationalizationManager; 038 import org.apache.wiki.preferences.Preferences; 039 import org.apache.wiki.preferences.Preferences.TimeFormat; 040 import org.apache.wiki.util.TextUtil; 041 import org.apache.wiki.util.XHTML; 042 import org.apache.wiki.util.XhtmlUtil; 043 import org.jdom2.Element; 044 045 /** 046 * Returns the Recent Changes in the wiki being a date-sorted list of page names. 047 * 048 * <p>Parameters: </p> 049 * <ul> 050 * <li><b>since</b> - show changes from the last n days, for example since=5 shows only the pages that were changed in the last five days</li> 051 * <li><b>format</b> - (full|compact) : if "full", then display a long version with all possible info. If "compact", then be as compact as possible.</li> 052 * <li><b>timeFormat</b> - the time format to use, the default is "HH:mm:ss"</li> 053 * <li><b>dateFormat</b> - the date format to use, the default is "dd.MM.yyyy"</li> 054 * </ul> 055 */ 056 public class RecentChangesPlugin extends AbstractReferralPlugin implements WikiPlugin { 057 058 private static final Logger log = Logger.getLogger(RecentChangesPlugin.class); 059 060 /** Parameter name for the separator format. Value is <tt>{@value}</tt>. */ 061 public static final String PARAM_FORMAT = "format"; 062 /** Parameter name for the separator timeFormat. Value is <tt>{@value}</tt>. */ 063 public static final String PARAM_TIME_FORMAT = "timeFormat"; 064 /** Parameter name for the separator dateFormat. Value is <tt>{@value}</tt>. */ 065 public static final String PARAM_DATE_FORMAT = "dateFormat"; 066 067 /** How many days we show by default. */ 068 private static final int DEFAULT_DAYS = 100*365; 069 public static final String DEFAULT_TIME_FORMAT ="HH:mm:ss"; 070 public static final String DEFAULT_DATE_FORMAT ="dd.MM.yyyy"; 071 072 073 /** 074 * {@inheritDoc} 075 */ 076 @SuppressWarnings("unchecked") 077 public String execute( WikiContext context, Map<String, String> params ) 078 throws PluginException 079 { 080 int since = TextUtil.parseIntParameter( params.get( "since" ), DEFAULT_DAYS ); 081 String spacing = "4"; 082 boolean showAuthor = true; 083 boolean showChangenote = true; 084 String tablewidth = "4"; 085 086 WikiEngine engine = context.getEngine(); 087 088 // 089 // Which format we want to see? 090 // 091 if( "compact".equals( params.get(PARAM_FORMAT) ) ) 092 { 093 spacing = "0"; 094 showAuthor = false; 095 showChangenote = false; 096 tablewidth = "2"; 097 } 098 099 Calendar sincedate = new GregorianCalendar(); 100 sincedate.add( Calendar.DAY_OF_MONTH, -since ); 101 102 log.debug("Calculating recent changes from "+sincedate.getTime()); 103 104 // FIXME: Should really have a since date on the getRecentChanges method. 105 Collection<WikiPage> changes = engine.getRecentChanges(); 106 super.initialize( context, params ); 107 changes = super.filterCollection(changes); 108 109 if ( changes != null ) 110 { 111 Date olddate = new Date(0); 112 113 DateFormat fmt = getDateFormat( context, params ); 114 DateFormat tfmt = getTimeFormat( context, params ); 115 116 Element rt = XhtmlUtil.element(XHTML.table); 117 rt.setAttribute(XHTML.ATTR_class,"recentchanges"); 118 rt.setAttribute(XHTML.ATTR_cellpadding,spacing); 119 120 for( Iterator<WikiPage> i = changes.iterator(); i.hasNext(); ) 121 { 122 WikiPage pageref = (WikiPage) i.next(); 123 124 Date lastmod = pageref.getLastModified(); 125 126 if( lastmod.before( sincedate.getTime() ) ) 127 { 128 break; 129 } 130 131 if( !isSameDay( lastmod, olddate ) ) 132 { 133 Element row = XhtmlUtil.element(XHTML.tr); 134 Element col = XhtmlUtil.element(XHTML.td); 135 col.setAttribute(XHTML.ATTR_colspan,tablewidth); 136 col.setAttribute(XHTML.ATTR_class,"date"); 137 col.addContent(XhtmlUtil.element(XHTML.b,fmt.format(lastmod))); 138 139 rt.addContent(row); 140 row.addContent(col); 141 olddate = lastmod; 142 } 143 144 String href = context.getURL( pageref instanceof Attachment ? WikiContext.ATTACH : WikiContext.VIEW, 145 pageref.getName() ) ; 146 147 Element link = XhtmlUtil.link(href,engine.beautifyTitle(pageref.getName())); 148 149 Element row = XhtmlUtil.element(XHTML.tr); 150 Element col = XhtmlUtil.element(XHTML.td); 151 col.setAttribute(XHTML.ATTR_width,"30%"); 152 col.addContent(link); 153 154 // 155 // Add the direct link to the attachment info. 156 // 157 if( pageref instanceof Attachment ) 158 { 159 link = XhtmlUtil.link(context.getURL(WikiContext.INFO,pageref.getName()),null); 160 link.setAttribute(XHTML.ATTR_class,"infolink"); 161 162 Element img = XhtmlUtil.img(context.getURL(WikiContext.NONE,"images/attachment_small.png"),null); 163 link.addContent(img); 164 165 col.addContent(link); 166 } 167 168 169 row.addContent(col); 170 rt.addContent(row); 171 172 if( pageref instanceof Attachment ) 173 { 174 Element td = XhtmlUtil.element(XHTML.td,tfmt.format(lastmod)); 175 td.setAttribute(XHTML.ATTR_class,"lastchange"); 176 row.addContent(td); 177 } 178 else 179 { 180 Element infocol = XhtmlUtil.element(XHTML.td); 181 infocol.setAttribute(XHTML.ATTR_class,"lastchange"); 182 infocol.addContent(XhtmlUtil.link(context.getURL(WikiContext.DIFF, pageref.getName(), "r1=-1"),tfmt.format(lastmod))); 183 row.addContent(infocol); 184 } 185 186 // 187 // Display author information. 188 // 189 190 if( showAuthor ) 191 { 192 String author = pageref.getAuthor(); 193 194 Element authorinfo = XhtmlUtil.element(XHTML.td); 195 authorinfo.setAttribute(XHTML.ATTR_class,"author"); 196 197 if( author != null ) 198 { 199 if( engine.pageExists(author) ) 200 { 201 authorinfo.addContent(XhtmlUtil.link(context.getURL(WikiContext.VIEW, author),author)); 202 } 203 else 204 { 205 authorinfo.addContent(author); 206 } 207 } 208 else 209 { 210 authorinfo.addContent( Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE ) 211 .getString( "common.unknownauthor" ) ); 212 } 213 214 row.addContent(authorinfo); 215 } 216 217 // Change note 218 if( showChangenote ) 219 { 220 String changenote = (String)pageref.getAttribute(WikiPage.CHANGENOTE); 221 Element td_changenote = XhtmlUtil.element(XHTML.td,changenote); 222 td_changenote.setAttribute(XHTML.ATTR_class,"changenote"); 223 row.addContent(td_changenote); 224 } 225 226 // Revert note 227 /* 228 if( context.hasAdminPermissions() ) 229 { 230 row.addElement( new td("Revert") ); 231 } 232 */ 233 } 234 return XhtmlUtil.serialize(rt,true); 235 } 236 return ""; 237 } 238 239 240 private boolean isSameDay( Date a, Date b ) 241 { 242 Calendar aa = Calendar.getInstance(); aa.setTime(a); 243 Calendar bb = Calendar.getInstance(); bb.setTime(b); 244 245 return aa.get( Calendar.YEAR ) == bb.get( Calendar.YEAR ) 246 && aa.get( Calendar.DAY_OF_YEAR ) == bb.get( Calendar.DAY_OF_YEAR ); 247 } 248 249 250 // TODO: Ideally the default behavior should be to return the default format for the default 251 // locale, but that is at odds with the 1st version of this plugin. We seek to preserve the 252 // behaviour of that first version, so to get the default format, the user must explicitly do 253 // something like: dateFormat='' timeformat='' which is a odd, but probably okay. 254 private DateFormat getTimeFormat( WikiContext context, Map<String, String> params ) 255 { 256 String formatString = get(params, DEFAULT_TIME_FORMAT, PARAM_TIME_FORMAT); 257 258 if ("".equals(formatString.trim())) 259 return Preferences.getDateFormat( context, TimeFormat.TIME ); 260 261 return new SimpleDateFormat(formatString); 262 } 263 264 265 266 private DateFormat getDateFormat( WikiContext context, Map<String, String> params ) 267 { 268 String formatString = get(params, DEFAULT_DATE_FORMAT, PARAM_DATE_FORMAT); 269 270 if( formatString.trim().equals("") ) 271 { 272 return Preferences.getDateFormat( context, TimeFormat.DATE ); 273 } 274 275 return new SimpleDateFormat( formatString ); 276 } 277 278 279 private String get( Map<String, String> params, String defaultValue, String paramName ) 280 { 281 String value = params.get(paramName); 282 return value == null ? defaultValue : value; 283 } 284 285 286 }