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}