Personal tools


Extension:SimpleForms.php

From OrganicDesign

Jump to: navigation, search
<?php
# MediaWiki SimpleForms Extension
# - See http://www.mediawiki.org/wiki/Extension:Simple_Forms for installation and usage details
# - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
# - Author: http://www.organicdesign.co.nz/nad
# - Started: 2007-04-25, see article history
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
define('SIMPLEFORMS_VERSION', '0.4.9, 2008-04-24');
 
# 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
$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
$wgSimpleFormsAllowRemoteAddr = array($_SERVER['SERVER_ADDR'],'127.0.0.1'); # Allow anonymous edits from these addresses
 
$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'      => '[http://www.organicdesign.co.nz/nad User:Nad]',
    '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';
    $_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;
    }
 
# 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,$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 [http://www.mediawiki.org/wiki/Extension:Simple_Forms 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'; }
 
    }
 
# 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();
    }
 
# Needed in MediaWiki >1.8.0 for magic word hooks to work properly
function wfSimpleFormsLanguageGetMagic(&$magicWords,$langCode = 0) {
    global $wgSimpleFormsFormMagic,$wgSimpleFormsInputMagic