Extension:SimpleForms.php

From Organic Design wiki
Revision as of 22:04, 11 July 2008 by Nad (talk | contribs) (0.4.10 - fixed index bug for win users)

<?php

  1. MediaWiki SimpleForms ExtensionTemplate:Php
Info.svg These are the MediaWiki extensions we're using and/or developing. Please refer to the information on the mediawiki.org wiki for installation and usage details. Extensions here which have no corresponding mediawiki article are either not ready for use or have been superseded. You can also browse our extension code in our local Subversion repository or our GitHub mirror.

Category:Extensions in progress

  1. - See http://www.mediawiki.org/wiki/Extension:Simple_Forms for installation and usage details
  2. - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
  3. - Author: http://www.organicdesign.co.nz/nad
  4. - Started: 2007-04-25, see article history

if (!defined('MEDIAWIKI')) die('Not an entry point.'); define('SIMPLEFORMS_VERSION', '0.4.10, 2008-07-12');

  1. 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

  1. Parser function names

$wgSimpleFormsFormMagic = "form"; # the parser-function name for form containers $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

  1. 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

  1. Allow anonymous edits from these addresses

$wgSimpleFormsAllowRemoteAddr = array('127.0.0.1'); if (isset($_SERVER['SERVER_ADDR'])) $wgSimpleFormsAllowRemoteAddr[] = $_SERVER['SERVER_ADDR'];

$wgSimpleFormsEnableCaching = true; define('SIMPLEFORMS_UNTITLED','UNTITLED'); define('SFEB_NAME', 0); define('SFEB_OFFSET',1); define('SFEB_LENGTH',2); define('SFEB_DEPTH', 3);

$wgExtensionFunctions[] = 'wfSetupSimpleForms'; $wgHooks['LanguageGetMagic'][] = 'wfSimpleFormsLanguageGetMagic';

$wgExtensionCredits['parserhook'][] = array(

   'name'        => 'Simple Forms',
   'author'      => 'User:Nad',
   'description' => 'Functions to make and process forms.',
   'url'         => 'http://www.mediawiki.org/wiki/Extension:Simple_Forms',
   'version'     => SIMPLEFORMS_VERSION
   );
  1. 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];
   }
  1. todo: handle action=edit by making $_REQUEST['preload']='UNTITLED' and still add the AAFC hook
  2. handle action=raw by changing action to render and adding SimpleForms::raw to an appropriate hook
  1. 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
  2. - 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';
   $_REQUEST['title'] = SIMPLEFORMS_UNTITLED;
   $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(' ');
           $wgOut->addScript('<style>h1.firstHeading{display:none}</style>');
           }
       }
    return true;
   }
  1. 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;
   }
  1. Define a singleton for SimpleForms operations

class SimpleForms {

   var $id = 0;
   # Constructor
   function SimpleForms() {
       global $wgParser,$wgHooks,$wgTitle,$wgSimpleFormsFormMagic,$wgSimpleFormsInputMagic,$wgSimpleFormsRequestMagic,$wgSimpleFormsParaTypeMagic,$wgSimpleFormsEnableCaching;
       $wgParser->setFunctionHook($wgSimpleFormsFormMagic,     array($this,'formMagic'));
       $wgParser->setFunctionHook($wgSimpleFormsInputMagic,    array($this,'inputMagic'));
       $wgParser->setFunctionHook($wgSimpleFormsRequestMagic,  array($this,'requestMagic'));
       $wgParser->setFunctionHook($wgSimpleFormsParaTypeMagic, array($this,'paramTypeMagic'));
       $this->createUntitled();
       $this->processRequest();
       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 = ;
       $action = isset($argv['action']) ? $argv['action'] : $wgScript;
       unset($argv['action']);
       $form   = ;
       $args   = ;
       foreach ($argv as $arg) if (!is_object($arg)) {
           if (preg_match('/^([a-z0-9_]+?)\\s*=\\s*(.+)$/is',$arg,$match)) $args .= " $match[1]=\"$match[2]\""; else $form = $arg;
           }
       $form = "<form$args action=\"$action\" id=\"$id\">$hidden$form</form>";
       $this->id = uniqid('sf-');
       $form = preg_replace("/^\\s+/m",,$form);
       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') {
           unset($argv['type']);
           foreach ($argv as $k => $v) $args .= " $k=\"$v\"";
           $input = "<textarea$args>$content</textarea>";
           }
       # Select list
       elseif ( $type == 'select' )
       {
           unset( $argv['type'] );
           if ( isset( $argv['multiple'] ) )
           {
               if ( isset( $argv['name'] ) )
                   $argv['name'] .= '[]';
           }
           if ( isset( $argv['value'] ) )
           {
               $val = $argv['value'];
               unset( $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 )
           {
               $sel = $opt == $val ? ' selected' : ;
               $input .= "<option$sel>$opt</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';
           unset($argv['update']);
           unset($argv['format']);
                       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';
                   alert(t);/*
                                       i = document.createElement('input');
                                       i.setAttribute('type','hidden');
                                       i.setAttribute('name','templates');
                                       i.setAttribute('value','update');
                                       f.appendChild(i);
                                       i = document.createElement('input');
                                       i.setAttribute('type','hidden');
                                       i.setAttribute('name','content');
                                       i.setAttribute('value',t);
                                       f.appendChild(i);*/";
                               unset($argv['template']);
                               } 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';
               unset($argv['type']);
               $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 ) {
                         a.push(i[k].name+'='+i[k].options[i[k].selectedIndex].text);
                       }
                     } else if (i[k].name && i[k].value &&
                         (i[k].type != 'radio' || i[k].checked) &&
                         (i[k].type != 'checkbox' || i[k].checked)) {
                            a.push(i[k].name+'='+i[k].value);
                     }
                   }
                   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
       array_shift( $args );
       // 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
       while ( count( $args ) > 0 )
       {
           $key = array_shift( $args );
           // 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
       array_shift( $args );
       // 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
       while ( count( $args ) > 0 )
       {
           $key = array_shift( $args );
           // 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;
       $this->setCaching();
       $expand = $wgRequest->getText('templates') == 'expand';
       if ($expand) $text = $wgParser->preprocess($text,new Title(),new ParserOptions());
       $wgOut->disable();
       wfResetOutputBuffers();
       header('Content-Type: application/octet-stream');
       echo($text);
       return false;
       }
   # Return rendered content of page
   function render(&$out,&$text) {
       $this->setCaching();
       $out->disable();
       wfResetOutputBuffers();
       echo($text);
       return false;
       }
   # Disable caching if necessary
   function setCaching() {
       global $wgOut,$wgEnableParserCache,$wgSimpleFormsEnableCaching;
       if ($wgSimpleFormsEnableCaching) return;
       $wgOut->enableClientCache(false);
       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)
       if ($title != SIMPLEFORMS_UNTITLED && isset($_REQUEST[SIMPLEFORMS_CONTENT])) {
           $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'))) {
                   $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()) {
           $article = new Article($title);
           $article->insertNewArticle(
               'Dummy article used by Extension:SimpleForms',
               'Dummy article created for Simple Forms extension',
               true,
               false
               );
           }
       }
   # 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'; }
   }
  1. Called from $wgExtensionFunctions array when initialising extensions

function wfSetupSimpleForms() {

   global $wgLanguageCode,$wgMessageCache,$wgHooks,$wgRequest,$wgSimpleForms;
   # Add messages
   if ($wgLanguageCode == 'en') {
       $wgMessageCache->addMessages(array(
           'sf_editsummary' => 'Article updated via HTTP request'
           ));
       }
   # Instantiate a singleton for the extension
   $wgSimpleForms = new SimpleForms();
   }
  1. Needed in MediaWiki >1.8.0 for magic word hooks to work properly

function wfSimpleFormsLanguageGetMagic(&$magicWords,$langCode = 0) {

   global $wgSimpleFormsFormMagic,$wgSimpleFormsInputMagic,$wgSimpleFormsRequestMagic,$wgSimpleFormsParaTypeMagic;
   $magicWords[$wgSimpleFormsFormMagic]     = array(0,$wgSimpleFormsFormMagic);
   $magicWords[$wgSimpleFormsInputMagic]    = array(0,$wgSimpleFormsInputMagic);
   $magicWords[$wgSimpleFormsRequestMagic]  = array(0,$wgSimpleFormsRequestMagic);
   $magicWords[$wgSimpleFormsParaTypeMagic] = array(0,$wgSimpleFormsParaTypeMagic);
   return true;
   }