PmWiki/emenu2/emenu2.r120108.php
<?php if (!defined('PmWiki')) exit();
/*****************************************************************\
* emenu2.php - intelligent expanding menu's for pmwiki *
* Copyright (C) 2007 Pieter Wuille *
* Some of this code is based on emenu.php by Douglas Stebila. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
\*****************************************************************/
/**
* This plugin provides expanding menu's for use in a sidebar eg.
* It will automatically expand the tree to show the currently visible page,
* but expansion of any nodes can be controlled through markup.
*
* Expandable items will get a '+' prefix, while expanded items will get a
* '-' prefix. In case these items are WikiLinks, this + or - will be added
* to the visible part of the link, making it clickable and compatible with
* the gemini/fixflow skins, which put all links in the sidebar on their own
* line. There is no limit on the number of indentation levels.
*
* A menu is written by using a normal list (using '*'s for indentation)
* enclosed within (:emenu2:) ... (:emenu2end:) directives.
* A menu item can be made visible/expanded by writing
* (:emenu2expand Group.Page:). (:emenu2collapse:) can revert this. The current
* page is made expanded/visible by default, but this can be reverted with
* (:emenu2collapse {$FullName}).
*
* This plugin is somewhat like TreeMenu (but completely server-side, without
* javascript, and somewhat like ExpandingMenu (but more intelligent and
* flexible). It replaces this last one.
*
*/
$RecipeInfo['ExpandingMenu2']['Version'] = '2012-01-08';
# expand and collapse markup
Markup ('emenu2exp', 'fulltext', '/\\(:emenu2expand ([^:]*):\\)/e',"eMenuExpand(PSS('$1'),1)");
Markup ('emenu2col', '>emenu2exp', '/\\(:emenu2collapse ([^:]*):\\)/e',"eMenuExpand(PSS('$1'),-1)");
# handle emenu's
MarkUp ('emenu2', '<links', '/^\\(:(emenu2|emenu2end):\\)/e',"eMenu('$1','$pagename')");
MarkUp ('emenu2line', '>emenu2', '/^(.*)/e',"eMenuLine(PSS('$1'))");
# styles for links depending on {0=before, 1=on, 2=after} current page
SDV($Emenu2Style, array('%bgcolor=#ffc0c0%','%bgcolor=#ffff40%','%bgcolor=#c0ffc0%'));
# handle (:emenu2:) ... (:emenu2end:)
# stores lines in a global variable, and pass these lines to eMenuConvert in the end
function eMenu($pos,$pagename) {
global $eMenuActive,$eMenuLines;
if ($pos == 'emenu2') { /* begin */
if (!$eMenuActive) {
$eMenuActive=1;
$eMenuLines=array();
return;
}
}
if ($pos == 'emenu2end') { /* end */
if ($eMenuActive) {
$eMenuActive=0;
return "<:block><div class='emenu'>".eMenuConvert($eMenuLines,$pagename)."<:block></div>";
}
}
return "<:block>";
}
# add line to global variable, or pass-through if not within a (:emenu2:) construct
function eMenuLine($line) {
global $eMenuActive,$eMenuLines;
if ($eMenuActive) {
array_push($eMenuLines,$line);
} else { /* pass through */
return $line;
}
}
# handle (:emenu2expand:) or (:emenu2collapse:)
# a (:emenu2expand [pagename]:) will cause this page's fullname to be stored
# in a associative array. The value associated with that key is the difference
# between the number of expands and the number of collapses. If this number
# is strictly positive, nodes referring to that page will always be visible
# and expanded.
function eMenuExpand($links,$mode) {
global $eMenuExpand, $pagename;
foreach (explode('/ +/',$links) as $link) { /* split in links */
$fn=analyseLink($link, $pagename);
$fn=$fn[1]; /* find fullname */
$eMenuExpand[$fn]+=$mode; /* and change that page's expansion mode */
}
}
# split code in links and non-links
# the result is an array, containing
# - array($code, $target, $view) for links
# - array($code, NULL, NULL) for other parts
function eMenuParseLinks($code,$pagename) {
$ret = array();
$pos=0;
preg_match_all("/\\[\\[([^\\]]+)\\]\\]/",$code,$matches,PREG_SET_ORDER | PREG_OFFSET_CAPTURE); /* find all links */
foreach ($matches as $link) { /* loop over all links */
if ($link[0][1]>$pos) /* add skipped non-link part to result */
array_push($ret,array(substr($code,$pos,$link[0][1]-$pos),NULL,NULL));
$pos=$link[0][1]+strlen($link[0][0]); /* end position of current link */
array_push($ret, analyseLink($link[1][0], $pagename));
}
if (strlen($code)>$pos) /* add final non-link part */
array_push($ret,array(substr($code,$pos),NULL,NULL));
return $ret;
}
# analyse a link = "m:g.p|v" ==> ([[m:g.p|v]], group.name, view)
function analyseLink($link,$pagename) {
$l=str_replace("~","Profile/",$link); /* handle [[~name]] links */
$l=str_replace("^!","Category/",$l); /* handle [[!category]] links */
preg_match("/^([^\\.:\\/]*?:)?(?:([^\\.:\\/]*?)([\\.\\/]))?([^\\.:\\/]*?)(?:\\|(.*))?\$/",$l,$lmatch); /* parse link (1:map, 2:group, 3:separator, 4:page, 5:view) */
if ($lmatch[5] == '') {
$view = ($lmatch[3]=='/' && $lmatch[1]=='') ? $lmatch[4] : $link;
$view = preg_replace("/\\(.*?\\)/","",$view); /* remove ( ) from view */
} else {
$view = $lmatch[5];
}
return array("[[$link]]", MakePageName($pagename, $lmatch[2].'.'.$lmatch[4]),$view);
}
# transform a parse-array (as returned by eMenuParseLinks) back into wiki code
# it is possible to give a prefix, which will be prefixed to the first piece
# in the parse-array. In case this first piece is a link, it will be prefixed
# to the links's visible part ($view)
function eMenuGenLine($parsed,$prefix) {
$ret='';
foreach ($parsed as $parse) {
if ($prefix == '') {
$ret .= $parse[0];
} else {
if (isset($parse[1])) {
$ret .= "[[$parse[1]|$prefix$parse[2]]]";
} else {
$ret .= "$prefix$parse[0]";
}
$prefix='';
}
}
return $ret;
}
# take a number of lines (as given between (:emenu2:) and (:emenu2end:)) and
# drop some of them.
function eMenuConvert($lines,$pagename) {
global $eMenuExpand,$eMenuCurrentExpanded, $Emenu2Style;
$tree = array(); /* internal tree representation; is in array of entries: array($parse,$parent or -1,$expanded,$children,$level) */
$mapje = array(); /* stack that gives index of all ancestors (maps level to index) */
$pos = 0;
$maxlev = 0; /* valid entries in $mapje */
if (!isset($eMenuCurrentExpanded)) {
$eMenuCurrentExpanded=1;
eMenuExpand($pagename,1); /* expand currently viewed page */
}
foreach ($lines as $line) {
$level=strspn($line,"*")+1;
$spaces=strspn(substr($line,$level-1)," ");
$parent = -1;
$lind=substr($line,$level+$spaces-1); /* take visible part of line (no *** and whitespace) */
$parsed=eMenuParseLinks($lind, $pagename); /* parse entry itself */
if ($level<=$maxlev+1 && isset($mapje[$level-1])) $parent=$mapje[$level-1]; /* find parent */
if (count($parsed) > 0 ) { /* around include we may get empty $parsed ==> if we use them as parent all children disappear without a link to click! */
$tree[$parent][3]=1; /* out parent has child now */
$mapje[$level]=$pos; /* we can become ancestor */
$maxlev=$level;
}
$tree[$pos] = array($parsed,$parent,0,0,$level-1); /* store new value in tree */
foreach ($parsed as $link) { /* loop over all parsed parts */
if (isset($link[1]) && isset($eMenuExpand[$link[1]]) && $eMenuExpand[$link[1]]>0) { /* check if it is a link, and needs expanding */
$tree[$pos][6] = 1; /* mark current page */
$loop=$pos;
while ($loop>=0) { /* loop over all ancestors */
$tree[$loop][2]=1; /* make them expanded */
$loop=$tree[$loop][1]; /* go level up */
}
break; /* node already expanded, don't need to check other links anymore */
}
}
$pos++;
}
$ret = "";
$styleX = 0; /* styleX {0=before, 1=on, 2=after} current page */
foreach ($tree as $line) { /* now loop over tree to see what needs showing */
if ($line[1]==-1 || $tree[$line[1]][2]==1) { /* if entry has no parent, or parent is expanded */
$ret .= str_repeat('*',$line[4]); /* put ***'s */
$pre = ''; /* normally no prefix */
if ($line[3]==1 && $line[2]==1) $pre="'''-''' "; /* unless children & expanded: - */
if ($line[3]==1 && $line[2]==0) $pre="'''+''' "; /* unless children & !expanded: + */
$styleX = (isset($line[6]) && $line[6] == 1) ? 1 : ($styleX >= 1 ? 2 : 0);
$ret .= $Emenu2Style[$styleX] . ($one = eMenuGenLine($line[0],$pre)) . "\n"; /* generate menu line */
}
}
PRR(); /* instruct pmwiki to reprocess our output (as it contains newline, requires re-splitting, ...) */
return $ret;
}
?>