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