Extension:Simple Forms

From MediaWiki.org
Jump to: navigation, search
Warning Warning: This is extension is no longer under active development by the Organic Design team. An alternative solution is now being adopted on our wikis which is to use raw HTML but have it restricted to the "Form" namespace that is editable only by sysops.

MediaWiki extensions manual
Crystal Clear action run.png
Simple Forms

Release status: beta

Implementation Parser function, Page action, Ajax
Description A set of simple mechanisms for making and processing forms
Author(s) User:Nad and Alessandra Bilardi (Bilarditalk)
Latest version 0.4.15 (2012-03-14)
MediaWiki 1.6.0 - 1.11.0 (for 1.12.0 and above, use patched version)
License No license specified
Download v0.4.15, v0.4.11, patched: v0.4.10.1

Translate the Simple Forms extension if it is available at translatewiki.net

Check usage and version matrix; code metrics

We've needed forms for quite a number of things in our Organic Design wiki, such as for creating or editing articles that are based on templates or for making DynamicPageList queries. Instead of making specific code for each form instance, we decided to create a generic set of simple mechanisms that could be re-used to make the creation of all forms and their processing much simpler.

The SimpleForms extension extends the functionality of MediaWiki in two main ways:

  • parser-functions: by adding extra parser functions (accessed using curly-brace syntax in an article's wikitext) that are used for constructing forms and accessing the posted data.
  • index.php parameters: by adding additional parameters to index.php, articles can be updated and created directly from parameters passed in links or posted data.

Installation & configuration[edit]

This extension is a single script that is included at the end of your LocalSettings.php file as usual. Download the current version from OrganicDesign:Extension:SimpleForms.php and save it in your extensions directory or in a subdirectory to suit the organisation of your extensions. There is also a patched version that makes the type=ajax option functional again in MW 1.12 and above; download it from OrganicDesign:Extension:SimpleForms.php/v0.4.10.1. With MW 1.15 download it see below.

There are also a number of global configuration variables for enabling, disabling and setting up the various features of the SimpleForms extension. These should be set in your LocalSettings file after the include of the SimpleForms.php script. Here is a description of the valid variables:

Variable Default value Meaning
$wgSimpleFormsFormMagic form the parser-function name for form containers
$wgSimpleFormsFormEndMagic formend the function name for form end tag
$wgSimpleFormsInputMagic input the parser-function name for making form-inputs
$wgSimpleFormsRequestMagic request the parser-function name for accessing the GET/POST variables
$wgSimpleFormsRequestPrefix empty prefix that restricts #request and GET/POST variable names to their own namespace, set to "" to disable
$wgSimpleFormsAllowCreate true whether or not creating articles from query-string is allowed
$wgSimpleFormsAllowEdit true whether or not editing articles from query-string is allowed
$wgSimpleFormsAllowRemoteAddr Server-IP, IP addresses in this list are allowed to update or create articles without requiring login. This makes it easy for programs on the server to update wiki content without needing to parse and post a login form.
$wgSimpleFormsServerUser empty Set this to a username to show in the changes for edits by the server rather than showing the IP address


The Simple Forms extension can use Ajax technology to allow forms or links to work asynchronously (i.e., not involving a page reload). The content in the server's response can be directed to replace a particular portion of the page identified by its id attribute. Simple Forms used to use the MooTools JavaScript framework for its Ajax functionality, but now it only requires the inbuilt MediaWiki Ajax functions. These must be enabled by setting $wgUseAjaxManual:$wgUseAjax to true in your LocalSettings.php file before the inclusion of Simple Forms, as in the following example.

$wgUseAjax = true;
include("$IP/extensions/Simple Forms/Simple Forms.php");

Form Syntax & Style[edit]

Form syntax is made clearer by using indentation, but it is important to note that whitespace indentation using space characters may lead to undesired results. For this reason, indentation should always be done using tab characters rather than spaces, because SimpleForms removes all tabs from the form definition before it gets processed.

Class and id attributes can be added to the #form or #input parser-function syntax, or to surrounding div elements, etc., to customise form design and layout from your wiki's CSS stylesheet. CSS rules can be added to the MediaWiki:Common.css article rather than editing the CSS file.

  • Ajax links have ajax added to their class attribute so they can be given a specific link style.

SimpleForms parser functions[edit]

We wanted forms to be created or modified by unprivileged users, so we decided against allowing form articles to use raw HTML and instead added some parser functions, which allow forms to be created, and the posted results captured, using normal wikitext notation.


The first parser function is #form, which defines the main form container. Three parameters can be supplied; the first two are method and action, which serve the same purpose as usual, except that method can be the name of a javascript function if it's not GET or POST. The third parameter is target, which either has the normal meaning, or, if the action refers to a function, then the target property refers to a part of the document the result should be sent to.

The JavaScript functions that the form can execute should return a URL (including an optional query-string component), which will then be asynchronously requested from the server using an xmlhttp object. The returned content will replace the content (innerHTML property) of the document element having the id specified in the form's target property.

There's currently just one JavaScript function, which is called live; using this as the action of the form requests the URL using an asynchronous xmlhttp-POST request and adds all the form's input values to the request.


Form inputs are made with the #input parser function; no JavaScript is allowed in the values, except for names of the functions that are registered in the global $wgJavaScriptFunctions array.

Select-lists and textareas can be created using the #input function with type set to select, textarea rather than having them consuming their own magic.

Input types[edit]

In addition to the normal input types list text, image or checkbox, there are the following additional types and properties:

type=select: this is used for dropdown selection lists instead of having a dedicated #select parser function. The select options are provided as a wikitext bulleted list. They can be provided manually or generated via extensions such as Dynamic Page List. Manual example:


Dynamic Page List example, producing a dropdown of all wiki categories:


type=textarea: this is used for textareas instead of having a dedicated #textarea parser function.

type=submit: this does the same as usual, but can also be combined with method=ajax to make the form submission asynchronous.

type=ajax: the works like a normal input with type=submit, which submits the forms except that the submission will occur asynchronously without reloading the page. Add the update parameter set to the id attribute of the element on the page that the results should be rendered in. The Ajax input type also allows a parameter called link, which can be set to a URL or article title and causes the input to render as a normal hyperlink instead of as a form button. These link inputs are independent of any form; see OrganicDesign:19 June 2007 for a working example of this type of input.


This allows values from the query-string (GET) or POSTed data to be included in the article to be used by other parser functions when calculating or rendering their results. For example, an article could be made something like the following example, which would list the items in a category if it's been specified, or render a form otherwise:

{{#if: {{#request:cat}}
        {{#input:type=submit|value=List members}}
  • Note1: Indenting is optional, but must use tabs not spaces, because spaces at the start of the line cause <pre> sections
  • Note2: This example works, but requires the ParserFunctions and DynamicPageList extensions.

The form that is rendered also exhibits a DPL query, which creates a list of all categories for the dropdown select-list. The #input function expects to have a standard wikitext bullet list supplied for its list of options, so the DPL query sets the mode and listseparators parameters appropriately.


The last function is #formend, which defines the end of form container.

SimpleForms index.php parameters[edit]

The SimpleForms extension adds a number of extra parameters to index.php, which allows articles to be updated and created directly from HTTP requests without having to process HTML content. Following is an explanation of each of the additional parameters available.


The SimpleForms extension offers the ability to specify wikitext content directly in the posted data or query string using the content parameter. This is useful for adding default content to newly created articles, but the main reason we created it was so that forms could use their JavaScript functions to compose wikitext from the form's inputs and then request the parsed result. This effectively allows forms to execute dynamic, transient queries without having to adjust any article content. It's similar to doing a page-preview, but doesn't use any edit form.

<nowiki> tag usage in the content parameter

The wikitext in the content parameter can be surrounded by a <nowiki> element so that it's easy to assign wikitext queries to it without them being parsed. These nowiki elements will be automatically stripped by SimpleForms when the form is posted.

Using content and title together

If the content parameter is used without any title parameter, then it will be parsed and the result returned without any article being accessed at all by MediaWiki. This feature is used by the search example shown below. If there is a title parameter, but the article doesn't exist, then the article will be created from the content (subject to the $wgSimpleFormsAllowCreate variable). And, finally, if there is both a content and title parameter and the article does already exist, then what happens to the content depends on the caction (content-action) parameter, which is described below.

  • The content query-string item can be used in conjunction with the usual actions, but would usually only be used with action=edit, action=render (or maybe action=raw&templates=expand).
  • Currently the content query-item does not work with action=edit
  • There is a MediaWiki API under development to allow adjustment of content via the request variables inherently, which this extension will make use of when it is ready.
  • Currently friendly URLs in conjunction with the content item (e.g., http://www.foo.com/MyArticle?content=SomeWikitext) does not work. So long-form URLs (eg. http://www.foo.com/wiki/index.php?title=MyArticle&content=SomeWikitext) should always be used, but it's advisable to stick with long-form for functional URLs anyway, as they're wiki-environment independent.

caction = replace | append | prepend[edit]

The content item can be used with another item called caction (content-action), which defines how the content should be incorporated into an existing article specified by the title parameter.

replace (default)


If the regexp parameter can be set to a PERL-compatible regular expression, an attempt will be made to replace the any matching patterns of text in the article with the content parameter. If there are no matching patterns, then the content will be replaced, appended or prepended according to the caction parameter.

templates = update[edit]

This query-string parameter is usually used with action=raw to cause the raw wikitext to have its double-braces (templates, variables and parser-functions) expanded first.

The Simple Forms extension allows the templates parameter to also be used with content. The only applicable value for the templates parameter when used with content is update, which causes any templates in the content to update their counterpart in the current content. Any template having no match will be appended. Any having more than one match will cause Simple Forms to try to reduce the matches to a single result by comparing values of the first named parameter. The following table helps to illustrate this:

Initial article text Content of templates to merge Updated article text

In this example, the content supplied has an unambiguous match with the bar template definition, which gets replaced.


In this case, the content has been appended because there were no template matches.

{{#contact:name=Jane|address=foo ave}}
{{#contact:name=Jane|address=foo ave}}

Here all the templates match by name, so an attempt is made to reduce this ambiguous state by comparing the first parameters, in this case the "name" parameter, which matches "Jane". The contact entry for "Jane" gets replaced, while the phone number has been removed because it was not supplied in the new content. If none of the first parameters matched, the update would be appended to the article as in the previous example.

{{#contact:name=Jane|address=foo ave}}
{{#contact:name=Jane|address=foo ave}}

This example shows content to update containing more than one template definition (both the items from the previous two examples). As can be seen, the result is the same as if the two were processed one at a time, i.e., the first updates the "Jane" entry and the second appends since there's no unique match.


If a more specific edit summary is preferred to the default, then it can be supplied in this parameter.

minor (not done yet)[edit]

If any changes are made, they will be marked as minor edits.


If the content parameter is used without a title parameter, then the returned page containing the parsed content will, by default, have no title, but if a title for the page is desired it can be set using this parameter. The search-form example below uses this feature to add a "search results" title to the page.


If the client is auto-authenticated as being the server (where the requesting IP address is one of those contained in the $wgSimpleFormsAllowRemoteAddr array), then it can be changed to a different name by setting this parameter. This allows the server to forward edits to the wiki from other sources and protocols on behalf of the wiki users.


Set this to an article title that should be returned after processing the request. This is useful if the page being updated is very large and doesn't need to be returned.

action = render[edit]

SimpleForms allows the usual MediaWiki render action to also work with special pages. This is very useful as it allows results of special page queries such as Special:DynamicPageList to be used with Ajax requests.

SimpleForms Usage Examples[edit]

Ajax example[edit]

This example renders a form with a dropdown list containing all the categories in the wiki; when one of them is selected and the submit button clicked, an Ajax request is generated that returns a list of all articles in the selected category. This list is then put into the content of a div element below the form. To see the example working, see OrganicDesign:18 June 2007 (also OrganicDesign:19 June 2007 may be of interest as an example of using Ajax links). Here's the wikitext source of the example,

        {{#input: type = hidden | name = content | value = <nowiki>{{#dpl:category={{#request:cat}}}}</nowiki> }}
        {{#input: type  = select
                | name  = cat
                | *Select category {{#dpl: namespace = Category | format = ,\n*%TITLE%,, }}
        {{#input: type = ajax | value = List members | update = listcat-result}}

<div id="listcat-result">NO RESULTS TO DISPLAY YET</div>

The hidden input named content contains the DPL query, which produces the list of article in the category. The query uses the #request parser-function to obtain the selected category, which was posted in the input named cat. Notice that the wikitext of the DPL query has to be enclosed in a <nowiki> so that the unparsed wikitext query can be assigned to the hidden value. After the form content has been posted, SimpleForms will remove the surrounding <nowiki>'s before the content gets parsed.

The last input with type=ajax works just like a normal submit button, but will work asynchronously using Ajax. The update parameter is used to direct the content of the server response to the element having id of "listcat-result", which is the div below the form.

A user login form[edit]

This form example replicates the same form inputs as a normal login form and posts them to the Special:Userlogin page.

        {{#input:type=hidden|name=wpLoginattempt|value=Log in}}

A PayPal donation form[edit]

Here's an example that creates a standard PayPal donation form. Update the values to your own PayPal-registered email address and product name. Notice the nowiki tags, which are required to protect the URLs from the wiki-parser.

        {{#input:type=hidden|name=business |value = donations@company.com }}
        {{#input:type=hidden|name=item_name|value = Product Name }}

A form to categorise the current article[edit]

This example shows how to make a form allowing the user to categorise the current page. When an item is selected from the dropdown list, the form's content and regexp parameters are updated. The regexp parameter is used to prevent more than one occurrence of the same category link. If there are no matching category links, then a new one will be appended.


        {{#input:type=hidden|name=summary|value=Article category updated from form}}

        {{#input: type   = select
                | *Select category{{#dpl: namespace = Category | format = ,\n*%TITLE%,, }}
                | onChange = 


Blog Example[edit]

This example allows the user to post comments to an article's talk page using a dynamic Ajax form. Comments are posted to the article's talk page dynamically. The talk page is transcluded and a form allows users to enter a heading, their name and a comment, which is then dynamically appended to the talk page and the transcluded content updated via an Ajax request. The submit button looks rather complicated, this is because the content being appended to the talk page needs to be composed by the Javascript from the title, name and body inputs.

== Comments ==
<div id='wgBlogComments'>{{Talk:{{PAGENAMEE}}}}</div>


Heading:   {{#input:type=text|id=blogTitle}}
Your name: {{#input:type=text|id=blogName}}
Comment:   {{#input:type=textarea|rows=2|id=blogBody}}

{{#input:type=ajax|value=Post comment|update=wgBlogComments|onClick=
var summary='comment posted by '+document.getElementById('blogName').value;
document.getElementById('blogSummary').setAttribute('value','Blog '+summary);}}



 * SimpleForms extension - Provides functions to make and process forms
 * See http://www.mediawiki.org/wiki/Extension:Simple_Forms for installation and usage details
 * Started: 2007-04-25
 * @package MediaWiki
 * @subpackage Extensions
 * @author Aran Dunkley [http://www.organicdesign.co.nz/nad User:Nad]
 * @copyright © 2007 Aran Dunkley
 * @licence GNU General Public Licence 2.0 or later
if (!defined('MEDIAWIKI')) die('Not an entry point.');
/* define('SIMPLEFORMS_VERSION', '0.4.11, 2009-01-14'); * Aran Dunkley */
/* define('SIMPLEFORMS_VERSION', '0.4.13, 2009-09-29'); * Alessandra Bilardi */
/* define('SIMPLEFORMS_VERSION', '0.4.14, 2012-02-10'); * User:Alexandre Porto */
define('SIMPLEFORMS_VERSION', '0.4.15, 2012-03-14'); /* User:Alexandre Porto */
# index.php parameter names
define('SIMPLEFORMS_CONTENT',  'content');   # used for parsing wikitext content
define('SIMPLEFORMS_CACTION',  'caction');   # specify whether to prepend, append or replace existing content
define('SIMPLEFORMS_SUMMARY',  'summary');   # specify an edit summary when updating or creating content
define('SIMPLEFORMS_PAGENAME', 'pagename');  # specify a page heading to use when rendering content with no title
define('SIMPLEFORMS_MINOR',    'minor');     # specify that the edit/create be flagged as a minor edit
define('SIMPLEFORMS_TACTION',  'templates'); # specify that the edit/create be flagged as a minor edit
define('SIMPLEFORMS_USERNAME', 'username');  # specify a different username to use when the server is editing
define('SIMPLEFORMS_RETURN',   'returnto');  # specify a page to return to after processing the request
define('SIMPLEFORMS_REGEXP',   'regexp');    # if the content-action is replace, a perl regular expression can be used
# Parser function names
$wgSimpleFormsFormMagic     = "form";        # the parser-function name for form containers
$wgSimpleFormsFormEndMagic  = "formend";     # the parser-function name for form end 
$wgSimpleFormsInputMagic    = "input";       # the parser-function name for form inputs
$wgSimpleFormsRequestMagic  = "request";     # the parser-function name for accessing the post/get variables
$wgSimpleFormsParaTypeMagic = "paratype";    # the parser-function name for checking post/get parameter types
# Configuration
$wgSimpleFormsRequestPrefix = "";            # restricts #request and GET/POST variable names to their own namespace, set to "" to disable
$wgSimpleFormsServerUser    = "";            # Set this to an existing username so server IP doesn't show up in changes
$wgSimpleFormsAllowCreate   = true;          # Allow creating new articles from content query item
$wgSimpleFormsAllowEdit     = true;          # Allow appending, prepending or replacing of content in existing articles from query item
# Allow anonymous edits from these addresses
$wgSimpleFormsAllowRemoteAddr = array('');
if (isset($_SERVER['SERVER_ADDR'])) $wgSimpleFormsAllowRemoteAddr[] = $_SERVER['SERVER_ADDR'];
$wgSimpleFormsEnableCaching = true;
define('SFEB_NAME',   0);
define('SFEB_OFFSET', 1);
define('SFEB_LENGTH', 2);
define('SFEB_DEPTH',  3);
$wgExtensionFunctions[]		= 'wfSetupSimpleForms';
$wgHooks['LanguageGetMagic'][] = 'wfSimpleFormsLanguageGetMagic';
$wgExtensionCredits['parserhook'][] = array(
        'path' => __FILE__,
	'name'        => 'Simple Forms',
	'author'      => '[http://www.organicdesign.co.nz/nad User:Nad] and [http://www.mediawiki.org/wiki/User:Bilardi Alessandra Bilardi]',
	'description' => 'Functions to make and process forms.',
	'url'         => 'http://www.mediawiki.org/wiki/Extension:Simple_Forms',
	'version'     => SIMPLEFORMS_VERSION
# If it's a simple-forms Ajax call, don't use dispatcher
if ($wgUseAjax && isset($_REQUEST['action']) && $_REQUEST['action'] == 'ajax' && $_REQUEST['rs'] == 'wfSimpleFormsAjax') {
	$_REQUEST['action'] = 'render';
	if (is_array($_REQUEST['rsargs']))
		foreach ($_REQUEST['rsargs'] as $arg)
			if (preg_match('/^(.+?)=(.+)$/', $arg, $m))
				$_REQUEST[$m[1]] = $m[2];
# todo: handle action=edit by making $_REQUEST['preload']='UNTITLED' and still add the AAFC hook
#	   handle action=raw by changing action to render and adding SimpleForms::raw to an appropriate hook
# If there is content passed in the request but no title, set title to the dummy "UNTITLED" article, and add a hook to replace the content
# - there's probably a better way to do this, but this will do for now
if (isset($_REQUEST[SIMPLEFORMS_CONTENT]) && !isset($_REQUEST['title'])) {
	$wgHooks['ArticleAfterFetchContent'][] = 'wfSimpleFormsUntitledContent';
	$wgSimpleFormsEnableCaching = false;
function wfSimpleFormsUntitledContent(&$article, &$text) {
	global $wgOut, $wgRequest;
	if ($article->getTitle()->getText() == SIMPLEFORMS_UNTITLED) {
		$text = $wgRequest->getText(SIMPLEFORMS_CONTENT);
		if ($title = $wgRequest->getText(SIMPLEFORMS_PAGENAME)) $wgOut->setPageTitle($title);
		else {
			$wgOut->setPageTitle(' ');
	return true;
# If the request originates locally, auto-authenticate the user to the server-user
$wgHooks['AutoAuthenticate'][] = 'wfSimpleFormsAutoAuthenticate';
function wfSimpleFormsAutoAuthenticate(&$user) {
	global $wgRequest, $wgSimpleFormsServerUser, $wgSimpleFormsAllowRemoteAddr;
	if ($username = $wgRequest->getText(SIMPLEFORMS_USERNAME)) $wgSimpleFormsServerUser = $username;
	if (!empty($wgSimpleFormsServerUser) && in_array($_SERVER['REMOTE_ADDR'], $wgSimpleFormsAllowRemoteAddr))
		$user = User::newFromName($wgSimpleFormsServerUser);
	return true;
 * Define a singleton for SimpleForms operations
class SimpleForms {
	var $id = 0;
	 * Constructor
	function SimpleForms() {
		global $wgParser, $wgHooks, $wgTitle, $wgSimpleFormsFormMagic, $wgSimpleFormsFormEndMagic, $wgSimpleFormsInputMagic,
		$wgSimpleFormsRequestMagic, $wgSimpleFormsParaTypeMagic, $wgSimpleFormsEnableCaching;
		$wgParser->setFunctionHook($wgSimpleFormsFormMagic,     array($this,'formMagic'));
		$wgParser->setFunctionHook($wgSimpleFormsFormEndMagic,     array($this,'formEndMagic'));
		$wgParser->setFunctionHook($wgSimpleFormsInputMagic,    array($this,'inputMagic'));
		$wgParser->setFunctionHook($wgSimpleFormsRequestMagic,  array($this,'requestMagic'));
		$wgParser->setFunctionHook($wgSimpleFormsParaTypeMagic, array($this,'paramTypeMagic'));
		if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'render' && (!is_object($wgTitle) || isset($_REQUEST['content']))) {
			$wgHooks['OutputPageBeforeHTML'][] = array($this, 'render');
			$wgSimpleFormsEnableCaching = false;
		$this->id = uniqid('sf-');
	 * Renders a form and wraps it in tags for processing by tagHook
	 * - if it's an edit-form it will return empty-string unless $this->edit is true
	 * i.e. $this->edit would be set by the edit-hook or create-specialpage parsing it
	function formMagic(&$parser) {
		global $wgScript, $wgSimpleFormsEnableCaching;
		if (!$wgSimpleFormsEnableCaching) $parser->disableCache();
		$argv = func_get_args();
		$id = $this->id;
		if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'render' && isset($_REQUEST['wiklet']))
			$hidden = '<input type="hidden" name="action" value="render"/>
			<input type="hidden" name="wiklet"/>';
		else $hidden = '';
		$form = '';
		$args = '';
		$argl = array();
		foreach ($argv as $arg) if (!is_object($arg)) {
			if (preg_match('/^([a-z0-9_]+?)\\s*=\\s*(.+)$/is', $arg, $match)) {
				$args .= " $match[1]=\"$match[2]\"";
				$argl[$match[1]] = $match[2];
			} else $form = $arg;
		$action = isset($argl['action']) ? $argl['action'] : $wgScript;
		$form = "<form$args action=\"$action\" id=\"$id\">$hidden$form";
		$this->id = uniqid('sf-');
		$form = preg_replace("/^\\s+/m",'',$form);
		return $parser->insertStripItem( $form, $parser->mStripState );
                /*return array($form, 'noparse' => true, 'isHTML' => true);*/

	 * Renders a form end
	function formEndMagic(&$parser) {
		$form = "</form>";
		return $parser->insertStripItem( $form, $parser->mStripState );
                /*return array($form, 'noparse' => true, 'isHTML' => true);*/
	 * Renders a form input
	function inputMagic(&$parser) {
		global $wgSimpleFormsRequestPrefix, $wgSimpleFormsEnableCaching;
		if (!$wgSimpleFormsEnableCaching) $parser->disableCache();
		$content = '';
		$method  = '';
		$type    = '';
		$args    = '';
		$argv    = array();
		# Process args
		foreach (func_get_args() as $arg) if (!is_object($arg)) {
			if (preg_match('/^([a-z0-9_]+?)\\s*=\\s*(.+)$/is', $arg, $match)) $argv[trim($match[1])] = trim($match[2]);
			else $content = trim($arg);
		if (isset($argv['type'])) $type = $argv['type']; else $type = '';
		if (isset($argv['name'])) $argv['name'] = $wgSimpleFormsRequestPrefix.$argv['name'];
		# Textarea
		if ($type == 'textarea') {
			foreach ($argv as $k => $v) $args .= " $k=\"$v\"";
			$input = "<textarea$args>$content</textarea>";
		# Select list
		elseif ($type == 'select' ) {
			if (isset($argv['multiple'])) {
				if (isset($argv['name'])) $argv['name'] .= '[]';
			if (isset($argv['value'])) {
				$val = $argv['value'];
			} else $val = '';
			foreach ($argv as $k => $v) $args .= " $k=\"$v\"";
			preg_match_all( '/^\\*\\s*(.*?)\\s*$/m', $content, $m );
			$input = "<select$args>\n";
			foreach ($m[1] as $opt ) {
				$txt = strtok( $opt, '¦' );
				if ( $opt != $txt ) {
					$opt = $txt;
					$txt = strtok( '¦' );
				$sel = $opt == $val ? ' selected' : '';
				$input .= "<option value=$opt$sel>$txt</option>\n";
			$input .= "</select>\n";
		# Ajax link or button
		elseif ($type == 'ajax') {
			$update = isset($argv['update']) ? $argv['update'] : $this->id;
			$format = isset($argv['format']) ? $argv['format'] : 'button';
			if (isset($argv['template'])) {
				$template = '{'.'{'.$argv['template'];
				$template = "var t = '$template\\n';
					inputs = f.getElementsByTagName('select');
					for (i = 0; i < inputs.length; i++)
						if (n = inputs[i].getAttribute('name'))
							t += '|' + n + '=' + inputs[i].getAttribute('selected') + '\\n';
					t = t + '}'+'}\\n';
					i = document.createElement('input');
					i = document.createElement('input');
			} else $template = '';
			if ($format == 'link') {
				# Render the Ajax input as a link independent of any form
				$element = 'a';
				$t = isset($argv['title']) ? $argv['title'] : false;
				if ($content == '') $content = $t;
				if ($t) $t = Title::newFromText($t);
				$argv['class'] = !$t || $t->exists() ? 'ajax' : 'new ajax';
				$params = array();
				foreach ($argv as $k => $v) if ($k != 'class') $params[] = "'$k=$v'";
				$params = join(',',$params);
				$argv['href'] = "javascript:var x = sajax_do_call('wfSimpleFormsAjax',[$params],document.getElementById('$update'))";
			else {
				# Render the Ajax input as a form submit button
				$argv['type'] = 'button';
				$element	  = 'input';
				if (!isset($argv['onClick'])) $argv['onClick'] = '';
				$argv['onClick'] .= "a = [];
					f = document.getElementById('{$this->id}');
					i = f.elements;
					for (var k = 0; k < f.elements.length; k++) {
					  if (i[k].type == 'select-one') {
						if (i[k].selectedIndex !== undefined ) {
					  } else if (i[k].name && i[k].value &&
						  (i[k].type != 'radio' || i[k].checked) &&
						  (i[k].type != 'checkbox' || i[k].checked)) {
					sajax_request_type = 'POST';
					x = sajax_do_call('wfSimpleFormsAjax',a,document.getElementById('$update'))";
			foreach ($argv as $k => $v) $args .= " $k=\"$v\"";
			$input = "<$element$args>$content</$element>\n";
		# Default: render as normal input element
		else {
			foreach ($argv as $k => $v) $args .= " $k=\"$v\"";
			$input = "<input$args/>";
		$input = preg_replace("/^\\s+/m",'',$input);
		return array($input,'noparse' => true, 'isHTML' => true);
	 * Return value from the global $_REQUEST array (containing GET/POST variables)
	function requestMagic(&$parser) {
		global $wgRequest, $wgSimpleFormsRequestPrefix, $wgContLang;
		$args = func_get_args();
		# the first arg is the parser.  We already have it (by
		# reference even), so we can remove it from the array
		# get the request parameter name
		$paramName = array_shift($args);
		# only thing left in $args at this point are the array keys
		# If no keys are specified, we just call getText()
		if (count($args) == 0) {
			$paramValue = $wgRequest->getText($wgSimpleFormsRequestPrefix.$paramName);
			return $paramValue;
		# when the parameter is a scalar calling getArray() puts it in an
		# array and returns the array, so we need to do a scalar check
		if (!is_null($wgRequest->getVal($wgSimpleFormsRequestPrefix.$paramName))) return '';
		# get the array associated with this parameter name
		$paramValue = $wgRequest->getArray($wgSimpleFormsRequestPrefix.$paramName);
		# time to descend into the depths of the array associated with the
		# parameter name
		foreach ($args as $key) {
			# do we have more keys than we have array nests?
			if (!is_array($paramValue)) return '';
			# a little closer to the value we want
			$paramValue = $paramValue[$key];
		# do we have more array nests than we have keys, or a null?
		if (is_array($paramValue) || is_null($paramValue)) return '';
		# we've found a param value!
		$paramValue = str_replace("\r\n", "\n", $wgContLang->recodeInput($paramValue));
		return $paramValue;
	 * requestMagic() returns an empty string under three conditions:
	 *   1) no such parameter was passed via the request,
	 *   2) the specified parameter is an array, and
	 *   3) the specified parameter was set to an empty string.
	 * Because of this we need a function to determine which is the case.  This
	 * function returns '0' if the parameter doesn't exist, '1' if the parameter
	 * is a scalar, and '2' if the parameter is an array.
	function paramTypeMagic(&$parser) {
		global $wgRequest, $wgSimpleFormsRequestPrefix;
		$args = func_get_args();
		# the first arg is the parser, we already have it by
		# reference, so we can remove it from the array
		# get the request parameter name
		$paramName = array_shift($args);
		# only thing left in $args at this point are the array keys
		# If no keys are specified, we just try to get a scalar
		if (count($args) == 0) {
			$paramValue = $wgRequest->getVal($wgSimpleFormsRequestPrefix.$paramName);
			if (is_null($paramValue)) {
				# getVal() returns null if the reqest parameter is an array, so
				# we need to verify that the parameter was not passed.
				$paramValue = $wgRequest->getArray($wgSimpleFormsRequestPrefix.$paramName);
				return is_null($paramValue) ? '0' : '2';
			# found a scalar
			return '1';
		# when the parameter is a scalar calling getArray() puts it in an
		# array and returns the array, so we need to do a scalar check
		if (!is_null($wgRequest->getVal($wgSimpleFormsRequestPrefix.$paramName))) return '0';
		# get the array associated with this parameter name
		$paramValue = $wgRequest->getArray($wgSimpleFormsRequestPrefix.$paramName);
		# descend into the depths of the array
		foreach ($args as $key) {
			# do we have more keys than we have array nests?
			if (!is_array($paramValue) || !array_key_exists($key, $paramValue)) return '0';
			# a little closer to the value we want
			$paramValue = $paramValue[$key];
		# do we have more array nests than we have keys?
		return is_array($paramValue) ? '2' : '1';
	 * Return the raw content
	function raw($text) {
		global $wgOut,$wgParser,$wgRequest;
		$expand = $wgRequest->getText('templates') == 'expand';
		if ($expand) $text = $wgParser->preprocess($text,new Title(),new ParserOptions());
		header('Content-Type: application/octet-stream');
		return false;
	 * Return rendered content of page
	function render(&$out, &$text) {
		return false;
	 * Disable caching if necessary
	function setCaching() {
		global $wgOut, $wgEnableParserCache, $wgSimpleFormsEnableCaching;
		if ($wgSimpleFormsEnableCaching) return;
		header("Cache-Control: no-cache, must-revalidate");
		header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
	 * Processes HTTP requests containing wikitext content
	function processRequest() {
		global $wgOut, $wgRequest, $wgUser, $wgTitle,
			   $wgSimpleFormsAllowRemoteAddr, $wgSimpleFormsAllowCreate, $wgSimpleFormsAllowEdit;
		$content = trim($wgRequest->getText(SIMPLEFORMS_CONTENT));
		$action  = $wgRequest->getText('action');
		$title   = $wgRequest->getText('title');
		# Handle content with action=raw case (allows templates=expand too)
		if ($action == 'raw' && isset($_REQUEST[SIMPLEFORMS_CONTENT])) $this->raw($content);
		# Handle content and title case (will either update or create an article)
			$title   = Title::newFromText($wgRequest->getText('title'));
			if ($title->getNamespace() == NS_SPECIAL) return;
			if (!is_object($wgTitle)) $wgTitle = $title; # hack to stop DPL crashing
			$article = new Article($title);
			$allow   = in_array($_SERVER['REMOTE_ADDR'], $wgSimpleFormsAllowRemoteAddr);
			$summary = $wgRequest->getText(SIMPLEFORMS_SUMMARY);
			$minor   = $wgRequest->getText(SIMPLEFORMS_MINOR);
			$return  = $wgRequest->getText(SIMPLEFORMS_RETURN);
			# If title exists and allowed to edit, prepend/append/replace content
			if ($title->exists()) {
				if ($wgSimpleFormsAllowEdit && ($allow || $wgUser->isAllowed('edit') && !$wgUser->isBlocked())) {
					$update = $this->updateTemplates($article->getContent(),$content);
					$article->updateArticle($update, $summary ? $summary : wfMsg('sf_editsummary'), false, false);
				else $wgOut->setPageTitle(wfMsg('whitelistedittitle'));
			# No such title, create new article from content if allowed to create
			else {
				if ($wgSimpleFormsAllowCreate && ($allow || $wgUser->isAllowed('edit')))
					$article->insertNewArticle($content, $summary ? $summary : wfMsg('sf_editsummary', 'created'), false, false);
				else $wgOut->setPageTitle(wfMsg('whitelistedittitle'));
			# If returnto is set, add a redirect header and die
			if ($return) die(header('Location: '.Title::newFromText($return)->getFullURL()));
	 * Create a dummy article for rendering content not associated with any title (unless it already exists)
	 * - there's probably a better way to do this
	function createUntitled() {
		$title = Title::newFromText(SIMPLEFORMS_UNTITLED);
		if (!$title->exists()) {
			global $wgUser;
			if ( $wgUser->getId() && $wgUser->isAllowed( 'edit' ) ) {
				$flags = EDIT_NEW;
				if ( $wgUser->isAllowed( 'bot' ) ) {
					$flags |= EDIT_FORCE_BOT;
				$article = new Article($title);
					'Dummy article used by [http://www.mediawiki.org/wiki/Extension:Simple_Forms Extension:SimpleForms]',
					'Dummy article created for Simple Forms extension',
				die( header( 'Location: ' . $title->getFullURL() ) );
	 * Update templates wikitext content
	 * - $updates must start and end with double-braces
	 * - $updates may contain multiple template updates
	 * - each update must only match one template, comparison of args will reduce multiple matches
	function updateTemplates($content, $updates) {
		global $wgRequest;
		$caction = $wgRequest->getText(SIMPLEFORMS_CACTION);
		$taction = $wgRequest->getText(SIMPLEFORMS_TACTION);
		$regexp  = $wgRequest->getText(SIMPLEFORMS_REGEXP);
		# Resort to normal content-action if $updates is not exclusively template definitions or updating templates disabled
		if ($taction == 'update' and preg_match('/^\\{\\{.+\\}\\}$/is', $updates, $match)) {
			# pattern to extract the first name and value of the first arg from template definition
			$pattern = '/^.+?[:\\|]\\s*(\\w+)\\s*=\\s*(.*?)\\s*[\\|\\}]/s';
			$addtext = '';
			# Get the offsets and lengths of template definitions in content and updates wikitexts
			$cbraces = $this->examineBraces($content);
			$ubraces = $this->examineBraces($updates);
			# Loop through the top-level braces in $updates
			foreach ($ubraces as $ubrace) if ($ubrace[SFEB_DEPTH] == 1) {
				# Get the update text
				$utext = substr($updates,$ubrace[SFEB_OFFSET],$ubrace[SFEB_LENGTH]);
				# Get braces in content with the same name as this update
				$matches = array();
				$uname   = $ubrace[SFEB_NAME];
				foreach ($cbraces as $ci => $cbrace) if ($cbrace[SFEB_NAME] == $uname) $matches[] = $ci;
				# If more than one matches, try to reduce to one by comparing the first arg of each with the updates first arg
				if (count($matches) > 1 && preg_match($pattern, $utext, $uarg)) {
					$tmp = array();
					foreach ($matches as $ci) {
						$cbrace = &$cbraces[$ci];
						$cbtext = substr($content, $cbrace[SFEB_OFFSET], $cbrace[SFEB_LENGTH]);
						if (preg_match($pattern, $cbtext, $carg) && $carg[1] == $uarg[1] && $carg[2] == $uarg[2])
							$tmp[] = $ci;
					$matches = &$tmp;
				# If matches has been reduced to a single item, update the template in the content
				if (count($matches) == 1) {
					$coffset = $cbraces[$matches[0]][SFEB_OFFSET];
					$clength = $cbraces[$matches[0]][SFEB_LENGTH];
					$content = substr_replace($content, $utext, $coffset, $clength);
				# Otherwise (if no matches, or many matches) do normal content-action on the update
				else $addtext .= "$utext\n";
		# Do normal content-action if $updates was not purely templates
		else $addtext = $updates;
		# Do regular expression replacement if regexp parameter set
		$addtext = trim($addtext);
		$content = trim($content);
		if ($regexp) {
			$content = preg_replace("|$regexp|", $addtext, $content, -1, $count);
			if ($count) $addtext = false;
		# Add any prepend/append updates using the content-action
		if ($addtext) {
			if	 ($caction == 'prepend') $content = "$addtext\n$content";
			elseif ($caction == 'append')  $content = "$content\n$addtext";
			elseif ($caction == 'replace') $content = $addtext;
		return $content;
	 * Return a list of info about each template definition in the passed wikitext content
	 * - list item format is NAME, OFFSET, LENGTH, DEPTH
	function examineBraces(&$content) {
		$braces = array();
		$depths = array();
		$depth = 1;
		$index = 0;
		while (preg_match('/\\{\\{\\s*([#a-z0-9_]*)|\\}\\}/is', $content, $match, PREG_OFFSET_CAPTURE, $index)) {
			$index = $match[0][1]+2;
			if ($match[0][0] == '}}') {
				$brace = &$braces[$depths[$depth-1]];
				$brace[SFEB_LENGTH] = $match[0][1]-$brace[SFEB_OFFSET]+2;
				$brace[SFEB_DEPTH] = --$depth;
			else {
				$depths[$depth++] = count($braces);
				$braces[] = array(SFEB_NAME => $match[1][0], SFEB_OFFSET => $match[0][1]);
		return $braces;
	 * Needed in some versions to prevent Special:Version from breaking
	function __toString() { return 'SimpleForms'; }
 * Called from $wgExtensionFunctions array when initialising extensions
function wfSetupSimpleForms() {
	global $wgLanguageCode,$wgMessageCache,$wgHooks,$wgRequest,$wgSimpleForms;
	# Add messages
	if ($wgLanguageCode == 'en') {
			'sf_editsummary' => 'Article updated via HTTP request'
	# Instantiate a singleton for the extension
	$wgSimpleForms = new SimpleForms();
 * Needed in MediaWiki >1.8.0 for magic word hooks to work properly
function wfSimpleFormsLanguageGetMagic(&$magicWords,$langCode = 0) {
	global $wgSimpleFormsFormMagic, $wgSimpleFormsFormEndMagic, $wgSimpleFormsInputMagic, $wgSimpleFormsRequestMagic, $wgSimpleFormsParaTypeMagic;
	$magicWords[$wgSimpleFormsFormMagic]     = array(0, $wgSimpleFormsFormMagic);
	$magicWords[$wgSimpleFormsFormEndMagic]  = array(0, $wgSimpleFormsFormEndMagic);
	$magicWords[$wgSimpleFormsInputMagic]    = array(0, $wgSimpleFormsInputMagic);
	$magicWords[$wgSimpleFormsRequestMagic]  = array(0, $wgSimpleFormsRequestMagic);
	$magicWords[$wgSimpleFormsParaTypeMagic] = array(0, $wgSimpleFormsParaTypeMagic);
	return true;

Using MediaWiki Ajax instead of MooTools[edit]

As of version 0.4.1, SimpleForms no longer requires the MooTools JavaScript framework. One of the main additions is the following snippet,

if ($wgUseAjax && $_REQUEST['action'] == 'ajax' && $_REQUEST['rs'] == 'wfSimpleFormsAjax') {
	$_REQUEST['action'] = 'render';
	if (is_array($_REQUEST['rsargs']))
		foreach ($_REQUEST['rsargs'] as $arg)
			if (preg_match('/^(.+?)=(.+)$/',$arg,$m))
				$_REQUEST[$m[1]] = $m[2];

This allows us to use the sajax_do_call function from MediaWiki's ajax.js, but this works by requesting the AjaxDispatcher class to execute a function specified in the request and to return the results of the function. This is not quite the way SimpleForms needs it to work because the AjaxDispatcher is instantiated before any high-level MediaWiki objects have been instantiated, but SimpleForms needs to return the fully rendered page content with all extensions activated etc.

The code above gets around this problem by detecting if incoming requests from sajax_do_call are specifying a function called "wfSimpleFormsAjax", and, if so, the action is changed to render so the request will no be handled by the AjaxDispatcher. The sajax_do_call argument list is shifted into the main $_REQUEST array as if it came from a normal GET or POST request. Note that there is actually no function called wfSimpleFormsAjax; it is just a dummy value used to identify the 'sajax_do_call request as one for SimpleForms to handle instead of the AjaxDispatcher.

Here's an example of using sajax_do_call with SimpleForms and sending a title parameter:


Change log[edit]

  • Version 0.4.15 (2012-03-14): Bug fix - createUntitled ---- Alexandre Porto msg 19:29, 14 March 2012 (UTC)
  • Version 0.4.14 (2012-02-10): Add selected option with '¦' and compatible with mw 1.18.x. ---- Alexandre Porto msg 14:39, 10 February 2012 (UTC)
  • Version 0.4.12 (2009-07-22): Add #formend function to insert HTML tag into form elements. --Bilardi 17:04, 22 July 2009 (UTC)
  • Version 0.4.11 (2009-1-14): Reformat and fix 'action' bug (last 'Organic Designs' hosted revision) --Nad 06:41, 14 January 2009
  • Version 0.4.10 (2008-07-11): fixed index bug for win users --Nad 22:04, 11 July 2008
  • Version 0.4.9 (2008-04-24): Ajax buttons now compatible with IE 6.0 for select-one type inputs, and avoiding the IE6.0 getElementByTag('*') bug. --Danbrice 19:24, 24 April 2008
  • Version 0.4.8 (2008-03-29): For Ajax, Radio and Checkbox values must be used only when they are 'checked'. --Danbrice 05:36, 29 March 2008 (UTC)
  • Version 0.4.7 (2008-03-08): I/O support for multiple attribute in select. —Sledged (talk) 00:45, 8 March 2008 (UTC)
  • Version 0.4.6 (2007-11-08): Caching bug fix
  • Version 0.4.5 (2007-10-27): Attempt bug fix - content still being returned from parser-cache when action=render on some installs
  • Version 0.4.4 (2007-10-24): Bug fix - undefined index on line 53
  • Version 0.4.3 (2007-10-22): Bug fix - content being returned from parser-cache when action=render on some installations - forced no-cache
  • Version 0.4.2 (2007-10-08): Bug fix - needs var in JS-href for Ajax links to work
  • Version 0.4.1 (2007-10-04): No dependency on MooTools anymore!
  • Version 0.4.0 (2007-10-04): Ajax links are now independent of MooTools, but not form-submit still requires it
  • Version 0.3.13 (2007-10-03): Added support for Ajax requests from MediaWiki's sajax_do_call function instead of MooTools
  • Version 0.3.12 (2007-09-24): Slight bug in last adjustments which breaks MW1.11 fixed
  • Version 0.3.11 (2007-09-22): Minor technical adjustments
  • Version 0.3.10 (2007-09-19): Bug fixed which prevented forms from having only single inputs
  • Version 0.3.9 (2007-09-03): Bail from processRequest() if title is special
  • Version 0.3.8 (2007-09-01): Fixed bug preventing content from updating article when caction=replace
  • Version 0.3.7 (2007-08-29): Use a unique-id for forms instead of counter as forms in Ajax requests have conflicting id's
  • Version 0.3.6 (2007-08-29): Remove the sf-prefix by default as it's causing trouble
  • Version 0.3.5 (2007-08-27): Fixed bug preventing content action from being able to create new articles
  • Version 0.3.3 (2007-07-24): Fixed a bug causing action=render to fail when used with content and no title
  • Version 0.3.2 (2007-07-09): Removed special-page and #edit parser-function, SimpleForms will not be implementing these
  • Version 0.3.0 (2007-07-07): Make action=render work with special pages and with article updates
  • Version 0.2.6 (2007-06-28): Patch to allow form-submit to work properly in Extension:Wiklets while they're still iFrames
  • Version 0.2.5 (2007-06-19): regexp parameter added
  • Version 0.2.3 (2007-06-19): content without title works with action=raw (including optional templates=expand)
  • Version 0.2.2 (2007-06-19): action=render added automatically for Ajax inputs, Ajax link inputs done
  • Version 0.2.1 (2007-06-19): Allow <nowiki>'s around content
  • Version 0.2.0 (2007-06-18): added Ajax functionality using MooTools
  • Version 0.1.1 (2007-06-16): returnto parameter added
  • Version 0.1.0 (2007-06-15): templates parameter working
  • Version 0.0.9 (2007-06-11): Rendering of content without associated title working
  • Version 0.0.3 (2007-05-10): Main form and input parser-functions working, ready for basic use

Todo & Plans[edit]

  • Make interfacing forms with template definitions much easier.

Tips & Tricks[edit]

  • For an input type=textarea, the parameter cols only works if accompanied by style=width:auto, otherwise it is ignored:
{{#input: type=textarea | cols=30 | style=width:auto | rows=2}}
  • To get the selected value of an input type=select via javascript, the code
works in Firefox but not in IE6. The alternative code below works in both browsers:
  • When using checkboxes:
    • The result of {{#request:yourcheckbox}} will be the string on when the box is checked, or empty otherwise.
    • To set the initial value of the checkbox as checked, include the parameter checked=checked in the input. Therefore, to set the checkbox to whatever the user selected in the previous submit, use:

Bugs & Problems[edit]

  • There's a bug that prevents the content parameter from being empty, which is a problem if using with regexp to remove matching items.
  • action=render is only working for some special pages (like Special:Version, but not others like allpages or DPL.
  • content is not working with edit yet.
  • It should use edit tokens; see API for discussion about this.
  • Test Ajax with request-prefix.

See also[edit]