    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.logging.log4j.LogManager;
    022import org.apache.logging.log4j.Logger;
    023import org.apache.oro.text.regex.MalformedPatternException;
    024import org.apache.oro.text.regex.Pattern;
    025import org.apache.oro.text.regex.PatternCompiler;
    026import org.apache.oro.text.regex.PatternMatcher;
    027import org.apache.oro.text.regex.Perl5Compiler;
    028import org.apache.oro.text.regex.Perl5Matcher;
    029import org.apache.wiki.api.core.Context;
    030import org.apache.wiki.api.core.ContextEnum;
    031import org.apache.wiki.api.core.Engine;
    032import org.apache.wiki.api.core.Page;
    033import org.apache.wiki.api.exceptions.PluginException;
    034import org.apache.wiki.api.plugin.Plugin;
    035import org.apache.wiki.pages.PageManager;
    036import org.apache.wiki.references.ReferenceManager;
    037import org.apache.wiki.util.TextUtil;
    038
    039import java.util.ArrayList;
    040import java.util.Collection;
    041import java.util.HashSet;
    042import java.util.Map;
    043
    044
    045/**
    046 *  Displays the pages referring to the current page.
    047 *
    048 *  <p>Parameters</p>
    049 *  <ul>
    050 *    <li><b>name</b> - Name of the root page. Default name of calling page
    051 *    <li><b>type</b> - local|externalattachment
    052 *    <li><b>depth</b> - How many levels of pages to be parsed.
    053 *    <li><b>include</b> - Include only these pages. (eg. include='UC.*|BP.*' )
    054 *    <li><b>exclude</b> - Exclude with this pattern. (eg. exclude='LeftMenu' )
    055 *    <li><b>format</b> -  full|compact, FULL now expands all levels correctly
    056 *  </ul>
    057 *
    058 */
    059public class ReferredPagesPlugin implements Plugin {
    060
    061    private static final Logger LOG = LogManager.getLogger( ReferredPagesPlugin.class );
    062    private Engine m_engine;
    063    private int m_depth;
    064    private final HashSet< String > m_exists  = new HashSet<>();
    065    private final StringBuffer m_result  = new StringBuffer( 1024 );
    066    private final PatternMatcher m_matcher = new Perl5Matcher();
    067    private Pattern m_includePattern;
    068    private Pattern m_excludePattern;
    069    private int items;
    070    private boolean m_formatCompact = true;
    071    private boolean m_formatSort;
    072
    073    /** The parameter name for the root page to start from.  Value is <tt>{@value}</tt>. */
    074    public static final String PARAM_ROOT = "page";
    075
    076    /** The parameter name for the depth.  Value is <tt>{@value}</tt>. */
    077    public static final String PARAM_DEPTH = "depth";
    078
    079    /** The parameter name for the type of the references.  Value is <tt>{@value}</tt>. */
    080    public static final String PARAM_TYPE = "type";
    081
    082    /** The parameter name for the included pages.  Value is <tt>{@value}</tt>. */
    083    public static final String PARAM_INCLUDE = "include";
    084
    085    /** The parameter name for the excluded pages.  Value is <tt>{@value}</tt>. */
    086    public static final String PARAM_EXCLUDE = "exclude";
    087
    088    /** The parameter name for the format.  Value is <tt>{@value}</tt>. */
    089    public static final String PARAM_FORMAT = "format";
    090
    091    /** Parameter name for setting the number of columns that will be displayed by the plugin.  Value is <tt>{@value}</tt>. Available since 2.11.0. */
    092    public static final String PARAM_COLUMNS = "columns";
    093
    094    /** The minimum depth. Value is <tt>{@value}</tt>. */
    095    public static final int MIN_DEPTH = 1;
    096
    097    /** The maximum depth. Value is <tt>{@value}</tt>. */
    098    public static final int MAX_DEPTH = 8;
    099
    100    /**
    101     *  {@inheritDoc}
    102     */
    103    @Override
    104    public String execute( final Context context, final Map< String, String > params ) throws PluginException {
    105        m_engine = context.getEngine();
    106        final Page page = context.getPage();
    107        if( page == null ) {
    108            return "";
    109        }
    110
    111        // parse parameters
    112        String rootname = params.get( PARAM_ROOT );
    113        if( rootname == null ) {
    114            rootname = page.getName() ;
    115        }
    116
    117        String format = params.get( PARAM_FORMAT );
    118        if( format == null) {
    119            format = "";
    120        }
    121        if( format.contains( "full" ) ) {
    122            m_formatCompact = false ;
    123        }
    124        if( format.contains( "sort" ) ) {
    125            m_formatSort = true  ;
    126        }
    127
    128        m_depth = TextUtil.parseIntParameter( params.get( PARAM_DEPTH ), MIN_DEPTH );
    129        if( m_depth > MAX_DEPTH ) {
    130            m_depth = MAX_DEPTH;
    131        }
    132
    133        String includePattern = params.get(PARAM_INCLUDE);
    134        if( includePattern == null ) {
    135            includePattern = ".*";
    136        }
    137
    138        String excludePattern = params.get(PARAM_EXCLUDE);
    139        if( excludePattern == null ) {
    140            excludePattern = "^$";
    141        }
    142
    143        final String columns = params.get( PARAM_COLUMNS );
    144        if( columns != null ) {
    145            items = TextUtil.parseIntParameter( columns, 0 );
    146        }
    147
    148        LOG.debug( "Fetching referred pages for "+ rootname +
    149                   " with a depth of "+ m_depth +
    150                   " with include pattern of "+ includePattern +
    151                   " with exclude pattern of "+ excludePattern +
    152                   " with " + columns + " items" );
    153
    154        //
    155        // do the actual work
    156        //
    157        final String href  = context.getViewURL( rootname );
    158        final String title = "ReferredPagesPlugin: depth[" + m_depth +
    159                             "] include[" + includePattern + "] exclude[" + excludePattern +
    160                             "] format[" + ( m_formatCompact ? "compact" : "full" ) +
    161                             ( m_formatSort ? " sort" : "" ) + "]";
    162
    163        if( items > 1 ) {
    164            m_result.append( "<div class=\"ReferredPagesPlugin\" style=\"" )
    165                    .append( "columns:" ).append( columns ).append( ";" )
    166                    .append( "moz-columns:" ).append( columns ).append( ";" )
    167                    .append( "webkit-columns:" ).append( columns ).append( ";" )
    168                    .append( "\">\n" );
    169        } else {
    170            m_result.append( "<div class=\"ReferredPagesPlugin\">\n" );
    171        }
    172        m_result.append( "<a class=\"wikipage\" href=\"" )
    173                .append( href ).append( "\" title=\"" )
    174                .append( TextUtil.replaceEntities( title ) )
    175                .append( "\">" )
    176                .append( TextUtil.replaceEntities( rootname ) )
    177                .append( "</a>\n" );
    178        m_exists.add( rootname );
    179
    180        // pre compile all needed patterns
    181        // glob compiler :  * is 0..n instance of any char  -- more convenient as input
    182        // perl5 compiler : .* is 0..n instances of any char -- more powerful
    183        //PatternCompiler g_compiler = new GlobCompiler();
    184        final PatternCompiler compiler = new Perl5Compiler();
    185
    186        try {
    187            m_includePattern = compiler.compile( includePattern );
    188            m_excludePattern = compiler.compile( excludePattern );
    189        } catch( final MalformedPatternException e ) {
    190            if( m_includePattern == null ) {
    191                throw new PluginException( "Illegal include pattern detected." );
    192            } else if( m_excludePattern == null ) {
    193                throw new PluginException( "Illegal exclude pattern detected." );
    194            } else {
    195                throw new PluginException( "Illegal internal pattern detected." );
    196            }
    197        }
    198
    199        // go get all referred links
    200        getReferredPages(context,rootname, 0);
    201
    202        // close and finish
    203        m_result.append ("</div>\n" ) ;
    204
    205        return m_result.toString() ;
    206    }
    207
    208    /**
    209     * Retrieves a list of all referred pages. Is called recursively depending on the depth parameter.
    210     */
    211    private void getReferredPages( final Context context, final String pagename, int depth ) {
    212        if( depth >= m_depth ) {
    213            return;  // end of recursion
    214        }
    215        if( pagename == null ) {
    216            return;
    217        }
    218        if( !m_engine.getManager( PageManager.class ).wikiPageExists(pagename) ) {
    219            return;
    220        }
    221
    222        final ReferenceManager mgr = m_engine.getManager( ReferenceManager.class );
    223        final Collection< String > allPages = mgr.findRefersTo( pagename );
    224        handleLinks( context, allPages, ++depth, pagename );
    225    }
    226
    227    private void handleLinks( final Context context, final Collection<String> links, final int depth, final String pagename ) {
    228        boolean isUL = false;
    229        final HashSet< String > localLinkSet = new HashSet<>();  // needed to skip multiple links to the same page
    230        localLinkSet.add( pagename );
    231
    232        final ArrayList< String > allLinks = new ArrayList<>();
    233
    234        if( links != null )
    235            allLinks.addAll( links );
    236
    237        if( m_formatSort ) context.getEngine().getManager( PageManager.class ).getPageSorter().sort( allLinks );
    238
    239        for( final String link : allLinks ) {
    240            if( localLinkSet.contains( link ) ) {
    241                continue; // skip multiple links to the same page
    242            }
    243            localLinkSet.add( link );
    244
    245            if( !m_engine.getManager( PageManager.class ).wikiPageExists( link ) ) {
    246                continue; // hide links to non-existing pages
    247            }
    248            if(  m_matcher.matches( link , m_excludePattern ) ) {
    249                continue;
    250            }
    251            if( !m_matcher.matches( link , m_includePattern ) ) {
    252                continue;
    253            }
    254
    255            if( m_exists.contains( link ) ) {
    256                if( !m_formatCompact ) {
    257                    if( !isUL ) {
    258                        isUL = true;
    259                        m_result.append("<ul>\n");
    260                    }
    261
    262                    //See https://www.w3.org/wiki/HTML_lists  for proper nesting of UL and LI
    263                    m_result.append( "<li> " ).append( TextUtil.replaceEntities( link ) ).append( "\n" );
    264                    getReferredPages( context, link, depth );  // added recursive call - on general request
    265                    m_result.append( "\n</li>\n" );
    266                }
    267            } else {
    268                if( !isUL ) {
    269                    isUL = true;
    270                    m_result.append("<ul>\n");
    271                }
    272
    273                final String href = context.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), link );
    274                m_result.append( "<li><a class=\"wikipage\" href=\"" ).append( href ).append( "\">" ).append( TextUtil.replaceEntities( link ) ).append( "</a>\n" );
    275                m_exists.add( link );
    276                getReferredPages( context, link, depth );
    277                m_result.append( "\n</li>\n" );
    278            }
    279        }
    280
    281        if( isUL ) {
    282            m_result.append("</ul>\n");
    283        }
    284    }
    285
    286}