You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2008/02/13 06:54:24 UTC
svn commit: r627255 [21/41] - in
/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src: ./ com/ com/ecyrd/
com/ecyrd/jspwiki/ com/ecyrd/jspwiki/action/ com/ecyrd/jspwiki/attachment/
com/ecyrd/jspwiki/auth/ com/ecyrd/jspwiki/auth/acl/ com/ecyrd/jspwiki...
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/filters/SpamFilter.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/filters/SpamFilter.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/filters/SpamFilter.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/filters/SpamFilter.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,1140 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.filters;
+
+import java.io.*;
+import java.util.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.PageContext;
+
+import net.sf.akismet.Akismet;
+
+import org.apache.commons.jrcs.diff.*;
+import org.apache.commons.jrcs.diff.myers.MyersDiff;
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.log4j.Logger;
+import org.apache.oro.text.regex.*;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.action.CommentActionBean;
+import com.ecyrd.jspwiki.action.NoneActionBean;
+import com.ecyrd.jspwiki.action.ViewActionBean;
+import com.ecyrd.jspwiki.attachment.Attachment;
+import com.ecyrd.jspwiki.auth.user.UserProfile;
+import com.ecyrd.jspwiki.providers.ProviderException;
+import com.ecyrd.jspwiki.ui.EditorManager;
+import com.ecyrd.jspwiki.ui.WikiInterceptor;
+
+/**
+ * This is Herb, the JSPWiki spamfilter that can also do choke modifications.
+ *
+ * Parameters:
+ * <ul>
+ * <li>wordlist - Page name where the regexps are found. Use [{SET spamwords='regexp list separated with spaces'}] on
+ * that page. Default is "SpamFilterWordList".
+ * <li>blacklist - The name of an attachment containing the list of spam patterns, one per line. Default is
+ * "SpamFilterWordList/blacklist.txt"</li>
+ * <li>errorpage - The page to which the user is redirected. Has a special variable $msg which states the reason. Default is "RejectedMessage".
+ * <li>pagechangesinminute - How many page changes are allowed/minute. Default is 5.</li>
+ * <li>similarchanges - How many similar page changes are allowed before the host is banned. Default is 2. (since 2.4.72)</li>
+ * <li>bantime - How long an IP address stays on the temporary ban list (default is 60 for 60 minutes).</li>
+ * <li>maxurls - How many URLs can be added to the page before it is considered spam (default is 5)</li>
+ * <li>akismet-apikey - The Akismet API key (see akismet.org)</li>
+ * <li>ignoreauthenticated - If set to "true", all authenticated users are ignored and never caught in SpamFilter</li>
+ * <li>captcha - Sets the captcha technology to use. Current allowed values are "none" and "asirra".</li>
+ * <li>strategy - Sets the filtering strategy to use. If set to "eager", will stop at the first probable
+ * match, and won't consider any other tests. This is the default, as it's considerably lighter. If set to "score", will go through all of the tests
+ * and calculates a score for the spam, which is then compared to a filter level value.
+ * </ul>
+ *
+ * <p>Changes by admin users are ignored in any case.</p>
+ *
+ * @since 2.1.112
+ * @author Janne Jalkanen
+ */
+public class SpamFilter
+ extends BasicPageFilter
+{
+ private static final String ATTR_SPAMFILTER_SCORE = "spamfilter.score";
+ private static final String REASON_REGEXP = "Regexp";
+ private static final String REASON_IP_BANNED_TEMPORARILY = "IPBannedTemporarily";
+ private static final String REASON_BOT_TRAP = "BotTrap";
+ private static final String REASON_AKISMET = "Akismet";
+ private static final String REASON_TOO_MANY_URLS = "TooManyUrls";
+ private static final String REASON_SIMILAR_MODIFICATIONS = "SimilarModifications";
+ private static final String REASON_TOO_MANY_MODIFICATIONS = "TooManyModifications";
+ private static final String REASON_UTF8_TRAP = "UTF8Trap";
+
+ private static final String LISTVAR = "spamwords";
+ public static final String PROP_WORDLIST = "wordlist";
+ public static final String PROP_ERRORPAGE = "errorpage";
+ public static final String PROP_PAGECHANGES = "pagechangesinminute";
+ public static final String PROP_SIMILARCHANGES = "similarchanges";
+ public static final String PROP_BANTIME = "bantime";
+ public static final String PROP_BLACKLIST = "blacklist";
+ public static final String PROP_MAXURLS = "maxurls";
+ public static final String PROP_AKISMET_API_KEY = "akismet-apikey";
+ public static final String PROP_IGNORE_AUTHENTICATED = "ignoreauthenticated";
+ public static final String PROP_CAPTCHA = "captcha";
+ public static final String PROP_FILTERSTRATEGY = "strategy";
+
+ public static final String STRATEGY_EAGER = "eager";
+ public static final String STRATEGY_SCORE = "score";
+
+ private static final String URL_REGEXP = "(http://|https://|mailto:)([A-Za-z0-9_/\\.\\+\\?\\#\\-\\@=&;]+)";
+
+ private String m_forbiddenWordsPage = "SpamFilterWordList";
+ private String m_errorPage = "RejectedMessage";
+ private String m_blacklist = "SpamFilterWordList/blacklist.txt";
+
+ private PatternMatcher m_matcher = new Perl5Matcher();
+ private PatternCompiler m_compiler = new Perl5Compiler();
+
+ private Collection m_spamPatterns = null;
+
+ private Date m_lastRebuild = new Date( 0L );
+
+ static Logger spamlog = Logger.getLogger( "SpamLog" );
+ static Logger log = Logger.getLogger( SpamFilter.class );
+
+
+ private Vector m_temporaryBanList = new Vector();
+
+ private int m_banTime = 60; // minutes
+
+ private Vector m_lastModifications = new Vector();
+
+ /**
+ * How many times a single IP address can change a page per minute?
+ */
+ private int m_limitSinglePageChanges = 5;
+
+ /**
+ * How many times can you add the exact same string to a page?
+ */
+ private int m_limitSimilarChanges = 2;
+
+ /**
+ * How many URLs can be added at maximum.
+ */
+ private int m_maxUrls = 10;
+
+ private Pattern m_urlPattern;
+ private Akismet m_akismet;
+
+ private String m_akismetAPIKey = null;
+
+ private boolean m_useCaptcha = false;
+
+ /** The limit at which we consider something to be spam. */
+ private int m_scoreLimit = 1;
+
+ /**
+ * If set to true, will ignore anyone who is in Authenticated role.
+ */
+ private boolean m_ignoreAuthenticated = false;
+
+ private boolean m_stopAtFirstMatch = true;
+
+ public void initialize( WikiEngine engine, Properties properties )
+ {
+ m_forbiddenWordsPage = properties.getProperty( PROP_WORDLIST,
+ m_forbiddenWordsPage );
+ m_errorPage = properties.getProperty( PROP_ERRORPAGE,
+ m_errorPage );
+
+ m_limitSinglePageChanges = TextUtil.getIntegerProperty( properties,
+ PROP_PAGECHANGES,
+ m_limitSinglePageChanges );
+
+ m_limitSimilarChanges = TextUtil.getIntegerProperty( properties,
+ PROP_SIMILARCHANGES,
+ m_limitSimilarChanges );
+
+ m_maxUrls = TextUtil.getIntegerProperty( properties,
+ PROP_MAXURLS,
+ m_maxUrls );
+
+ m_banTime = TextUtil.getIntegerProperty( properties,
+ PROP_BANTIME,
+ m_banTime );
+
+ m_blacklist = properties.getProperty( PROP_BLACKLIST, m_blacklist );
+
+ m_ignoreAuthenticated = TextUtil.getBooleanProperty( properties,
+ PROP_IGNORE_AUTHENTICATED,
+ m_ignoreAuthenticated );
+
+ m_useCaptcha = properties.getProperty( PROP_CAPTCHA, "" ).equals("asirra");
+
+ try
+ {
+ m_urlPattern = m_compiler.compile( URL_REGEXP );
+ }
+ catch( MalformedPatternException e )
+ {
+ log.fatal("Internal error: Someone put in a faulty pattern.",e);
+ throw new InternalWikiException("Faulty pattern.");
+ }
+
+ m_akismetAPIKey = TextUtil.getStringProperty( properties,
+ PROP_AKISMET_API_KEY,
+ m_akismetAPIKey );
+
+ m_stopAtFirstMatch = TextUtil.getStringProperty( properties,
+ PROP_FILTERSTRATEGY,
+ STRATEGY_EAGER ).equals(STRATEGY_EAGER);
+
+ log.info("# Spam filter initialized. Temporary ban time "+m_banTime+
+ " mins, max page changes/minute: "+m_limitSinglePageChanges );
+
+
+ }
+
+ private static final int REJECT = 0;
+ private static final int ACCEPT = 1;
+ private static final int NOTE = 2;
+
+ private static String log( WikiContext ctx, int type, String source, String message )
+ {
+ message = TextUtil.replaceString( message, "\r\n", "\\r\\n" );
+ message = TextUtil.replaceString( message, "\"", "\\\"" );
+
+ String uid = getUniqueID();
+
+ String page = ctx.getPage().getName();
+ String reason = "UNKNOWN";
+ String addr = ctx.getHttpRequest() != null ? ctx.getHttpRequest().getRemoteAddr() : "-";
+
+ switch( type )
+ {
+ case REJECT:
+ reason = "REJECTED";
+ break;
+ case ACCEPT:
+ reason = "ACCEPTED";
+ break;
+ case NOTE:
+ reason = "NOTE";
+ break;
+ default:
+ throw new InternalWikiException("Illegal type "+type);
+ }
+
+ spamlog.info( reason+" "+source+" "+uid+" "+addr+" \""+page+"\" "+message );
+
+ return uid;
+ }
+
+
+ public String preSave( WikiContext context, String content )
+ throws RedirectException
+ {
+ cleanBanList();
+ refreshBlacklists(context);
+
+ String change = getChange( context, content );
+
+ if(!ignoreThisUser(context))
+ {
+ checkBanList( context, change );
+ checkSinglePageChange( context, content, change );
+ checkPatternList(context, content, change);
+ }
+
+ if( !m_stopAtFirstMatch )
+ {
+ Integer score = (Integer)context.getVariable(ATTR_SPAMFILTER_SCORE);
+
+ if( score != null && score.intValue() >= m_scoreLimit )
+ {
+ throw new RedirectException( "Herb says you got too many points",
+ getRedirectPage(context) );
+ }
+ }
+
+ log( context, ACCEPT, "-", change );
+ return content;
+ }
+
+ private void checkStrategy( WikiContext context, String error, String message )
+ throws RedirectException
+ {
+ if( m_stopAtFirstMatch )
+ {
+ throw new RedirectException( message, getRedirectPage(context) );
+ }
+
+ Integer score = (Integer)context.getVariable( ATTR_SPAMFILTER_SCORE );
+
+ if( score != null )
+ score = new Integer( score.intValue()+1 );
+ else
+ score = new Integer( 1 );
+
+ context.setVariable( ATTR_SPAMFILTER_SCORE, score );
+ }
+ /**
+ * Parses a list of patterns and returns a Collection of compiled Pattern
+ * objects.
+ *
+ * @param source
+ * @param list
+ * @return
+ */
+ private Collection parseWordList( WikiPage source, String list )
+ {
+ ArrayList compiledpatterns = new ArrayList();
+
+ if( list != null )
+ {
+ StringTokenizer tok = new StringTokenizer( list, " \t\n" );
+
+ while( tok.hasMoreTokens() )
+ {
+ String pattern = tok.nextToken();
+
+ try
+ {
+ compiledpatterns.add( m_compiler.compile( pattern ) );
+ }
+ catch( MalformedPatternException e )
+ {
+ log.debug( "Malformed spam filter pattern "+pattern );
+
+ source.setAttribute("error", "Malformed spam filter pattern "+pattern);
+ }
+ }
+ }
+
+ return compiledpatterns;
+ }
+
+ /**
+ * Takes a MT-Blacklist -formatted blacklist and returns a list of compiled
+ * Pattern objects.
+ *
+ * @param list
+ * @return
+ */
+ private Collection parseBlacklist( String list )
+ {
+ ArrayList compiledpatterns = new ArrayList();
+
+ if( list != null )
+ {
+ try
+ {
+ BufferedReader in = new BufferedReader( new StringReader(list) );
+
+ String line;
+
+ while( (line = in.readLine()) != null )
+ {
+ line = line.trim();
+ if( line.length() == 0 ) continue; // Empty line
+ if( line.startsWith("#") ) continue; // It's a comment
+
+ int ws = line.indexOf(' ');
+
+ if( ws == -1 ) ws = line.indexOf('\t');
+
+ if( ws != -1 ) line = line.substring(0,ws);
+
+ try
+ {
+ compiledpatterns.add( m_compiler.compile( line ) );
+ }
+ catch( MalformedPatternException e )
+ {
+ log.debug( "Malformed spam filter pattern "+line );
+ }
+ }
+ }
+ catch( IOException e )
+ {
+ log.info("Could not read patterns; returning what I got",e);
+ }
+ }
+
+ return compiledpatterns;
+ }
+
+ /**
+ * Takes a single page change and performs a load of tests on the content change.
+ * An admin can modify anything.
+ *
+ * @param context
+ * @param content
+ * @throws RedirectException
+ */
+ private synchronized void checkSinglePageChange( WikiContext context, String content, String change )
+ throws RedirectException
+ {
+ HttpServletRequest req = context.getHttpRequest();
+
+ if( req != null )
+ {
+ String addr = req.getRemoteAddr();
+ int hostCounter = 0;
+ int changeCounter = 0;
+
+ log.debug("Change is "+change);
+
+ long time = System.currentTimeMillis()-60*1000L; // 1 minute
+
+ for( Iterator i = m_lastModifications.iterator(); i.hasNext(); )
+ {
+ Host host = (Host)i.next();
+
+ //
+ // Check if this item is invalid
+ //
+ if( host.getAddedTime() < time )
+ {
+ log.debug("Removed host "+host.getAddress()+" from modification queue (expired)");
+ i.remove();
+ continue;
+ }
+
+ //
+ // Check if this IP address has been seen before
+ //
+
+ if( host.getAddress().equals(addr) )
+ {
+ hostCounter++;
+ }
+
+ //
+ // Check, if this change has been seen before
+ //
+
+ if( host.getChange() != null && host.getChange().equals(change) )
+ {
+ changeCounter++;
+ }
+ }
+
+ //
+ // Now, let's check against the limits.
+ //
+ if( hostCounter >= m_limitSinglePageChanges )
+ {
+ Host host = new Host( addr, null );
+
+ m_temporaryBanList.add( host );
+
+ String uid = log( context, REJECT, REASON_TOO_MANY_MODIFICATIONS, change );
+ log.info( "SPAM:TooManyModifications ("+uid+"). Added host "+addr+" to temporary ban list for doing too many modifications/minute" );
+ checkStrategy( context, REASON_TOO_MANY_MODIFICATIONS, "Herb says you look like a spammer, and I trust Herb! (Incident code "+uid+")" );
+ }
+
+ if( changeCounter >= m_limitSimilarChanges )
+ {
+ Host host = new Host( addr, null );
+
+ m_temporaryBanList.add( host );
+
+ String uid = log( context, REJECT, REASON_SIMILAR_MODIFICATIONS, change );
+
+ log.info("SPAM:SimilarModifications ("+uid+"). Added host "+addr+" to temporary ban list for doing too many similar modifications" );
+ checkStrategy( context, REASON_SIMILAR_MODIFICATIONS, "Herb says you look like a spammer, and I trust Herb! (Incident code "+uid+")");
+ }
+
+ //
+ // Calculate the number of links in the addition.
+ //
+
+ String tstChange = change;
+ int urlCounter = 0;
+
+ while( m_matcher.contains(tstChange,m_urlPattern) )
+ {
+ MatchResult m = m_matcher.getMatch();
+
+ tstChange = tstChange.substring( m.endOffset(0) );
+
+ urlCounter++;
+ }
+
+ if( urlCounter > m_maxUrls )
+ {
+ Host host = new Host( addr, null );
+
+ m_temporaryBanList.add( host );
+
+ String uid = log( context, REJECT, REASON_TOO_MANY_URLS, change );
+
+ log.info("SPAM:TooManyUrls ("+uid+"). Added host "+addr+" to temporary ban list for adding too many URLs" );
+ checkStrategy( context, REASON_TOO_MANY_URLS, "Herb says you look like a spammer, and I trust Herb! (Incident code "+uid+")" );
+ }
+
+ //
+ // Check bot trap
+ //
+
+ checkBotTrap( context, change );
+
+ //
+ // Check UTF-8 mangling
+ //
+
+ checkUTF8( context, change );
+
+ //
+ // Do Akismet check. This is good to be the last, because this is the most
+ // expensive operation.
+ //
+
+ checkAkismet( context, change );
+
+ m_lastModifications.add( new Host( addr, change ) );
+ }
+ }
+
+
+ /**
+ * Checks against the akismet system.
+ *
+ * @param context
+ * @param change
+ * @throws RedirectException
+ */
+ private void checkAkismet( WikiContext context, String change )
+ throws RedirectException
+ {
+ if( m_akismetAPIKey != null )
+ {
+ if( m_akismet == null )
+ {
+ log.info("Initializing Akismet spam protection.");
+
+ m_akismet = new Akismet( m_akismetAPIKey, context.getEngine().getBaseURL() );
+
+ if( !m_akismet.verifyAPIKey() )
+ {
+ log.error("Akismet API key cannot be verified. Please check your config.");
+ m_akismetAPIKey = null;
+ m_akismet = null;
+ }
+ }
+
+ HttpServletRequest req = context.getHttpRequest();
+
+ if( req != null && m_akismet != null )
+ {
+ log.debug("Calling Akismet to check for spam...");
+
+ StopWatch sw = new StopWatch();
+ sw.start();
+
+ String ipAddress = req.getRemoteAddr();
+ String userAgent = req.getHeader("User-Agent");
+ String referrer = req.getHeader( "Referer");
+ String permalink = context.getViewURL( context.getPage().getName() );
+ String commentType = context instanceof CommentActionBean ? "comment" : "edit";
+ String commentAuthor = context.getCurrentUser().getName();
+ String commentAuthorEmail = null;
+ String commentAuthorURL = null;
+
+ boolean isSpam = m_akismet.commentCheck( ipAddress,
+ userAgent,
+ referrer,
+ permalink,
+ commentType,
+ commentAuthor,
+ commentAuthorEmail,
+ commentAuthorURL,
+ change,
+ null );
+
+ sw.stop();
+
+ log.debug("Akismet request done in: "+sw);
+
+ if( isSpam )
+ {
+ // Host host = new Host( ipAddress, null );
+
+ // m_temporaryBanList.add( host );
+
+ String uid = log( context, REJECT, REASON_AKISMET, change );
+
+ log.info("SPAM:Akismet ("+uid+"). Akismet thinks this change is spam; added host to temporary ban list.");
+
+ checkStrategy( context, REASON_AKISMET, "Akismet tells Herb you're a spammer, Herb trusts Akismet, and I trust Herb! (Incident code "+uid+")");
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a static string which can be used to detect spambots which
+ * just wildly fill in all the fields.
+ *
+ * @return A string
+ */
+ public static String getBotFieldName()
+ {
+ return "submit_auth";
+ }
+
+ /**
+ * This checks whether an invisible field is available in the request, and
+ * whether it's contents are suspected spam.
+ *
+ * @param context
+ * @param change
+ * @throws RedirectException
+ */
+ private void checkBotTrap( WikiContext context, String change ) throws RedirectException
+ {
+ HttpServletRequest request = context.getHttpRequest();
+
+ if( request != null )
+ {
+ String unspam = request.getParameter( getBotFieldName() );
+ if( unspam != null && unspam.length() > 0 )
+ {
+ String uid = log( context, REJECT, REASON_BOT_TRAP, change );
+
+ log.info("SPAM:BotTrap ("+uid+"). Wildly behaving bot detected.");
+
+ checkStrategy( context, REASON_BOT_TRAP, "Spamming attempt detected. (Incident code "+uid+")");
+
+ }
+ }
+ }
+
+ private void checkUTF8( WikiContext context, String change ) throws RedirectException
+ {
+ HttpServletRequest request = context.getHttpRequest();
+
+ if( request != null )
+ {
+ String utf8field = request.getParameter( "encodingcheck" );
+
+ if( utf8field != null && !utf8field.equals("\u3041") )
+ {
+ String uid = log( context, REJECT, REASON_UTF8_TRAP, change );
+
+ log.info("SPAM:UTF8Trap ("+uid+"). Wildly posting dumb bot detected.");
+
+ checkStrategy( context, REASON_UTF8_TRAP, "Spamming attempt detected. (Incident code "+uid+")");
+ }
+ }
+ }
+
+ /**
+ * Goes through the ban list and cleans away any host which has expired from it.
+ */
+ private synchronized void cleanBanList()
+ {
+ long now = System.currentTimeMillis();
+
+ for( Iterator i = m_temporaryBanList.iterator(); i.hasNext(); )
+ {
+ Host host = (Host)i.next();
+
+ if( host.getReleaseTime() < now )
+ {
+ log.debug("Removed host "+host.getAddress()+" from temporary ban list (expired)");
+ i.remove();
+ }
+ }
+ }
+
+ /**
+ * Checks the ban list if the IP address of the changer is already on it.
+ *
+ * @param context
+ * @throws RedirectException
+ */
+
+ private void checkBanList( WikiContext context, String change )
+ throws RedirectException
+ {
+ HttpServletRequest req = context.getHttpRequest();
+
+ if( req != null )
+ {
+ String remote = req.getRemoteAddr();
+
+ long now = System.currentTimeMillis();
+
+ for( Iterator i = m_temporaryBanList.iterator(); i.hasNext(); )
+ {
+ Host host = (Host)i.next();
+
+ if( host.getAddress().equals(remote) )
+ {
+ long timeleft = (host.getReleaseTime() - now) / 1000L;
+
+ log( context, REJECT, REASON_IP_BANNED_TEMPORARILY, change );
+
+ checkStrategy( context, REASON_IP_BANNED_TEMPORARILY, "You have been temporarily banned from modifying this wiki. ("+timeleft+" seconds of ban left)");
+ }
+ }
+ }
+
+ }
+
+ /**
+ * If the spam filter notices changes in the black list page, it will refresh
+ * them automatically.
+ *
+ * @param context
+ */
+ private void refreshBlacklists( WikiContext context )
+ {
+ try
+ {
+ WikiPage source = context.getEngine().getPage( m_forbiddenWordsPage );
+ Attachment att = context.getEngine().getAttachmentManager().getAttachmentInfo( context, m_blacklist );
+
+ boolean rebuild = false;
+
+ //
+ // Rebuild, if the page or the attachment has changed since.
+ //
+ if( source != null )
+ {
+ if( m_spamPatterns == null || m_spamPatterns.isEmpty() || source.getLastModified().after(m_lastRebuild) )
+ {
+ rebuild = true;
+ }
+ }
+
+ if( att != null )
+ {
+ if( m_spamPatterns == null || m_spamPatterns.isEmpty() || att.getLastModified().after(m_lastRebuild) )
+ {
+ rebuild = true;
+ }
+ }
+
+
+ //
+ // Do the actual rebuilding. For simplicity's sake, we always rebuild the complete
+ // filter list regardless of what changed.
+ //
+
+ if( rebuild )
+ {
+ m_lastRebuild = new Date();
+
+ m_spamPatterns = parseWordList( source,
+ (source != null) ? (String)source.getAttribute( LISTVAR ) : null );
+
+ log.info("Spam filter reloaded - recognizing "+m_spamPatterns.size()+" patterns from page "+m_forbiddenWordsPage);
+
+ if( att != null )
+ {
+ InputStream in = context.getEngine().getAttachmentManager().getAttachmentStream(att);
+
+ StringWriter out = new StringWriter();
+
+ FileUtil.copyContents( new InputStreamReader(in,"UTF-8"), out );
+
+ Collection blackList = parseBlacklist( out.toString() );
+
+ log.info("...recognizing additional "+blackList.size()+" patterns from blacklist "+m_blacklist);
+
+ m_spamPatterns.addAll( blackList );
+ }
+ }
+ }
+ catch( IOException ex )
+ {
+ log.info("Unable to read attachment data, continuing...",ex);
+ }
+ catch( ProviderException ex )
+ {
+ log.info("Failed to read spam filter attachment, continuing...",ex);
+ }
+
+ }
+
+ /**
+ * Does a check against a known pattern list.
+ *
+ * @param context
+ * @param content
+ * @param change
+ * @throws RedirectException
+ */
+ private void checkPatternList(WikiContext context, String content, String change) throws RedirectException
+ {
+ //
+ // If we have no spam patterns defined, or we're trying to save
+ // the page containing the patterns, just return.
+ //
+ if( m_spamPatterns == null || context.getPage().getName().equals( m_forbiddenWordsPage ) )
+ {
+ return;
+ }
+
+ if( context.getHttpRequest() != null )
+ change += context.getHttpRequest().getRemoteAddr();
+
+ for( Iterator i = m_spamPatterns.iterator(); i.hasNext(); )
+ {
+ Pattern p = (Pattern) i.next();
+
+ // log.debug("Attempting to match page contents with "+p.getPattern());
+
+ if( m_matcher.contains( change, p ) )
+ {
+ //
+ // Spam filter has a match.
+ //
+ String uid = log( context, REJECT, REASON_REGEXP+"("+p.getPattern()+")", change);
+
+ log.info("SPAM:Regexp ("+uid+"). Content matches the spam filter '"+p.getPattern()+"'");
+
+ checkStrategy( context, REASON_REGEXP, "Herb says '"+p.getPattern()+"' is a bad spam word and I trust Herb! (Incident code "+uid+")");
+ }
+ }
+ }
+
+ /**
+ * Creates a simple text string describing the added content.
+ *
+ * @param context
+ * @param newText
+ * @return Empty string, if there is no change.
+ */
+ private static String getChange( WikiContext context, String newText )
+ {
+ WikiPage page = context.getPage();
+ StringBuffer change = new StringBuffer();
+ WikiEngine engine = context.getEngine();
+ // Get current page version
+
+ try
+ {
+ String oldText = engine.getPureText(page.getName(), WikiProvider.LATEST_VERSION);
+
+ String[] first = Diff.stringToArray(oldText);
+ String[] second = Diff.stringToArray(newText);
+ Revision rev = Diff.diff(first, second, new MyersDiff());
+
+ if( rev == null || rev.size() == 0 )
+ {
+ return "";
+ }
+
+
+ for( int i = 0; i < rev.size(); i++ )
+ {
+ Delta d = rev.getDelta(i);
+
+ if( d instanceof AddDelta )
+ {
+ d.getRevised().toString( change, "", "\r\n" );
+ }
+ else if( d instanceof ChangeDelta )
+ {
+ d.getRevised().toString( change, "", "\r\n" );
+ }
+ }
+ }
+ catch (DifferentiationFailedException e)
+ {
+ log.error( "Diff failed", e );
+ }
+
+ //
+ // Don't forget to include the change note, too
+ //
+ String changeNote = (String)page.getAttribute(WikiPage.CHANGENOTE);
+
+ if( changeNote != null )
+ {
+ change.append("\r\n");
+ change.append(changeNote);
+ }
+
+ //
+ // And author as well
+ //
+
+ if( page.getAuthor() != null )
+ {
+ change.append("\r\n"+page.getAuthor());
+ }
+
+ return change.toString();
+ }
+
+ /**
+ * Returns true, if this user should be ignored.
+ *
+ * @param context
+ * @return
+ */
+ private boolean ignoreThisUser(WikiContext context)
+ {
+ if( context.hasAdminPermissions() )
+ {
+ return true;
+ }
+
+ if( m_ignoreAuthenticated && context.getWikiSession().isAuthenticated() )
+ {
+ return true;
+ }
+
+ if( context.getVariable("captcha") != null )
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a random string of six uppercase characters.
+ *
+ * @return A random string
+ */
+ private static String getUniqueID()
+ {
+ StringBuffer sb = new StringBuffer();
+ Random rand = new Random();
+
+ for( int i = 0; i < 6; i++ )
+ {
+ char x = (char)('A'+rand.nextInt(26));
+
+ sb.append(x);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns a page to which we shall redirect, based on the current value
+ * of the "captcha" parameter.
+ *
+ * @param ctx WikiContext
+ * @return An URL to redirect to
+ */
+ private String getRedirectPage( WikiContext ctx )
+ {
+ Map<String,String> urlParams = new HashMap<String,String>();
+ if( m_useCaptcha )
+ {
+ urlParams.put( "page", ctx.getEngine().encodeName( ctx.getPage().getName() ) );
+ return ctx.getContext().getURL( NoneActionBean.class, "Captcha.jsp", urlParams );
+ }
+
+ return ctx.getContext().getURL( ViewActionBean.class, m_errorPage);
+ }
+
+ /**
+ * Checks whether the UserProfile matches certain checks.
+ *
+ * @param profile
+ * @return
+ * @since 2.6.1
+ */
+ public boolean isValidUserProfile( WikiContext context, UserProfile profile )
+ {
+ try
+ {
+ checkPatternList( context, profile.getEmail(), profile.getEmail() );
+ checkPatternList( context, profile.getFullname(), profile.getFullname() );
+ checkPatternList( context, profile.getLoginName(), profile.getLoginName() );
+ }
+ catch( RedirectException e )
+ {
+ log.info("Detected attempt to create a spammer user account (see above for rejection reason)");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * This method is used to calculate an unique code when submitting the page
+ * to detect edit conflicts. It currently incorporates the last-modified
+ * date of the page, and the IP address of the submitter.
+ *
+ * @param page The WikiPage under edit
+ * @param request The HTTP Request
+ * @since 2.6
+ * @return A hash value for this page and session
+ */
+ public static final String getSpamHash( WikiPage page, HttpServletRequest request )
+ {
+ long lastModified = 0;
+
+ if( page.getLastModified() != null )
+ lastModified = page.getLastModified().getTime();
+
+ long remote = request.getRemoteAddr().hashCode();
+
+ return Long.toString( lastModified ^ remote );
+ }
+
+ /**
+ * Returns the name of the hash field to be used in this request.
+ * The value is unique per session, and once the session has expired,
+ * you cannot edit anymore.
+ *
+ * @param request The page request
+ * @return The name to be used in the hash field
+ * @since 2.6
+ */
+
+ private static String c_hashName;
+ private static long c_lastUpdate;
+
+ /** The HASH_DELAY value is a maximum amount of time that an user can keep
+ * a session open, because after the value has expired, we will invent a new
+ * hash field name. By default this is {@value} hours, which should be ample
+ * time for someone.
+ */
+ private static final long HASH_DELAY = 24;
+
+ public static final String getHashFieldName( HttpServletRequest request )
+ {
+ String hash = null;
+
+ if( request.getSession() != null )
+ {
+ hash = (String)request.getSession().getAttribute("_hash");
+
+ if( hash == null )
+ {
+ hash = c_hashName;
+
+ request.getSession().setAttribute( "_hash", hash );
+ }
+ }
+
+ if( c_hashName == null || c_lastUpdate < (System.currentTimeMillis() - HASH_DELAY*60*60*1000) )
+ {
+ c_hashName = getUniqueID().toLowerCase();
+
+ c_lastUpdate = System.currentTimeMillis();
+ }
+
+ return hash != null ? hash : c_hashName;
+ }
+
+
+ /**
+ * This method checks if the hash value is still valid, i.e. if it exists at all. This
+ * can occur in two cases: either this is a spam bot which is not adaptive, or it is
+ * someone who has been editing one page for too long, and their session has expired.
+ * <p>
+ * This method puts a redirect to the http response field to page "SessionExpired"
+ * and logs the incident in the spam log (it may or may not be spam, but it's rather likely
+ * that it is).
+ *
+ * @param context
+ * @param pageContext
+ * @return True, if hash is okay. False, if hash is not okay, and you need to redirect.
+ * @throws IOException If redirection fails
+ * @since 2.6
+ */
+ public static final boolean checkHash( WikiContext context, PageContext pageContext )
+ throws IOException
+ {
+ String hashName = getHashFieldName( (HttpServletRequest)pageContext.getRequest() );
+
+ if( pageContext.getRequest().getParameter(hashName) == null )
+ {
+ if( pageContext.getAttribute( hashName ) == null )
+ {
+ String change = getChange( context, EditorManager.getEditedText( pageContext ) );
+
+ log( context, REJECT, "MissingHash", change );
+
+ String redirect = context.getContext().getURL( ViewActionBean.class, "SessionExpired");
+ ((HttpServletResponse)pageContext.getResponse()).sendRedirect( redirect );
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static final String insertInputFields( PageContext pageContext )
+ {
+ WikiEngine engine = (WikiEngine)pageContext.getAttribute( WikiInterceptor.ATTR_WIKIENGINE, PageContext.REQUEST_SCOPE );
+
+ StringBuffer sb = new StringBuffer();
+ if (engine.getContentEncoding().equals("UTF-8"))
+ {
+ sb.append("<input name='encodingcheck' type='hidden' value='\u3041' />\n");
+ }
+
+ return sb.toString();
+ }
+ /**
+ * A local class for storing host information.
+ *
+ * @author jalkanen
+ *
+ * @since
+ */
+ private class Host
+ {
+ private long m_addedTime = System.currentTimeMillis();
+ private long m_releaseTime;
+ private String m_address;
+ private String m_change;
+
+ public String getAddress()
+ {
+ return m_address;
+ }
+
+ public long getReleaseTime()
+ {
+ return m_releaseTime;
+ }
+
+ public long getAddedTime()
+ {
+ return m_addedTime;
+ }
+
+ public String getChange()
+ {
+ return m_change;
+ }
+
+ public Host( String ipaddress, String change )
+ {
+ m_address = ipaddress;
+ m_change = change;
+
+ m_releaseTime = System.currentTimeMillis() + m_banTime * 60 * 1000L;
+ }
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormClose.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormClose.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormClose.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormClose.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,66 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.plugin.PluginException;
+import com.ecyrd.jspwiki.plugin.WikiPlugin;
+
+import java.util.*;
+
+/**
+ * Closes a WikiForm.
+ *
+ * @author ebu
+ */
+public class FormClose
+ extends FormElement
+{
+ /**
+ * Builds a Form close tag. Removes any information on the form from
+ * the WikiContext.
+ */
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ StringBuffer tags = new StringBuffer();
+ tags.append( "</form>\n" );
+ tags.append( "</div>" );
+
+ // Don't render if no error and error-only-rendering is on.
+ FormInfo info = getFormInfo( ctx );
+ if( info != null )
+ {
+ if( info.hide() )
+ {
+ ResourceBundle rb = ctx.getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
+ return( "<p>" + rb.getString( "formclose.noneedtoshow" ) + "</p>" );
+ }
+ }
+
+ // Get rid of remaining form data, so it doesn't mess up other forms.
+ // After this, it is safe to add other Forms.
+ storeFormInfo( ctx, null );
+
+ return tags.toString();
+
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormElement.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormElement.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormElement.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormElement.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,100 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.plugin.WikiPlugin;
+
+/**
+ */
+public abstract class FormElement
+ implements WikiPlugin
+{
+ /**
+ * In order to make the form-to-handler parameter transfer easier,
+ * we prefix all user-specified FORM element names with HANDLERPARAM_PREFIX
+ * the HTML output. This lets us differentiate user-defined FormHandler
+ * parameters from Form parameters.
+ * The submit handler must then use MapUtil.requestToMap() to
+ * strip them before executing the handler itself.
+ */
+ public static final String HANDLERPARAM_PREFIX = "nbf_";
+
+ /**
+ * The submit servlet may decide to store a FormInfo with user-entered
+ * form values in the Session. It should use this name, and this class
+ * checks for it to pre-fill fields from a previous form submit.
+ */
+ public static final String FORM_VALUES_CARRIER = "nbpf_values";
+
+ // Show values:
+ public static final String HIDE_SUCCESS = "onsuccess";
+
+ // Parameter names:
+ /** Plugin parameter, optional, indicates servlet to post to. */
+ public static final String PARAM_SUBMITHANDLER = "submit";
+ /** Plugin parameter, mandatory, indicates what form element to insert. */
+ public static final String PARAM_ELEMENT = "element";
+ /**
+ * Plugin parameter, mandatory in output element, indicates
+ * WikiPlugin to use to handle form submitted data.
+ */
+ public static final String PARAM_HANDLER = "handler";
+ /** Plugin parameter, mandatory in open/output: name of the form. */
+ public static final String PARAM_FORM = "form";
+ /** Plugin parameter, mandatory in input elements: name of an element. */
+ public static final String PARAM_INPUTNAME = "name";
+ /** Plugin parameter, optional: default value for an input. */
+ public static final String PARAM_VALUE = "value";
+ /** Experimental: hide the form if it was submitted successfully. */
+ public static final String PARAM_HIDEFORM = "hide";
+
+ /** If set to 'handler' in output element, the handler plugin is
+ * called even on first invocation (no submit). The plugin can
+ * then place values into its parameter map, and these are seen by
+ * subsequent Form elements. (Store a value in the plugin with the
+ * same key as an input element, and the value will be visible in
+ * the initial form.)
+ */
+ public static final String PARAM_POPULATE = "populate";
+ /** HTTP parameter, inserted as hidden variable into the generated form. */
+ public static final String PARAM_FORMNAMEHIDDEN = "formname";
+
+ // Key to store the form info container in the context by:
+ //public static final String CONTEXT_FORMINFO = "FormPluginInfo";
+
+ /**
+ * Utility method stores a FormInfo object into the WikiContext.
+ */
+ protected void storeFormInfo( WikiContext ctx, FormInfo info )
+ {
+ ctx.setVariable( FORM_VALUES_CARRIER, info );
+ }
+
+ /**
+ * Attempts to retrieve information on the currently handled
+ * form from the WikiContext.
+ */
+ protected FormInfo getFormInfo( WikiContext ctx )
+ {
+ return (FormInfo)ctx.getVariable( FORM_VALUES_CARRIER );
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormHandler.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormHandler.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormHandler.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormHandler.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,36 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+
+/**
+ * A FormHandler performs logic based on input from an
+ * HTTP FORM, transmitted through a JSPWiki WikiPlugin
+ * (see Form.java).
+ *
+ * <P>This interface is currently empty and unused. It acts
+ * as a place holder: we probably want to switch from
+ * WikiPlugins to FormHandlers in Form.java, to enforce
+ * authentication, form execution permissions, and so on.
+ */
+public interface FormHandler
+{
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInfo.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInfo.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInfo.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInfo.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,160 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Container for carrying HTTP FORM information between
+ * WikiPlugin invocations in the Session.
+ *
+ * @author ebu
+ */
+public class FormInfo
+ implements Serializable
+{
+ private static final long serialVersionUID = 0L;
+
+ public static final int EXECUTED = 1;
+ public static final int OK = 0;
+ public static final int ERROR = -1;
+
+ public int m_status;
+ public boolean m_hide;
+ public String m_action;
+ public String m_name;
+ public String m_handler;
+ public String m_result;
+ public String m_error;
+ //public PluginParameters submission;
+ public Map m_submission;
+
+ public FormInfo()
+ {
+ m_status = OK;
+ }
+
+ public void setStatus( int val )
+ {
+ m_status = val;
+ }
+
+ public int getStatus()
+ {
+ return m_status;
+ }
+
+ public void setHide( boolean val )
+ {
+ m_hide = val;
+ }
+
+ public boolean hide()
+ {
+ return m_hide;
+ }
+
+ public void setAction( String val )
+ {
+ m_action = val;
+ }
+
+ public String getAction()
+ {
+ return m_action;
+ }
+
+ public void setName( String val )
+ {
+ m_name = val;
+ }
+
+ public String getName()
+ {
+ return m_name;
+ }
+
+ public void setHandler( String val )
+ {
+ m_handler = val;
+ }
+
+ public String getHandler()
+ {
+ return m_handler;
+ }
+
+ public void setResult( String val )
+ {
+ m_result = val;
+ }
+
+ public String getResult()
+ {
+ return m_result;
+ }
+
+ public void setError( String val )
+ {
+ m_error = val;
+ }
+
+ public String getError()
+ {
+ return m_error;
+ }
+
+ /**
+ * Copies the given values into the handler parameter map using Map.putAll().
+ * @param val parameter name-value pairs for a Form handler WikiPlugin
+ */
+ public void setSubmission( Map val )
+ {
+ m_submission = new HashMap();
+ m_submission.putAll( val );
+ }
+
+ /**
+ * Adds the given values into the handler parameter map.
+ * @param val parameter name-value pairs for a Form handler WikiPlugin
+ */
+ public void addSubmission( Map val )
+ {
+ if( m_submission == null )
+ m_submission = new HashMap();
+ m_submission.putAll( val );
+ }
+
+ /**
+ * Returns parameter name-value pairs for a Form handler WikiPlugin.
+ * The names are those of Form input fields, and the values whatever
+ * the user selected in the form. The FormSet plugin can also be used
+ * to provide initial values.
+ *
+ * @return Handler parameter name-value pairs.
+ */
+ public Map getSubmission()
+ {
+ return m_submission;
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInput.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInput.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInput.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormInput.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,92 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.plugin.PluginException;
+import java.util.*;
+
+import org.apache.ecs.xhtml.input;
+
+/**
+ * Creates a simple input text field.
+ */
+public class FormInput
+ extends FormElement
+{
+ public static final String PARAM_TYPE = "type";
+ public static final String PARAM_SIZE = "size";
+
+ /**
+ * Generates a dynamic form element on the WikiPage.
+ */
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ String inputName = (String)params.get( PARAM_INPUTNAME );
+ String inputValue = (String)params.get( PARAM_VALUE );
+ String inputType = (String)params.get( PARAM_TYPE );
+ String size = (String)params.get( PARAM_SIZE );
+
+ if( inputName == null )
+ throw new PluginException( "Input element is missing parameter 'name'." );
+ if( inputValue == null )
+ inputValue = "";
+
+ // Don't render if no error and error-only-rendering is on.
+ FormInfo info = getFormInfo( ctx );
+ Map previousValues = null;
+ if( info != null )
+ {
+ if( info.hide() )
+ {
+ return( "<p>(no need to show input field now)</p>" );
+ }
+ previousValues = info.getSubmission();
+ }
+
+ if( previousValues == null )
+ {
+ previousValues = new HashMap();
+ }
+
+ // In order to isolate posted form elements into their own
+ // map, prefix the variable name here. It will be stripped
+ // when the handler plugin is executed.
+ input field = new input( inputType,
+ HANDLERPARAM_PREFIX + inputName,
+ inputValue );
+
+ String checked = (String)params.get("checked");
+ field.setChecked( TextUtil.isPositive(checked)
+ || "checked".equalsIgnoreCase(checked) );
+
+ String oldValue = (String)previousValues.get( inputName );
+ if( oldValue != null )
+ {
+ field.setValue( oldValue );
+ }
+
+ if( size != null ) field.setSize( size );
+
+ return field.toString(ctx.getEngine().getContentEncoding());
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOpen.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOpen.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOpen.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOpen.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,152 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import java.text.MessageFormat;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.action.ViewActionBean;
+import com.ecyrd.jspwiki.plugin.PluginException;
+import com.ecyrd.jspwiki.plugin.WikiPlugin;
+
+/**
+ * Opens a WikiForm.
+ *
+ * Builds the HTML code for opening a FORM.
+ *
+ * <p>Since we're only providing an opening FORM tag, we can't use
+ * the ECS utilities.
+ *
+ * A Form plugin line that produces one looks like this:
+ * <p><pre>
+ * [{FormOpen name='formname' handler='pluginname'
+ * submit='submitservlet'
+ * show='always'
+ * }]
+ * </pre>
+ *
+ * <p>Mandatory parameters:
+ * <br>The <i>name</i> field identifies this particular form to the
+ * Form plugin across pages.
+ * <br>The <i>handler</i> field is a WikiPlugin name; it will be
+ * invoked with the form field values.
+ *
+ * <p>Optional parameters:
+ * <p>The submitservlet is the name of a JSP/servlet capable of
+ * handling the input from this form. It is optional; the default
+ * value is the current page (which can handle the input by using
+ * this Plugin.)
+ *
+ * <p>The <i>hide</i> parameter affects the visibility of this
+ * form. If left out, the form is always shown. If set to
+ * 'onsuccess', the form is not shown if it was submitted
+ * successfully. (Note that a reload of the page would cause the
+ * context to reset, and the form would be shown again. This may
+ * be a useless option.)
+ *
+ * @author ebu
+ */
+public class FormOpen
+ extends FormElement
+{
+ private static org.apache.log4j.Logger log =
+ org.apache.log4j.Logger.getLogger( FormOpen.class );
+
+ public static final String PARAM_METHOD = "method";
+
+ /**
+ */
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ ResourceBundle rb = ctx.getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
+ String formName = (String)params.get( PARAM_FORM );
+ if( formName == null )
+ {
+ Object[] args = { PARAM_FORM };
+ throw new PluginException( MessageFormat.format( rb.getString( "formopen.missingparam" ), args ) );
+ }
+ String hide = (String)params.get( PARAM_HIDEFORM );
+ String sourcePage = ctx.getPage().getName();
+ String submitServlet = (String)params.get( PARAM_SUBMITHANDLER );
+ if( submitServlet == null )
+ submitServlet = ctx.getContext().getURL( ViewActionBean.class, sourcePage );
+
+ String method = (String)params.get( PARAM_METHOD );
+ if( method == null ) method="post";
+
+ if( !(method.equalsIgnoreCase("get") || method.equalsIgnoreCase("post")) )
+ {
+ throw new PluginException( rb.getString( "formopen.postorgetonly" ) );
+ }
+
+ FormInfo info = getFormInfo( ctx );
+ if( info != null )
+ {
+ // Previous information may be the result of submitting
+ // this form, or of a FormSet plugin, or both. If it
+ // exists and is for this form, fine.
+ if( formName.equals( info.getName() ) )
+ {
+ log.debug( "Previous FormInfo for this form was found in context." );
+ // If the FormInfo exists, and if we're supposed to display on
+ // error only, we need to exit now.
+ if( hide != null &&
+ HIDE_SUCCESS.equals( hide ) &&
+ info.getStatus() == FormInfo.EXECUTED )
+ {
+ info.setHide( true );
+ return( "<p>" + rb.getString( "formopen.noneedtoshow" ) + "</p>" );
+ }
+ }
+ else
+ {
+ // This would mean that a new form was started without
+ // closing an old one. Get rid of the garbage.
+ info = new FormInfo();
+ }
+ }
+ else
+ {
+ // No previous FormInfo available; store now, so it'll be
+ // available for upcoming Form input elements.
+ info = new FormInfo();
+ storeFormInfo( ctx, info );
+ }
+
+ info.setName( formName );
+ info.setAction( submitServlet );
+
+ StringBuffer tag = new StringBuffer( 40 );
+ tag.append( "<div class=\"wikiform\">\n" );
+ tag.append( "<form action=\"" + submitServlet );
+ tag.append( "\" name=\"" + formName );
+ tag.append( "\" accept-charset=\"" + ctx.getEngine().getContentEncoding() );
+ tag.append( "\" method=\""+method+"\" enctype=\"application/x-www-form-urlencoded\">\n" );
+ tag.append( " <input type=\"hidden\" name=\"" + PARAM_FORMNAMEHIDDEN );
+ tag.append( "\" value=\"" + formName + "\"/>\n" );
+
+ return tag.toString();
+ }
+
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOutput.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOutput.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOutput.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormOutput.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,149 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import java.text.MessageFormat;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.action.ViewActionBean;
+import com.ecyrd.jspwiki.plugin.PluginException;
+import com.ecyrd.jspwiki.plugin.PluginManager;
+import com.ecyrd.jspwiki.plugin.WikiPlugin;
+import com.ecyrd.jspwiki.util.FormUtil;
+
+/**
+ */
+public class FormOutput
+ extends FormElement
+{
+ /**
+ * Executes the FormHandler specified in a Form 'output' plugin,
+ * using entries provided in the HttpRequest as FormHandler
+ * parameters.
+ * <p>
+ * If the parameter 'populate' was given, the WikiPlugin it names
+ * is used to get default values. (It probably makes a lot of
+ * sense for this to be the same plugin as the handler.)
+ * Information for the populator can be given with the FormSet
+ * plugin. If 'populate' is not specified, the form is not
+ * displayed.
+ * <p>
+ * Should there be no HTTP request associated with this request,
+ * the method will return immediately with an empty string.
+ */
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ //
+ // If there is no HTTP request, returns immediately.
+ //
+ if( ctx.getHttpRequest() == null )
+ {
+ return "";
+ }
+ ResourceBundle rb = ctx.getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
+
+ // If we are NOT here due to this form being submitted, we do nothing.
+ // The submitted form MUST have parameter 'formname' equal to the name
+ // parameter of this Form plugin.
+
+ String formName = (String)params.get( PARAM_FORM );
+ String submitForm = ctx.getHttpParameter( PARAM_FORMNAMEHIDDEN );
+ String populator = (String)params.get( PARAM_POPULATE );
+
+ if( submitForm == null || formName == null ||
+ !formName.equals( submitForm ) )
+ {
+ // No submitForm -> this was not a submission from the
+ // generated form. If populate is specified, we'll go
+ // ahead and let the handler (populator) put stuff into
+ // the context, otherwise we'll just hide.
+ if( populator == null || !PARAM_HANDLER.equals( populator ) )
+ return "";
+ // If population was allowed, we should first
+ }
+
+ String handler = (String)params.get( PARAM_HANDLER );
+ if( handler == null || handler.length() == 0 )
+ {
+ Object[] args = { PARAM_HANDLER };
+ // Need to print out an error here as this form is misconfigured
+ return "<p class=\"error\">" + MessageFormat.format( rb.getString( "formoutput.missingargument" ), args ) + "</p>";
+ }
+
+ String sourcePage = ctx.getPage().getName();
+ String submitServlet = ctx.getContext().getURL( ViewActionBean.class, sourcePage );
+
+ // If there is previous FormInfo available - say, from a
+ // FormSet plugin - use it.
+ FormInfo info = getFormInfo( ctx );
+ if( info == null )
+ {
+ // Reconstruct the form info from post data
+ info = new FormInfo();
+ info.setName( formName );
+ }
+ // Force override of handler and submit.
+ info.setHandler( handler );
+ info.setAction( submitServlet );
+
+ // Sift out all extra parameters, leaving only those submitted
+ // in the HTML FORM.
+ Map handlerParams = FormUtil.requestToMap( ctx.getHttpRequest(),
+ HANDLERPARAM_PREFIX );
+ // Previous submission info may be available from FormSet
+ // plugin - add, don't replace.
+ info.addSubmission( handlerParams );
+
+ // Pass the _body parameter from FormOutput on to the handler
+ info.getSubmission().put(PluginManager.PARAM_BODY,
+ params.get(PluginManager.PARAM_BODY));
+
+ String handlerOutput = null;
+ String error = null;
+ try
+ {
+ // The plugin _can_ modify the parameters, so we make sure
+ // they stay with us.
+ handlerOutput = ctx.getEngine().getPluginManager().execute( ctx, handler, info.getSubmission() );
+ info.setResult( handlerOutput );
+ info.setStatus( FormInfo.EXECUTED );
+ }
+ catch( PluginException pe )
+ {
+ error = "<p class=\"error\">" + pe.getMessage() + "</p>";
+ info.setError( error );
+ info.setStatus( FormInfo.ERROR );
+ }
+
+ // We store the forminfo, so following Form plugin invocations on this
+ // page can decide what to do based on its values.
+ storeFormInfo( ctx, info );
+
+ if( error != null )
+ return error;
+
+ return handlerOutput != null ? handlerOutput : "";
+ }
+
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSelect.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSelect.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSelect.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSelect.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,176 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.plugin.PluginException;
+import com.ecyrd.jspwiki.plugin.WikiPlugin;
+
+import java.util.*;
+
+import org.apache.ecs.ConcreteElement;
+import org.apache.ecs.xhtml.option;
+import org.apache.ecs.xhtml.select;
+
+/**
+ * @author ebu
+ */
+public class FormSelect
+ extends FormElement
+{
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ // Don't render if no error and error-only-rendering is on.
+ FormInfo info = getFormInfo( ctx );
+
+ ResourceBundle rb = ctx.getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
+ Map previousValues = null;
+
+ if( info != null )
+ {
+ if( info.hide() )
+ {
+ return "<p>" + rb.getString( "forminput.noneedtoshow" ) + "</p>";
+ }
+ previousValues = info.getSubmission();
+ }
+
+ if( previousValues == null )
+ {
+ previousValues = new HashMap();
+ }
+
+ ConcreteElement field = null;
+
+ field = buildSelect( params, previousValues, rb );
+
+ // We should look for extra params, e.g. width, ..., here.
+ if( field != null )
+ return field.toString(ctx.getEngine().getContentEncoding());
+
+ return "";
+ }
+
+
+ /**
+ * Builds a Select element.
+ */
+ private select buildSelect( Map pluginParams, Map ctxValues, ResourceBundle rb )
+ throws PluginException
+ {
+ String inputName = (String)pluginParams.get( PARAM_INPUTNAME );
+ if( inputName == null )
+ {
+ throw new PluginException( rb.getString( "formselect.namemissing" ) );
+ }
+
+ String inputValue = (String)pluginParams.get( PARAM_VALUE );
+ String previousValue = (String)ctxValues.get( inputName );
+ //
+ // We provide several ways to override the separator, in case
+ // some input application the default value.
+ //
+ String optionSeparator = (String)pluginParams.get( "separator" );
+ if( optionSeparator == null )
+ optionSeparator = (String)ctxValues.get( "separator." + inputName);
+ if( optionSeparator == null )
+ optionSeparator = (String)ctxValues.get( "select.separator" );
+ if( optionSeparator == null )
+ optionSeparator = ";";
+
+ String optionSelector = (String)pluginParams.get( "selector" );
+ if( optionSelector == null )
+ optionSelector = (String)ctxValues.get( "selector." + inputName );
+ if( optionSelector == null )
+ optionSelector = (String)ctxValues.get( "select.selector" );
+ if( optionSelector == null )
+ optionSelector = "*";
+ if( optionSelector.equals( optionSeparator ) )
+ optionSelector = null;
+ if( inputValue == null )
+ inputValue = "";
+
+ // If values from the context contain the separator, we assume
+ // that the plugin or something else has given us a better
+ // list to display.
+ boolean contextValueOverride = false;
+ if( previousValue != null )
+ {
+ if( previousValue.indexOf( optionSeparator ) != -1 )
+ {
+ inputValue = previousValue;
+ previousValue = null;
+ }
+ else
+ {
+ // If a context value exists, but it's not a list,
+ // it'll just override any existing selector
+ // indications.
+ contextValueOverride = true;
+ }
+ }
+
+ String[] options = inputValue.split( optionSeparator );
+ if( options == null )
+ options = new String[0];
+ int previouslySelected = -1;
+
+ option[] optionElements = new option[options.length];
+
+ //
+ // Figure out which one of the options to select: prefer the one
+ // that was previously selected, otherwise try to find the one
+ // with the "select" marker.
+ //
+ for( int i = 0; i < options.length; i++ )
+ {
+ int indicated = -1;
+ options[i] = options[i].trim();
+
+ if( options[i].startsWith( optionSelector ) )
+ {
+ options[i] = options[i].substring( optionSelector.length() );
+ indicated = i;
+ }
+ if( previouslySelected == -1 )
+ {
+ if( !contextValueOverride && indicated > 0 )
+ {
+ previouslySelected = indicated;
+ }
+ else if( previousValue != null &&
+ options[i].equals( previousValue ) )
+ {
+ previouslySelected = i;
+ }
+ }
+
+ optionElements[i] = new option( options[i] );
+ optionElements[i].addElement( options[i] );
+ }
+
+ if( previouslySelected > -1 ) optionElements[previouslySelected].setSelected(true);
+ select field = new select( HANDLERPARAM_PREFIX + inputName, optionElements );
+
+ return( field );
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSet.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSet.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSet.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormSet.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,94 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.plugin.*;
+import java.util.*;
+
+/**
+ * FormSet is a companion WikiPlugin for Form.
+ *
+ * <p>The mandatory 'form' parameter specifies which form the variable
+ * applies to. Any other parameters are put directly into a FormInfo
+ * object that will be available to a Form plugin called 'form'
+ * (presumably invoked later on the same WikiPage).
+ *
+ * <p>If the name of a FormSet parameter is the same as the name of
+ * a Form plugin input element later on the same page, the Form will
+ * consider the given value the default for the form field. (However,
+ * the handler for the Form is free to use the value as it wishes, and
+ * even override it.)
+ *
+ * <p>If the name of a parameter is not present in Form input fields,
+ * the parameter is presumably meant for sending initial information
+ * to the Form handler. If this is the case, you may want to specify the
+ * <i>populate=''</i> in the Form <i>open</i> element, otherwise the
+ * form won't be displayed on the first invocation.
+ *
+ * <p>This object looks for a FormInfo object named
+ * FORM_VALUES_CARRIER in the WikiContext. If found, it checks that
+ * its name matches the 'form' parameter, and if it does, adds the
+ * plugin parameters to the FormInfo. If the names don't match, the
+ * old FormInfo is discarded and a new one is created. Only one
+ * FormInfo is supported at a time. A practical consequence of this is
+ * that a FormSet invocation only applies to the Form plugins that
+ * follow it; any further Forms need new FormSet calls.
+ *
+ * @see FormInfo
+ * @author ebu
+ */
+public class FormSet
+ implements WikiPlugin
+{
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ String formName = (String)params.get( FormElement.PARAM_FORM );
+ if( formName == null || formName.trim().length() == 0 )
+ {
+ return "";
+ }
+
+ FormInfo info = (FormInfo)ctx.getVariable( FormElement.FORM_VALUES_CARRIER );
+
+ if( info == null || formName.equals( info.getName() ) == false )
+ {
+ info = new FormInfo();
+ ctx.setVariable( FormElement.FORM_VALUES_CARRIER, info );
+ }
+
+ //
+ // Create a copy for the context. Unfortunately we need to
+ // create slightly modified copy, because otherwise on next
+ // invocation this might be coming from a cache; so we can't
+ // modify the original param string.
+ //
+ info.setName( formName );
+ HashMap hm = new HashMap();
+ hm.putAll( params );
+
+ hm.remove( FormElement.PARAM_FORM );
+ info.addSubmission( hm );
+
+ return "";
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormTextarea.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormTextarea.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormTextarea.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/FormTextarea.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,105 @@
+/*
+ WikiForms - a WikiPage FORM handler for JSPWiki.
+
+ Copyright (C) 2003 BaseN.
+
+ JSPWiki Copyright (C) 2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+*/
+package com.ecyrd.jspwiki.forms;
+
+import com.ecyrd.jspwiki.*;
+import com.ecyrd.jspwiki.plugin.PluginException;
+import com.ecyrd.jspwiki.plugin.WikiPlugin;
+
+import java.util.*;
+
+import org.apache.ecs.ConcreteElement;
+import org.apache.ecs.xhtml.textarea;
+
+/**
+ * @author ebu
+ */
+public class FormTextarea
+ extends FormElement
+{
+ public static final String PARAM_ROWS = "rows";
+ public static final String PARAM_COLS = "cols";
+
+ public String execute( WikiContext ctx, Map params )
+ throws PluginException
+ {
+ // Don't render if no error and error-only-rendering is on.
+ FormInfo info = getFormInfo( ctx );
+ Map previousValues = null;
+ ResourceBundle rb = ctx.getBundle(WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
+
+ if( info != null )
+ {
+ if( info.hide() )
+ {
+ return "<p>" + rb.getString( "formclose.noneedtoshow" ) + "</p>";
+ }
+ previousValues = info.getSubmission();
+ }
+
+ if( previousValues == null )
+ {
+ previousValues = new HashMap();
+ }
+
+ ConcreteElement field = null;
+
+ field = buildTextArea( params, previousValues, rb );
+
+ // We should look for extra params, e.g. width, ..., here.
+ if( field != null )
+ return field.toString( ctx.getEngine().getContentEncoding() );
+
+ return "";
+ }
+
+ private textarea buildTextArea( Map params, Map previousValues, ResourceBundle rb )
+ throws PluginException
+ {
+ String inputName = (String)params.get( PARAM_INPUTNAME );
+ String rows = (String)params.get( PARAM_ROWS );
+ String cols = (String)params.get( PARAM_COLS );
+
+ if( inputName == null )
+ throw new PluginException( rb.getString( "formtextarea.namemissing" ) );
+
+ // In order to isolate posted form elements into their own
+ // map, prefix the variable name here. It will be stripped
+ // when the handler plugin is executed.
+ textarea field = new textarea( HANDLERPARAM_PREFIX + inputName,
+ rows, cols);
+
+ if( previousValues != null )
+ {
+ String oldValue = (String)previousValues.get( inputName );
+ if( oldValue != null )
+ {
+ field.addElement( oldValue );
+ }
+ else
+ {
+ oldValue = (String)params.get( PARAM_VALUE );
+ if( oldValue != null ) field.addElement( oldValue );
+ }
+ }
+ return field;
+ }
+}
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/package.html
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/package.html?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/package.html (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/forms/package.html Tue Feb 12 21:53:55 2008
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<title>com.ecyrd.jspwiki.forms</title>
+</head>
+<body>
+
+Contains classes for doing form handling within JSPWiki.
+
+<h2>Package Specification</h2>
+
+This package contains all form-related classes, interfaces and helpers.
+
+<h2>Related Documentation</h2>
+
+</body>
+</html>
\ No newline at end of file
Added: incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/htmltowiki/ForgetNullValuesLinkedHashMap.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/htmltowiki/ForgetNullValuesLinkedHashMap.java?rev=627255&view=auto
==============================================================================
--- incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/htmltowiki/ForgetNullValuesLinkedHashMap.java (added)
+++ incubator/jspwiki/branches/JSPWIKI_STRIPES_BRANCH/src/com/ecyrd/jspwiki/htmltowiki/ForgetNullValuesLinkedHashMap.java Tue Feb 12 21:53:55 2008
@@ -0,0 +1,42 @@
+/*
+ JSPWiki - a JSP-based WikiWiki clone.
+
+ Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+package com.ecyrd.jspwiki.htmltowiki;
+
+import java.util.LinkedHashMap;
+
+/**
+ * A LinkedHashMap that does not put null values into the map.
+ *
+ * @author Sebastian Baltes (sbaltes@gmx.com)
+ */
+public class ForgetNullValuesLinkedHashMap extends LinkedHashMap
+{
+ private static final long serialVersionUID = 0L;
+
+ public Object put( Object key, Object value )
+ {
+ if( value != null )
+ {
+ return super.put( key, value );
+ }
+
+ return null;
+ }
+}