Extension:FormHandler send form by Email

From MediaWiki.org
Jump to navigation Jump to search
MediaWiki extensions manualManual:Extensions
Crystal Clear action run.svg
FormHandler send form by Email

Release status:Extension status beta

ImplementationTemplate:Extension#type Tag
DescriptionTemplate:Extension#description Allows to create forms on wiki pages in a safe way
Author(s)Template:Extension#username David Buchmann
dbu (at) users.sourceforge.net (Dbutalk)
Latest versionTemplate:Extension#version 0.2 (2007-04-16)
MediaWikiTemplate:Extension#mediawiki 1.8 - 1.12
Database changesTemplate:Extension#needs-updatephp No
LicenseTemplate:Extension#license GPLv2+
Download see below

Translate the FormHandler send form by Email extension if it is available at translatewiki.net

Check usage and version matrix.

The FormHandler send form by Email extension allows to create forms on wiki pages in a safe way. The contents can be sent by email. It sends all fields and the user IP and - if the user is logged in - his/her 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.

Note Note: This is not thoroughly tested. There is a version that should work with MediaWiki 1.6 and newer (tested with 1.8) and an older one that was tested with MediaWiki 1.5.7. The only difference between the versions lies in 0.2 using the UserMailer and MailAddress classes as required by MediaWiki since REL_1.6. If you have any questions/comments, please send the author an email.

Security Warning: You should use this extension only if page editing is restricted to logged in and selected people you can trust not to edit a page in a manner to send spam with the help of the extension. If somebody can propose a usable security scheme to control this problem, please tell.

Installation[edit]

  • Add the following code at the bottom of your LocalSettings.php:
    require_once "$IP/extensions/FormHandler/FormHandler.php";
    
  • YesY Done – Navigate to Special:Version on your wiki to verify that the extension is successfully installed.

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

Usage[edit]

Version 0.1 and 0.2 support 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 <form>. It has some attributes to configure the form:

  • 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. If sender is not set, it is also used as 'from' address.
  • sender: if present, must be a valid email address which is used as 'from' address to avoid problem with user specified email address. If it is present, it is used as 'from' instead of the user suplied email address in the email field.

It is an error to not set at least one of sender or email.

Example
<form name="test" method="email" target="admin@domain.com" email="Your Email" sender="web@domain.com" submit="Send Inquiry" reset="Reset">
    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"
</form>

Implementation[edit]

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[edit]

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[edit]

  • 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......DONE...check discussion page-- Shabnam Garg
  • It would be easy to implement writing to a file or into into a database.

Version History[edit]

  • 0.2: Made it working with newer Mediawiki, having the UserMailer class: Objects for email addresses and changed order of from and to address. Thanks to the people commenting on the discussion page.
  • 0.1: Initial Version

Code[edit]

FormHandler.php[edit]

<?php

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

/*
 * Protect against register_globals vulnerabilities.
 * This line must be present before any global variable is referenced.
 */
if( !defined( 'MEDIAWIKI' ) ) {
    echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
    die( -1 );
}

// Extension credits that will show up on Special:Version
$wgExtensionCredits['parserhook'][] = array(
        'path'           => __FILE__,
        'name'           => 'FormHandler send form by Email',
        'version'        => '0.2',
        'author'         => 'David Buchmann',
        'url'            => 'https://www.mediawiki.org/wiki/Extension:FormHandler_send_form_by_Email',
        'description'    => 'Provide a &lt;form&gt; tag that sends email on form submission.'
);

$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 <example> ... </example>
  # 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 <example argument="foo" bar>..
 * Put this on the sandbox page:  (works in MediaWiki 1.5.5)
 *   <example argument="foo" argument2="bar">Testing text **example** in between the new tags</example>
 */
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, -1); //remove quotation marks
	} else {
	  $atar[$name] = substr($value, 1, -1); //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 address 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 address, however, '.$this->target.' is not valid';

	if ($this->sender!==false && ! $this->isValidEmail($this->sender)) 
	  return 'Sorry, this form is invalid. The sender address 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 = "<h2>An Error Occurred</h2><p>$error</p>";

    $output .= '<form action="'.$wgTitle->mTextform.'" method="post">
<input type="hidden" name="action" value="purge" />
              <table class="FormHandler">';
    if ($this->email !== false) $output .= '<tr><td>'.$this->email.'</td><td><input type="text" name="FormHandlerEmail" value="'. 
      (($this->request->getText('FormHandlerEmail') == '') ? $wgUser->mEmail : 
       $this->request->getText('FormHandlerEmail')) .
      '"/></td></tr>'."\n";

    foreach ($this->fields as $name => $field) {
      $output .= '<tr><td>'.$wgOut->parse($field['prompt'], false)."</td><td>\n";

      switch($field['type']) {
        case 'text':
	  $output .= '<input type="text" name="FormHandler_'.$field['name'].'" value="'.$field['value'].'" />';
	  break;
        case 'hidden':
	  //saver not to pass by client at all. $output .= '<input type="hidden" name="FormHandler_'.$field['name'].'" value="'.$field['value'].'" />';	  
	  break;
        case 'textarea':
          $output .= '<textarea style="width:auto;" name="FormHandler_'.$field['name'].'"';
          $output .= ' rows="'.$field['rows'].'"';
          $output .= ' cols="'.$field['cols'].'"';
          $output .= '>';
          $output .= $field['value'];
          $output .= '</textarea>'."\n";
	  break;
        case 'select':
  	  $output .= '<select name="FormHandler_'.$field['name'].'">';
	  foreach($field['option'] as $option) {
	    $output .= '<option '.($option===$field['value'] ? 'selected="true"' : '').">$option</option>";
	  }
	  $output .= '</select>';
	  break;
        case 'invalid':
	  $output .= "Could not understand line $name: '".$field['value']."'";
	  break;
        default:
	  $output .= 'Unknown field type '.$field['type'];
	  break;
      }
      if (isset($field['required'])) $output .= '*';
      $output .= "</td></tr>\n";
    }

    $output .= '<tr><td style="text-align:center; padding-top:15px;">';
    if (isset($this->argv['reset'])) $output .= '<input type="reset" value="'.$this->argv['reset'].'" />';
    $output .= '</td><td style="text-align:center; padding-top:15px;"><input type="submit" /></td></tr>
     </table></form>';
    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'] . '<br />'; //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:<br />\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 address 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;
	}

        //16.4.2008: use new mailer class. new order $to, $from
        //fixme: there are no sanity checks for email addresses, neither here nor in MailAddress
	$error = UserMailer::send(new MailAddress($this->target), 
			     new MailAddress($this->sender),
			     'Contact form '.$this->argv['name'],
			     $message,
			     new MailAddress($usermail));
	if ($error===true) {
	  return 'Thank you for sending a message to '.$this->target."<br /><br />\n&lt;pre>".nl2br($message).'&lt;/pre>';
	} else {
	  return "Sorry, sending the form failed.\n" . $error->getMessage();
	}
	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));
  }
}