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 }