| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.wiki.plugin; |
| |
| import org.apache.log4j.Logger; |
| import org.apache.wiki.api.core.Context; |
| import org.apache.wiki.api.core.ContextEnum; |
| import org.apache.wiki.api.core.Engine; |
| import org.apache.wiki.api.core.Page; |
| import org.apache.wiki.api.exceptions.PluginException; |
| import org.apache.wiki.api.exceptions.ProviderException; |
| import org.apache.wiki.api.plugin.ParserStagePlugin; |
| import org.apache.wiki.api.plugin.Plugin; |
| import org.apache.wiki.api.plugin.PluginElement; |
| import org.apache.wiki.api.providers.WikiProvider; |
| import org.apache.wiki.auth.AuthorizationManager; |
| import org.apache.wiki.auth.permissions.PagePermission; |
| import org.apache.wiki.pages.PageManager; |
| import org.apache.wiki.preferences.Preferences; |
| import org.apache.wiki.preferences.Preferences.TimeFormat; |
| import org.apache.wiki.references.ReferenceManager; |
| import org.apache.wiki.render.RenderingManager; |
| import org.apache.wiki.util.TextUtil; |
| |
| import java.text.DateFormat; |
| import java.text.MessageFormat; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.ResourceBundle; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * <p>Builds a simple weblog. |
| * The pageformat can use the following params:</p> |
| * <p>%p - Page name</p> |
| * <p>Parameters:</p> |
| * <ul> |
| * <li><b>page</b> - which page is used to do the blog; default is the current page.</li> |
| * <li><b>entryFormat</b> - how to display the date on pages, using the J2SE SimpleDateFormat |
| * syntax. Defaults to the current locale's DateFormat.LONG format |
| * for the date, and current locale's DateFormat.SHORT for the time. |
| * Thus, for the US locale this will print dates similar to |
| * this: September 4, 2005 11:54 PM</li> |
| * <li><b>days</b> - how many days the weblog aggregator should show. If set to |
| * "all", shows all pages.</li> |
| * <li><b>pageformat</b> - What the entry pages should look like.</li> |
| * <li><b>startDate</b> - Date when to start. Format is "ddMMyy."</li> |
| * <li><b>maxEntries</b> - How many entries to show at most.</li> |
| * <li><b>preview</b> - How many characters of the text to show on the preview page.</li> |
| * </ul> |
| * <p>The "days" and "startDate" can also be sent in HTTP parameters, |
| * and the names are "weblog.days" and "weblog.startDate", respectively.</p> |
| * <p>The weblog plugin also adds an attribute to each page it is on: |
| * "weblogplugin.isweblog" is set to "true". This can be used to quickly |
| * peruse pages which have weblogs.</p> |
| * @since 1.9.21 |
| */ |
| |
| // FIXME: Add "entries" param as an alternative to "days". |
| // FIXME: Entries arrive in wrong order. |
| |
| public class WeblogPlugin implements Plugin, ParserStagePlugin { |
| |
| private static final Logger log = Logger.getLogger(WeblogPlugin.class); |
| private static final Pattern HEADINGPATTERN; |
| |
| /** How many days are considered by default. Default value is {@value} */ |
| private static final int DEFAULT_DAYS = 7; |
| private static final String DEFAULT_PAGEFORMAT = "%p_blogentry_"; |
| |
| /** The default date format used in the blog entry page names. */ |
| public static final String DEFAULT_DATEFORMAT = "ddMMyy"; |
| |
| /** Parameter name for the startDate. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_STARTDATE = "startDate"; |
| /** Parameter name for the entryFormat. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_ENTRYFORMAT = "entryFormat"; |
| /** Parameter name for the days. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_DAYS = "days"; |
| /** Parameter name for the allowComments. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_ALLOWCOMMENTS = "allowComments"; |
| /** Parameter name for the maxEntries. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_MAXENTRIES = "maxEntries"; |
| /** Parameter name for the page. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_PAGE = "page"; |
| /** Parameter name for the preview. Value is <tt>{@value}</tt>. */ |
| public static final String PARAM_PREVIEW = "preview"; |
| |
| /** The attribute which is stashed to the WikiPage attributes to check if a page |
| * is a weblog or not. You may check for its presence. |
| */ |
| public static final String ATTR_ISWEBLOG = "weblogplugin.isweblog"; |
| |
| static { |
| // This is a pretty ugly, brute-force regex. But it will do for now... |
| HEADINGPATTERN = Pattern.compile("(<h[1-4][^>]*>)(.*)(</h[1-4]>)", Pattern.CASE_INSENSITIVE); |
| } |
| |
| /** |
| * Create an entry name based on the blogname, a date, and an entry number. |
| * |
| * @param pageName Name of the blog |
| * @param date The date (in ddMMyy format) |
| * @param entryNum The entry number. |
| * @return A formatted page name. |
| */ |
| public static String makeEntryPage( final String pageName, final String date, final String entryNum ) { |
| return TextUtil.replaceString(DEFAULT_PAGEFORMAT,"%p",pageName)+date+"_"+entryNum; |
| } |
| |
| /** |
| * Return just the basename for entires without date and entry numebr. |
| * |
| * @param pageName The name of the blog. |
| * @return A formatted name. |
| */ |
| public static String makeEntryPage( final String pageName ) |
| { |
| return TextUtil.replaceString(DEFAULT_PAGEFORMAT,"%p",pageName); |
| } |
| |
| /** |
| * Returns the entry page without the entry number. |
| * |
| * @param pageName Blog name. |
| * @param date The date. |
| * @return A base name for the blog entries. |
| */ |
| public static String makeEntryPage( final String pageName, final String date ) { |
| return TextUtil.replaceString(DEFAULT_PAGEFORMAT,"%p",pageName)+date; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String execute( final Context context, final Map< String, String > params ) throws PluginException { |
| final Calendar startTime; |
| final Calendar stopTime; |
| int numDays = DEFAULT_DAYS; |
| final Engine engine = context.getEngine(); |
| final AuthorizationManager mgr = engine.getManager( AuthorizationManager.class ); |
| |
| // |
| // Parse parameters. |
| // |
| String days; |
| final DateFormat entryFormat; |
| String startDay; |
| boolean hasComments = false; |
| int maxEntries; |
| String weblogName; |
| |
| if( (weblogName = params.get(PARAM_PAGE)) == null ) { |
| weblogName = context.getPage().getName(); |
| } |
| |
| if( (days = context.getHttpParameter( "weblog."+PARAM_DAYS )) == null ) { |
| days = params.get( PARAM_DAYS ); |
| } |
| |
| if( ( params.get(PARAM_ENTRYFORMAT)) == null ) { |
| entryFormat = Preferences.getDateFormat( context, TimeFormat.DATETIME ); |
| } else { |
| entryFormat = new SimpleDateFormat( params.get(PARAM_ENTRYFORMAT) ); |
| } |
| |
| if( days != null ) { |
| if( days.equalsIgnoreCase("all") ) { |
| numDays = Integer.MAX_VALUE; |
| } else { |
| numDays = TextUtil.parseIntParameter( days, DEFAULT_DAYS ); |
| } |
| } |
| |
| |
| if( (startDay = params.get(PARAM_STARTDATE)) == null ) { |
| startDay = context.getHttpParameter( "weblog."+PARAM_STARTDATE ); |
| } |
| |
| if( TextUtil.isPositive( params.get(PARAM_ALLOWCOMMENTS) ) ) { |
| hasComments = true; |
| } |
| |
| maxEntries = TextUtil.parseIntParameter( params.get(PARAM_MAXENTRIES), Integer.MAX_VALUE ); |
| |
| // |
| // Determine the date range which to include. |
| // |
| startTime = Calendar.getInstance(); |
| stopTime = Calendar.getInstance(); |
| |
| if( startDay != null ) { |
| final SimpleDateFormat fmt = new SimpleDateFormat( DEFAULT_DATEFORMAT ); |
| try { |
| final Date d = fmt.parse( startDay ); |
| startTime.setTime( d ); |
| stopTime.setTime( d ); |
| } catch( final ParseException e ) { |
| return "Illegal time format: "+startDay; |
| } |
| } |
| |
| // |
| // Mark this to be a weblog |
| // |
| context.getPage().setAttribute(ATTR_ISWEBLOG, "true"); |
| |
| // |
| // We make a wild guess here that nobody can do millisecond accuracy here. |
| // |
| startTime.add( Calendar.DAY_OF_MONTH, -numDays ); |
| startTime.set( Calendar.HOUR, 0 ); |
| startTime.set( Calendar.MINUTE, 0 ); |
| startTime.set( Calendar.SECOND, 0 ); |
| stopTime.set( Calendar.HOUR, 23 ); |
| stopTime.set( Calendar.MINUTE, 59 ); |
| stopTime.set( Calendar.SECOND, 59 ); |
| |
| final StringBuilder sb = new StringBuilder(); |
| final List< Page > blogEntries = findBlogEntries( engine, weblogName, startTime.getTime(), stopTime.getTime() ); |
| blogEntries.sort( new PageDateComparator() ); |
| |
| sb.append("<div class=\"weblog\">\n"); |
| |
| for( final Iterator< Page > i = blogEntries.iterator(); i.hasNext() && maxEntries-- > 0 ; ) { |
| final Page p = i.next(); |
| if( mgr.checkPermission( context.getWikiSession(), new PagePermission(p, PagePermission.VIEW_ACTION) ) ) { |
| addEntryHTML( context, entryFormat, hasComments, sb, p, params ); |
| } |
| } |
| |
| sb.append("</div>\n"); |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Generates HTML for an entry. |
| * |
| * @param context |
| * @param entryFormat |
| * @param hasComments True, if comments are enabled. |
| * @param buffer The buffer to which we add. |
| * @param entry |
| * @throws ProviderException |
| */ |
| private void addEntryHTML( final Context context, final DateFormat entryFormat, final boolean hasComments, |
| final StringBuilder buffer, final Page entry, final Map< String, String > params) { |
| final Engine engine = context.getEngine(); |
| final ResourceBundle rb = Preferences.getBundle(context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE); |
| |
| buffer.append("<div class=\"weblogentry\">\n"); |
| |
| // |
| // Heading |
| // |
| buffer.append("<div class=\"weblogentryheading\">\n"); |
| |
| final Date entryDate = entry.getLastModified(); |
| buffer.append( entryFormat != null ? entryFormat.format(entryDate) : entryDate ); |
| buffer.append("</div>\n"); |
| |
| // |
| // Append the text of the latest version. Reset the context to that page. |
| // |
| final Context entryCtx = context.clone(); |
| entryCtx.setPage( entry ); |
| |
| String html = engine.getManager( RenderingManager.class ).getHTML( entryCtx, engine.getManager( PageManager.class ).getPage( entry.getName() ) ); |
| |
| // Extract the first h1/h2/h3 as title, and replace with null |
| buffer.append("<div class=\"weblogentrytitle\">\n"); |
| final Matcher matcher = HEADINGPATTERN.matcher( html ); |
| if ( matcher.find() ) { |
| final String title = matcher.group(2); |
| html = matcher.replaceFirst(""); |
| buffer.append( title ); |
| } else { |
| buffer.append( entry.getName() ); |
| } |
| buffer.append("</div>\n"); |
| buffer.append("<div class=\"weblogentrybody\">\n"); |
| |
| final int preview = TextUtil.parseIntParameter(params.get(PARAM_PREVIEW), 0); |
| if (preview > 0) { |
| // |
| // We start with the first 'preview' number of characters from the text, |
| // and then add characters to it until we get to a linebreak or a period. |
| // The idea is that cutting off at a linebreak is less likely |
| // to disturb the HTML and leave us with garbled output. |
| // |
| boolean hasBeenCutOff = false; |
| int cutoff = Math.min(preview, html.length()); |
| while (cutoff < html.length()) { |
| if (html.charAt(cutoff) == '\r' || html.charAt(cutoff) == '\n') { |
| hasBeenCutOff = true; |
| break; |
| } else if (html.charAt(cutoff) == '.') { |
| // we do want the period |
| cutoff++; |
| hasBeenCutOff = true; |
| break; |
| } |
| cutoff++; |
| } |
| buffer.append(html.substring(0, cutoff)); |
| if (hasBeenCutOff) { |
| buffer.append(" <a href=\""+entryCtx.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), entry.getName())+"\">"+rb.getString("weblogentryplugin.more")+"</a>\n"); |
| } |
| } else { |
| buffer.append(html); |
| } |
| buffer.append("</div>\n"); |
| |
| // |
| // Append footer |
| // |
| buffer.append("<div class=\"weblogentryfooter\">\n"); |
| |
| String author = entry.getAuthor(); |
| |
| if( author != null ) { |
| if( engine.getManager( PageManager.class ).wikiPageExists(author) ) { |
| author = "<a href=\""+entryCtx.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), author )+"\">"+engine.getManager( RenderingManager.class ).beautifyTitle(author)+"</a>"; |
| } |
| } else { |
| author = "AnonymousCoward"; |
| } |
| |
| buffer.append( MessageFormat.format( rb.getString("weblogentryplugin.postedby"), author)); |
| buffer.append( "<a href=\"" + entryCtx.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), entry.getName() ) + "\">" + rb.getString("weblogentryplugin.permalink") + "</a>" ); |
| final String commentPageName = TextUtil.replaceString( entry.getName(), "blogentry", "comments" ); |
| |
| if( hasComments ) { |
| final int numComments = guessNumberOfComments( engine, commentPageName ); |
| |
| // |
| // We add the number of comments to the URL so that the user's browsers would realize that the page has changed. |
| // |
| buffer.append( " " ); |
| |
| final String addcomment = rb.getString("weblogentryplugin.addcomment"); |
| |
| buffer.append( "<a href=\""+ |
| entryCtx.getURL( ContextEnum.PAGE_COMMENT.getRequestContext(), commentPageName, "nc=" + numComments ) + "\">" + |
| MessageFormat.format( addcomment, numComments ) + |
| "</a>" ); |
| } |
| |
| buffer.append("</div>\n"); |
| |
| // Done, close |
| buffer.append("</div>\n"); |
| } |
| |
| private int guessNumberOfComments( final Engine engine, final String commentpage ) { |
| final String pagedata = engine.getManager( PageManager.class ).getPureText( commentpage, WikiProvider.LATEST_VERSION ); |
| if( pagedata == null || pagedata.trim().length() == 0 ) { |
| return 0; |
| } |
| |
| return TextUtil.countSections( pagedata ); |
| } |
| |
| /** |
| * Attempts to locate all pages that correspond to the |
| * blog entry pattern. Will only consider the days on the dates; not the hours and minutes. |
| * |
| * @param engine Engine which is used to get the pages |
| * @param baseName The basename (e.g. "Main" if you want "Main_blogentry_xxxx") |
| * @param start The date which is the first to be considered |
| * @param end The end date which is the last to be considered |
| * @return a list of pages with their FIRST revisions. |
| */ |
| public List< Page > findBlogEntries( final Engine engine, String baseName, final Date start, final Date end ) { |
| final PageManager mgr = engine.getManager( PageManager.class ); |
| final Set< String > allPages = engine.getManager( ReferenceManager.class ).findCreated(); |
| final ArrayList< Page > result = new ArrayList<>(); |
| |
| baseName = makeEntryPage( baseName ); |
| |
| for( final String pageName : allPages ) { |
| if( pageName.startsWith( baseName ) ) { |
| try { |
| final Page firstVersion = mgr.getPageInfo( pageName, 1 ); |
| final Date d = firstVersion.getLastModified(); |
| |
| if( d.after( start ) && d.before( end ) ) { |
| result.add( firstVersion ); |
| } |
| } catch( final Exception e ) { |
| log.debug( "Page name :" + pageName + " was suspected as a blog entry but it isn't because of parsing errors", e ); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Reverse comparison. |
| */ |
| private static class PageDateComparator implements Comparator< Page > { |
| |
| /**{@inheritDoc}*/ |
| @Override |
| public int compare( final Page page1, final Page page2 ) { |
| if( page1 == null || page2 == null ) { |
| return 0; |
| } |
| return page2.getLastModified().compareTo( page1.getLastModified() ); |
| } |
| |
| } |
| |
| /** |
| * Mark us as being a real weblog. |
| * |
| * {@inheritDoc} |
| */ |
| @Override |
| public void executeParser( final PluginElement element, final Context context, final Map< String, String > params ) { |
| context.getPage().setAttribute( ATTR_ISWEBLOG, "true" ); |
| } |
| |
| } |