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