<?php
/////////////////////////////////////////////////
// PukiWiki - Yet another WikiWikiWeb clone.
//
// $Id:$
//

/*
**ls2_1.inc.php
ls2 拡張。リストする階層が指定できる。

*説明 [#m17ed6bb]
ls2 拡張。[[自作プラグイン/ls3.inc.php]] とは違い、
ページ名による階層構造だけでリストする純粋な ls2 の拡張です。
**標準プラグイン ls2 からの変更点 [#k9cb86b6]
-階層指定可能。
-階層的リスト表示機能。
-相対パス的表示機能。
-include の無限ループを修正。

*書式 [#jcb5f796]
 #ls2(パターン[,オプション])

**パラメータ [#r8a06bfd]
-パターン(最初に指定)~
リストするページ名のパターン。省略するときもカンマが必要。省略時はカレントページ+"/"が指定されたことになる。また / を指定した場合はすべてのページにマッチする。
-reverse~
ページの並び順を反転し、降順にする
-compact~
見出しレベルを調整する。
LS2_1_LIST_COMPACT((LS2_1_LIST_COMPACT はファイルの直接編集により設定可能です。))がTRUEの時は常に compact
-depth=\d*[-+]?\d*((\d*[-+]?\d* は正規表現による表記です。\d は数字のことです。))~
階層指定。1 なら 1 階層下のページのみを表示する。
2-4 のような指定も可能 (2,3,4 の意)。2- のように指定すると 2 階層下以下のページ。
2+1 のような指定も可能 (2 とそこから 1 階層下。つまり 2,3 の意)。
//0-2 = false-2 = -2. 1-0 = 1-false = 1-. 2+ = 2+false = 2+0. +2 = false+2 = 0+2.
//0 または - または + は指定しないときと同じ。
//0 becomes false. - = false-false. + = false+false.
-relative~
相対パス的表示
-hierarchy~
階層的リスト表示
-non_list~
pukiwiki.ini.php で定義される $non_list によるリスト排除。
LS2_1_NON_LIST((LS2_1_NON_LIST はファイルの直接編集により設定可能です。))がTRUEの時は常に $non_list を利用する。
現在デフォルトで TRUE です。
-number=-?\d+ ((-?\d+ は正規表現による表記です。))~
リンク表示件数指定。リクエストにより作成。Blog2プラグインを使用するときに便利らしいです。~
''仕様変更しました。'' number=10 で頭から10件表示します。number=-10 のように - をつけると後ろの10件になります。それでも逆順にはならないので reverse を使用してください。
-except=正規表現~
リストしないページを正規表現にて指定。$non_list だけでは足りないときに使用。
-new~
そのページの更新日時を表示するとともにNew表示をする。
-title~
ページ中の見出しもリストする
-include~
インクルードしているページもリストする
-link~
actionプラグインを呼び出すリンクを表示。このとき、オプションと判定されない最初の引数をリンク名として利用できる。例)#ls2_1(,link,リンクです)
-title_compact~
title オプション用の compact 機能。
LS2_1_LIST_TITLE_COMPACTがTRUEの時は常に compact
-title_number=\d+
titleの表示件数指定。リクエストにより作成。~
title_number=10で頭から10件表示します。現在 - 機能はありません。
*/

//見出しアンカーの書式
define('ls2_1_CONTENT_HEAD','#content_1_');

//見出しアンカーの開始番号
define('ls2_1_ANCHOR_ORIGIN',0);

//見出しレベルを調整する
define('ls2_1_LIST_COMPACT',FALSE);
define('ls2_1_LIST_TITLE_COMPACT',FALSE);

//$non_list によるページ排除を使用する(デフォルト TRUE)
define('ls2_1_NON_LIST',TRUE);

function plugin_ls2_1_action()
{
	global $vars;
	global $_ls2_msg_title;
	
	$params = array();
	foreach (array('title','include','reverse','compact','title_compact','depth','relative','hierarchy','non_list','number','title_number','except','new') as $key)
	{
		foreach($vars as $key => $val)
		{
			$params[$key] = $val;
		}
	}
	$prefix = array_key_exists('prefix',$vars) ? $vars['prefix'] : '';
	$body = ls2_1_show_lists($prefix,$params);
	
	return array(
		'body'=>$body,
		'msg'=>str_replace('$1',htmlspecialchars($prefix),$_ls2_msg_title)
	);
}

function plugin_ls2_1_convert()
{
	global $script,$vars;
	global $_ls2_msg_title;

	$prefix = '';
	if (func_num_args())
	{
		$args = func_get_args();
		$prefix = array_shift($args);
	}
	else
	{
		$args = array();
	}
	if ($prefix == '')
	{
		$prefix = strip_bracket($vars['page']).'/';
	}
	else if($prefix === '/'){
		$prefix = '';
	}
	
	$params = array(
		'link'    => FALSE,
		'title'   => FALSE,
		'include' => FALSE,
		'reverse' => FALSE,
		'compact' => ls2_1_LIST_COMPACT,
		'title_compact' => ls2_1_LIST_TITLE_COMPACT,
		'depth'   => FALSE,
		'relative' => FALSE,
		'hierarchy' => FALSE,
		'non_list' => ls2_1_NON_LIST,
		'number' => FALSE,
		'title_number' => FALSE,
		'except' => FALSE,
		'new' => FALSE,
		'_args'   => array(),
	);
	array_walk($args, 'ls2_1_check_arg', &$params);

	if ($params['link'])
	{
		$title = (count($params['_args']) > 0) ?
			htmlspecialchars($params['_args'][0]) :
			str_replace('$1',htmlspecialchars($prefix),$_ls2_msg_title);

		$tmp = array();
		$tmp[] = 'plugin=ls2_1&amp;prefix='.rawurlencode($prefix);
		foreach ($params as $key => $val)
		{
			if(strpos($key,'_')===0 or $key === 'link')
			{
				continue;
			}
			if($val)
			{
				$tmp[] = "$key=$val";
			}
		}
		return '<p><a href="'.$script.'?'.join('&amp;',$tmp).'">'.$title.'</a></p>'."\n";
	}
	return ls2_1_show_lists($prefix,$params);
}

function ls2_1_show_lists($prefix,&$params)
{
	global $_ls2_err_nopages;
	global $non_list;

	$pages = array();
	
	//depth オプション解析
	if($params['depth'])
	{
		$prefixdepth = substr_count($prefix,"/") -1 ; //パターン文字列の階層数。$depthflag 判断に使用。
		list($lowdepth,$highdepth,$justdepth) = ls2_1_depth_option_analysis($params['depth']);
	}
	//number オプション解析
	if($params['number'])
	{
		list($headnumber,$tailnumber) = ls2_1_number_option_analysis($params['number']);
	}

	$pagestmp = array();
	foreach (get_existpages() as $_page)
	{
		//depth 制限
		$depthflag = TRUE;
		if($params['depth'])
		{
			$_pagedepth  = substr_count($_page,"/");
			if($lowdepth)
			{
				$depthflag &= $_pagedepth >= $prefixdepth + $lowdepth;
			}
			if($highdepth)
			{
				$depthflag &= $_pagedepth <= $prefixdepth + $highdepth;
			}
			elseif($justdepth>=1)
			{
				$depthflag &= $_pagedepth == $prefixdepth + $justdepth;
			}
		}

		//non_list 制限
		if( $params['non_list'] )
		{
			$list = !preg_match("/$non_list/",$_page);
		}
		else
		{
			$list = TRUE;
		}

		//except 制限
		if( $params['except'] )
		{
			$except = $params['except'];
			$except = !preg_match("/$except/",$_page);
		}
		else
		{
			$except = TRUE;
		}

		//パターン制限
		if( strlen($prefix) )
		{
			$match = (strpos($_page,$prefix) === 0);
		}
		else
		{
			$match = TRUE;
		}

		// すべて TRUE なら追加。
		if ($match and $depthflag and $list and $except)
		{
			$pagestmp[] = $_page;
		}
	}

	//表示 number 制限
	if($params['number'])
	{
		$asize = sizeof($pagestmp);
		if($headnumber>0)
		{
			$start=0;
			$end= ( $headnumber < $asize ) ? $headnumber : $asize;
		}
		elseif($tailnumber>0)
		{
			$end=$asize;
			$start=( $end-$tailnumber > 0 ) ? $end-$tailnumber : 0;
		}
		for($i = $start; $i < $end ; $i++)
		{
			$pages[] = $pagestmp[$i];
		}
	}
	else
	{
		$pages = $pagestmp;
	}

	natcasesort($pages);
	
	if ($params['reverse'])
	{
		$pages = array_reverse($pages);
	}
	foreach ($pages as $page)
	{
		$params["page_$page"] = 0;
	}
	if (count($pages) == 0)
	{
		return str_replace('$1',htmlspecialchars($prefix),$_ls2_err_nopages);
	}
	
	$params['result'] = array();
	$params['saved'] = array();

	//hierarchy オプション。リストレベル制御
	
	//階層指定をしている場合は、リスト表示上でのトップがずれる。
	$top_level = 1;
	if($params['depth'])
	{
		$top_level = ( $lowdepth > $justdepth ) ? $lowdepth : $justdepth; 
	}
	if( $top_level < 1)
	{
		$top_level = 1;
	}
	
	if($params['hierarchy'])
	{
		if($params['compact'])
		{
			//パターンの最後の / 以下を取り除く。例) sample/test/d -> sample/test
			//$prefix_dir = preg_replace('/[^\/]+$/','',$prefix);
			if( ( $pos = strrpos($prefix,'/') ) !== false )
			{
				$prefix_dir = substr($prefix,0,$pos+1);
			}
		}
		else
		{
			$prefixdepth = substr_count($prefix,"/");
		}

		foreach ($pages as $page)
		{
			if($params['compact'])
			{
				//パターンをとりのぞく
				$tmp = substr($page,strlen($prefix_dir));
				$level=1;

				//depth オプションが指定されていた場合 $top_level が変わります。
				while(substr_count($tmp,"/") > $top_level -1 ) 
				{
					//一階層ずつとりのぞく
					//$tmp = preg_replace('/\/[^\/]*$/','',$tmp);
					if( ( $pos = strrpos($tmp,'/') ) !== false )
					{
						$tmp = substr($tmp,0,$pos);
					}
					//compact なので上位のページが存在していればリスト階層レベルが増える
					if(is_page($prefix_dir.$tmp))
					{
						$level++;
					}
				}
			}
			// compact でない場合は上位のページの有無は問わないので / の数で十分。
			else
			{
				$level = substr_count($page,"/") - $prefixdepth + 1;
				//depth オプションが指定されていた場合 $top_level が変わります。
				if($params['depth'])
				{
					$level = $level - ( $top_level -1 );
				}
			}
			ls2_1_get_headings($page,$params,$level,FALSE,$prefix,$top_level);
		}
	}
	else{
		foreach ($pages as $page)
		{
			ls2_1_get_headings($page,$params,1,FALSE,$prefix);
		}
	}
	return join("\n",$params['result']).join("\n",$params['saved']);
}

function ls2_1_get_headings($page,&$params,$level,$include = FALSE, $prefix, $top_level = 1)
{
	global $script;
	static $_ls2_anchor = 0;
	
	$is_done = (isset($params["page_$page"]) and $params["page_$page"] > 0); //ページが表示済みのときTrue
	
	if (!$is_done)
	{
		$params["page_$page"] = ++$_ls2_anchor;
	}
	
	$r_page = rawurlencode($page);
	$s_page = htmlspecialchars($page);
	$title = $s_page.' '.get_pg_passage($page,FALSE);
	$href = $script.'?cmd=read&amp;page='.$r_page;

	// relative オプション。リンク名制御。
	if($params['relative'])
	{
		//パターンの最後の / 以下を取り除く。例) sample/test/d -> sample/test
		//$prefix_dir = preg_replace('/[^\/]+$/','',$prefix);
		if( ( $pos = strrpos($prefix,'/') ) !== false )
		{
			$prefix_dir = substr($prefix,0,$pos+1);
		}
		
		//ページ名からそのパターンをとり除く。
		//$s_page = ereg_replace("^$prefix_dir",'',$s_page);
		$s_page = substr($s_page,strlen($prefix_dir));
		
		// relative オプションと hierarchy オプションが同時に指定された場合は
		// パターンを取り除くだけでなく、上位の存在しているページ名も取り除く。
		if($params['hierarchy'])
		{
			$tmp = $s_page;
			
			//depth オプションが指定されていた場合 $top_level が変わります。
			while(substr_count($tmp,"/") > $top_level -1 )
			{
				//一階層ずつとりのぞく
				if( ( $pos = strrpos($tmp,'/') ) !== false )
				{
					$tmp = substr($tmp,0,$pos);
				}

				//上位のページが存在していれば、その文字列を取り除き、相対名にする。
				if(is_page($prefix_dir.$tmp))
				{
					//$s_page = ereg_replace("^$tmp/",'',$s_page);
					$s_page=substr($s_page,strlen("$tmp/"));
					break;
				}
			}
		}
	}
	
	//new オプション
	$new='';
	if( $params['new'] )
	{
		require_once(PLUGIN_DIR.'new.inc.php');
		plugin_new_init();
		$new = plugin_new_inline(format_date(get_filetime($page)));
	}
		
	ls2_1_list_push($params,$level);
	$ret = $include ? '<li>include ' : '<li>';
	if ($is_done and ($params['title'] or $params['include']))
	{
		$ret .= "<a href=\"$href\" title=\"$title\">$s_page</a> ";
		$ret .= "<a href=\"#list_{$params["page_$page"]}\"><sup>&uarr;</sup></a>";
		array_push($params['result'],$ret);
		return;
	}

	else
	{
		$ret .= "<a id=\"list_{$params["page_$page"]}\" href=\"$href\" title=\"$title\">$s_page</a> $new";
		array_push($params['result'],$ret);
	}
	
	$anchor = ls2_1_ANCHOR_ORIGIN;
	if ($params['title'] or $params['include'])
	{
		//title_number オプション解析
		if($params['title_number'])
		{
			list($title_number,$garbage) = ls2_1_number_option_analysis($params['title_number']);
		}
		$titlenum=0;
		foreach (get_source($page) as $line)
		{
			if ($params['title'] and preg_match('/^(\*{1,3})/',$line,$matches))
			{
				if( $params['title_number'] )
				{
					if( $titlenum >= $title_number ) continue;
					$titlenum++;
				}
				
				$id = make_heading($line);
				$hlevel = strlen($matches[1]);
				$id = ls2_1_CONTENT_HEAD.$anchor++;
				ls2_1_list_push($params,$level + $hlevel );
				array_push($params['result'], "<li><a href=\"$href$id\">$line</a>");
				//array_push($params['result'], "<li>title: <a href=\"$href$id\">$line</a>");
				//title: という文字列を付けようか付けまいか悩み中。
			}
			else if ($params['include']
				and preg_match('/^#include\((.+)\)/',$line,$matches) and is_page($matches[1]))
			{
				ls2_1_get_headings($matches[1],$params,$level + $hlevel + 1,TRUE, $prefix, $top_level);
			}
		}
	}
}
//リスト構造を構築する
function ls2_1_list_push(&$params,$level)
{
	global $_ul_left_margin, $_ul_margin, $_list_pad_str;
	
	$result =& $params['result'];
	$saved  =& $params['saved'];
	$cont   = TRUE;
	$open   = "<ul%s>";
	$close  = '</li></ul>';
	
	while (count($saved) > $level or
		(count($saved) > 0 and $saved[0] != $close))
	{
		array_push($result, array_shift($saved));
	}
	
	$margin = $level - count($saved);
	
	while (count($saved) < ($level - 1))
	{
		array_unshift($saved, ''); //count($saved)を増やすためのdummy
	}
	
	if (count($saved) < $level)
	{
		$cont = FALSE;
		array_unshift($saved, $close);
		
		$left = ($level == $margin) ? $_ul_left_margin : 0;
		if ($params['title_compact'])
		{
			// マージンを固定
			$left += $_ul_margin;
			// レベルを修正
			$level -= ($margin - 1);
		}
		else
		{
			$left += $margin * $_ul_margin;
		}
		$str = sprintf($_list_pad_str, $level, $left, $left);
		array_push($result, sprintf($open, $str));
	}
	if ($cont)
	{
		array_push($result, '</li>');
	}
}

//オプション \d?[+-]?\d? を解析。
function ls2_1_depth_option_analysis($arg)
{
	$low=0;
	$high=0;
	$just='';
	if( !preg_match('/^\d*\-?\d*$/',$arg) or $arg == '' )
	{
		return;
	}
	
	if(substr_count($arg,"-")) // \d-\d の場合
	{
		list($low,$high)=split("-",$arg,2);
	}
	elseif(substr_count($arg,"+")) // \d+\d の場合
	{
		list($low,$high)=split("+",$arg,2);
		$high += $low;
	}
	else // \d だけの場合
	{
		$just = $arg;
	}
	return array($low,$high,$just);
}

//オプション -?\d+ を解析。
function ls2_1_number_option_analysis($arg)
{
	$head=0;
	$tail=0;
	if( !preg_match('/^-?\d+$/',$arg) or $arg == '' )
	{
		return;
	}
	if(substr_count($arg,"-")) // - がある場合
	{
		$tail=substr($arg,1);
	}
	else
	{
		$head=$arg;
	}
	return array($head,$tail);
}

//オプションを解析する
function ls2_1_check_arg($pval, $pkey, &$params)
{
	// $pval に depth=2-3 や relative のような値が入る。実質 $pkey は意味なし。
	if ($pval == '')
	{
		return;
	}
	list($key,$val)=split("=", $pval);
	if( array_key_exists( $key, $params ) )
	{
		if( $val )
		{
			//せいぜい一度やるぐらいなので ereg でもよしとする。技術メモ
			$val = ereg_replace("^$key=",'',$val);
			//$val = htmlspecialchars($val);
			$params[$key] = $val;
		}
		else
		{
			$params[$key] = TRUE;
		}
	}
	else
	{
		$params['_args'][] = $pval;
	}
}
?>
