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