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