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}