Extension:TreeAndMenu
<?php /**
- MediaWiki TreeAndMenu ExtensionTemplate:Php
- - See http://www.mediawiki.org/wiki/Extension:TreeAndMenu for installation and usage details
- - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
- - Author: http://www.organicdesign.co.nz/nad
- - Started: 2008-04-17 (This code is Extension:Treeview5.php with Son of Suckerfish dropdown menus added)
- /
if (!defined('MEDIAWIKI')) die('Not an entry point.');
define('TREEANDMENU_VERSION','1.0.0, 2008-04-17');
- Set any unset images to default titles
if (!isset($wgTreeViewImages) || !is_array($wgTreeViewImages)) $wgTreeViewImages = array();
$wgTreeMagic = "tree"; # the parser-function name for trees $wgMenuMagic = "menu"; # the parser-function name for dropdown menus $wgTreeViewShowLines = false; # whether to render the dotted lines joining nodes $wgExtensionFunctions[] = 'wfSetupTreeAndMenu'; $wgHooks['LanguageGetMagic'][] = 'wfTreeAndMenuLanguageGetMagic';
$wgExtensionCredits['parserhook'][] = array( 'name' => 'TreeAndMenu', 'author' => 'Nad, Sven', 'url' => 'http://www.mediawiki.org/wiki/Extension:Treeview', 'description' => 'Adds #tree and #menu parser functions which contain bullet-lists to be rendered as collapsible treeview\'s or dropdown menus. The treeview\'s use the dTree JavaScript tree menu, and the dropdown menu\'s use Son of Suckerfish', 'version' => TREEANDMENU_VERSION );
class TreeAndMenu {
var $version = TREEANDMENU_VERSION; var $uniq = ; # uniq part of all tree id's var $uniqname = 'tam'; # input name for uniqid var $id = ; # id for specific tree var $baseDir = ; # internal absolute path to treeview directory var $baseUrl = ; # external URL to treeview directory (relative to domain) var $images = ; # internal JS to update dTree images var $useLines = true; # internal variable determining whether to render connector lines var $args = array(); # args for each tree
/**
* Constructor
*/
function __construct() {
global $wgOut,$wgHooks,$wgParser,$wgScriptPath,$wgJsMimeType,
$wgTreeMagic,$wgMenuMagic,$wgTreeViewImages,$wgTreeViewShowLines;
# Add hooks $wgParser->setFunctionHook($wgTreeMagic,array($this,'expandTree')); $wgParser->setFunctionHook($wgMenuMagic,array($this,'expandMenu')); $wgHooks['ParserAfterTidy'][] = array($this,'renderTreeAndMenu');
# Update general tree paths and properties $this->baseDir = dirname(__FILE__); $this->baseUrl = preg_replace('|^.+(?=[/\\\\]extensions)|',$wgScriptPath,$this->baseDir); $this->useLines = $wgTreeViewShowLines ? 'true' : 'false'; $this->uniq = uniqid($this->uniqname);
# Convert image titles to file paths and store as JS to update dTree foreach ($wgTreeViewImages as $k => $v) { $title = Title::newFromText($v,NS_IMAGE); $image = Image::newFromTitle($title); $v = $image && $image->exists() ? $image->getURL() : $wgTreeViewImages[$k]; $this->images .= "tree.icon['$k'] = '$v';"; }
# Add link to output to load dtree.js script $wgOut->addScript("<script type=\"$wgJsMimeType\" src=\"{$this->baseUrl}/dtree.js\"></script>\n"); $wgOut->addScript("<script type=\"$wgJsMimeType\"><![CDATA[//><"."!-- sfHover = function() { var sfEls = document.getElementById('nav').getElementsByTagName('li'); for (var i=0; i<sfEls.length; i++) { sfEls[i].onmouseover=function() { this.className+=' sfhover'; } sfEls[i].onmouseout=function() { this.className=this.className.replace(new RegExp(' sfhover\\b'),); } } } if (window.attachEvent) window.attachEvent('onload',sfHover); //--><!]]></script>"); }
/**
* Expand #tree parser-functions
*/
public function expandTree() {
$args = func_get_args();
return $this->expandTreeAndMenu('tree',$args);
}
/** * Expand #menu parser-functions */ public function expandMenu() { $args = func_get_args(); return $this->expandTreeAndMenu('menu',$args); }
/** * Expand either kind of parser-function (reformats tree rows for matching later) and store args */ private function expandTreeAndMenu($magic,$args) { $parser = array_shift($args);
# Store args for this tree for later use foreach ($args as $arg) if (preg_match('/^(\\w+?)\\s*=\\s*(.+)$/s',$arg,$m)) $args[$m[1]] = $m[2]; else $text = $arg;
# Create a unique id for this tree or use id supplied in args and store args wrt id $this->id = isset($args['id']) ? $args['id'] : uniqid(); $args['type'] = $magic; $this->args[$this->id] = $args;
# Reformat tree rows for matching in ParserAfterStrip $text = preg_replace('/(?<=\\*)\\s*\\[\\[Image:(.+?)\\]\\]/',"{$this->uniq}3$1{$this->uniq}4",$text); $text = preg_replace_callback('/^(\\*+)(.*?)$/m',array($this,'formatRow'),$text);
return $text; }
/**
* Reformat tree bullet structure recording row, depth and id in a format which is not altered by wiki-parsing
* - format is: 1{uniq}-{id}-{depth}-{item}-2{uniq}
* - sequences of this format will be matched in ParserAfterTidy and converted into dTree JavaScript
* - NOTE: we can't encode a unique row-id because if the same tree instranscluded twice a cached version
* may be used (even if parser-cache disabled) this also means that tree id's may be repeated
*/
private function formatRow($m) {
return "\x7f1{$this->uniq}\x7f{$this->id}\x7f".(strlen($m[1])-1)."\x7f$m[2]\x7f2{$this->uniq}";
}
/**
* Called after parser has finished (ParserAfterTidy) so all transcluded parts can be assembled into final trees
*/
public function renderTreeAndMenu(&$parser,&$text) {
global $wgJsMimeType;
$u = $this->uniq;
# Determine which trees are sub trees # - there should be a more robust way to do this, # it's just based on the fact that all sub-tree's have a minus preceding their row data if (!preg_match_all("/\x7f\x7f1$u\x7f(.+?)\x7f/",$text,$subs)) $subs = array(1 => array());
# Extract all the formatted tree rows in the page and if any, replace with dTree JavaScript if (preg_match_all("/\x7f1$u\x7f(.+?)\x7f([0-9]+)\x7f({$u}3(.+?){$u}4)?(.*?)(?=\x7f[12]$u)/",$text,$matches,PREG_SET_ORDER)) {
# PASS-1: build $rows array containing depth, and tree start/end information $rows = array(); $depths = array( => 0); # depth of each tree root $rootId = ; # the id of the current root-tree (used as tree id in PASS2) $lastId = ; $lastDepth = 0; foreach ($matches as $match) { list(,$id,$depth,,$icon,$item) = $match; $start = false; if ($id != $lastId) { if (!isset($depths[$id])) $depths[$id] = $depths[$lastId]+$lastDepth; if ($start = $rootId != $id && !in_array($id,$subs[1])) $depths[$rootId = $id] = 0; } if ($item) $rows[] = array($rootId,$depth+$depths[$id],$icon,addslashes($item),$start); $lastId = $id; $lastDepth = $depth; }
# PASS-2: build the JavaScript and replace into $text $parents = array(); # parent node for each depth $last = -1; $nodes = ; foreach ($rows as $node => $info) { $node++; list($id,$depth,$icon,$item,$start) = $info; $args = $this->args[$id]; $type = $args['type']; if (!isset($args['root'])) $args['root'] = ; # tmp - need to handle rootless trees $end = $node == count($rows) || $rows[$node][4];
# Append node script for this row if ($depth > $last) { $parents[$depth] = $node-1;
if ($type == 'menu') $nodes .= "
- \n";
}
elseif ($depth < $last && $type == 'menu' && !$start) $nodes .= str_repeat('
',$last-$depth);
$parent = $parents[$depth];
$nodes .= $type == 'tree' ? "{$this->uniqname}$id.add($node,$parent,'$item');\n" : "
\n";
$last = $depth;
# Last row of current root, surround nodes dtree or menu script and div etc if ($end) { $class = "{$this->uniqname}-$type"; if ($type == 'tree') {
# Finalise a tree $add = isset($args['root']) ? "tree.add(0,-1,'".$args['root']."');" : ; $top = $bottom = $root = ; foreach ($args as $arg => $pos) if (($pos == 'top' || $pos == 'bottom' || $pos == 'root') && ($arg == 'open' || $arg == 'close')) $$pos .= "<a href=\"javascript: {$this->uniqname}$id.{$arg}All();\"> {$arg} all</a> ";
if ($top) $top = "
$top
"; if ($bottom) $bottom = "
$bottom
";
if ($root) $add = "tree.add(0,-1,'$root');";
$html = "$top
<script type=\"$wgJsMimeType\"> // TreeAndMenu{$this->version} tree = new dTree('{$this->uniqname}$id'); for (i in tree.icon) tree.icon[i] = '{$this->baseUrl}/'+tree.icon[i];{$this->images} tree.config.useLines = {$this->useLines}; $add {$this->uniqname}$id = tree; $nodes document.getElementById('$id').innerHTML = {$this->uniqname}$id.toString(); </script>
$bottom";
} else {
if ($depth > 0) $nodes .= str_repeat('',$depth); $html = "
- $nodes
\n"; # Finalise a menu
}
$text = preg_replace("/\x7f1$u\x7f$id\x7f.+?$/m",$html,$text,1); # replace first occurence of this trees root-id $nodes = ; } } }
$text = preg_replace("/\x7f1$u\x7f.+?[\\r\\n]+/m",,$text); # Remove all unreplaced row information return true; }
}
/**
* Called from $wgExtensionFunctions array when initialising extensions */
function wfSetupTreeAndMenu() { global $wgTreeAndMenu; $wgTreeAndMenu = new TreeAndMenu(); }
/**
* Needed in MediaWiki >1.8.0 for magic word hooks to work properly */
function wfTreeAndMenuLanguageGetMagic(&$magicWords,$langCode = 0) { global $wgTreeMagic,$wgMenuMagic; $magicWords[$wgTreeMagic] = array($langCode,$wgTreeMagic); $magicWords[$wgMenuMagic] = array($langCode,$wgMenuMagic); return true; }