|
|
Line 1: |
Line 1: |
− | <?php
| + | {{svn}} |
− | /**
| |
− | * MediaWiki TreeAndMenu Extension{{php}}{{Category:Extensions|TreeAndMenu}}
| |
− | * - 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' => '[http://www.organicdesign.co.nz/nad Nad], [http://www.organicdesign.co.nz/User:Sven 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 [http://www.destroydrop.com/javascripts/tree dTree] JavaScript tree menu,
| |
− | and the dropdown menu\'s use [http://www.htmldog.com/articles/suckerfish/dropdowns/ 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\"><!-- TreeAndMenu --></script>\n");
| |
− | }
| |
− | | |
− | | |
− | /**
| |
− | * 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,$item,$start);
| |
− | $lastId = $id;
| |
− | $lastDepth = $depth;
| |
− | }
| |
− | | |
− | # PASS-2: build the JavaScript and replace into $text
| |
− | $parents = array(); # parent node for each depth
| |
− | $parity = array(); # keep track of odd/even rows 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;
| |
− | $parent = $parents[$depth];
| |
− | if ($type == 'tree') $nodes .= "{$this->uniqname}$id.add($node,$parent,'".addslashes($item)."');\n";
| |
− | else {
| |
− | if (!$start) {
| |
− | if ($depth < $last) $nodes .= str_repeat('</ul></li>',$last-$depth);
| |
− | elseif ($depth > $last) $nodes .= "\n<ul>";
| |
− | }
| |
− | $parity[$depth] = isset($parity[$depth]) ? $parity[$depth]^1 : 0;
| |
− | $class = $parity[$depth] ? 'odd' : 'even';
| |
− | $nodes .= "<li class=\"$class\">$item";
| |
− | if ($depth >= $rows[$node][1]) $nodes .= "</li>\n";
| |
− | }
| |
− | $last = $depth;
| |
− | | |
− | # Last row of current root, surround nodes dtree or menu script and div etc
| |
− | if ($end) {
| |
− | $class = isset($args['class']) ? $args['class'] : "d$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 = "<p> $top</p>";
| |
− | if ($bottom) $bottom = "<p> $bottom</p>";
| |
− | if ($root) $add = "tree.add(0,-1,'$root');";
| |
− | $html = "$top<div class='$class' id='$id'>
| |
− | <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>
| |
− | </div>$bottom";
| |
− | }
| |
− | else {
| |
− |
| |
− | # Finalise a menu
| |
− | if ($depth > 0) $nodes .= str_repeat('</ul></li>',$depth);
| |
− | $nodes = preg_replace("/<(a.*? )title=\".+?\".*?>/","<$1>",$nodes); # IE has problems with title attribute in suckerfish menus
| |
− | $html = "
| |
− | <ul class='$class' id='$id'>\n$nodes</ul>
| |
− | <script type=\"$wgJsMimeType\">
| |
− | if (window.attachEvent) {
| |
− | var sfEls = document.getElementById('$id').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 *'),''); }
| |
− | }
| |
− | }
| |
− | </script>
| |
− | ";
| |
− | }
| |
− | | |
− | $text = preg_replace("/\x7f1$u\x7f$id\x7f.+?$/m",$html,$text,1); # replace first occurence of this trees root-id
| |
− | $nodes = '';
| |
− | $last = -1;
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | $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;
| |
− | } | |