Difference between revisions of "Extension:Treeview4.php"

From Organic Design wiki
m (change $uid to $voo to clarify)
m (formatting)
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
{{legacy|the [[MW:Extension:TreeAndMenu|TreeAndMenu extension]]}}{{voodoo}}
 +
<source lang="php">
 
<?php
 
<?php
/**
+
# MediaWiki Treeview Extension
* MediaWiki Treeview Extension{{php}}{{Category:Extensions|Treeview5}}{{Category:Code that uses voodoo|Treeview5}}
+
# - See http://www.mediawiki.org/wiki/Extension:Tree_view for installation and usage details
* - See http://www.mediawiki.org/wiki/Extension:Tree_view for installation and usage details
+
# - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
* - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
+
# - Author:  http://www.organicdesign.co.nz/nad
* - Author:  http://www.organicdesign.co.nz/nad
+
# - Started: (Version4) 2007-09-06
* - Started: (Version5) 2007-10-24{{Category:Extensions in progress|Treeview5}}
+
*/
 
 
 
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
 
+
define('TREEVIEW5_VERSION','5.0.2, 2008-02-27');
+
define('TREEVIEW4_VERSION','4.0.9, 2007-12-29');
 
+
 
# Set any unset images to default titles
 
# Set any unset images to default titles
 
if (!isset($wgTreeViewImages) || !is_array($wgTreeViewImages)) $wgTreeViewImages = array();
 
if (!isset($wgTreeViewImages) || !is_array($wgTreeViewImages)) $wgTreeViewImages = array();
 +
if (!isset($wgTreeViewImages['plus']))  $wgTreeViewImages['plus']  = 'Plus.gif';
 +
if (!isset($wgTreeViewImages['minus']))  $wgTreeViewImages['minus']  = 'Minus.gif';
 +
if (!isset($wgTreeViewImages['opened'])) $wgTreeViewImages['opened'] = 'Folder_opn_sml_yel.gif';
 +
if (!isset($wgTreeViewImages['closed'])) $wgTreeViewImages['closed'] = 'Folder_sml_yel.gif';
 +
if (!isset($wgTreeViewImages['doc']))    $wgTreeViewImages['doc']    = 'Doc-icon.gif';
 +
if (!isset($wgTreeViewImages['spacer'])) $wgTreeViewImages['spacer'] = 'Blank.gif';
  
 +
# These images are needed if you have $wgTreeViewShowLines set
 +
if (!isset($wgTreeViewImages['vert'])) $wgTreeViewImages['vert'] = 'Vertline.gif';
 +
if (!isset($wgTreeViewImages['node'])) $wgTreeViewImages['node'] = 'Node.gif';
 +
if (!isset($wgTreeViewImages['last'])) $wgTreeViewImages['last'] = 'Lastnode.gif';
 +
 
# Keep track of JavaScript added to page to avoid doubleups
 
# Keep track of JavaScript added to page to avoid doubleups
 
if (!isset($wgJS)) $wgJS = array();
 
if (!isset($wgJS)) $wgJS = array();
 
+
$wgTreeView5Magic             = "tree"; # the parser-function name for trees
+
$wgTreeView4Magic             = "tree"; # the parser-function name for trees
 +
$wgTreeViewIndent              = 0;      # the number of pixels each level indents by (0 means doc-icon width)
 
$wgTreeViewShowLines          = false;  # whether to render the dotted lines joining nodes
 
$wgTreeViewShowLines          = false;  # whether to render the dotted lines joining nodes
$wgExtensionFunctions[]        = 'wfSetupTreeView5';
+
$wgExtensionFunctions[]        = 'wfSetupTreeView4';
$wgHooks['LanguageGetMagic'][] = 'wfTreeView5LanguageGetMagic';
+
$wgHooks['LanguageGetMagic'][] = 'wfTreeView4LanguageGetMagic';
 
+
 
$wgExtensionCredits['parserhook'][] = array(
 
$wgExtensionCredits['parserhook'][] = array(
'name'        => 'Treeview5',
+
'name'        => 'Treeview4',
'author'      => '[http://www.organicdesign.co.nz/nad Nad], [http://www.organicdesign.co.nz/User:Sven Sven]',
+
'author'      => '[http://www.organicdesign.co.nz/nad User:Nad]',
 
'url'        => 'http://www.mediawiki.org/wiki/Extension:Treeview',
 
'url'        => 'http://www.mediawiki.org/wiki/Extension:Treeview',
'description' => 'Extends the wiki parser to allow bullet and numbered lists to work with recursion and optionally
+
'description' => 'Allows dynamic tree-views to be made with bullet-list syntax',
allows these to be rendered as collapsible trees using the free
+
'version'    => TREEVIEW4_VERSION
[http://www.destroydrop.com/javascripts/tree dTree] JavaScript tree menu.',
 
'version'    => TREEVIEW5_VERSION
 
 
);
 
);
 
+
class TreeView5 {
+
class TreeView4 {
 
+
var $version  = TREEVIEW5_VERSION;
+
var $version  = TREEVIEW4_VERSION;
var $voo;
+
var $width    = 16;
var $baseDir;
+
var $uniq    = array();
var $baseUrl;
+
var $uid      = '';
var $useLines;
+
var $js      = 0;
 
+
/**
+
# Constructor
* Constructor
+
function TreeView4() {
*/
+
global $wgParser,$wgHooks,$wgTreeView4Magic,$wgTreeViewIconMagic,$wgTreeViewImages,$wgTreeViewIndent;
function TreeView5() {
 
global $wgParser,$wgScriptPath,$wgTreeView5Magic,$wgTreeViewImages,$wgTreeViewShowLines;
 
  
 
# Convert image titles to file paths and obtain pixel width of items
 
# Convert image titles to file paths and obtain pixel width of items
 +
if ($wgTreeViewIndent) $this->width = $wgTreeViewIndent;
 
foreach ($wgTreeViewImages as $k => $v) {
 
foreach ($wgTreeViewImages as $k => $v) {
 
$title = Title::newFromText($v,NS_IMAGE);
 
$title = Title::newFromText($v,NS_IMAGE);
 
$image = Image::newFromTitle($title);
 
$image = Image::newFromTitle($title);
if ($image && $image->exists()) $wgTreeViewImages[$k] = $image->getURL();
+
if ($image && $image->exists()) {
 +
$wgTreeViewImages[$k] = $image->getURL();
 +
if ($wgTreeViewIndent < 1 && $k == 'doc') $this->width = $image->getWidth();
 +
}
 
}
 
}
  
$wgParser->setFunctionHook($wgTreeView5Magic,array($this,'Tree'));
+
$this->uid = uniqid('TVUNIQ');
+
$wgParser->setFunctionHook($wgTreeView4Magic,array($this,'Tree'));
$this->voo = uniqid('TVUNIQ');
+
}
$this->baseDir = dirname(__FILE__);
+
$this->baseUrl = preg_replace('|^.+(?=/extensions)|',$wgScriptPath,$this->baseDir);
+
# Restructure recursive trees into a single bullet list surrounded by internal treeview4 tags
$this->useLines = $wgTreeViewShowLines ? 'true' : 'false';
 
}
 
 
 
/**
 
* Restructure recursive trees into a single bullet list surrounded by internal treeview5 tags
 
*/
 
 
function Tree(&$parser) {
 
function Tree(&$parser) {
 
global $wgParser;
 
global $wgParser;
$id = uniqid("");
+
$id   = uniqid();
 
$args = "id='$id'";
 
$args = "id='$id'";
 
foreach (func_get_args() as $arg) if (!is_object($arg)) {
 
foreach (func_get_args() as $arg) if (!is_object($arg)) {
Line 75: Line 81:
 
# Black magic
 
# Black magic
 
$text = $this->uniq[$id] = preg_replace(
 
$text = $this->uniq[$id] = preg_replace(
'/^\\*(\\**)\\s*(\\x07UNIQ.+?-treeview5(.+?)-.+?-QINU\\x07?)/me',
+
'/^\\*(\\**)\\s*(\\x07UNIQ.+?-treeview4(.+?)-.+?-QINU\\x07?)/me',
 
'$this->uniq["$3"] ? preg_replace("/^(\\*+)/m","$1\$1",$this->uniq["$3"]) : "$1$2"',
 
'$this->uniq["$3"] ? preg_replace("/^(\\*+)/m","$1\$1",$this->uniq["$3"]) : "$1$2"',
 
$text
 
$text
Line 81: Line 87:
  
 
# And a little more voodoo here
 
# And a little more voodoo here
$wgParser->setHook("treeview5$id",array($this,'treeview'));
+
$wgParser->setHook("treeview4$id",array($this,'treeview'));
return "<treeview5$id $args>$text</treeview5$id>";
+
return "<treeview4$id $args>$text</treeview4$id>";
 
}
 
}
  
/**
+
# Convert a bullet list into a treeview
* Convert a bullet list into a treeview
 
*/
 
 
function treeview($text,$argv,&$parser) {
 
function treeview($text,$argv,&$parser) {
global $wgTreeViewImages,$wgJsMimeType;
+
global $wgTreeViewImages;
 
+
$id   = 'tv'.$argv['id']; // id of this tree (must be valid JS variable identifier)
+
$id     = $argv['id'];
$voo  = $this->voo;     // used by voodoo
+
$uid    = $this->uid;
$root = isset($argv['root']) ? $argv['root'] : '';
+
$ver    = $this->version;
 
+
$width  = $this->width;
 +
$plus    = $wgTreeViewImages['plus'];
 +
$minus  = $wgTreeViewImages['minus'];
 +
$opened  = $wgTreeViewImages['opened'];
 +
$closed  = $wgTreeViewImages['closed'];
 +
$spacer  = $wgTreeViewImages['spacer'];
 +
$doc    = $wgTreeViewImages['doc'];
 +
$vert    = $wgTreeViewImages['vert'];
 +
$node    = $wgTreeViewImages['node'];
 +
$last    = $wgTreeViewImages['last'];
 +
$default = isset($argv['openlevels']) ? $argv['openlevels']+1 : 1;
 +
$rows    = array();
 +
$lasts  = array();
 +
$cols    = 0;
 +
$next    = 0;
 +
$tree    = '';
 +
 
# Protect the asterisk structure and wiki-parse the bullet tree
 
# Protect the asterisk structure and wiki-parse the bullet tree
$text = preg_replace("/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/","{$voo}3$1{$voo}4",$text);
+
$text = preg_replace("/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/","{$uid}3$1{$uid}4",$text);
 
$text = preg_replace_callback("/^(\\*+)(.*?)$/m",array($this,'protectTree'),$text);
 
$text = preg_replace_callback("/^(\\*+)(.*?)$/m",array($this,'protectTree'),$text);
 
$out  = $parser->parse($text,$parser->mTitle,$parser->mOptions,false,false);
 
$out  = $parser->parse($text,$parser->mTitle,$parser->mOptions,false,false);
Line 102: Line 122:
  
 
# Extract the valid rows and process if any
 
# Extract the valid rows and process if any
if (preg_match_all("/1{$voo}([0-9]+)-({$voo}3(.+?){$voo}4)?(.+?){$voo}2/",$text,$matches,PREG_SET_ORDER)) {
+
if (preg_match_all("/1{$uid}([0-9]+)-({$uid}3(.+?){$uid}4)?(.+?){$uid}2/",$text,$matches,PREG_SET_ORDER)) {
 
# Convert the custom images in $wgTreeViewImages into JS statements
 
$images = '';
 
foreach ($wgTreeViewImages as $k => $v) $images .= "$id.icon['$k'] = '$v';";
 
 
# Build the dTree instantiation code
 
$dtree = "// TreeView{$this->version}
 
$id = new dTree('$id');
 
for (i in $id.icon) $id.icon[i] = '{$this->baseUrl}/'+$id.icon[i];
 
{$images}
 
$id.config.useLines = {$this->useLines};
 
$id.add(0, -1, '$rootname');
 
";
 
  
# Add the content to the dTree object
+
# Parse 1
$counter  = 1;
 
$parent    = 0;
 
$currdepth = 0;
 
 
foreach ($matches as $row) {
 
foreach ($matches as $row) {
 
list(,$depth,,$icon,$item) = $row;
 
list(,$depth,,$icon,$item) = $row;
 
if ($depth > $cols) $lasts[$cols = $depth] = true;
 
if ($depth > $cols) $lasts[$cols = $depth] = true;
$parent    = $currdepth < $depth-1 ? $counter-1 : $depth-1;
+
$rows[] = array($depth,$icon,$item);
$item      = str_replace("'","\\'",$item);
+
}
$dtree    .= "${id}.add($counter,$parent,'$item');\n";
+
 
$currdepth = $depth-1;
+
# Parse 2
$counter++;
+
for ($row = count($rows)-1; $row >= 0; $row--) {
 +
list($depth,$i,$item) = $rows[$row];
 +
$diff = $depth - $next;
 +
$show = $depth > $default ? " style='display:none'" : "";
 +
$span = $cols+2-$depth;
 +
$span = $span == 1 ? "" : " colspan='$span'";
 +
$td  = "";
 +
$ibg  = "";
 +
 
 +
# Determine the open/close link and the folder/doc icon
 +
if ($depth >= $default) { $open = $plus; $icon = $closed; }
 +
else { $open = $minus; $icon = $opened; $ibg = " style='background: url($vert) 0 10px no-repeat'"; }
 +
if ($depth >= $next) { $open = "<img src='$spacer' width='$width'/>"; $icon = $doc; $ibg = ''; }
 +
else $open = "<a href='javascript:toggleTreeviewItem(\"$id\",$row)'><img id='tvi$id$row' src='$open'/></a>";
 +
 
 +
# Handle custom image
 +
if ($i) {
 +
$i = Image::newFromTitle(Title::newFromText($i,NS_IMAGE));
 +
if ($i && $i->exists()) $icon = $i->getURL();
 +
}
 +
 
 +
# Construct the dotted joining lines and position the open/close link in the last one
 +
if ($diff > 0) $lasts[$depth] = true;
 +
for ($col = $depth+1; $col < $cols; $col++) $lasts[$col] = true;
 +
for ($col = 1; $col <= $depth; $col++) {
 +
if ($diff > 1 && $col > $next && $col < $depth-1) $lasts[$col] = true;
 +
if ($col == $depth) { $fg = $open; $bg = $lasts[$col] ? $last : $node; }
 +
else { $fg = ""; $bg = $lasts[$col] ? $spacer : $vert; }
 +
$td .= "<td style='background: url($bg) no-repeat'>$fg</td>";
 +
}
 +
$lasts[$depth] = false;
 +
 
 +
# Add the icon and the item content and append a table row to the tree definition
 +
$td .= "<td class='tree-icon'><img id='tvf$id$row' src='$icon'/></td>";
 +
$td .= "<td class='tree-text'$span>$item</td>";
 +
$tree = "<tr$show depth='$depth' id='tvr$id$row'>$td</tr>\n$tree";
 +
$next = $depth;
 +
}
 
}
 
}
  
# Output the dTree
+
# This invisible row at the start fixes a bug making some text items not maximum width
$dtree .= "document.write($id);\n";
+
$td = '';
 +
for ($col = 1; $col <= $cols+1; $col++) $td .= "<td width=0/>";
 +
$tree = "<tr>$td<td width=100%/></tr>\n$tree";
 +
 
 +
return "<table cellpadding=0 cellspacing=0 class='tree-view' id='tv$id' title='Extension:Treeview (ver $ver)'>\n$tree</table>";
 
}
 
}
  
return("<script type=\"$wgJsMimeType\">\n<!--\n$dtree-->\n</script>");
+
# Protect asterisk bullet structure from wiki parser
}
 
 
 
/**
 
* Protect asterisk bullet structure from wiki parser
 
*/
 
 
function protectTree($m) {
 
function protectTree($m) {
return "1{$this->voo}".strlen($m[1])."-$m[2]{$this->voo}2\n";
+
return "1{$this->uid}".strlen($m[1])."-$m[2]{$this->uid}2\n";
 
}
 
}
 
   
 
   
/**
+
# Add the javascript to the output object if not added yet and there is at least one tree
* Add the javascript to the output object if not added yet and there is at least one tree
 
*/
 
 
function addJS() {
 
function addJS() {
global $wgOut,$wgJS,$wgJsMimeType;
+
global $wgOut,$wgTreeViewImages,$wgJS,$wgJsMimeType;
if (isset($wgJS['TreeView5'])) return;
+
if (isset($wgJS['TreeView4'])) return;
$wgJS['TreeView5'] = true;
+
$wgJS['TreeView4'] = true;
$wgOut->addScript("<script type=\"$wgJsMimeType\" src=\"{$this->baseUrl}/dtree.js\" />\n");
+
$plus    = $wgTreeViewImages['plus'];
 +
$minus  = $wgTreeViewImages['minus'];
 +
$opened  = $wgTreeViewImages['opened'];
 +
$closed  = $wgTreeViewImages['closed'];
 +
$spacer  = $wgTreeViewImages['spacer'];
 +
$doc    = $wgTreeViewImages['doc'];
 +
$vert    = $wgTreeViewImages['vert'];
 +
$wgOut->addScript('<script type="'.$wgJsMimeType.'">
 +
function toggleTreeviewItem(id,row) {
 +
var plus  = "'.$plus.'";
 +
var minus  = "'.$minus.'";
 +
var opened = "'.$opened.'";
 +
var closed = "'.$closed.'";
 +
var doc    = "'.$doc.'";
 +
var item  = document.getElementById("tvr"+id+row);
 +
var next  = document.getElementById("tvr"+id+(row+1));
 +
var depth  = 0+item.getAttribute("depth");
 +
var close  = next.style.display != "none";
 +
var img    = document.getElementById("tvi"+id+row);
 +
var fld    = document.getElementById("tvf"+id+row);
 +
var src   = fld.getAttribute("src");
 +
if (src == opened) fld.setAttribute("src",closed);
 +
if (src == closed) fld.setAttribute("src",opened);
 +
fld.setAttribute("style",close ? "" : "background:url('.$vert.') repeat-y");
 +
img.setAttribute("src",close ? plus : minus);
 +
while ((item = document.getElementById("tvr"+id+(++row))) && (0+item.getAttribute("depth") > depth)) {
 +
if (close) item.style.display = "none";
 +
else if (depth == item.getAttribute("depth")-1) {
 +
item.style.display = "";
 +
if (img = document.getElementById("tvi"+id+row)) img.setAttribute("src",plus);
 +
if (fld = document.getElementById("tvf"+id+row))
 +
if (fld.getAttribute("src") == opened) {
 +
fld.setAttribute("src",closed);
 +
fld.serAttribute("style","");
 +
}
 +
}
 +
}
 +
}</script>');
 +
}
 
}
 
}
}
 
 
   
 
   
/**
+
# Called from $wgExtensionFunctions array when initialising extensions
* Called from $wgExtensionFunctions array when initialising extensions
+
function wfSetupTreeView4() {
*/
+
global $wgTreeView4;
function wfSetupTreeView5() {
+
$wgTreeView4 = new TreeView4();
global $wgTreeView5;
+
$wgTreeView4->addJS(); # Make code unconditional for now due to parser caching
$wgTreeView5 = new TreeView5();
 
$wgTreeView5->addJS(); # Make code unconditional for now due to parser caching  
 
 
}
 
}
 
   
 
   
/**
+
# Needed in MediaWiki >1.8.0 for magic word hooks to work properly
* Needed in MediaWiki >1.8.0 for magic word hooks to work properly
+
function wfTreeView4LanguageGetMagic(&$magicWords,$langCode = 0) {
*/
+
global $wgTreeView4,$wgTreeView4Magic;
function wfTreeView5LanguageGetMagic(&$magicWords,$langCode = 0) {
+
$magicWords[$wgTreeView4Magic] = array(0,$wgTreeView4Magic);
global $wgTreeView5,$wgTreeView5Magic;
 
$magicWords[$wgTreeView5Magic] = array(0,$wgTreeView5Magic);
 
 
return true;
 
return true;
 
}
 
}
 +
</source>

Latest revision as of 14:55, 31 October 2016

Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, this is only useful for a historic record of work done. You may find a link to the currently used concept or function in this article, if not you can contact the author to find out what has taken the place of this legacy item.

Please refer to [[the TreeAndMenu extension]] instead.

Voodoo.svg This code exhibits voodoo programming techniques. The most common of these is extending an instance's class at runtime after it has been instantiated, a technique that can be used to provide additional hooks into existing code without requiring modification of code-base files. For a list of all our scripts which exhibit voodoo, see Category:Code that uses voodoo.
<?php
# MediaWiki Treeview Extension
# - See http://www.mediawiki.org/wiki/Extension:Tree_view for installation and usage details
# - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
# - Author:  http://www.organicdesign.co.nz/nad
# - Started: (Version4) 2007-09-06
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
 
define('TREEVIEW4_VERSION','4.0.9, 2007-12-29');
 
# Set any unset images to default titles
if (!isset($wgTreeViewImages) || !is_array($wgTreeViewImages)) $wgTreeViewImages = array();
if (!isset($wgTreeViewImages['plus']))   $wgTreeViewImages['plus']   = 'Plus.gif';
if (!isset($wgTreeViewImages['minus']))  $wgTreeViewImages['minus']  = 'Minus.gif';
if (!isset($wgTreeViewImages['opened'])) $wgTreeViewImages['opened'] = 'Folder_opn_sml_yel.gif';
if (!isset($wgTreeViewImages['closed'])) $wgTreeViewImages['closed'] = 'Folder_sml_yel.gif';
if (!isset($wgTreeViewImages['doc']))    $wgTreeViewImages['doc']    = 'Doc-icon.gif';
if (!isset($wgTreeViewImages['spacer'])) $wgTreeViewImages['spacer'] = 'Blank.gif';

# These images are needed if you have $wgTreeViewShowLines set
if (!isset($wgTreeViewImages['vert'])) $wgTreeViewImages['vert'] = 'Vertline.gif';
if (!isset($wgTreeViewImages['node'])) $wgTreeViewImages['node'] = 'Node.gif';
if (!isset($wgTreeViewImages['last'])) $wgTreeViewImages['last'] = 'Lastnode.gif';
 
# Keep track of JavaScript added to page to avoid doubleups
if (!isset($wgJS)) $wgJS = array();
 
$wgTreeView4Magic              = "tree"; # the parser-function name for trees
$wgTreeViewIndent              = 0;      # the number of pixels each level indents by (0 means doc-icon width)
$wgTreeViewShowLines           = false;  # whether to render the dotted lines joining nodes
$wgExtensionFunctions[]        = 'wfSetupTreeView4';
$wgHooks['LanguageGetMagic'][] = 'wfTreeView4LanguageGetMagic';
 
$wgExtensionCredits['parserhook'][] = array(
	'name'        => 'Treeview4',
	'author'      => '[http://www.organicdesign.co.nz/nad User:Nad]',
	'url'         => 'http://www.mediawiki.org/wiki/Extension:Treeview',
	'description' => 'Allows dynamic tree-views to be made with bullet-list syntax',
	'version'     => TREEVIEW4_VERSION
	);
 
class TreeView4 {
 
	var $version  = TREEVIEW4_VERSION;
	var $width    = 16;
	var $uniq     = array();
	var $uid      = '';
	var $js       = 0;
 
	# Constructor
	function TreeView4() {
		global $wgParser,$wgHooks,$wgTreeView4Magic,$wgTreeViewIconMagic,$wgTreeViewImages,$wgTreeViewIndent;

		# Convert image titles to file paths and obtain pixel width of items
		if ($wgTreeViewIndent) $this->width = $wgTreeViewIndent;
		foreach ($wgTreeViewImages as $k => $v) {
			$title = Title::newFromText($v,NS_IMAGE);
			$image = Image::newFromTitle($title);
			if ($image && $image->exists()) {
				$wgTreeViewImages[$k] = $image->getURL();
				if ($wgTreeViewIndent < 1 && $k == 'doc') $this->width = $image->getWidth();
				}
			}

		$this->uid = uniqid('TVUNIQ');
		$wgParser->setFunctionHook($wgTreeView4Magic,array($this,'Tree'));
		}
 
	# Restructure recursive trees into a single bullet list surrounded by internal treeview4 tags
	function Tree(&$parser) {
		global $wgParser;
		$id   = uniqid();
		$args = "id='$id'";
		foreach (func_get_args() as $arg) if (!is_object($arg)) {
			if (preg_match('/^(.+?=.+)$/',$arg,$m)) $args .= " $arg"; else $text = $arg;
			}

		# Black magic
		$text = $this->uniq[$id] = preg_replace(
			'/^\\*(\\**)\\s*(\\x07UNIQ.+?-treeview4(.+?)-.+?-QINU\\x07?)/me',
			'$this->uniq["$3"] ? preg_replace("/^(\\*+)/m","$1\$1",$this->uniq["$3"]) : "$1$2"',
			$text
			);

		# And a little more voodoo here
		$wgParser->setHook("treeview4$id",array($this,'treeview'));
		return "<treeview4$id $args>$text</treeview4$id>";
		}

	# Convert a bullet list into a treeview
	function treeview($text,$argv,&$parser) {
		global $wgTreeViewImages;
 
		$id      = $argv['id'];
		$uid     = $this->uid;
		$ver     = $this->version;
		$width   = $this->width;
		$plus    = $wgTreeViewImages['plus'];
		$minus   = $wgTreeViewImages['minus'];
		$opened  = $wgTreeViewImages['opened'];
		$closed  = $wgTreeViewImages['closed'];
		$spacer  = $wgTreeViewImages['spacer'];
		$doc     = $wgTreeViewImages['doc'];
		$vert    = $wgTreeViewImages['vert'];
		$node    = $wgTreeViewImages['node'];
		$last    = $wgTreeViewImages['last'];
		$default = isset($argv['openlevels']) ? $argv['openlevels']+1 : 1;
		$rows    = array();
		$lasts   = array();
		$cols    = 0;
		$next    = 0;
		$tree    = '';
		
		# Protect the asterisk structure and wiki-parse the bullet tree
		$text = preg_replace("/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/","{$uid}3$1{$uid}4",$text);
		$text = preg_replace_callback("/^(\\*+)(.*?)$/m",array($this,'protectTree'),$text);
		$out  = $parser->parse($text,$parser->mTitle,$parser->mOptions,false,false);
		$text = $out->getText();

		# Extract the valid rows and process if any
		if (preg_match_all("/1{$uid}([0-9]+)-({$uid}3(.+?){$uid}4)?(.+?){$uid}2/",$text,$matches,PREG_SET_ORDER)) {

			# Parse 1
			foreach ($matches as $row) {
				list(,$depth,,$icon,$item) = $row;
				if ($depth > $cols) $lasts[$cols = $depth] = true;
				$rows[] = array($depth,$icon,$item);
				}

			# Parse 2
			for ($row = count($rows)-1; $row >= 0; $row--) {
				list($depth,$i,$item) = $rows[$row];
				$diff = $depth - $next;
				$show = $depth > $default ? " style='display:none'" : "";
				$span = $cols+2-$depth;
				$span = $span == 1 ? "" : " colspan='$span'";
				$td   = "";
				$ibg  = "";

				# Determine the open/close link and the folder/doc icon
				if ($depth >= $default) { $open = $plus; $icon = $closed; }
				else { $open = $minus; $icon = $opened; $ibg = " style='background: url($vert) 0 10px no-repeat'"; }
				if ($depth >= $next) { $open = "<img src='$spacer' width='$width'/>"; $icon = $doc; $ibg = ''; }
				else $open = "<a href='javascript:toggleTreeviewItem(\"$id\",$row)'><img id='tvi$id$row' src='$open'/></a>";

				# Handle custom image
				if ($i) {
					$i = Image::newFromTitle(Title::newFromText($i,NS_IMAGE));
					if ($i && $i->exists()) $icon = $i->getURL();
					}

				# Construct the dotted joining lines and position the open/close link in the last one
				if ($diff > 0) $lasts[$depth] = true;
				for ($col = $depth+1; $col < $cols; $col++) $lasts[$col] = true;
				for ($col = 1; $col <= $depth; $col++) {
					if ($diff > 1 && $col > $next && $col < $depth-1) $lasts[$col] = true;
					if ($col == $depth) { $fg = $open; $bg = $lasts[$col] ? $last : $node; }
					else { $fg = ""; $bg = $lasts[$col] ? $spacer : $vert; }
					$td .= "<td style='background: url($bg) no-repeat'>$fg</td>";
					}
				$lasts[$depth] = false;

				# Add the icon and the item content and append a table row to the tree definition
				$td .= "<td class='tree-icon'><img id='tvf$id$row' src='$icon'/></td>";
				$td .= "<td class='tree-text'$span>$item</td>";
				$tree = "<tr$show depth='$depth' id='tvr$id$row'>$td</tr>\n$tree";
				$next = $depth;
				}
			}

		# This invisible row at the start fixes a bug making some text items not maximum width
		$td = '';
		for ($col = 1; $col <= $cols+1; $col++) $td .= "<td width=0/>";
		$tree = "<tr>$td<td width=100%/></tr>\n$tree";

		return "<table cellpadding=0 cellspacing=0 class='tree-view' id='tv$id' title='Extension:Treeview (ver $ver)'>\n$tree</table>";
		}

	# Protect asterisk bullet structure from wiki parser
	function protectTree($m) {
		return "1{$this->uid}".strlen($m[1])."-$m[2]{$this->uid}2\n";
		}
 
	# Add the javascript to the output object if not added yet and there is at least one tree
	function addJS() {
		global $wgOut,$wgTreeViewImages,$wgJS,$wgJsMimeType;
		if (isset($wgJS['TreeView4'])) return;
		$wgJS['TreeView4'] = true;
		$plus    = $wgTreeViewImages['plus'];
		$minus   = $wgTreeViewImages['minus'];
		$opened  = $wgTreeViewImages['opened'];
		$closed  = $wgTreeViewImages['closed'];
		$spacer  = $wgTreeViewImages['spacer'];
		$doc     = $wgTreeViewImages['doc'];
		$vert    = $wgTreeViewImages['vert'];
		$wgOut->addScript('<script type="'.$wgJsMimeType.'">
			function toggleTreeviewItem(id,row) {
			var plus   = "'.$plus.'";
			var minus  = "'.$minus.'";
			var opened = "'.$opened.'";
			var closed = "'.$closed.'";
			var doc    = "'.$doc.'";
			var item   = document.getElementById("tvr"+id+row);
			var next   = document.getElementById("tvr"+id+(row+1));
			var depth  = 0+item.getAttribute("depth");
			var close  = next.style.display != "none";
			var img    = document.getElementById("tvi"+id+row);
			var fld    = document.getElementById("tvf"+id+row);
			var src    = fld.getAttribute("src");
			if (src == opened) fld.setAttribute("src",closed);
			if (src == closed) fld.setAttribute("src",opened);
			fld.setAttribute("style",close ? "" : "background:url('.$vert.') repeat-y");
			img.setAttribute("src",close ? plus : minus);
			while ((item = document.getElementById("tvr"+id+(++row))) && (0+item.getAttribute("depth") > depth)) {
				if (close) item.style.display = "none";
				else if (depth == item.getAttribute("depth")-1) {
					item.style.display = "";
					if (img = document.getElementById("tvi"+id+row)) img.setAttribute("src",plus);
					if (fld = document.getElementById("tvf"+id+row))
						if (fld.getAttribute("src") == opened) {
							fld.setAttribute("src",closed);
							fld.serAttribute("style","");
							}
					}
				}
			}</script>');
		}
	}
 
# Called from $wgExtensionFunctions array when initialising extensions
function wfSetupTreeView4() {
	global $wgTreeView4;
	$wgTreeView4 = new TreeView4();
	$wgTreeView4->addJS(); # Make code unconditional for now due to parser caching
	}
 
# Needed in MediaWiki >1.8.0 for magic word hooks to work properly
function wfTreeView4LanguageGetMagic(&$magicWords,$langCode = 0) {
	global $wgTreeView4,$wgTreeView4Magic;
	$magicWords[$wgTreeView4Magic] = array(0,$wgTreeView4Magic);
	return true;
	}