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}