Extension:FormHandler send form by Email

This extension allows to create forms on wiki pages in a save way. The contents can be sent by email. It sends all fields and the user IP and - if the user is logged in - his username. This extension is suitable for creating simple forms. If you need very sophisticated things, you are probably better off not doing it inside mediawiki anyway.

Author: David Buchmann, dbu (at) users.sourceforge.net

Version: 0.1, 16.3.2006

History: Initial Version

''This is the initial release. It has only been tested with wikimedia 1.5.7 and is probably not fully compatible with 1.4 or 1.3. If you have any questions/comments, please send me an email.''

Installation
To activate the extension, put the code into a file in your mediawiki installation and include it from your LocalSettings.php with: include("extensions/FormHandler.php");

Dont forget to put  as first and last thing around the code in your new file.

To avoid spam, you should make sure that only trusted users can edit the pages which use the extension.

Syntax
Version 0.1 supports the field types text, textarea and select. Additionally hidden can be used to pass additional information with the Email. The syntax is very simple: [type]:(*) [param]="[value]", with the *, you mark entries which are required, the rest is considered optional. The form is included with the extension tag. It has some attributes to configure the form:

It is an error to not set at least one of sender or email.
 * name: A name to identify the form, useful if you use different forms on a page (required)
 * method: tells how to treat data (for now, only email is implemented) (required)
 * target: target for this form (with method email, this is the email address and required)
 * submit: if present, defines the text of the submit button, otherwise it is "Submit"
 * reset: if present, adds a reset button with its value as caption. Otherwise, no reset button is provided with the form.
 * email: if present, an email field is added to the top of the form with this attributes value as prompt. It is used as reply-to when sending the form.
 * sender: if present, must be a valid email address which is used as sender address to avoid problem with user specified email address.

An Example
 hidden: name="testform" value="additional info" text:* name="name" prompt="Your RealName" select: name="category" prompt="Please select problem category" option="Linux Software" option="Linux Hardware" text:* name="summary" prompt="Problem summary" textarea: name="description" prompt="Problem description" rows="10" cols="50" select: name="priority" prompt="Priority" option="Low" option="Medium" option="High" value="Medium"

Implementation
hidden fields are not passed to the client at all, but sent as defined in the wiki page. This avoids users changing them. If you have a reasonable example why this could be a problem, i can happily change the behaviour.

Known Bugs
There is a problem with server side caching of the output of our extension. For now, we pass action=purge with every request which seems to help.

Todo

 * Develop a security concept. It must ensure that only trusted users can use the extension in the wiki pages. At the very last, a configuration option should limit the possible addresses to send form data to. (This however would require the admin to edit the php file, which is complicated.)
 * Add more supported field types, e.g. radio and checkbox
 * It would be easy to implement writing to a file or into into a database.

Code
Copy the following into a file named extensions/FormHandler.php

/* FormHandler extension, Version 0.1 * David Buchmann, 16.3.2006 * * See http://meta.wikimedia.org/wiki/User:Dbu for explanations and updates. */

$wgExtensionFunctions[] = "wfFormHandler";

function wfFormHandler { global $wgParser; # register the extension with the WikiText parser # the first parameter is the name of the new tag. # In this case it defines the tag ...  # the second parameter is the callback function for # processing the text between the tags $wgParser->setHook( "form", "renderForm" ); }

/* The callback function for converting the input text to HTML output * $argv is an array containing any arguments passed to the * extension like .. * Put this on the sandbox page: (works in MediaWiki 1.5.5) *  Testing text **example** in between the new tags */ function renderForm( $input, $argv) { // parser is not passed?? , &$wgParser ) { global $wgParser, $wgRequest;

$handler = new FormHandler($wgParser, $wgRequest, $input, $argv); return $handler->render; }

class FormHandler { var $defaultMethod = 'email'; // Mediawiki objects var $request, $input, $argv; // Form parameters var $reset, $submit, $target, $sender, $email;

/*  * array of arrays with parsed field info. * type: text, hidden, textarea, select * name: name in form * required: whether this field is required * value: default value * option: array of options for select * prompt: text for prompt of that field */ var $fields;

function FormHandler(&$parser, $request, $input, $argv) { $parser->disableCache; $this->request = $request; $this->input = $input; $this->argv = $argv; }

function parseInput { //parse form setup $argv = $this->argv;

$this->reset = isset($argv['reset']) ? $argv['reset'] : false; $this->submit = isset($argv['submit']) ? $argv['submit'] : 'Submit'; $this->email = isset($argv['email']) ? $argv['email'] : false; $this->sender = isset($argv['sender']) ? $argv['sender'] : false; $this->target = isset($argv['target']) ? $argv['target'] : false; $this->method = isset($argv['method']) ? $argv['method'] : $this->defaultMethod;

$lines = preg_split('/(\r\n|\n|\r)/', $this->input); foreach($lines as $num=>$line) { $line = trim($line); if (strlen($line)==0) continue;

$pos = strpos($line, ':'); if (! $pos) { //0 or false:not found $this->fields[$num]['type'] = 'invalid'; $this->fields[$num]['value'] = $line; continue; }

$type = substr($line, 0, $pos); if ($line{$pos+1}==='*') { $required = true; $pos++; } else { $required = false; }

$pattern = '/(\S+="[^"]*")/';     $attributes = preg_split ($pattern, trim(substr($line, $pos+1)), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);      $atar = array;      foreach($attributes as $attr) {	list($name, $value) = explode('=', $attr, 2); //limit to 2: not break if attribute value contains =	if ($name==='option') {	  $atar['option'][] = substr($value, 1, strlen($value)-2); //remove quotation marks	} else {	  $atar[$name] = substr($value, 1, strlen($value)-2); //remove quotation marks	}      }

if (isset($atar['name'])) { $this->fields[$atar['name']] = $atar; $this->fields[$atar['name']]['type'] = $type; if ($required) $this->fields[$atar['name']]['required'] = $required; } else { $this->fields[$num]['type'] = 'invalid'; $this->fields[$num]['value'] = "No name: '$line'"; }   }  }

function render { $this->parseInput; if ($this->sender===false && $this->email===false) return 'Sorry, this form is invalid. Either sender or caption to get user email have to be set.'; if ($this->method==='email') { if ($this->target===false) return 'Sorry, this form is invalid. For the email method, a target email adress must be specified.'; if (! $this->isValidEmail($this->target)) return 'Sorry, this form is invalid. For the email method, the target must be a valid email adress, however, '.$this->target.' is not valid';

if ($this->sender!==false && ! $this->isValidEmail($this->sender)) return 'Sorry, this form is invalid. The sender adress is invalid: '.$this->sender; }

if ($this->request->wasPosted) { return $this->submit; } else { return $this->show; }

}

function show($error=false) { global $wgOut, $wgTitle, $wgUser;

$output=''; if ($error !== false) $output = " An Error Occurred $error ";

$output .= 'mTextform.'" method="post">  ';   return $output; }

function submit { global $wgUser, $wgDBname, $wgIP; $error = ''; foreach($this->fields as $field) { $this->fields[$field['name']]['value'] = $this->request->getText('FormHandler_'.$field['name']); if (isset($field['required'])) { if (empty($_POST['FormHandler_'.$field['name']])) { $error .= $field['prompt']. ' '; //todo: better would be to highlight the fields. for this we would keep a list of required fields here. }     }    }    if (! empty($error)) { return $this->show("Not all required fields have been filled out: \n$error"); }

if ( 0 != $wgUser->getID ) { $username = $wgUser->getName; } else { $username = '(not logged in)'; }

$usermail = $this->request->getText('FormHandlerEmail'); if (empty($usermail)) $usermail=false;

$message = 'Form '.$this->argv['name']." has been submitted by $username (IP: $wgIP, Email: " . ($usermail ? $usermail : 'not specified') .') This Email is sent to you by Mediawiki FormHandler extension from http://'.$_SERVER['SERVER_NAME'].$_SERVER['PHP_SELF']."\n\n"; foreach($this->fields as $field) { $message .= $field['name']. ': ';     switch($field['type']) { case 'text': case 'select': case 'textarea': $value = $this->request->getText('FormHandler_'.$field['name']); break; case 'hidden': $value = $field['value']; //we do not put it into form and not treat it, but keep it at server side... break; case 'invalid': $value = 'There is an invalid line in the form: '.$field['value']; break; default: $value = 'Implementation Error in FormHandler: unexpected field type '.$field['type']; break; }     $message .= (empty($value) ? '[not set]' : $value). "\n"; }

switch ($this->argv['method']) { case 'email': require_once('UserMailer.php'); if ($usermail!==false && ! $this->isValidEmail($usermail)) return $this->show('Your specified Email adress is invalid: '.$usermail); //sender is either == usermail or tested above

if (! $this->sender) { if (! $usermail) return $this->show("The Email field is required, please fill in."); $this->sender=$usermail; }

$error = userMailer( $this->sender, 			    $this->target, 			     'Contact form '.$this->argv['name'],			     $message,			     $usermail); if (empty($error)) { return 'Thank you for sending a message to '.$this->target."

\n&lt;pre>".nl2br($message).'&lt;/pre>';	} else {	 return "Sorry, sending the form failed.\n" . htmlspecialchars($error);	}	break;      default:	return 'Sorry, this is an invalid form, i do not know the method to store the information: '.$this->argv['method'];    }  }  /*    * Check Email for validity, using a regular expression.   */    function isValidEmail($candidate) {    return (eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $candidate));  } }