    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.logging.log4j.LogManager;
    023import org.apache.logging.log4j.Logger;
    024import org.apache.wiki.WikiContext;
    025import org.apache.wiki.api.core.Context;
    026import org.apache.wiki.api.core.ContextEnum;
    027import org.apache.wiki.api.core.Engine;
    028import org.apache.wiki.api.core.Page;
    029import org.apache.wiki.api.exceptions.PluginException;
    030import org.apache.wiki.api.plugin.Plugin;
    031import org.apache.wiki.attachment.Attachment;
    032import org.apache.wiki.i18n.InternationalizationManager;
    033import org.apache.wiki.pages.PageManager;
    034import org.apache.wiki.preferences.Preferences;
    035import org.apache.wiki.preferences.Preferences.TimeFormat;
    036import org.apache.wiki.render.RenderingManager;
    037import org.apache.wiki.util.TextUtil;
    038import org.apache.wiki.util.XHTML;
    039import org.apache.wiki.util.XhtmlUtil;
    040import org.jdom2.Element;
    041
    042import java.text.DateFormat;
    043import java.text.SimpleDateFormat;
    044import java.util.Calendar;
    045import java.util.Collection;
    046import java.util.Date;
    047import java.util.GregorianCalendar;
    048import java.util.Locale;
    049import java.util.Map;
    050import java.util.ResourceBundle;
    051
    052
    053/**
    054 *  Returns the Recent Changes in the wiki being a date-sorted list of page names.
    055 *
    056 *  <p>Parameters: </p>
    057 *  <ul>
    058 *  <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>
    059 *  <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>
    060 *  <li><b>timeFormat</b> - the time format to use, the default is "HH:mm:ss"</li>
    061 *  <li><b>dateFormat</b> - the date format to use, the default is "dd.MM.yyyy"</li>
    062 *  </ul>
    063 */
    064public class RecentChangesPlugin extends AbstractReferralPlugin implements Plugin {
    065
    066    private static final Logger LOG = LogManager.getLogger( RecentChangesPlugin.class );
    067
    068    /** Parameter name for the separator format.  Value is <tt>{@value}</tt>. */
    069    public static final String PARAM_FORMAT      = "format";
    070    /** Parameter name for the separator timeFormat.  Value is <tt>{@value}</tt>. */
    071    public static final String PARAM_TIME_FORMAT = "timeFormat";
    072    /** Parameter name for the separator dateFormat.  Value is <tt>{@value}</tt>. */
    073    public static final String PARAM_DATE_FORMAT = "dateFormat";
    074
    075    /** How many days we show by default. */
    076    private static final int   DEFAULT_DAYS = 100*365;
    077    public static final String DEFAULT_TIME_FORMAT ="HH:mm:ss";
    078    public static final String DEFAULT_DATE_FORMAT ="dd.MM.yyyy";
    079
    080
    081    @Override
    082    public String getDisplayName(Locale locale) {
    083        final ResourceBundle rb = ResourceBundle.getBundle(PluginManager.PLUGIN_I18N_RESOURCE, locale);
    084        return rb.getString(this.getClass().getSimpleName());
    085    }
    086
    087
    088    /**
    089     * {@inheritDoc}
    090     */
    091    @Override
    092    public String execute( final Context context, final Map< String, String > params ) throws PluginException {
    093        final int since = TextUtil.parseIntParameter( params.get( "since" ), DEFAULT_DAYS );
    094        String spacing  = "4";
    095        boolean showAuthor = true;
    096        boolean showChangenote = true;
    097        String tablewidth = "4";
    098
    099        final Engine engine = context.getEngine();
    100
    101        //
    102        //  Which format we want to see?
    103        //
    104        if( "compact".equals( params.get( PARAM_FORMAT ) ) ) {
    105            spacing  = "0";
    106            showAuthor = false;
    107            showChangenote = false;
    108            tablewidth = "2";
    109        }
    110
    111        final Calendar sincedate = new GregorianCalendar();
    112        sincedate.add( Calendar.DAY_OF_MONTH, -since );
    113
    114        LOG.debug("Calculating recent changes from "+sincedate.getTime());
    115
    116        // FIXME: Should really have a since date on the getRecentChanges method.
    117        Collection< Page > changes = engine.getManager( PageManager.class ).getRecentChanges();
    118        super.initialize( context, params );
    119        changes = filterWikiPageCollection( changes );
    120
    121        if ( changes != null ) {
    122            Date olddate = new Date( 0 );
    123
    124            final DateFormat fmt = getDateFormat( context, params );
    125            final DateFormat tfmt = getTimeFormat( context, params );
    126
    127            final Element rt = XhtmlUtil.element( XHTML.table );
    128            rt.setAttribute( XHTML.ATTR_class, "recentchanges" );
    129            rt.setAttribute( XHTML.ATTR_cellpadding, spacing );
    130
    131            for( final Page pageref : changes ) {
    132                final Date lastmod = pageref.getLastModified();
    133
    134                if( lastmod.before( sincedate.getTime() ) ) {
    135                    break;
    136                }
    137
    138                if( !isSameDay( lastmod, olddate ) ) {
    139                    final Element row = XhtmlUtil.element( XHTML.tr );
    140                    final Element col = XhtmlUtil.element( XHTML.td );
    141                    col.setAttribute( XHTML.ATTR_colspan, tablewidth );
    142                    col.setAttribute( XHTML.ATTR_class, "date" );
    143                    col.addContent( XhtmlUtil.element( XHTML.b, fmt.format( lastmod ) ) );
    144
    145                    rt.addContent( row );
    146                    row.addContent( col );
    147                    olddate = lastmod;
    148                }
    149
    150                final String href = context.getURL( pageref instanceof Attachment ? ContextEnum.PAGE_ATTACH.getRequestContext()
    151                                                                                  : ContextEnum.PAGE_VIEW.getRequestContext(), pageref.getName() );
    152                Element link = XhtmlUtil.link( href, engine.getManager( RenderingManager.class ).beautifyTitle( pageref.getName() ) );
    153                final Element row = XhtmlUtil.element( XHTML.tr );
    154                final Element col = XhtmlUtil.element( XHTML.td );
    155                col.setAttribute( XHTML.ATTR_width, "30%" );
    156                col.addContent( link );
    157
    158                //
    159                //  Add the direct link to the attachment info.
    160                //
    161                if( pageref instanceof Attachment ) {
    162                    link = XhtmlUtil.link( context.getURL( WikiContext.INFO, pageref.getName() ), null );
    163                    link.setAttribute( XHTML.ATTR_class, "infolink" );
    164
    165                    final Element img = XhtmlUtil.img( context.getURL( WikiContext.NONE, "images/attachment_small.png" ), null );
    166                    link.addContent( img );
    167
    168                    col.addContent( link );
    169                }
    170
    171                row.addContent( col );
    172                rt.addContent( row );
    173
    174                if( pageref instanceof Attachment ) {
    175                    final Element td = XhtmlUtil.element( XHTML.td, tfmt.format( lastmod ) );
    176                    td.setAttribute( XHTML.ATTR_class, "lastchange" );
    177                    row.addContent( td );
    178                } else {
    179                    final Element infocol = XhtmlUtil.element( XHTML.td );
    180                    infocol.setAttribute( XHTML.ATTR_class, "lastchange" );
    181                    infocol.addContent( XhtmlUtil.link( context.getURL( WikiContext.DIFF, pageref.getName(), "r1=-1" ),
    182                                                        tfmt.format( lastmod ) ) );
    183                    row.addContent( infocol );
    184                }
    185
    186                //
    187                //  Display author information.
    188                //
    189                if( showAuthor ) {
    190                    final String author = pageref.getAuthor();
    191
    192                    final Element authorinfo = XhtmlUtil.element( XHTML.td );
    193                    authorinfo.setAttribute( XHTML.ATTR_class, "author" );
    194
    195                    if( author != null ) {
    196                        if( engine.getManager( PageManager.class ).wikiPageExists( author ) ) {
    197                            authorinfo.addContent( XhtmlUtil.link( context.getURL( WikiContext.VIEW, author ), author ) );
    198                        } else {
    199                            authorinfo.addContent( author );
    200                        }
    201                    } else {
    202                        authorinfo.addContent( Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE )
    203                                .getString( "common.unknownauthor" ) );
    204                    }
    205
    206                    row.addContent( authorinfo );
    207                }
    208
    209                // Change note
    210                if( showChangenote ) {
    211                    final String changenote = pageref.getAttribute( Page.CHANGENOTE );
    212                    final Element td_changenote = XhtmlUtil.element( XHTML.td, changenote );
    213                    td_changenote.setAttribute( XHTML.ATTR_class, "changenote" );
    214                    row.addContent( td_changenote );
    215                }
    216
    217                //  Revert note
    218/*
    219                if( context.hasAdminPermissions() )
    220                {
    221                    row.addElement( new td("Revert") );
    222                }
    223 */
    224            }
    225            return XhtmlUtil.serialize( rt, XhtmlUtil.EXPAND_EMPTY_NODES );
    226        }
    227        return "";
    228    }
    229
    230
    231    private boolean isSameDay( final Date a, final Date b ) {
    232        final Calendar aa = Calendar.getInstance(); aa.setTime( a );
    233        final Calendar bb = Calendar.getInstance(); bb.setTime( b );
    234
    235        return aa.get( Calendar.YEAR ) == bb.get( Calendar.YEAR )
    236            && aa.get( Calendar.DAY_OF_YEAR ) == bb.get( Calendar.DAY_OF_YEAR );
    237    }
    238
    239
    240    // TODO: Ideally the default behavior should be to return the default format for the default
    241    // locale, but that is at odds with the 1st version of this plugin. We seek to preserve the
    242    // behaviour of that first version, so to get the default format, the user must explicitly do
    243    // something like: dateFormat='' timeformat='' which is a odd, but probably okay.
    244    private DateFormat getTimeFormat( final Context context, final Map< String, String > params ) {
    245        final String formatString = get( params, DEFAULT_TIME_FORMAT, PARAM_TIME_FORMAT );
    246        if( StringUtils.isBlank( formatString ) ) {
    247            return Preferences.getDateFormat( context, TimeFormat.TIME );
    248        }
    249
    250        return new SimpleDateFormat( formatString );
    251    }
    252
    253    private DateFormat getDateFormat( final Context context, final Map< String, String > params ) {
    254        final String formatString = get( params, DEFAULT_DATE_FORMAT, PARAM_DATE_FORMAT );
    255        if( StringUtils.isBlank( formatString ) ) {
    256            return Preferences.getDateFormat( context, TimeFormat.DATE );
    257        }
    258
    259        return new SimpleDateFormat( formatString );
    260    }
    261
    262    private String get( final Map< String, String > params, final String defaultValue, final String paramName ) {
    263        final String value = params.get( paramName );
    264        return value == null ? defaultValue : value;
    265    }
    266
    267}