Extension:SimpleForms.php

From Organic Design wiki
Revision as of 10:27, 17 December 2007 by Nad (talk | contribs) (cat)

<?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.6, 2007-11-08');

  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

  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 $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' => '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,$wgSimpleFormsEnableCaching; $wgParser->setFunctionHook($wgSimpleFormsFormMagic, array($this,'formMagic')); $wgParser->setFunctionHook($wgSimpleFormsInputMagic, array($this,'inputMagic')); $wgParser->setFunctionHook($wgSimpleFormsRequestMagic, array($this,'requestMagic')); $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); }

# 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['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.getElementsByTagName('*'); for (k in i) if (i[k].name && i[k].value) 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); }

# Return value from the global $_REQUEST array (containing GET/POST variables) function requestMagic(&$parser,$key) { global $wgRequest,$wgSimpleFormsRequestPrefix; return $wgRequest->getText($wgSimpleFormsRequestPrefix.$key); }

# 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; $magicWords[$wgSimpleFormsFormMagic] = array(0,$wgSimpleFormsFormMagic); $magicWords[$wgSimpleFormsInputMagic] = array(0,$wgSimpleFormsInputMagic); $magicWords[$wgSimpleFormsRequestMagic] = array(0,$wgSimpleFormsRequestMagic); return true; }