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    }