OSDN Git Service

BugTrack/2283 ls2 plugin: Improve include loop handling
[pukiwiki/pukiwiki.git] / plugin / ls2.inc.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone.
3 // ls2.inc.php
4 // Copyright
5 //   2002-2017  PukiWiki Development Team
6 //   2002       panda  http://home.arino.jp/?ls2.inc.php 
7 //   2002       Y.MASUI GPL2 http://masui.net/pukiwiki/ masui@masui.net (ls.inc.php)
8 // License: GPL version 2
9 //
10 // List plugin 2
11
12 /*
13  * 配下のページや、その見出し(*,**,***)の一覧を表示する
14  * Usage
15  *  #ls2(pattern[,title|include|link|reverse|compact, ...],heading title)
16  *
17  * pattern  : 省略するときもカンマが必要
18  * 'title'  : 見出しの一覧を表示する
19  * 'include': インクルードしているページの見出しを再帰的に列挙する
20  * 'link   ': actionプラグインを呼び出すリンクを表示
21  * 'reverse': ページの並び順を反転し、降順にする
22  * 'compact': 見出しレベルを調整する
23  *     PLUGIN_LS2_LIST_COMPACTがTRUEの時は無効(変化しない)
24  * heading title: 見出しのタイトルを指定する (linkを指定した時のみ)
25  */
26
27 // 見出しアンカーの書式
28 define('PLUGIN_LS2_ANCHOR_PREFIX', '#content_1_');
29
30 // 見出しアンカーの開始番号
31 define('PLUGIN_LS2_ANCHOR_ORIGIN', 0);
32
33 // 見出しレベルを調整する(デフォルト値)
34 define('PLUGIN_LS2_LIST_COMPACT', FALSE);
35
36 function plugin_ls2_action()
37 {
38         global $vars, $_ls2_msg_title;
39
40         $params = array();
41         $keys   = array('title', 'include', 'reverse');
42         foreach ($keys as $key)
43                 $params[$key] = isset($vars[$key]);
44
45         $prefix = isset($vars['prefix']) ? $vars['prefix'] : '';
46         $body = plugin_ls2_show_lists($prefix, $params);
47
48         return array('body'=>$body,
49                 'msg'=>str_replace('$1', htmlsc($prefix), $_ls2_msg_title));
50 }
51
52 function plugin_ls2_convert()
53 {
54         global $vars, $_ls2_msg_title;
55
56         $params = array(
57                 'link'    => FALSE,
58                 'title'   => FALSE,
59                 'include' => FALSE,
60                 'reverse' => FALSE,
61                 'compact' => PLUGIN_LS2_LIST_COMPACT,
62                 '_args'   => array(),
63                 '_done'   => FALSE
64         );
65
66         $args = array();
67         $prefix = '';
68         if (func_num_args()) {
69                 $args   = func_get_args();
70                 $prefix = array_shift($args);
71         }
72         if ($prefix == '') $prefix = strip_bracket($vars['page']) . '/';
73
74         foreach ($args as $arg)
75                 plugin_ls2_check_arg($arg, $params);
76
77         $title = (! empty($params['_args'])) ? join(',', $params['_args']) :   // Manual
78                 str_replace('$1', htmlsc($prefix), $_ls2_msg_title); // Auto
79
80         if (! $params['link'])
81                 return plugin_ls2_show_lists($prefix, $params);
82
83         $tmp = array();
84         $tmp[] = 'plugin=ls2&amp;prefix=' . rawurlencode($prefix);
85         if (isset($params['title']))   $tmp[] = 'title=1';
86         if (isset($params['include'])) $tmp[] = 'include=1';
87
88         return '<p><a href="' . get_base_uri() . '?' . join('&amp;', $tmp) . '">' .
89                 $title . '</a></p>' . "\n";
90 }
91
92 function plugin_ls2_show_lists($prefix, & $params)
93 {
94         global $_ls2_err_nopages;
95
96         $pages = array();
97         if ($prefix != '') {
98                 foreach (get_existpages() as $_page)
99                         if (strpos($_page, $prefix) === 0)
100                                 $pages[] = $_page;
101         } else {
102                 $pages = get_existpages();
103         }
104
105         natcasesort($pages);
106         if ($params['reverse']) $pages = array_reverse($pages);
107
108         foreach ($pages as $page) $params['page_ ' . $page] = 0;
109
110         if (empty($pages)) {
111                 return str_replace('$1', htmlsc($prefix), $_ls2_err_nopages);
112         } else {
113                 $params['result'] = $params['saved'] = array();
114                 foreach ($pages as $page)
115                         $read_pages = array(); // read pages per page
116                         plugin_ls2_get_headings($page, $params, 1, false, $read_pages);
117                 return join("\n", $params['result']) . join("\n", $params['saved']);
118         }
119 }
120
121 function plugin_ls2_get_headings($page, & $params, $level, $include = FALSE,
122         &$read_pages)
123 {
124         static $_ls2_anchor = 0;
125
126         if (is_null($read_pages)) {
127                 $read_pages = array();
128         }
129         // ページが未表示のとき
130         $is_done = isset($read_pages[$page]);
131         if (! $is_done) $params["page_$page"] = ++$_ls2_anchor;
132
133         $s_page = htmlsc($page);
134         $title  = $s_page . ' ' . get_pg_passage($page, FALSE);
135         $href   = get_page_uri($page);
136
137         plugin_ls2_list_push($params, $level);
138         $ret = $include ? '<li>include ' : '<li>';
139
140         if ($is_done) {
141                 $ret .= '<a href="' . $href . '" title="' . $title . '">' . $s_page . '</a> ';
142                 $ret .= '<a href="#list_' . $params["page_$page"] . '"><sup>&uarr;</sup></a>';
143                 array_push($params['result'], $ret);
144                 return;
145         }
146
147         $ret .= '<a id="list_' . $params["page_$page"] . '" href="' . $href .
148                 '" title="' . $title . '">' . $s_page . '</a>';
149         array_push($params['result'], $ret);
150
151         $anchor = PLUGIN_LS2_ANCHOR_ORIGIN;
152         $matches = array();
153         $is_title = $params['title'];
154         $is_include = $params['include'];
155         if (!$is_title && !$is_include) {
156                 return;
157         }
158         foreach (get_source($page) as $line) {
159                 if ($is_title && preg_match('/^(\*{1,3})/', $line, $matches)) {
160                         $id    = make_heading($line);
161                         $heading_level = strlen($matches[1]);
162                         $id    = PLUGIN_LS2_ANCHOR_PREFIX . $anchor++;
163                         plugin_ls2_list_push($params, $level + $heading_level);
164                         array_push($params['result'],
165                                 '<li><a href="' . $href . $id . '">' . $line . '</a>');
166                 } else if ($is_include &&
167                         preg_match('/^#include\((.+)\)/', $line, $matches)) {
168                         $include_args = $matches[1];
169                         $page2 = $include_args;
170                         $m2 = null;
171                         if (preg_match('#^(("([^"]+)")|([^",]+))#', $include_args, $m2)) {
172                                 if ($m2[3]) {
173                                         $page2 = $m2[3];
174                                 } else if ($m2[4]) {
175                                         $page2 = $m2[4];
176                                 }
177                         }
178                         $sub_page = get_fullname($page2, $page);
179                         if (is_page($sub_page)) {
180                                 $read_pages[$page] = 1;
181                                 if (!isset($read_pages[$sub_page])) {
182                                         plugin_ls2_get_headings($sub_page, $params,
183                                                 $level + 1, TRUE, $read_pages);
184                                 }
185                         }
186                 }
187         }
188 }
189
190 //リスト構造を構築する
191 function plugin_ls2_list_push(& $params, $level)
192 {
193         $result = & $params['result'];
194         $saved  = & $params['saved'];
195         $cont   = TRUE;
196         $open   = '<ul%s>';
197         $close  = '</li></ul>';
198
199         while (count($saved) > $level || (! empty($saved) && $saved[0] != $close))
200                 array_push($result, array_shift($saved));
201
202         $margin = $level - count($saved);
203
204         // count($saved)を増やす
205         while (count($saved) < ($level - 1)) array_unshift($saved, '');
206
207         if (count($saved) < $level) {
208                 $cont = FALSE;
209                 array_unshift($saved, $close);
210
211                 $left = 0;
212                 if ($params['compact']) {
213                         $left = 1;   // マージンを固定
214                         $level -= ($margin - 1); // レベルを修正
215                 } else {
216                         $left = $margin;
217                 }
218                 $str = sprintf(pkwk_list_attrs_template(), $level, $left);
219                 array_push($result, sprintf($open, $str));
220         }
221
222         if ($cont) array_push($result, '</li>');
223 }
224
225 // オプションを解析する
226 function plugin_ls2_check_arg($value, & $params)
227 {
228         if ($value == '') {
229                 $params['_done'] = TRUE;
230                 return;
231         }
232
233         if (! $params['_done']) {
234                 foreach (array_keys($params) as $param) {
235                         if (strtolower($value)  == $param &&
236                             preg_match('/^[a-z]/', $param)) {
237                                 $params[$param] = TRUE;
238                                 return;
239                         }
240                 }
241                 $params['_done'] = TRUE;
242         }
243
244         $params['_args'][] = htmlsc($value); // Link title
245 }