Extension:WikiOpener

From MediaWiki.org
Jump to: navigation, search


MediaWiki extensions manualManual:Extensions
Crystal Clear app error.png

Release status:Extension status unstable

ImplementationTemplate:Extension#type Hook, Parser extension
DescriptionTemplate:Extension#description Gateway to interact with outside (database, DAS server, files, web services...).
Author(s)Template:Extension#username (Barriottalk)
Latest versionTemplate:Extension#version 1.1
MediaWikiTemplate:Extension#mediawiki 1.13.1 - 1.15.1
LicenseTemplate:Extension#license GPL
Download http://www.esat.kuleuven.be/~bioiuser/chdwiki/wikiopener.tar.gz

Translate the WikiOpener extension if it is available at translatewiki.net

Check usage and version matrix.

Foreword[edit]

One of the main limitation of wikis is the fact that the data must all be contained in the database on which it relies, meaning that it cannot import data coming from elsewhere. In some fields, were data are organised in tables, this limitation is quite harmful as it might prevent potential users to use Mediawiki to make their database available on the web and all the wiki facilities would then have to be reimplemented.

This extension has been developed in a bioinformatics/genomic context aiming at augmenting the wiki features to be able to include and update external (structured) data and run analysis tools. It allows to rapidly add specific component to include content extracted from databases (local or remote), files, and so on, to retrieve online data such as DAS servers, xml feeds, and so on, and also pass parameters to some software and format the results prior to their inclusion in the generated page.

For the above reasons, please bear in mind that this extension is generic and needs to be extended by specific components and is thus intended to developers.

What can this extension do?[edit]

The main purpose of this 'generic' extension is to interact with outside through the registration of specific components to:

  • manage structured data (databases, files, ...)
  • run (on-the-fly) analysis tools

With this aim, a new tag, wikiopener, has been defined and has the following usage: <wikiopener>Specific Component | Layout template | optional parameters separated by vertical bars</wikiopener>. The extension will fetch the layout template (wiki text stored as a wiki article) and will replace the variable names by their actual values returned by the specific component (PHP wrapper).

For example, a specific component can retrieve data from a database about genes associated to a specific congenital heart defect (CHD) and would be called as follows: <wikiopener>genesForChdProvider | genesForChdLayoutTemplate | Atrial septal defect</wikiopener>


Additionally, the extension provides the possibility to register specific components to be called to include content before and/or after an article. The registration can be done for all pages, a specific page, all pages in a namespace, or a specific page of a given namespace. When combined with the possibility of showing data present elsewhere, this is of great interest. Indeed, a page in which no data has already been manually entered via the classical way of editing wiki can already be populated via automatic queries in other databases. In our example of congenital heart defect database, we might have a wiki page containing information found in other databases about a specific heart defect with no other manually entered information.

Editing the other data sources on which the wiki relies should be as user friendly as editing a simple wiki page. This is why, to ease interaction with structured data, a mechanism to generate web forms has been implemented. Similar to the rendering of a components results, the layout is specified in wiki text in an article. Also, another article describes the form input fields (textfield, textarea, popup menu, ...). Then a specific component may be implemented to retrieve and provide default values in the web form and also to process posted values.

As said previously, articles with no wiki content may actually contain automatically included content (all pages in a namespace for example). However, these pages won't be discovered by the classical wiki Search tool, the extension takes this into account by extending :

  • the search function to perform a search in a database for example,
  • the articleExists function so that links look like they point to an existing page.

Download instructions[edit]

The extension code is available at http://www.esat.kuleuven.be/~bioiuser/chdwiki/wikiopener.tar.gz

Installation[edit]

After downloading, unpack and move (or copy for file ownership) the entire wikiopener directory to the extensions directory. Note that only wikiopener/wikiopener.php is required, the rest of the files are provided as a tutorial or quickstart guide.

To install this extension, add the following to LocalSettings.php:

#add configuration parameters here
#setup user rights here
require_once("$IP/extensions/wikiopener/wikiopener.php");

Manual modifications to MediaWiki source[edit]

The extension makes use of hooks to

  1. automatically include content before or after an article, therefore the hook must be added in includes/Article.php
  2. extend the search engine to be able to search outside resources (e.g. databases). This means that includes/searchEngine.php is to be modified.
  3. trick the articleExists function to properly link to empty wiki articles that do contain automatic content. This is done in includes/Title.php

Note: if you only intend to use the wikiopener tag, then these modifications are not needed.

includes/Article.php[edit]

MediaWiki 1.13.1[edit]

In MediaWiki 1.13.1 before line 801, i.e. in method view before the comment Fetch content and check for errors add the following hook:

 wfRunHooks('wgOutHookBeforeArticleContent',array(&$this,&$wgOut));

Then after the previous block (if (! $outputDone) ...), originally after line 813 in MediaWiki 1.13.1 and before the comment Another whitelist check in case oldid is altering the title:

 wfRunHooks('wgOutHookAfterArticleContent',array(&$this,&$wgOut));
MediaWiki 1.14.0 - 1.15.1[edit]

In method view before the comment If we got diff and oldid in the query, we want to see a diff page instead of the article. add the following hook (1.14.0 : before line 746; 1.15.1 : before line 797) :

wfRunHooks('wgOutHookBeforeArticleContent',array(&$this,&$wgOut));

Then, just before line 924 (MW 1.14.0) or line 1003 (MW 1.15.1) and comment title may have been set from the cache add the following hook:

wfRunHooks('wgOutHookAfterArticleContent',array(&$this,&$wgOut));

includes/SearchEngine.php[edit]

In method getNearMatch around lines 64-72 (MW 1.13.1) or lines 78-86 (MW 1.14.0 and MW 1.15.1), replace

 # Exact match? No need to look further.
 $title = Title::newFromText( $term );
 if (is_null($title))
   return NULL;
 if ( $title->getNamespace() == NS_SPECIAL || $title->exists() ) {
    return $title;
 }

with

 # Exact match? No need to look further.
 $title = Title::newFromText( $term );
 if (is_null($title))
    return NULL;
 $found = false;
 wfRunHooks('wgSearchEngineGetNearMatch',array(&$term,&$title,&$found));
 if ($found) {
    return $title;
 }				
 if ( $title->getNamespace() == NS_SPECIAL || $title->exists() ) {
    return $title;
 }

includes/Title.php[edit]

MediaWiki 1.13.1[edit]

Replace method (lines 3058-3069 in MW 1.13.1)

 public function isAlwaysKnown() {
    // If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
    // the full l10n of that language to be loaded. That takes much memory and
    // isn't needed. So we strip the language part away.
    // Also, extension messages which are not loaded, are shown as red, because
    // we don't call MessageCache::loadAllMessages.
    list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
    return $this->isExternal()
        || ( $this->mNamespace == NS_MAIN && $this->mDbkeyform == '' )
        || ( $this->mNamespace == NS_MEDIAWIKI && wfMsgWeirdKey( $basename ) );
 }

with

 public function isAlwaysKnown() {
    // If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
    // the full l10n of that language to be loaded. That takes much memory and
    // isn't needed. So we strip the language part away.
    // Also, extension messages which are not loaded, are shown as red, because
    // we don't call MessageCache::loadAllMessages.
    list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
    return _wikiopener_articleExists($this) || $this->isExternal()
        || ( $this->mNamespace == NS_MAIN && $this->mDbkeyform == '' )
        || ( $this->mNamespace == NS_MEDIAWIKI && wfMsgWeirdKey( $basename ) );
 }
MediaWiki 1.14.0 - 1.15.1[edit]

Replace method (lines 3143-3166 in MW 1.14.0 or lines 3390-3143 in MW 1.15.1)

public function isAlwaysKnown() {
	if( $this->mInterwiki != '' ) {
		return true;  // any interwiki link might be viewable, for all we know
	}
        switch( $this->mNamespace ) {			
	case NS_MEDIA:
	case NS_FILE:
		return wfFindFile( $this );  // file exists, possibly in a foreign repo
	case NS_SPECIAL:
		return SpecialPage::exists( $this->getDBKey() );  // valid special page
	case NS_MAIN:
		return $this->mDbkeyform == '';  // selflink, possibly with fragment
	case NS_MEDIAWIKI:
		// If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
		// the full l10n of that language to be loaded. That takes much memory and
		// isn't needed. So we strip the language part away.
		// Also, extension messages which are not loaded, are shown as red, because
		// we don't call MessageCache::loadAllMessages.
		list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
		return wfMsgWeirdKey( $basename );  // known system message
	default:
		return false;
	}
}

by method

public function isAlwaysKnown() {
	if( $this->mInterwiki != '' ) {
		return true;  // any interwiki link might be viewable, for all we know
		
	}
	switch( $this->mNamespace ) {			
	case NS_MEDIA:
	case NS_FILE:
		return wfFindFile( $this );  // file exists, possibly in a foreign repo
	case NS_SPECIAL:
		return SpecialPage::exists( $this->getDBKey() );  // valid special page
	case NS_MAIN:
		return $this->mDbkeyform == '';  // selflink, possibly with fragment
	case NS_MEDIAWIKI:
	        
		// If the page is form Mediawiki:message/lang, calling wfMsgWeirdKey causes
		// the full l10n of that language to be loaded. That takes much memory and
		// isn't needed. So we strip the language part away.
		// Also, extension messages which are not loaded, are shown as red, because
		// we don't call MessageCache::loadAllMessages.
		
		list( $basename, /* rest */ ) = explode( '/', $this->mDbkeyform, 2 );
		return  wfMsgWeirdKey( $basename );  // known system message
	default:
		return _wikiopener_articleExists($this) || false;
	}
}

Usage[edit]

Registering and using a component[edit]

Registration[edit]

Actually, components correspond to files. Therefore, we need to specify the path to the directory that contains those files.

In LocalSettings or wherever after the execution of

#add configuration parameters here
#setup user rights here
require_once("$IP/extensions/wikiopener/wikiopener.php");

Specify the path where the PHP files for the components can be found. For example:

# Specify path for components PHP files
_wikiopener_register_data_provider('extensions/wikiopener/components');

An example component: HugoTextMining[edit]

Create the file for your component. For example, we will define a HugoTextMining component that retrieves XML encoded data from a DAS server on publications related to the passed query, thus we will create the file hugotextmining.php in the directory extensions/wikiopener/components previously registered:

<?php
# Specify a default method to be called for that component
global $wg_hugotextmining_defaultMethod;
$wg_hugotextmining_defaultMethod = 'getReferences';

# Describe list of available methods for that component
global $wg_hugotextmining_methods;
$wg_hugotextmining_methods = array (
		'default'=>'see '.$wg_hugotextmining_defaultMethod.'.',
		'getReferences'=>'Find literature references where query is cited.'
);

# The core function that retrieves publications for the passed query
function _hugotextmining_getreferences($params) {
	$query = array_shift($params);
	$result = array();
	$result{'REFS'} = array();
	$xml = simplexml_load_file("http://www.ebi.ac.uk/das-srv/genedas/das/textmining/features?segment=$query");
	
	foreach ($xml->GFF->SEGMENT->FEATURE as $f) {
		$attributes = $f->TYPE->attributes();
 		

		if ($attributes['id'] == 'reference') {
			$ref = array();
			$ref{'PMID'} = (string)$f['id'];
			$ref{'NOTE'} = (string)$f->NOTE;
			$link = (string) $f->LINK->attributes();
			$ref{'LINK'} =$link;
			// buggy replacement of hrefs
			$ref{'NOTE'} = str_replace('<a href="','[', $ref{'NOTE'});
			$ref{'NOTE'} = str_replace('">',' ', $ref{'NOTE'});
			$ref{'NOTE'} = str_replace('</a>',']', $ref{'NOTE'});
			array_push($result{'REFS'},$ref);
		} 
		if (count($result{'REFS'}) > 2) return $result;
	}
	return $result;
}

Using a component[edit]

The syntax to call a component is the following: <wikiopener> component.method | layout [ | parameters separated by vertical bars ]</wikiopener>

For example to call our HugoTextMiningComponent to retrieve publications relative to protein TP53 with default layout we will write in the wikitext of an article: <wikiopener>HugoTextMining.getReferences|defaultLayout|TP53</wikiopener>

Alternatively, we can define a specific layout. For this, replace the defaultLayout in the previous call by the wikipage containing the template: <wikiopener>HugoTextMining.getReferences|HugoTextMining.getReferences.layout|TP53</wikiopener>

To this, create a wikipage called HugoTextMining.getReferences.layout to create the layout.

Here is an example of a possible layout:

{REFS}.foreach:
* [{LINK} {PMID}]: {NOTE}
{REFS}.end_foreach

We will obtain a bulleted list. Each item will be made of

  1. a link to the publication's abstract [{LINK} {PMID}]
  2. extract of the publications where the query is cited

Naming conventions[edit]

$wg_{component name/filename}_defaultMethod Default method to be called (Note: needs to be declared global).
$wg_{component name}_methods Associative array describing available methods of the form 'method'=>'short description'
function _{component name}_{method name}($params) Available method signature

Automating the call to components[edit]

It is possible to register a component to be called:

  • on a specific namespace:article (though it's easier to put the call directly in the article source)
  • on every page of a given namespace
  • on every page

Going on with our HugoTextMining example, we will create the HugoTextMining namespace and register this component to be called to include references at the end of every article in this namespace.

For this, in LocalSettings or wherever after the execution of

#add configuration parameters here
#setup user rights here
require_once("$IP/extensions/wikiopener/wikiopener.php");

# Specify path for components PHP files
_wikiopener_register_data_provider('extensions/wikiopener/components');

we add the following

# create extra namespace
$wgExtraNamespaces = array(100=>'HugoTextMining',101=>'HugoTextMiningTalk'); // Extra namespace must be declared
# register component for namespace the parameters are (namespace, function to call)
_wikiopener_register_namespace_specific_content_afterArticle('hugotextmining','HugoTextMining');

# function called
function HugoTextMining(&$article, &$wgOut) {
   # we simply add the call to our component with the name of the article viewed
   $wgOut->addPrimaryWikiText("\n<wikiopener>HugoTextMining|HugoTextMining.getReferences.layout|".$article->getTitle()->getText()."</wikiopener>\n",
$article,
false);
}

The result will appear on any page of that namespace, for example for TP53: index.php/HugoTextMining:TP53.

Taking automatically created pages into account[edit]

The possibility to add automatic content on pages of a given namespace may present the drawback that given pages exist when you enter their URL (e.g. index.php/HugoTextMining:TP53) but cannot be linked to (red link) and cannot be searched via the classical wiki search tool.

The wikiopener extension allows to circumvent these drawback.

Add existing pages[edit]

When applying the function _wikiopener_register_namespace_specific_content_afterArticle that allow to fill every page of the namespace HugoTextMining: with data available in an external resource, these pages cannot be linked to as, for the MediaWiki motor, this page has never been created. These pages can thus only be reached by entering the URL in the address bar of the browser.

We thus have to indicate manually to MediaWiki which pages exist. In our text-mining example, let's assume that all genes for which there is at least one reference do correspond to a page of the wiki.

To this, add the following lines to the LocalSettings.php file.

# register component for the existing page in the namespace. The parameters are (namespace, function to call). The function to call must return true (the page exist) or false (the page does not exist).

_wikiopener_register_article_exists('hugotextmining','_hugotextminingArticle_exists');

# function called. This function must return true (the page exist) or false (the page does not exist).
function _hugotextminingArticle_exists($title) {
	$page = strtolower($title->getText());
	$query = $page;
	$result = false;
	$result{'REFS'} = array();
	$xml = simplexml_load_file("http://www.ebi.ac.uk/das-srv/genedas/das/textmining/features?segment=$query");
	foreach ($xml->GFF->SEGMENT->FEATURE as $f) {
		if ((string)$f->TYPE == 'reference') {
		  $result = true;
		  break;
		}
	}
	return $result;
}

Search automatic[edit]

If you want to search for a page that was not created but that is automatically filled. You need to circumvent the MediaWiki basic search function.

In our TextMining test case, we have to circumvent the search function so that it redirects the user to the right page of the wiki if the gene name he enters in the search filled contains references in the database.

# search page
# register the search component and indicates the function that will make the search.
_wikiopener_register_searchEngine_getNearMatch('_search_gene');

# If there is references for this gene, the function redirects the user to the HugoTextMining:$title
# Note that the "URL OF YOUR SERVER" string must be replaced by the right URL (e.g. http://www.myurl.com/") 
function _search_gene($term, $title) {
	$query = $title;
	$result = false;
	simplexml_load_file("http://www.ebi.ac.uk/das-srv/genedas/das/textmining/features?segment=$query");
	foreach ($xml->GFF->SEGMENT->FEATURE as $f) {
		if ((string)$f->TYPE == 'reference') {
		  $result = true;
		  break;
		}
	}
  	if ($result) {
  		$url = "URL OF YOUR SERVER".$wgScriptPath."/index.php";
    		$url .= "/HugoTextMining:$query";
    		header ("Location: ".$url);
	}
}

Tip : removing the empty page tag[edit]

Even if the the page is filled with content from external resources, the page will contain the classical wiki tag There is currently no text in this page. You can search for .... To remove it from all pages of a given namespace, you can follow this tip.

First, add the following function in the MediaWiki code (e.g. in wikiopener.php or in LocalSettings.php).

function _wikiopener_remove_empty_article_banner(&$wgOut) {  
    // REMOVE TEXT SAYING THAT THE ARTICLE IS EMPTY
    $removeScript="<script language='javascript'>\n".
	
      "// setStyleByClass: given an element type and a class selector,\n".
      "// style property and value, apply the style.\n".
      "// args:\n".
      "//  t - type of tag to check for (e.g., SPAN)\n".
      "//  c - class name\n".
      "//  p - CSS property\n".
      "//  v - value\n".
      "var ie = (document.all) ? true : false;\n".

      "function setStyleByClass(t,c,p,v){\n".
      "var elements;\n".
      "if(t == '*') {\n".
      "// '*' not supported by IE/Win 5.5 and below\n".
      "elements = (ie) ? document.all : document.getElementsByTagName('*');\n".
      "} else {\n".
      "elements = document.getElementsByTagName(t);\n".
      "}\n".
      "for(var i = 0; i < elements.length; i++){\n".
      "var node = elements.item(i);\n".
      "for(var j = 0; j < node.attributes.length; j++) {\n".
      "if(node.attributes.item(j).nodeName == 'class') {\n".
      "if(node.attributes.item(j).nodeValue == c) {\n".
      "node.style[p] = v;\n".
      "}\n".
      "}\n".
      "}\n".
      "}\n".
      "}\n";
      
      $removeScript .= " setStyleByClass('div','noarticletext','display','none');\n".
       
    "</script>\n";
    $wgOut->addHtml($removeScript);
}

Then add an automatic call to this function after each page of the given namespace. In our classical example, the HugoTextMining function we previously described will become.

function HugoTextMining(&$article, &$wgOut) {
   # we simply add the call to our component with the name of the article viewed
   $wgOut->addPrimaryWikiText("\n<wikiopener>HugoTextMining|HugoTextMining.getReferences.layout|".$article->getTitle()->getText()."</wikiopener>\n", $article, false);
   # call the function
   _wikiopener_remove_empty_article_banner($wgOut);


}

Web Forms[edit]

A simple mechanism for web form creation and posting is implemented. Its principle is to register the provided internal component to a given namespace (in our example, it will be 'Form'). Then, when an article from that namespace is consulted, the layout is retrieved from the wiki (in our example 'Form.article.layout', article being the actual page viewed) as well as the specification of the input fields (in our example 'Form.article.inputFields). Then, the form is generated and filled with values provided by a specific component (in our example, it is registered in extensions/forms directory and must be named article.php, article being the page viewed).

Form namespace creation[edit]

The first step is to create a namespace to automatically fetch form layout and specifications, and then to generate the final forms.

In our example, we will add the following to LocalSettings.php

# create namespace to hold web forms
$wgExtraNamespaces[102] = 'Form';
# register component for namespace the parameters are (namespace, function to call)
_wikiopener_register_namespace_specific_content_afterArticle('form','_form_namespace');
_wikiopener_register_data_provider('extensions/wikiopener/forms');
# simple function to handle forms
function _form_namespace(&$article, &$wgOut) {
	_wikiopener_form($article, $wgOut,'Form');
}

Creating a Web Form[edit]

We will create a form to interact with a fake database. Therefore, our form example is named FakeDb and will be accessible at index.php/Form:FakeDb. To specify the layout, we have to create a page Form.FakeDb.layout with the following wiki source:

 {| cellspacing="1"
 |-
 | bgcolor="#ddddff" | Support 
 | {support}
 |-
 | bgcolor="#ddddff" | Congenital Heart Defect
 | {chd}
 |-
 | bgcolor="#ddddff" | Gene 
 | {gene}
 |-
 | bgcolor="#ddddff" | Comments 
 | {comments}
 |}

 {save} {delete} 
 {id}

which should look like

Support {support}
Congenital Herat Defect {chd}
Gene {gene}
Comments {comments}

{save} {delete}

{id}

In this layout, the input fields are named between curly braces and must be specified in index.php/Form.FakeDb.inputFields, which for our example contains the following:

support      | select     | 1    | {SUPPORT}      | no good correlation between CHD type and candidate gene=0;unconfirmed: a single case report=1;likely: 2 or more patients (with CHD and a mutation in the candidate gene)=2;confirmed: 2 or more independent reports > 1% incidence=3
chd          | text       | 80   | {CHD}          | unused
gene         | text       | 16   | {GENE}         | unused
comments     | textarea   | 3x80 | {COMMENTS}     | Free text to comment the update.
save         | submit     | 1    | Save           |
delete       | confirm    | 1    | Delete         | Delete this association | youpi
id           | hidden     | 16   | {ID}           | used for updates


The syntax is :

  • input field name
  • type (text for textfield, textarea, select, submit, confirm for submit with javascript confirmation, hidden, locked for uneditable textfield)
  • size: not always relevant which in this case is ignored. For text, it corresponds to the width. For textarea, it is in the form nblines x nbColumns.
  • default value: retrieved from the specific component and thus should correspond to what is returned by the specific component PHP code.
  • select options for a select field, otherwise the last value is ignored. The syntax for select options is pairs of text=value separated by semi colons.


Now, we need to provide default values and process what is posted by the form. In our example, we have to create the extensions/forms/fakedb.php (fakedb is the page name AND the filename).This file must contain the implementation of 2 methods:

  • _fakedb_fetchData($_GET): to provide default values. The $_GET variable is passed so that Form:FakeDB?id=3 will be prefilled with value correspond to the tuple having id=3 in our database.
  • _fakedb_processPost($_POST)
<?php

global $wg_fakedb_defaultMethod;
$wg_fakedb_defaultMethod = 'fetchData';

global $wg_fakedb_methods;
$wg_hugotextmining_methods = array (
		'default'     =>'see '.$wg_fakedb_defaultMethod.'.',
		'fetchData'   =>'Retrieves data to prefill the form.',
		'processPost' =>'Process posted data.',
);

# For our example, we declare static data, but this can be replaced by a database connection
global $wg_fakeDbData;
$wg_fakeDbData = array(
	'1' => array(
		'SUPPORT' 	=> 2,
		'CHD'		=> 'Tetralogy of Fallot',
		'GENE'		=> 'NKX2.5',
		'COMMENTS'	=> ''
	),
	'2' => array(
		'SUPPORT' 	=> 3,
		'CHD'		=> 'Tetralogy of Fallot',
		'GENE'		=> 'GATA4',
		'COMMENTS'	=> ''
	),
	'3' => array(
		'SUPPORT' 	=> 2,
		'CHD'		=> 'Atrial setpal defect',
		'GENE'		=> 'NKX2.5',
		'COMMENTS'	=> 'Two mutations in NKX2.5 were found in 2 out of 16 unrelated families with ASD and one mutation in NKX2.5 in one out of 13 sporadic patients with ASD. Additionally, one mutation in GATA4 was identified in two out of these 16 unrelated families with ASD (Sarkozy A. et al., 2005). Three mutations in NKX2.5 were found in 3 out of 16 families with ASD. Additionally, two GATA4 mutations in two of these families were identified. (Hirayama-Yamada K et al., 2005). 3 families (33 affected patients with isolated nonsyndromic ASD): 3 mutations (Schott JJ et al., 1998); 3 out of 71 (4%) index patients with secundum ASD (McElhinney DB et al., 2003).'
	)
);



function _fakedb_fetchData($params) {
	$result = array();
	if (isset($params{'id'})) {
		global $wg_fakeDbData;
		$entry = $wg_fakeDbData{$params{'id'}};
		$result{'ID'} = $params{'id'};
		$result{'SUPPORT'} = $entry{'SUPPORT'};
		$result{'CHD'} = $entry{'CHD'};
		$result{'GENE'} = $entry{'GENE'};
		$result{'COMMENTS'} = $entry{'COMMENTS'};
	}
	else {
		$result{'ID'} = 4;
		$result{'SUPPORT'} = 0;
		$result{'CHD'} = 'Enter CHD name here';
		$result{'GENE'} = 'Enter gene name here';
		$result{'COMMENTS'} = 'Provide any additional comments here.';
	}	
	return $result;
	}

function _fakedb_processPost($params) {
	return "Here, anything could be done anything with posted data:\n\n"._wikiopener_wiki_dump($params);
}

Now, we are ready, and the result can be seen at Form:FakeDb for a new entry or Form:FakeDb?id=3 to edit an existing entry.

Using an external resource for the select fields[edit]

Let us be a bit more tricky!

When there are many possibilites for the select field. It might be useful to load all the possibilites from an external resource or a database instead of filling the numerous possibilities in the Form.Article.inputFields wiki page. To do this, we need to use the <wikiopenerformfield> tag in the Form.Article.inputFields wiki page. This tag behaves exactly as the previously described <wikiopener> tag except that it only can be used in inputFields pages.

Let's update the previous example, with a new form that we call Form:FakeDbSelect. In this example, the CHD field of the previous Form:FakeDb form will automatically be prefilled with data coming from an external file called chd.tab that contains a list of CHD (one desease per line).

First of all, we need to use in the <wikiopenerformfield> in the Form.FakeDbSelect.inputFields.

support      | select     | 1    | {SUPPORT}      | no good correlation between CHD type and candidate gene=0;unconfirmed: a single case report=1;likely: 2 or more patients (with CHD and a mutation in the candidate gene)=2;confirmed: 2 or more independent reports > 1% incidence=3
chd          | select     | 1    | {CHD}          | <wikiopenerformfield>chdlist|selectlayout</wikiopenerformfield>
gene         | text       | 16   | {GENE}         | unused
comments     | textarea   | 3x80 | {COMMENTS}     | Free text to comment the update.
save         | submit     | 1    | Save           |
delete       | confirm    | 1    | Delete         | Delete this association | youpi
id           | hidden     | 16   | {ID}           | used for updates

Secondly, we have to create a wiki page containing the layout. This page, called Form.FakeDbSelect.layout will be exactly similar to the page Form.FakeDb.layout we described in the previous section.

 {| cellspacing="1"
 |-
 | bgcolor="#ddddff" | Support 
 | {support}
 |-
 | bgcolor="#ddddff" | Congenital Heart Defect
 | {chd}
 |-
 | bgcolor="#ddddff" | Gene 
 | {gene}
 |-
 | bgcolor="#ddddff" | Comments 
 | {comments}
 |}

 {save} {delete} 
 {id}


Thirdly, as shown in the <wikiopenerformfield> tag, we need to implement the default function of the component file components/chdlist.php that will return the list of CHD stored in the chd.tab file in the format required by the select (option 1=1;option 2=2).

<?php
global $wg_chdlist_defaultMethod;
$wg_chdlist_defaultMethod = 'getChds';
global $wg_chdlist_methods;
$wg_chdlist_methods = array (
		'default'=>'see '.$wg_chdlist_defaultMethod.'.',
		'getReferences'=>'Return the list of CHDS stored in the chd.tab file'
);

function _chdlist_getChds() {
	$result = array();
	$result{'SELECT'} = array();
	$file = "extensions/wikiopener/chd.tab";
	$fh = fopen($file, 'r');
	$chdlist = fread($fh, filesize($file));
	$chds = explode("\n", $chdlist);
	$chdselect = array();
	for ($i = 0; $i < count($chds); $i++) {
	  $chd = $chds[$i];
	  array_push($chdselect, $chd."=".($i+1));
	}
	$result{'SELECT'} = implode(";", $chdselect);
	return $result;
}



?>

The function _chdlist_getChds() will return an array containing the only field SELECT. The layout wiki page selectlayout is thus only
{SELECT}

Finally, we have to modify the fetchDataw function that we presented for the file fakedb.php so that it takes into account the numerical value of the CHD corresponding to the entry that will be edited. This is thus the content of the file fakedbselect.php

<?php

global $wg_fakedbselect_defaultMethod;
$wg_fakedbselect_defaultMethod = 'fetchData';

global $wg_fakedbselect_methods;
$wg_hugotextmining_methods = array (
		'default'     =>'see '.$wg_fakedbselect_defaultMethod.'.',
		'fetchData'   =>'Retrieves data to prefill the form.',
		'processPost' =>'Process posted data.',
);

# For our example, we declare static data, but this can be replaced by a database connection
global $wg_fakedbselectData;
$wg_fakedbselectData = array(
	'1' => array(
		'SUPPORT' 	=> 2,
		'CHD'		=> 'Tetralogy of Fallot',
		'GENE'		=> 'NKX2.5',
		'COMMENTS'	=> ''
	),
	'2' => array(
		'SUPPORT' 	=> 3,
		'CHD'		=> 'Tetralogy of Fallot',
		'GENE'		=> 'GATA4',
		'COMMENTS'	=> ''
	),
	'3' => array(
		'SUPPORT' 	=> 2,
		'CHD'		=> 'Atrial septal defect',
		'GENE'		=> 'NKX2.5',
		'COMMENTS'	=> 'Two mutations in NKX2.5 were found in 2 out of 16 unrelated families with ASD and one mutation in NKX2.5 in one out of 13 sporadic patients with ASD. Additionally, one mutation in GATA4 was identified in two out of these 16 unrelated families with ASD (Sarkozy A. et al., 2005). Three mutations in NKX2.5 were found in 3 out of 16 families with ASD. Additionally, two GATA4 mutations in two of these families were identified. (Hirayama-Yamada K et al., 2005). 3 families (33 affected patients with isolated nonsyndromic ASD): 3 mutations (Schott JJ et al., 1998); 3 out of 71 (4%) index patients with secundum ASD (McElhinney DB et al., 2003).'
	)
);

function _fakedbselect_fetchData($params) {
	$result = array();
	if (isset($params{'id'})) {
		global $wg_fakedbselectData;
		$entry = $wg_fakedbselectData{$params{'id'}};
		$result{'ID'} = $params{'id'};
		$result{'SUPPORT'} = $entry{'SUPPORT'};
		$chd = $entry{'CHD'};
		print $chd;
		$file = "extensions/wikiopener/chd.tab";
		$fh = fopen($file, 'r');		
		$chdlist = fread($fh, filesize($file));
		$chds = explode("\n", $chdlist);
		$chd_value = 0;
		for ($i = 0; $i < count($chds); $i++) {
	  		if ($chds[$i] == $chd) {
	  			$chd_value = $i+1;
	  			break;
	  		}
		}		
		$result{'CHD'} = $chd_value;
		$result{'GENE'} = $entry{'GENE'};
		$result{'COMMENTS'} = $entry{'COMMENTS'};
	}
	else {  
		$result{'ID'} = 4;
		$result{'SUPPORT'} = 0;
		$result{'CHD'} = 'Enter CHD name here';
		$result{'GENE'} = 'Enter gene name here';
		$result{'COMMENTS'} = 'Provide any additional comments here.';
	}
	return $result;
	}

function _fakedbselect_processPost($params) {
	return "Here, anything could be done anything with posted data:\n\n"._wikiopener_wiki_dump($params);
}

We can now observe the result of our manipulations by going to page Form:FakeDbSelect?id=3. The field CHD should be displayed as a select field.

Code[edit]

extensions/wikiopener/wikiopener.php:

<?php


 /**
 * Mediawiki API wikiopener Extension (previously inout): 
 * "Load data from external sources".
 * Copyright (C) 2009 Roland Barriot - Sylvain Brohee
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **/

$wgExtensionCredits['other'][] = array(
       'name' => 'Wikiopener',
       'author' =>'Roland Barriot', 
       'url' => 'http://www.mediawiki.org/wiki/User:Barriot', 
       'description' => 'This Extension serves as a gateway to the outside.'
       );
$wgWikiopenerArticleExists  = array();  // extension -> functions to be called to check if a page exists (-blue read-  or -red edit- link)
$wgWikiopenerDataProviderPaths               	= array();  // set of paths to search for files
$wgWikiopenerNamespaceSpecificBeforeArticle      = array();  // namespace -> function to be called
$wgWikiopenerNamespacePageSpecificBeforeArticle  = array();  // namespace -> page -> fonction to be called
$wgWikiopenerNamespaceSpecificAfterArticle       = array();  // namespace -> fonction to be called
$wgWikiopenerNamespacePageSpecificAfterArticle   = array();  // namespace -> page -> fonction to be called
$wgWikiopenerSearchEngine_getNearMatch  			= array(); // custom search engines to use to find near match

//// <wikiopener> Extend Wiki </wikiopener> 
// Register the wikiopener extension with MediaWiki 
$wgExtensionFunctions[] = '_wikiopener_register_extension';
function _wikiopener_register_extension() {
  global $wgParser;
  $wgParser->setHook('wikiopener', '_wikiopener_parse');
}


//// Processes & Renders $input.
// @param string
//  The string should be in the following format:
//      component.method|layout template[|param1|...]
// @return string
function _wikiopener_parse($input) {
	global $wgParser;
	$exploded = explode('|', $input);
	if (count($exploded)<2) {
		return "<strong>Wikiopener: no input:</strong> Syntax is <code><nowiki>&lt;wikiopener>Component.method|Layout[|parameters]&lt;/wikiopener></nowiki></code>";
	}

	$dataProvider = array_shift($exploded);
	$dataProvider = strtolower($dataProvider);
	$templateName = array_shift($exploded);
	$explodeds = explode('.', $dataProvider);
	$component = array_shift($explodeds);
	$method    = array_shift($explodeds);
	$method = _wikiopener_loadComponentMethod($component, $method);
	if (is_callable($method)) {
		$data = call_user_func($method,$exploded);   
		$result = _wikiopener_render($templateName,$data);  
		return $wgParser->recursiveTagParse($result);
	}
	else if ($method > 0)
		return "<strong>Wikiopener Error:</strong> method not found '<strong>$method</strong>'";			
	else return "<strong>Wikiopener Error:</strong> component not found '<strong>$component</strong>'";
}

//// REGISTRATION 
// where to find .php files for data provider
function _wikiopener_register_data_provider($path) {
	global $wgWikiopenerDataProviderPaths;
	array_push($wgWikiopenerDataProviderPaths,$path);
}

// functions to be called to include automatically some content for given namespace (and page)
function _wikiopener_register_namespace_specific_content_beforeArticle($namespace,$function) {
	global $wgWikiopenerNamespaceSpecificBeforeArticle;
	$wgWikiopenerNamespaceSpecificBeforeArticle{$namespace} = $function;
}

function _wikiopener_register_namespace_page_specific_content_beforeArticle($namespace,$page,$function) {
	global $wgWikiopenerNamespacePageSpecificBeforeArticle;
	if (!isset($wgWikiopenerNamespacePageSpecificBeforeArticle{$namespace}))
		$wgWikiopenerNamespacePageSpecificBeforeArticle{$namespace} = array();
	$wgWikiopenerNamespacePageSpecificBeforeArticle{$namespace}{$page} = $function;	
}

function _wikiopener_register_namespace_specific_content_afterArticle($namespace,$function) {
	global $wgWikiopenerNamespaceSpecificAfterArticle;
	$wgWikiopenerNamespaceSpecificAfterArticle{$namespace} = $function;
}

function _wikiopener_register_namespace_page_specific_content_afterArticle($namespace,$page,$function) {
	global $wgWikiopenerNamespacePageSpecificAfterArticle;
	if (!isset($wgWikiopenerNamespacePageSpecificAfterArticle{$namespace}))
		$wgWikiopenerNamespacePageSpecificAfterArticle{$namespace} = array();
	$wgWikiopenerNamespacePageSpecificAfterArticle{$namespace}{$page} = $function;	
}

// Registering functions that enalble wikiopener to know where to find the data and what function to call when
// Specify a function to call when a link is rendered blue or red depending on the existence of a page
function _wikiopener_register_article_exists($namespace,$function) {
	global $wgWikiopenerArticleExists;
	$wgWikiopenerArticleExists{$namespace} = $function;
}

function _wikiopener_register_searchEngine_getNearMatch($function) {
	global $wgWikiopenerSearchEngine_getNearMatch;
	array_push($wgWikiopenerSearchEngine_getNearMatch,$function);
}

//// HOOKS
$wgHooks['wgOutHookBeforeArticleContent'][] = '_wikiopener_fnNamespaceSpecificContentHook_beforeArticle';
$wgHooks['wgOutHookAfterArticleContent'][] = '_wikiopener_fnNamespaceSpecificContentHook_afterArticle';
$wgHooks['wgSearchEngineGetNearMatch'][] = '_wikiopener_fnSearchEngine_getNearMatch';

//// HOOKS FUNCTIONS
function _wikiopener_fnNamespaceSpecificContentHook_beforeArticle(&$article, &$wgOut) {  
	global $wgExtraNamespaces, $wgWikiopenerNamespaceSpecificBeforeArticle, $wgWikiopenerNamespacePageSpecificBeforeArticle;
	if (! isset($wgExtraNamespaces{$article->getTitle()->getNamespace()}))
		return true;
	$namespace = strtolower($wgExtraNamespaces{$article->getTitle()->getNamespace()});
	$page      = strtolower($article->getTitle()->getText());

	foreach ($wgWikiopenerNamespaceSpecificBeforeArticle as $ns=>$f) {
		if ($ns == $namespace && is_callable($f))
			call_user_func($f,$article,$wgOut);
	}
	
	foreach ($wgWikiopenerNamespacePageSpecificBeforeArticle as $ns=>&$p) {
		foreach ($p as $u=>$f) {
			if ($ns == $namespace && $page == $u && is_callable($f))
				call_user_func($f,$article,$wgOut);
		}
	}
	return true;
}

function _wikiopener_fnNamespaceSpecificContentHook_afterArticle(&$article, &$wgOut) {
        
	global $wgExtraNamespaces, $wgWikiopenerNamespaceSpecificAfterArticle, $wgWikiopenerNamespacePageSpecificAfterArticle;  
	if (! isset($wgExtraNamespaces{$article->getTitle()->getNamespace()}))
		return true;
	$namespace = strtolower($wgExtraNamespaces{$article->getTitle()->getNamespace()});
	$page      = strtolower($article->getTitle()->getText());

	foreach ($wgWikiopenerNamespaceSpecificAfterArticle as $ns=>$f) {
		
		if ($ns == $namespace && is_callable($f)) {
			call_user_func($f,$article,$wgOut);
			
		}
	}
	foreach ($wgWikiopenerNamespacePageSpecificAfterArticle as $ns=>&$p) {
		foreach ($p as $u=>$f) {
			if ($ns == $namespace && $page == $u && is_callable($f))
				call_user_func($f,$article,$wgOut);
		}
	}
	return true;
}

function _wikiopener_fnSearchEngine_getNearMatch(&$term, &$title, &$found) {
	global $wgWikiopenerSearchEngine_getNearMatch;
	foreach ($wgWikiopenerSearchEngine_getNearMatch as $f) {
		if (is_callable($f))
			$found = call_user_func($f,$term, $title);
		if ($found) return true;
	}
	return true;
}

function _wikiopener_articleExists($title) {
	global $wgWikiopenerArticleExists;
	foreach ($wgWikiopenerArticleExists as $ext=>$fct) {
		if (is_callable($fct))
			if (call_user_func($fct,$title))
				return true;  
	}
	return false;
}

// RENDERING
function _wikiopener_render($template, $data) {
  $templateSrc = _wikiopener_getRawPage($template);
  if (! $templateSrc) {
	$result = "'''Wikiopener warning:''' layout not found '$template', using default.\n\n". _wikiopener_wiki_dump($data);
  }
  else $result = _wikiopener_templateRenderer($templateSrc,$data);
  return $result;
}

function _wikiopener_wiki_dump($variable, $spc='') {
    if ($variable === true) {
        return 'true';
    } 
	else if ($variable === false) {
        return 'false';
    } 
	else if ($variable === null) {
        return 'null';
    } 
	else if (is_array($variable)) {
        $res = "";
        foreach ($variable as $key => $value) { 
			$res .= $spc."'''$key''' "; 
			if (is_array($value)) 
				$res .= "=> {\n\n";
			else $res.= "= ";
			$res.= _wikiopener_wiki_dump($value, $spc.'&nbsp;&nbsp;&nbsp;');
			if (is_array($value)) 
				$res .= $spc."}\n\n";
			
		}
        return $res;
    } 
	else {
        return strval($variable)."\n\n";
    }
}

function _wikiopener_templateRenderer($template,&$data) {
  $result = '';
  $arrays = array();
  foreach ($data as $mk => $val) {
    if (is_array($val))
	array_push($arrays,'{'.$mk.'}');
  }
  if (_wikiopener_contains_any($template,$arrays)) { // replacing complex type
    $tag = _wikiopener_firstTag($template,$arrays);
    $pos = strpos($template,$tag);
      
    // first renders the begining of the template
    if ($pos>0) {
      $result.=_wikiopener_templateRenderer(substr($template,0,$pos),$data);
    }
    $template = substr($template,$pos+strlen($tag));

    // analyse what method has been called
    $posend = strpos($template,':');
    $method = $template;
    if ($posend!==false) {
      $method = substr($template,1,$posend-1);
      $template = substr($template,$posend+1);
    }

    if ($method == 'foreach' || $method == 'loop') {
      // find closing tag
      $close = strripos($template,$tag.".end_$method");
      $loopcontent = substr($template,0,$close);
      $template    = substr($template,$close+strlen($tag.".end_$method"));
      $nbFound=0;
      foreach ($data{substr($tag,1,strlen($tag)-2)} as $elem) { // DO THE LOOP
		$result.=_wikiopener_templateRenderer($loopcontent,$elem);
		$nbFound++;
      }
      if ($nbFound==0) $result .= "none\n";
	
    } 
      
    // finally renders the end of the line
    if (strlen($template) >0) $result.=_wikiopener_templateRenderer($template,$data);
  }
  else { // not list
    $patterns = array();
    $replacements = array();
    foreach ($data as $k => $v) {
      array_push($patterns, '{'.$k.'}');
      array_push($replacements,$v);
    }

    $old_level = error_reporting(0);
    $template = str_replace($patterns, $replacements, $template);
    error_reporting($old_level);
    $result.=$template;
  }
  return $result;
}

//// UTILITY FUNCTIONS
function _wikiopener_loadComponentMethod($component,$method) {
	global $wgWikiopenerDataProviderPaths;
	$components = 0;
	foreach ($wgWikiopenerDataProviderPaths as $dp) {

		$requiredFile = "$dp/$component.php";
		if (file_exists($requiredFile)) {
			$components++;
			require_once($requiredFile);
			$m    = '_'.$component.'_'.$method;

			if (!is_callable($m)) {
				global ${'wg_'.$component.'_defaultMethod'}; // retrieves default method name
				$m = '_'.$component.'_'.strtolower(${'wg_'.$component.'_defaultMethod'});
			}
			if (is_callable($m))
				return $m;
		}
	}
	return $components;
}

function _wikiopener_contains_any($string,$arr) {
  $res = false;
  foreach ($arr as $v)
    if (strpos($string,$v)!==false)
      return true;
  return $res;
}

function _wikiopener_firstTag($string,$arr) {
  $min=strlen($string);
  $tag='';
  foreach ($arr as $v)
    if (strpos($string,$v)!==false && strpos($string,$v) < $min) {
      $tag = $v;
      $min = strpos($string,$v);
    }
  return $tag;
}

////WIKI HELPER FUNCTIONS
function _wikiopener_getRawPage($pageName) {
  $title = Title::newFromText($pageName);
  $art = new Article($title);
  $raw = new RawPage($art);
  return $raw->getRawText();
}

//// FORM HANDLING
function _wikiopener_form(&$article, &$wgOut, $namespace) {
	global $wgWikiopenerDataProviderPaths;
	$templateName=$article->getTitle()->getPartialURL();
	// STORE GET PARAMETERS IF ANY
	$get_params = $_GET;
	$arr = explode("%3F",$templateName); // splits on &	
	if (count($arr)>1) {
		$templateName = array_shift($arr);
		$get_params = array();
		$pstr = join("?",$arr);
		$ps = explode("%26",$pstr); // splits on &
		foreach ($ps as $p) {
			$arr = explode("%3D",$p); // splits on =
			$get_params{$arr[0]} = $arr[1];
		}
	}
	$dataProvider=strtolower($templateName);
	// find data provider
	$dataProviderPath = null;
	foreach ($wgWikiopenerDataProviderPaths as $dp) {
		$requiredFile = "$dp/$dataProvider.php";
		if (file_exists($requiredFile))
			$dataProviderPath = $dp;
	}
	if ($dataProviderPath!=null) {
		$requiredFile = "$dataProviderPath/$dataProvider.php";
		require_once($requiredFile);
		// PROCESS POSTs
		if (isset($_POST['_wikiopener_POST'])) {
			$outcome = '';
			if (is_callable('_'.$dataProvider.'_processPost'))
				$outcome = call_user_func('_'.$dataProvider.'_processPost',$_POST);
			else $outcome = " '''Error''' while posting ''$templateName'' form: function not implemented.";
			$wgOut->addWikiText($outcome);
		}
		else { // GENERATE FORM BASED ON TEMPLATES AND DATA PROVIDERS
			$formWiki   = _wikiopener_getRawPage($namespace.".".$templateName.".layout");
			$formFields = _wikiopener_getRawPage($namespace.".".$templateName.".inputFields");
			if (preg_match("/\<wikiopenerformfield\>/",$formFields)) {
			  $wikiopenerformfields = explode("<wikiopenerformfield>", $formFields);
			  for ($i = 1; $i < count ($wikiopenerformfields); $i++) {
			    $wikiopenerformfield = $wikiopenerformfields[$i];
			    $wikiopenerformfield_substr = substr($wikiopenerformfield, 0, strpos($wikiopenerformfield, "</wikiopenerformfield>"));
			    $exploded = explode('|', $wikiopenerformfield_substr);
                            $dataProviderff = array_shift($exploded);
                            $dataProviderff = strtolower($dataProviderff);
                            $templateName = array_shift($exploded);
                            $explodeds = explode('.', $dataProviderff);
                            $component = array_shift($explodeds);
                            $method    = array_shift($explodeds);
                            $method = _wikiopener_loadComponentMethod($component, $method);
                            if (is_callable($method)) {
                              $data = call_user_func($method,$exploded);   
                              $result = _wikiopener_render($templateName,$data);
                            }
                            $formFields = str_replace($wikiopenerformfield_substr,$result, $formFields);
			  }
			}
			$fields = _wikiopener_parseFormFields($formFields);
			global $wgOut;
			//$formText = $wgParser->internalParse($formWiki); 
			$formText = $wgOut->parse($formWiki);
			$data = call_user_func('_'.$dataProvider."_fetchData",$get_params);// wikify form content before adding prohibited mediawiki html entities (form...)
			// REPLACE INPUT FIELDs & VALUES
			foreach ($fields as $k=>&$f) {
				$replacement = "Unknown form input field type: '''".$f{'type'}."'''";
				if (is_callable('_wikiopener_'.$f{'type'}.'Field'))
					$replacement = call_user_func('_wikiopener_'.$f{'type'}.'Field', $f, $data);
				else 
					$replacement = "Error in form $namespace:$templateName: invalid input type: '".$f{'type'}."'";

				if (strlen($f{'tip'})>0) {
					$replacement = _wikiopener_templateRenderer($replacement,$data);
				}
				// replace input field
				$formText = str_replace('{'.$f{'name'}.'}',$replacement,$formText);
				// add hidden field for previous value
				$formText .= _wikiopener_templateRenderer("\n<input type=\"hidden\" name=\"_".$f{'name'}."_\" value=\"".$f{'value'}."\">\n",$data);
			}
			$form = "<form id=\"wikiopener_form\" method=\"post\" enctype=\"multipart/form-data\"\">".
				$formText.
				'<input type="hidden" name="_wikiopener_POST" value="true">'.
				"</form>";
			$wgOut->addHtml($form);
		}
	}
	else {
		$wgOut->addWikiText("'''Problem while retrieving data for form:''' could not open file '$requiredFile'\n\nProblem in page name you asked ?");
	}
}

function _wikiopener_parseFormFields($formFields) {
  $lines = explode("\n", $formFields);
  $result = array();
  foreach ($lines as &$line) {
    $i = explode("|",$line);
    $name = trim(array_shift($i));
    $f = array();
    $f{'name'}  = $name;
    $f{'type'}  = strtolower(trim(array_shift($i)));
    $f{'size'}  = strtolower(trim(array_shift($i)));
    $f{'value'} = trim(array_shift($i));
    if ($f{'type'} == 'select' || $f{'type'} == 'checkbox' || $f{'type'} == 'radio') {
      $f{'labels'} = trim(array_shift($i));
    }
    $f{'tip'}   = trim(array_shift($i));
	$f{'supplementary'} = $f{'tip'};
    $result{$name} = $f;
  }
  return $result;
}

// FORM INPUT FIELDS
function _wikiopener_selectField($f,$data) {
  $replacement = "<select name=\"".$f{'name'}."\"".$f{'supplementary'}.">";
  // identify data source
  if (strpos($f{'labels'},'{')!==false) {
    // find key
    $bid = split('=',$f{'labels'});
    $lab = $bid[0];
    $key = $bid[1];
    // find tags
    $tags = array();
    $ind=0;	 	  
    while (strpos($lab,'{',$ind)!==false) {
      $beg = strpos($lab,'{',$ind);
      $end = strpos($lab,'}',$ind);
      array_push($tags,substr($lab,$beg+1,$end-$beg-1));
      $ind=$end+1;
    }
    $f{'value'}=_wikiopener_templateRenderer($f{'value'},$data);
    
    

    while (count($data{$tags[0]})>0) {
      $aLabel=$lab;
      foreach ($tags as $t) {
	$v = array_shift($data{$t});
	if ('{'.$t.'}' == $key) $replacement .= "<option value=\"".$v."\"";
	if ($v == $f{'value'})  $replacement .= " selected ";
	$aLabel = str_replace('{'.$t.'}',$v,$aLabel);
      }
      $replacement.=">$aLabel</option>\n";
    }
  }
  else {
    $labels = split(';',$f{'labels'});
    foreach ($labels as $l) {
      // selected ?
      $a = split('=',$l);
      $replacement.="<option value=\"".$a[1]."\"";
      $val = _wikiopener_templateRenderer($f{'value'},$data);
      $field = $f{'value'};
      $field = str_replace("{", "", $field);
      $field = str_replace("}", "", $field);
      if ($val == $a[1] || $a[1] == $data{'$field'}) $replacement.=" selected ";
      $replacement.=">".$a[0]."</option>";
    }
  }
  $replacement.="</select>";
  return $replacement;
}
function _wikiopener_textareaField($f,$data) {
  $a = split("x",$f{'size'});
  $rows = array_shift($a);
  $cols = array_shift($a);
  $replacement = "<textarea name=\"".$f{'name'}."\" rows=\"$rows\" cols=\"$cols\">".$f{'value'}."</textarea>";
  $replacement = _wikiopener_templateRenderer($replacement,$data);
  return $replacement;
}
function _wikiopener_textField($f,$data) {
  $replacement="<input type=\"text\" name=\"".$f{'name'}."\" size=\"".$f{'size'}."\" value=\"".$f{'value'}."\">";
  $replacement = _wikiopener_templateRenderer($replacement,$data);
  return $replacement;
}
function _wikiopener_lockedTextField($f,$data) {
  $replacement="<input type=\"text\" name=\"".$f{'name'}."\" size=\"".$f{'size'}."\" value=\"".$f{'value'}."\" style=\"color: #000;\"disabled>";
  $replacement = _wikiopener_templateRenderer($replacement,$data);
  return $replacement;
}
function _wikiopener_hiddenField($f,$data) {
  $replacement="<input type=\"hidden\" name=\"".$f{'name'}."\" value=\"".$f{'value'}."\">";
  $replacement = _wikiopener_templateRenderer($replacement,$data);
  return $replacement;
}
function _wikiopener_submitField($f, $data) {
  $replacement = '<input type="submit" name="'.$f{'name'}.'" value="'.$f{'value'}.'">';
  $replacement = _wikiopener_templateRenderer($replacement,$data);
  return $replacement;
}
function _wikiopener_confirmField($f, $data) {
  $replacement = '<input type="submit" name="'.$f{'name'}.'" value="'.$f{'value'}.'" onClick="return confirmFormSubmission()">'.
	"<script>\n".
	"function confirmFormSubmission() {\n".
	"  var answer = confirm('Really do this?');\n".
	"  if (answer) {\n".
	"      return true;\n".
	"  }\n".
	"  return false;\n".
	"}\n".
	"</script>\n";
  $replacement = _wikiopener_templateRenderer($replacement,$data);
  return $replacement;
}