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.api.core.Context; 025import org.apache.wiki.api.core.ContextEnum; 026import org.apache.wiki.api.core.Engine; 027import org.apache.wiki.api.core.Page; 028import org.apache.wiki.api.exceptions.PluginException; 029import org.apache.wiki.api.plugin.Plugin; 030import org.apache.wiki.attachment.Attachment; 031import org.apache.wiki.i18n.InternationalizationManager; 032import org.apache.wiki.pages.PageManager; 033import org.apache.wiki.preferences.Preferences; 034import org.apache.wiki.preferences.Preferences.TimeFormat; 035import org.apache.wiki.render.RenderingManager; 036import org.apache.wiki.util.TextUtil; 037import org.apache.wiki.util.XHTML; 038import org.apache.wiki.util.XhtmlUtil; 039import org.jdom2.Element; 040 041import java.text.DateFormat; 042import java.text.SimpleDateFormat; 043import java.util.Calendar; 044import java.util.Collection; 045import java.util.Date; 046import java.util.GregorianCalendar; 047import java.util.Map; 048 049 050/** 051 * Returns the Recent Changes in the wiki being a date-sorted list of page names. 052 * 053 * <p>Parameters: </p> 054 * <ul> 055 * <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> 056 * <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> 057 * <li><b>timeFormat</b> - the time format to use, the default is "HH:mm:ss"</li> 058 * <li><b>dateFormat</b> - the date format to use, the default is "dd.MM.yyyy"</li> 059 * </ul> 060 */ 061public class RecentChangesPlugin extends AbstractReferralPlugin implements Plugin { 062 063 private static final Logger log = Logger.getLogger( RecentChangesPlugin.class ); 064 065 /** Parameter name for the separator format. Value is <tt>{@value}</tt>. */ 066 public static final String PARAM_FORMAT = "format"; 067 /** Parameter name for the separator timeFormat. Value is <tt>{@value}</tt>. */ 068 public static final String PARAM_TIME_FORMAT = "timeFormat"; 069 /** Parameter name for the separator dateFormat. Value is <tt>{@value}</tt>. */ 070 public static final String PARAM_DATE_FORMAT = "dateFormat"; 071 072 /** How many days we show by default. */ 073 private static final int DEFAULT_DAYS = 100*365; 074 public static final String DEFAULT_TIME_FORMAT ="HH:mm:ss"; 075 public static final String DEFAULT_DATE_FORMAT ="dd.MM.yyyy"; 076 077 078 /** 079 * {@inheritDoc} 080 */ 081 @Override 082 public String execute( final Context context, final Map< String, String > params ) throws PluginException { 083 final int since = TextUtil.parseIntParameter( params.get( "since" ), DEFAULT_DAYS ); 084 String spacing = "4"; 085 boolean showAuthor = true; 086 boolean showChangenote = true; 087 String tablewidth = "4"; 088 089 final Engine engine = context.getEngine(); 090 091 // 092 // Which format we want to see? 093 // 094 if( "compact".equals( params.get( PARAM_FORMAT ) ) ) { 095 spacing = "0"; 096 showAuthor = false; 097 showChangenote = false; 098 tablewidth = "2"; 099 } 100 101 final Calendar sincedate = new GregorianCalendar(); 102 sincedate.add( Calendar.DAY_OF_MONTH, -since ); 103 104 log.debug("Calculating recent changes from "+sincedate.getTime()); 105 106 // FIXME: Should really have a since date on the getRecentChanges method. 107 Collection< Page > changes = engine.getManager( PageManager.class ).getRecentChanges(); 108 super.initialize( context, params ); 109 changes = filterWikiPageCollection( changes ); 110 111 if ( changes != null ) { 112 Date olddate = new Date( 0 ); 113 114 final DateFormat fmt = getDateFormat( context, params ); 115 final DateFormat tfmt = getTimeFormat( context, params ); 116 117 final Element rt = XhtmlUtil.element( XHTML.table ); 118 rt.setAttribute( XHTML.ATTR_class, "recentchanges" ); 119 rt.setAttribute( XHTML.ATTR_cellpadding, spacing ); 120 121 for( final Page pageref : changes ) { 122 final Date lastmod = pageref.getLastModified(); 123 124 if( lastmod.before( sincedate.getTime() ) ) { 125 break; 126 } 127 128 if( !isSameDay( lastmod, olddate ) ) { 129 final Element row = XhtmlUtil.element( XHTML.tr ); 130 final Element col = XhtmlUtil.element( XHTML.td ); 131 col.setAttribute( XHTML.ATTR_colspan, tablewidth ); 132 col.setAttribute( XHTML.ATTR_class, "date" ); 133 col.addContent( XhtmlUtil.element( XHTML.b, fmt.format( lastmod ) ) ); 134 135 rt.addContent( row ); 136 row.addContent( col ); 137 olddate = lastmod; 138 } 139 140 final String href = context.getURL( pageref instanceof Attachment ? ContextEnum.PAGE_ATTACH.getRequestContext() 141 : ContextEnum.PAGE_VIEW.getRequestContext(), pageref.getName() ); 142 Element link = XhtmlUtil.link( href, engine.getManager( RenderingManager.class ).beautifyTitle( pageref.getName() ) ); 143 final Element row = XhtmlUtil.element( XHTML.tr ); 144 final 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 final 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 final Element td = XhtmlUtil.element( XHTML.td, tfmt.format( lastmod ) ); 166 td.setAttribute( XHTML.ATTR_class, "lastchange" ); 167 row.addContent( td ); 168 } else { 169 final 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" ), 172 tfmt.format( lastmod ) ) ); 173 row.addContent( infocol ); 174 } 175 176 // 177 // Display author information. 178 // 179 if( showAuthor ) { 180 final String author = pageref.getAuthor(); 181 182 final Element authorinfo = XhtmlUtil.element( XHTML.td ); 183 authorinfo.setAttribute( XHTML.ATTR_class, "author" ); 184 185 if( author != null ) { 186 if( engine.getManager( PageManager.class ).wikiPageExists( 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 final String changenote = pageref.getAttribute( Page.CHANGENOTE ); 202 final 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( final Date a, final Date b ) { 222 final Calendar aa = Calendar.getInstance(); aa.setTime( a ); 223 final 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( final Context context, final Map< String, String > params ) { 235 final String formatString = get( params, DEFAULT_TIME_FORMAT, PARAM_TIME_FORMAT ); 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( final Context context, final Map< String, String > params ) { 244 final String formatString = get( params, DEFAULT_DATE_FORMAT, PARAM_DATE_FORMAT ); 245 if( StringUtils.isBlank( formatString ) ) { 246 return Preferences.getDateFormat( context, TimeFormat.DATE ); 247 } 248 249 return new SimpleDateFormat( formatString ); 250 } 251 252 private String get( final Map< String, String > params, final String defaultValue, final String paramName ) { 253 final String value = params.get( paramName ); 254 return value == null ? defaultValue : value; 255 } 256 257}