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