<?php if (!defined('PmWiki')) exit();

/* cluster.php  for PmWiki 2
 * DESCRIPTION:  The Cluster recipe adds group-clustering
 * functionality to PmWiki, in a pseudo-hierarchical way.
 * For info, see http://www.pmwiki.org/wiki/Cookbook/Cluster
 * Based on Hg by Dan Vis  <editor àt fast döt st>, Copyright 2007.
 *  see http://www.pmwiki.org/wiki/Cookbook/Hg
 * Modified by: Kathryn Andersen
 * Further mods by Hans Bracker
 * An mangled again by Feral, 2007-03-20, 2007-03-21
 */
$RecipeInfo['Cluster']['Version'] = '2007-03-21';



/* ================================================================
 * Customisation Variables for use in local config file
 */
SDV($ClusterSeparator, '-');
SDV($ClusterMaxLevels, 7);
SDV($ClusterBreadCrumbSeparator, " $ClusterSeparator ");
SDV($EnableBreadCrumbName, 1);
SDV($EnableBreadCrumbTitles, 0);


/* ================================================================
 * script Setup
 */
$pagename = ResolvePageName($pagename);
$EnablePGCust = 0;
$m = preg_split('/[.\\/]/', $pagename);
$group = $m[0];
$name = $m[1];


$FmtPV['$g0'] = 'ClusterCountLevels($group)';
$FmtPV['$n0'] = 'ClusterCountLevels($name)';
ClusterHelper_ConstructG1N1PageVars($group, 'g', $name, 'n');

$FmtPV['$BreadCrumbsDepth'] = 'ClusterCountLevels($group)';

/* ================================================================
 * Page Variables
 */

$FmtPV['$BreadCrumb']		= '"'.ClusterBreadCrumb($pagename, "").'"';
$FmtPV['$BreadCrumbTitle']	= '"'.ClusterBreadCrumb($pagename, "title=1").'"';

$FmtPV['$GroupTitle']		= '"'.ClusterGroupTitle($pagename, "").'"';
$FmtPV['$GroupTitleSpaced']	= '"'.ClusterGroupTitle($pagename, "space=true").'"';

$FmtPV['$ClusterSideBar']	= '"'.ClusterPageName($group, "SideBar").'"';
$FmtPV['$ClusterRightBar']	= '"'.ClusterPageName($group, "RightBar").'"';

$FmtPV['$CS']				= '"'.$ClusterSeparator.'"';
$FmtPV['$ClusterFirst1']	= '"'.ClusterSlice($pagename, "first=1").'"';
$FmtPV['$ClusterFirst2']	= '"'.ClusterSlice($pagename, "first=2").'"';
$FmtPV['$ClusterFirst3']	= '"'.ClusterSlice($pagename, "first=3").'"';
$FmtPV['$ClusterFirst4']	= '"'.ClusterSlice($pagename, "first=4").'"';
$FmtPV['$ClusterFirst5']	= '"'.ClusterSlice($pagename, "first=5").'"';
$FmtPV['$ClusterFirst6']	= '"'.ClusterSlice($pagename, "first=6").'"';
$FmtPV['$ClusterFirst7']	= '"'.ClusterSlice($pagename, "first=7").'"';
$FmtPV['$ClusterLast1']		= '"'.ClusterSlice($pagename, "last=1").'"';
$FmtPV['$ClusterLast2']		= '"'.ClusterSlice($pagename, "last=2").'"';
$FmtPV['$ClusterLast3']		= '"'.ClusterSlice($pagename, "last=3").'"';
$FmtPV['$ClusterLast4']		= '"'.ClusterSlice($pagename, "last=4").'"';
$FmtPV['$ClusterLast5']		= '"'.ClusterSlice($pagename, "last=5").'"';
$FmtPV['$ClusterLast6']		= '"'.ClusterSlice($pagename, "last=6").'"';
$FmtPV['$ClusterLast7']		= '"'.ClusterSlice($pagename, "last=7").'"';


/* ================================================================
 * Set up clustered configurations
 */

$found_config = false;
$found_grouphead = false;
$found_groupfoot = false;
$found_attr = false;
if (file_exists("$LocalDir/$group.$name.php")) {
	include_once("$LocalDir/$group.$name.php");
	$found_config = true;
}
$PageCSSListFmt["pub/css/$group.$name.css"] = "$PubDirUrl/css/$group.$name.css"; 
$groups = explode($ClusterSeparator, $group);
while (count($groups) != 0) {
	$cluster_group = implode ($ClusterSeparator, $groups);
	if (!$found_config && file_exists("$LocalDir/$cluster_group.php")) {
		include_once("$LocalDir/$cluster_group.php");
		$found_config = true;
	}
	if (!$found_grouphead && PageExists("$cluster_group.GroupHeader")) {
		$GroupHeaderFmt = "(:nl:)(:include $cluster_group.GroupHeader:)(:nl:)";
		$found_grouphead = true;
	}
	if (!$found_groupfoot && PageExists("$cluster_group.GroupFooter")) {
		$GroupFooterFmt = "(:nl:)(:include $cluster_group.GroupFooter:)(:nl:)";
		$found_groupfoot = true;
	}
	if (!$found_attr && PageExists("$cluster_group.GroupAttributes")) {
		$GroupAttributesFmt = "$cluster_group.GroupAttributes";
		$found_groupfoot = true;
	}
	$PageCSSListFmt["pub/css/$cluster_group.css"] = "$PubDirUrl/css/$cluster_group.css"; 
	array_pop($groups);
}

// set the defaults for those that haven't been found
if (!$found_config && file_exists("$LocalDir/$default.php")) {
	include_once("$LocalDir/$default.php");
}
if (!$found_grouphead && PageExists("$SiteGroup.GroupHeader")) {
	$GroupHeaderFmt = "(:nl:)(:include $SiteGroup.GroupHeader:)(:nl:)";
}
if (!$found_groupfoot && PageExists("$SiteGroup.GroupFooter")) {
	$GroupFooterFmt = "(:nl:)(:include $SiteGroup.GroupFooter:)(:nl:)";
}
$PageCSSListFmt["pub/css/local.css"] = "$PubDirUrl/css/local.css";
$PageCSSListFmt = array_reverse($PageCSSListFmt, true);


/* ================================================================
 * Functions
 */
function ClusterCountLevels($name) {
	global $ClusterSeparator;
	$parts = explode($ClusterSeparator, $name);
	return count($parts);
}


# return the pagename of the closest ancestor
function ClusterPageName($group, $name) {
	global $ClusterSeparator;
	global $SiteGroup;
	$groups = explode($ClusterSeparator, $group);
	while (count($groups) != 0) {
		$cluster_group = implode ($ClusterSeparator, $groups);
		if (PageExists("$cluster_group.$name")) {
			// short-circuit return!
			return "$cluster_group.$name";
		}
		array_pop($groups);
	}
	return "$SiteGroup.$name";
}


/* ================================================================
 * Markup for link shortcuts
 */
Markup('[[cluster','<var','/\\[\\[(-|[\\*\\^]+)(.*?)\\]\\]/e', 'ClusterLinks($pagename, "$1", "$2")');
function ClusterLinks($pagename, $prefix, $inlink) {
	global $ClusterSeparator;
	$group = FmtPageName('$Group', $pagename);
	if ($prefix == "-") {
		return "[[$group$ClusterSeparator$inlink]]";
	}
	$groups = explode($ClusterSeparator, $group);
	$c = strlen($prefix);
	$i = 0;
	while ($i < $c) {
		if (substr($prefix, 0, 1) == "*") $link = $link . $groups[$i] . $ClusterSeparator;
		if (substr($prefix, 0, 1) == "^") array_pop($groups);
		$i = $i + 1;
	}
	if (substr($prefix, 0, 1) == "*") $link = substr ($link, 0, -1);
	if (substr($prefix, 0, 1) == "^") $link = implode($ClusterSeparator, $groups);
	return "[[$link$inlink]]";
}


// ------------------------------------------------------------------------
function ClusterHelper_ConstructG1N1PageVars($group_part, $group_prefix, $name_part, $name_prefix)
{ // {{{
	global $ClusterSeparator;
	global $FmtPV;
	global $ClusterMaxLevels;

	$group_parts		= explode($ClusterSeparator, $group_part);
	$group_parts_count	= count($group_parts);
	$name_parts			= explode($ClusterSeparator, $name_part);
	$name_parts_count	= count($name_parts);

	for ($index=0; $index <= $ClusterMaxLevels; $index++)
	{
		// Group...
		if	(	$index < $group_parts_count)
		{
			$FmtPV['$'.$group_prefix.($index+1)] = '"'.$group_parts[$index].'"';
		}
		else
		{ // Past end of $group_parts;
			$FmtPV['$'.$group_prefix.($index+1)] = '""';
		}

		// Name
		if	(	$index < $name_parts_count)
		{
			$FmtPV['$'.$name_prefix.($index+1)] = '"'.$name_parts[$index].'"';
		}
		else
		{ // Past end of $name_parts;
			$FmtPV['$'.$name_prefix.($index+1)] = '""';
		}
	}
} // }}}
// ------------------------------------------------------------------------
function ClusterHelper_FetchGroups($pagename)
{ // {{{
	global $ClusterSeparator;
	$group = PageVar($pagename,'$Group');

	$groups_orig = explode($ClusterSeparator, $group);
	$groups = array();
	$index = 0;
	foreach($groups_orig as $item)
	{
		$path = '';
		for	($num = 0; $num <= $index; $num++)
		{
			if	(	$num != 0)
			{
				$path.= $ClusterSeparator;
			}

			$path.= $groups_orig[$num];
		}

		$groups[] = array('name' => $item, 'path' =>$path);

		$index++;
	}

//	print_r($groups);
//	var_dump($groups);
//	die;
	return $groups;
} // }}}
// ------------------------------------------------------------------------
function ClusterHelper_FetchGroupTitle($group)
{ // {{{
	global $DefaultName;

	$pagepossibles = array(
		"$group.GroupAttributes",
		"$group.GroupHeader",
		"$group.GroupFooter",
		"$group.$group",
		"$group.$DefaultName",
	);
	$group_title=null;
	foreach($pagepossibles as $try)
	{
		if	(	PageExists($try) )
		{
			$page = ReadPage($try, READPAGE_CURRENT);
			if	(	$page['title'])
			{
				$group_title = $page['title'];
				break;
			}
		}
	}
	return $group_title;
} // }}}
// ------------------------------------------------------------------------


// ------------------------------------------------------------------------
Markup('breadcrumb', 'directives',
	'/\\(:breadcrumb\\s*(.*?):\\)/ei',
	"ClusterBreadCrumb(\$pagename, PSS('$1'))"
	);
// return a "breadcrumb trail" from the Cluster elements
function ClusterBreadCrumb($pagename, $opt)
{ // {{{

	// For $usetitles = true;
	// In pmwiki.php: $PagePathFmt = array('{$Group}.$1','$1.$1','$1.{$DefaultName}');
	global $DefaultName;
	global $AsSpacedFunction;

	global $ClusterSeparator, $ClusterBreadCrumbSeparator, 
		$EnableBreadCrumbName, $EnableBreadCrumbTitles, $FmtPV;
	$defaults = array(
		'left' => 0, // Num elements to remove from left side of group
		'right' => 0, // Num elements to remove from right side of group
		'separator' => $ClusterBreadCrumbSeparator,
		'title' => $EnableBreadCrumbTitles,
		'name' => $EnableBreadCrumbName,
	);
	$opt = array_merge($defaults, ParseArgs($opt));

	$groups = ClusterHelper_FetchGroups($pagename);


	# Remove right side first;
	$trim = abs($opt['right']);
	if	(	$trim >= count($groups) )
	{
		$trim = count($groups)-1;
	}
	$index = 0;
	while ($index < $trim)
	{
		array_pop($groups);
		$index++;
	}

	# Remove left side second, but leave at least 1 element (right most);
	$trim = abs($opt['left']);
	if	(	$trim >= count($groups) )
	{
		$trim = count($groups)-1;
	}

	$index = 0;
	while ($index < $trim)
	{
		array_shift($groups);
		$index++;
	}

	$index = 0;
	foreach($groups as $item)
	{
		if	(	$index != 0)
		{
			$out.= $opt['separator'];
		}

		$out.= '[[';

		$out.= $item['path'].'/|';
		if	(	$opt['title'])
		{

			$path_name = ClusterHelper_FetchGroupTitle($item['path']);
			if	(	!$path_name)
			{
				if	(	$opt['title']==1)
				{
					$path_name = $AsSpacedFunction($item['name']);
				}
				else
				{
					$path_name = $item['name'];
				}
			}

			$out.= $path_name;
		}
		else
		{
			$out.= $item['name'];
		}
		$out.= ']]';

		$index++;
	}

	# Page Name
	if	(	$opt['name'])
	{
		$out .= $opt['separator']."[[$pagename|+]]";
	}
	return $out;
} // }}}
// ------------------------------------------------------------------------


// ------------------------------------------------------------------------
Markup('feralclustergrouptitle', 'directives',
	'/\\(:clustergrouptitle\\s*(.*?):\\)/ei',
	"ClusterGroupTitle(\$pagename, PSS('$1'))"
	);
// return the title of an element.
function ClusterGroupTitle($pagename, $opt)
{ // {{{
	global $ClusterSeparator;
	global $AsSpacedFunction;

	$defaults = array(
		'space' => false,
		'index' => 'last',
		'onebasedindex' => true,
		'link' => false,
	);
	$opt = array_merge($defaults, ParseArgs($opt));

	$groups = ClusterHelper_FetchGroups($pagename);


	$index_wanted = $opt['index'];
	if	(	$index_wanted == 'last')
	{
		$group_piece = array_pop($groups);
	}
	else
	{
		// [Feral:079/07@02:59] we presume $index_wanted is a number; convert it to such.
		$index_wanted=abs($index_wanted);

		if	(	$opt['onebasedindex'])
		{
			$index_wanted--;
		}

		if	(	$index_wanted < count($groups) )
		{
			$group_piece = $groups[$index_wanted];
		}
		else
		{	// [Feral:079/07@03:13] out of bounds; *shrug*
			return '';
		}
	}

	$group_title=ClusterHelper_FetchGroupTitle($group_piece['path']);
	if	(	!$group_title)
	{
		$group_title = $group_piece['name'];
	}

	if	(	$opt['space'])
	{
		$group_title = $AsSpacedFunction($group_title);
	}

	$out = $group_title;

	if	(	$opt['link'])
	{
		$out = "[[{$group_piece['path']}/|$group_title]]";
	}

	return $out;
} // }}}
// ------------------------------------------------------------------------


// ------------------------------------------------------------------------
Markup('clusterslice', 'directives',
	'/\\(:clusterslice\\s*(.*?):\\)/ei',
	"ClusterSlice(\$pagename, PSS('$1'))"
	);
// return a 'slice' of the cluster trail;
function ClusterSlice($pagename, $opt)
{ // {{{

	// For $usetitles = true;
	// In pmwiki.php: $PagePathFmt = array('{$Group}.$1','$1.$1','$1.{$DefaultName}');
	global $DefaultName;
	global $AsSpacedFunction;

	global $ClusterSeparator, $EnableBreadCrumbName, $EnableBreadCrumbTitles, $FmtPV;
	$defaults = array(
		'left' => 0,		// Num elements to remove from left side of group
		'right' => 0,		// Num elements to remove from right side of group

		'first' => null,		// EXCLUSIVE to last, synonym to firstlast=+
		'last' => null,		// EXCLUSIVE to first, synonym to firstlast=-
		'firstlast' => null,	// return this many + first, - last elements.

		'separator' => $ClusterSeparator,
		'return' => 'names',
	);
	$opt = array_merge($defaults, ParseArgs($opt));

//		'first' => 0,		// EXCLUSIVE to last, synonym to firstlast=+
//		'last' => 0,		// EXCLUSIVE to first, synonym to firstlast=-
//		'firstlast' => 0,	// return this many + first, - last elements.

	if	(	isset($opt['last']))
	{
		$firstlast = -abs($opt['last']);
	}
	if	(	isset($opt['first']))
	{
		$firstlast = +abs($opt['first']);
	}
	if	(	isset($opt['firstlast']))
	{
		$firstlast = (int)$opt['firstlast'];
	}


	$groups = ClusterHelper_FetchGroups($pagename);


	# Remove right side first;
	$trim = abs($opt['right']);
	if	(	$trim >= count($groups) )
	{
		$trim = count($groups)-1;
	}
	$index = 0;
	while ($index < $trim)
	{
		array_pop($groups);
		$index++;
	}

	# Remove left side second, but leave at least 1 element (right most);
	$trim = abs($opt['left']);
	if	(	$trim >= count($groups) )
	{
		$trim = count($groups)-1;
	}

	$index = 0;
	while ($index < $trim)
	{
		array_shift($groups);
		$index++;
	}


	if	(	$firstlast and $firstlast != 0)
	{

		if	(	$firstlast > 0)
		{ // Positive; I.e. first
			$firstlast = abs($firstlast);
			$groups = array_slice($groups, 0, $firstlast);
		}
		else
		{ // Negitive; I.e. last
			$firstlast = abs($firstlast);
			$groups = array_slice($groups, -$firstlast);
		}
	}

	// Return links;
	$index = 0;
	foreach($groups as $item)
	{

		if	(	$index != 0)
		{
			$out.= $opt['separator'];
		}

		if	(	$opt['return'] == 'links' )
		{
			$out.= '[[';
			$out.= $item['path'].'/|';
			$out.= $item['name'];
			$out.= ']]';
		}
		elseif	(	$opt['return'] == 'groups' )
		{
			$out.= $item['path'];
		}
		elseif	(	$opt['return'] == 'names' )
		{
			$out.= $item['name'];
		}
		else
		{
			return ("Error:ClusterSlice:Unknown return style: ".$opt['return']);
		}

		$index++;
	}

	return $out;
} // }}}
// ------------------------------------------------------------------------