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