OSDN Git Service

BugTrack/2260 Use get_base_uri() instead of $script
[pukiwiki/pukiwiki.git] / plugin / tracker.inc.php
1 <?php
2 // PukiWiki - Yet another WikiWikiWeb clone
3 // tracker.inc.php
4 // Copyright 2003-2017 PukiWiki Development Team
5 // License: GPL v2 or (at your option) any later version
6 //
7 // Issue tracker plugin (See Also bugtrack plugin)
8
9 // tracker_listで表示しないページ名(正規表現で)
10 // 'SubMenu'ページ および '/'を含むページを除外する
11 define('TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#');
12 // 制限しない場合はこちら
13 //define('TRACKER_LIST_EXCLUDE_PATTERN','#(?!)#');
14
15 // 項目の取り出しに失敗したページを一覧に表示する
16 define('TRACKER_LIST_SHOW_ERROR_PAGE',TRUE);
17
18 function plugin_tracker_convert()
19 {
20         global $vars;
21
22         $script = get_base_uri();
23         if (PKWK_READONLY) return ''; // Show nothing
24
25         $base = $refer = $vars['page'];
26
27         $config_name = 'default';
28         $form = 'form';
29         $options = array();
30         if (func_num_args())
31         {
32                 $args = func_get_args();
33                 switch (count($args))
34                 {
35                         case 3:
36                                 $options = array_splice($args,2);
37                         case 2:
38                                 $args[1] = get_fullname($args[1],$base);
39                                 $base = is_pagename($args[1]) ? $args[1] : $base;
40                         case 1:
41                                 $config_name = ($args[0] != '') ? $args[0] : $config_name;
42                                 list($config_name,$form) = array_pad(explode('/',$config_name,2),2,$form);
43                 }
44         }
45
46         $config = new Config('plugin/tracker/'.$config_name);
47
48         if (!$config->read())
49         {
50                 return "<p>config file '".htmlsc($config_name)."' not found.</p>";
51         }
52
53         $config->config_name = $config_name;
54
55         $fields = plugin_tracker_get_fields($base,$refer,$config);
56
57         $form = $config->page.'/'.$form;
58         if (!is_page($form))
59         {
60                 return "<p>config file '".make_pagelink($form)."' not found.</p>";
61         }
62         $retval = convert_html(plugin_tracker_get_source($form));
63         $hiddens = '';
64
65         foreach (array_keys($fields) as $name)
66         {
67                 $replace = $fields[$name]->get_tag();
68                 if (is_a($fields[$name],'Tracker_field_hidden'))
69                 {
70                         $hiddens .= $replace;
71                         $replace = '';
72                 }
73                 $retval = str_replace("[$name]",$replace,$retval);
74         }
75         return <<<EOD
76 <form enctype="multipart/form-data" action="$script" method="post">
77 <div>
78 $retval
79 $hiddens
80 </div>
81 </form>
82 EOD;
83 }
84 function plugin_tracker_action()
85 {
86         global $post, $vars, $now;
87
88         if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
89
90         $config_name = array_key_exists('_config',$post) ? $post['_config'] : '';
91
92         $config = new Config('plugin/tracker/'.$config_name);
93         if (!$config->read())
94         {
95                 return "<p>config file '".htmlsc($config_name)."' not found.</p>";
96         }
97         $config->config_name = $config_name;
98         $source = $config->page.'/page';
99
100         $refer = array_key_exists('_refer',$post) ? $post['_refer'] : $post['_base'];
101
102         if (!is_pagename($refer))
103         {
104                 return array(
105                         'msg'=>'cannot write',
106                         'body'=>'page name ('.htmlsc($refer).') is not valid.'
107                 );
108         }
109         if (!is_page($source))
110         {
111                 return array(
112                         'msg'=>'cannot write',
113                         'body'=>'page template ('.htmlsc($source).') is not exist.'
114                 );
115         }
116         // ページ名を決定
117         $base = $post['_base'];
118         if (!is_pagename($base))
119         {
120                 return array(
121                         'msg'=>'cannot write',
122                         'body'=>'page name ('.htmlsc($base).') is not valid.'
123                 );
124         }
125         $name = (array_key_exists('_name',$post)) ? $post['_name'] : '';
126         $_page = (array_key_exists('_page',$post)) ? $post['_page'] : '';
127         if (is_pagename($_page)) {
128                 // Create _page page if _page is in parameters
129                 $page = $real = $_page;
130         } else if (is_pagename($name)) {
131                 // Create "$base/$name" page if _name is in parameters
132                 $real = $name;
133                 $page = get_fullname('./' . $name, $base);
134         } else {
135                 $page = '';
136         }
137         if (!is_pagename($page) || is_page($page)) {
138                 // Need new page name => Get last article number + 1
139                 $page_list = plugin_tracker_get_page_list($base, false);
140                 usort($page_list, '_plugin_tracker_list_paganame_compare');
141                 if (count($page_list) === 0) {
142                         $num = 1;
143                 } else {
144                         $latest_page = $page_list[count($page_list) - 1]['name'];
145                         $num = intval(substr($latest_page, strlen($base) + 1)) + 1;
146                 }
147                 $real = '' . $num;
148                 $page = $base . '/' . $num;
149         }
150         // ページデータを生成
151         $postdata = plugin_tracker_get_source($source);
152
153         // 規定のデータ
154         $_post = array_merge($post,$_FILES);
155         $_post['_date'] = $now;
156         $_post['_page'] = $page;
157         $_post['_name'] = $name;
158         $_post['_real'] = $real;
159         // $_post['_refer'] = $_post['refer'];
160
161         $fields = plugin_tracker_get_fields($page,$refer,$config);
162
163         // Creating an empty page, before attaching files
164         touch(get_filename($page));
165
166         foreach (array_keys($fields) as $key)
167         {
168                 $value = array_key_exists($key,$_post) ?
169                         $fields[$key]->format_value($_post[$key]) : '';
170
171                 foreach (array_keys($postdata) as $num)
172                 {
173                         if (trim($postdata[$num]) == '')
174                         {
175                                 continue;
176                         }
177                         $postdata[$num] = str_replace(
178                                 "[$key]",
179                                 ($postdata[$num]{0} == '|' or $postdata[$num]{0} == ':') ?
180                                         str_replace('|','&#x7c;',$value) : $value,
181                                 $postdata[$num]
182                         );
183                 }
184         }
185
186         // Writing page data, without touch
187         page_write($page, join('', $postdata));
188         pkwk_headers_sent();
189         header('Location: ' . get_page_uri($page, PKWK_URI_ROOT));
190         exit;
191 }
192
193 /**
194  * Page_list comparator
195  */
196 function _plugin_tracker_list_paganame_compare($a, $b)
197 {
198         return strnatcmp($a['name'], $b['name']);
199 }
200
201 /**
202  * Get page list for "$page/"
203  */
204 function plugin_tracker_get_page_list($page, $needs_filetime) {
205         $page_list = array();
206         $pattern = $page . '/';
207         $pattern_len = strlen($pattern);
208         foreach (get_existpages() as $p) {
209                 if (strncmp($p, $pattern, $pattern_len) === 0 && pkwk_ctype_digit(substr($p, $pattern_len))) {
210                         if ($needs_filetime) {
211                                 $page_list[] = array('name'=>$p,'filetime'=>get_filetime($p));
212                         } else {
213                                 $page_list[] = array('name'=>$p);
214                         }
215                 }
216         }
217         return $page_list;
218 }
219
220
221 /*
222 function plugin_tracker_inline()
223 {
224         global $vars;
225
226         if (PKWK_READONLY) return ''; // Show nothing
227
228         $args = func_get_args();
229         if (count($args) < 3)
230         {
231                 return FALSE;
232         }
233         $body = array_pop($args);
234         list($config_name,$field) = $args;
235
236         $config = new Config('plugin/tracker/'.$config_name);
237
238         if (!$config->read())
239         {
240                 return "config file '".htmlsc($config_name)."' not found.";
241         }
242
243         $config->config_name = $config_name;
244
245         $fields = plugin_tracker_get_fields($vars['page'],$vars['page'],$config);
246         $fields[$field]->default_value = $body;
247         return $fields[$field]->get_tag();
248 }
249 */
250 // フィールドオブジェクトを構築する
251 function plugin_tracker_get_fields($base,$refer,&$config)
252 {
253         global $now,$_tracker_messages;
254
255         $fields = array();
256         // 予約語
257         foreach (array(
258                 '_date'=>'text',    // 投稿日時
259                 '_update'=>'date',  // 最終更新
260                 '_past'=>'past',    // 経過(passage)
261                 '_page'=>'page',    // ページ名
262                 '_name'=>'text',    // 指定されたページ名
263                 '_real'=>'real',    // 実際のページ名
264                 '_refer'=>'page',   // 参照元(フォームのあるページ)
265                 '_base'=>'page',    // 基準ページ
266                 '_submit'=>'submit' // 追加ボタン
267                 ) as $field=>$class)
268         {
269                 $class = 'Tracker_field_'.$class;
270                 $fields[$field] = new $class(array($field,$_tracker_messages["btn$field"],'','20',''),$base,$refer,$config);
271         }
272
273         foreach ($config->get('fields') as $field)
274         {
275                 // 0=>項目名 1=>見出し 2=>形式 3=>オプション 4=>デフォルト値
276                 $class = 'Tracker_field_'.$field[2];
277                 if (!class_exists($class))
278                 { // デフォルト
279                         $class = 'Tracker_field_text';
280                         $field[2] = 'text';
281                         $field[3] = '20';
282                 }
283                 $fields[$field[0]] = new $class($field,$base,$refer,$config);
284         }
285         return $fields;
286 }
287 // フィールドクラス
288 class Tracker_field
289 {
290         var $name;
291         var $title;
292         var $values;
293         var $default_value;
294         var $page;
295         var $refer;
296         var $config;
297         var $data;
298         var $sort_type = SORT_REGULAR;
299         var $id = 0;
300
301         function Tracker_field($field,$page,$refer,&$config)
302         {
303                 $this->__construct($field, $page, $refer, $config);
304         }
305         function __construct($field,$page,$refer,&$config)
306         {
307                 global $post;
308                 static $id = 0;
309
310                 $this->id = ++$id;
311                 $this->name = $field[0];
312                 $this->title = $field[1];
313                 $this->values = explode(',',$field[3]);
314                 $this->default_value = $field[4];
315                 $this->page = $page;
316                 $this->refer = $refer;
317                 $this->config = &$config;
318                 $this->data = array_key_exists($this->name,$post) ? $post[$this->name] : '';
319         }
320         function get_tag()
321         {
322         }
323         function get_style($str)
324         {
325                 return '%s';
326         }
327         function format_value($value)
328         {
329                 return $value;
330         }
331         function format_cell($str)
332         {
333                 return $str;
334         }
335         function get_value($value)
336         {
337                 return $value;
338         }
339 }
340 class Tracker_field_text extends Tracker_field
341 {
342         var $sort_type = SORT_STRING;
343
344         function get_tag()
345         {
346                 $s_name = htmlsc($this->name);
347                 $s_size = htmlsc($this->values[0]);
348                 $s_value = htmlsc($this->default_value);
349                 return "<input type=\"text\" name=\"$s_name\" size=\"$s_size\" value=\"$s_value\" />";
350         }
351 }
352 class Tracker_field_page extends Tracker_field_text
353 {
354         var $sort_type = SORT_STRING;
355
356         function format_value($value)
357         {
358                 global $WikiName;
359
360                 $value = strip_bracket($value);
361                 if (is_pagename($value))
362                 {
363                         $value = "[[$value]]";
364                 }
365                 return parent::format_value($value);
366         }
367 }
368 class Tracker_field_real extends Tracker_field_text
369 {
370         var $sort_type = SORT_REGULAR;
371 }
372 class Tracker_field_title extends Tracker_field_text
373 {
374         var $sort_type = SORT_STRING;
375
376         function format_cell($str)
377         {
378                 make_heading($str);
379                 return $str;
380         }
381 }
382 class Tracker_field_textarea extends Tracker_field
383 {
384         var $sort_type = SORT_STRING;
385
386         function get_tag()
387         {
388                 $s_name = htmlsc($this->name);
389                 $s_cols = htmlsc($this->values[0]);
390                 $s_rows = htmlsc($this->values[1]);
391                 $s_value = htmlsc($this->default_value);
392                 return "<textarea name=\"$s_name\" cols=\"$s_cols\" rows=\"$s_rows\">$s_value</textarea>";
393         }
394         function format_cell($str)
395         {
396                 $str = preg_replace('/[\r\n]+/','',$str);
397                 if (!empty($this->values[2]) and strlen($str) > ($this->values[2] + 3))
398                 {
399                         $str = mb_substr($str,0,$this->values[2]).'...';
400                 }
401                 return $str;
402         }
403 }
404 class Tracker_field_format extends Tracker_field
405 {
406         var $sort_type = SORT_STRING;
407
408         var $styles = array();
409         var $formats = array();
410
411         function Tracker_field_format($field,$page,$refer,&$config)
412         {
413                 $this->__construct($field, $page, $refer, $config);
414         }
415         function __construct($field,$page,$refer,&$config)
416         {
417                 parent::__construct($field,$page,$refer,$config);
418
419                 foreach ($this->config->get($this->name) as $option)
420                 {
421                         list($key,$style,$format) = array_pad(array_map(create_function('$a','return trim($a);'),$option),3,'');
422                         if ($style != '')
423                         {
424                                 $this->styles[$key] = $style;
425                         }
426                         if ($format != '')
427                         {
428                                 $this->formats[$key] = $format;
429                         }
430                 }
431         }
432         function get_tag()
433         {
434                 $s_name = htmlsc($this->name);
435                 $s_size = htmlsc($this->values[0]);
436                 return "<input type=\"text\" name=\"$s_name\" size=\"$s_size\" />";
437         }
438         function get_key($str)
439         {
440                 return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
441         }
442         function format_value($str)
443         {
444                 if (is_array($str))
445                 {
446                         return join(', ',array_map(array($this,'format_value'),$str));
447                 }
448                 $key = $this->get_key($str);
449                 return array_key_exists($key,$this->formats) ? str_replace('%s',$str,$this->formats[$key]) : $str;
450         }
451         function get_style($str)
452         {
453                 $key = $this->get_key($str);
454                 return array_key_exists($key,$this->styles) ? $this->styles[$key] : '%s';
455         }
456 }
457 class Tracker_field_file extends Tracker_field_format
458 {
459         var $sort_type = SORT_STRING;
460
461         function get_tag()
462         {
463                 $s_name = htmlsc($this->name);
464                 $s_size = htmlsc($this->values[0]);
465                 return "<input type=\"file\" name=\"$s_name\" size=\"$s_size\" />";
466         }
467         function format_value($str)
468         {
469                 if (array_key_exists($this->name,$_FILES))
470                 {
471                         require_once(PLUGIN_DIR.'attach.inc.php');
472                         $result = attach_upload($_FILES[$this->name],$this->page);
473                         if ($result['result']) // アップロード成功
474                         {
475                                 return parent::format_value($this->page.'/'.$_FILES[$this->name]['name']);
476                         }
477                 }
478                 // ファイルが指定されていないか、アップロードに失敗
479                 return parent::format_value('');
480         }
481 }
482 class Tracker_field_radio extends Tracker_field_format
483 {
484         var $sort_type = SORT_NUMERIC;
485
486         function get_tag()
487         {
488                 $s_name = htmlsc($this->name);
489                 $retval = '';
490                 $id = 0;
491                 foreach ($this->config->get($this->name) as $option)
492                 {
493                         $s_option = htmlsc($option[0]);
494                         $checked = trim($option[0]) == trim($this->default_value) ? ' checked="checked"' : '';
495                         ++$id;
496                         $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
497                         $retval .= '<input type="radio" name="' .  $s_name . '" id="' . $s_id .
498                                 '" value="' . $s_option . '"' . $checked . ' />' .
499                                 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
500                 }
501
502                 return $retval;
503         }
504         function get_key($str)
505         {
506                 return $str;
507         }
508         function get_value($value)
509         {
510                 static $options = array();
511                 if (!array_key_exists($this->name,$options))
512                 {
513                         $options[$this->name] = array_flip(array_map(create_function('$arr','return $arr[0];'),$this->config->get($this->name)));
514                 }
515                 return array_key_exists($value,$options[$this->name]) ? $options[$this->name][$value] : $value;
516         }
517 }
518 class Tracker_field_select extends Tracker_field_radio
519 {
520         var $sort_type = SORT_NUMERIC;
521
522         function get_tag($empty=FALSE)
523         {
524                 $s_name = htmlsc($this->name);
525                 $s_size = (array_key_exists(0,$this->values) and is_numeric($this->values[0])) ?
526                         ' size="'.htmlsc($this->values[0]).'"' : '';
527                 $s_multiple = (array_key_exists(1,$this->values) and strtolower($this->values[1]) == 'multiple') ?
528                         ' multiple="multiple"' : '';
529                 $retval = "<select name=\"{$s_name}[]\"$s_size$s_multiple>\n";
530                 if ($empty)
531                 {
532                         $retval .= " <option value=\"\"></option>\n";
533                 }
534                 $defaults = array_flip(preg_split('/\s*,\s*/',$this->default_value,-1,PREG_SPLIT_NO_EMPTY));
535                 foreach ($this->config->get($this->name) as $option)
536                 {
537                         $s_option = htmlsc($option[0]);
538                         $selected = array_key_exists(trim($option[0]),$defaults) ? ' selected="selected"' : '';
539                         $retval .= " <option value=\"$s_option\"$selected>$s_option</option>\n";
540                 }
541                 $retval .= "</select>";
542
543                 return $retval;
544         }
545 }
546 class Tracker_field_checkbox extends Tracker_field_radio
547 {
548         var $sort_type = SORT_NUMERIC;
549
550         function get_tag($empty=FALSE)
551         {
552                 $s_name = htmlsc($this->name);
553                 $defaults = array_flip(preg_split('/\s*,\s*/',$this->default_value,-1,PREG_SPLIT_NO_EMPTY));
554                 $retval = '';
555                 $id = 0;
556                 foreach ($this->config->get($this->name) as $option)
557                 {
558                         $s_option = htmlsc($option[0]);
559                         $checked = array_key_exists(trim($option[0]),$defaults) ?
560                                 ' checked="checked"' : '';
561                         ++$id;
562                         $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
563                         $retval .= '<input type="checkbox" name="' . $s_name .
564                                 '[]" id="' . $s_id . '" value="' . $s_option . '"' . $checked . ' />' .
565                                 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
566                 }
567
568                 return $retval;
569         }
570 }
571 class Tracker_field_hidden extends Tracker_field_radio
572 {
573         var $sort_type = SORT_NUMERIC;
574
575         function get_tag($empty=FALSE)
576         {
577                 $s_name = htmlsc($this->name);
578                 $s_default = htmlsc($this->default_value);
579                 $retval = "<input type=\"hidden\" name=\"$s_name\" value=\"$s_default\" />\n";
580
581                 return $retval;
582         }
583 }
584 class Tracker_field_submit extends Tracker_field
585 {
586         function get_tag()
587         {
588                 $s_title = htmlsc($this->title);
589                 $s_page = htmlsc($this->page);
590                 $s_refer = htmlsc($this->refer);
591                 $s_config = htmlsc($this->config->config_name);
592
593                 return <<<EOD
594 <input type="submit" value="$s_title" />
595 <input type="hidden" name="plugin" value="tracker" />
596 <input type="hidden" name="_refer" value="$s_refer" />
597 <input type="hidden" name="_base" value="$s_page" />
598 <input type="hidden" name="_config" value="$s_config" />
599 EOD;
600         }
601 }
602 class Tracker_field_date extends Tracker_field
603 {
604         var $sort_type = SORT_NUMERIC;
605
606         function format_cell($timestamp)
607         {
608                 return format_date($timestamp);
609         }
610 }
611 class Tracker_field_past extends Tracker_field
612 {
613         var $sort_type = SORT_NUMERIC;
614
615         function format_cell($timestamp)
616         {
617                 return get_passage($timestamp,FALSE);
618         }
619         function get_value($value)
620         {
621                 return UTIME - $value;
622         }
623 }
624 ///////////////////////////////////////////////////////////////////////////
625 // 一覧表示
626 function plugin_tracker_list_convert()
627 {
628         global $vars;
629
630         $config = 'default';
631         $page = $refer = $vars['page'];
632         $field = '_page';
633         $order = '';
634         $list = 'list';
635         $limit = NULL;
636         if (func_num_args())
637         {
638                 $args = func_get_args();
639                 switch (count($args))
640                 {
641                         case 4:
642                                 $limit = is_numeric($args[3]) ? $args[3] : $limit;
643                         case 3:
644                                 $order = $args[2];
645                         case 2:
646                                 $args[1] = get_fullname($args[1],$page);
647                                 $page = is_pagename($args[1]) ? $args[1] : $page;
648                         case 1:
649                                 $config = ($args[0] != '') ? $args[0] : $config;
650                                 list($config,$list) = array_pad(explode('/',$config,2),2,$list);
651                 }
652         }
653         return plugin_tracker_getlist($page,$refer,$config,$list,$order,$limit);
654 }
655 function plugin_tracker_list_action()
656 {
657         global $vars,$_tracker_messages;
658
659         $page = $refer = $vars['refer'];
660         $s_page = make_pagelink($page);
661         $config = $vars['config'];
662         $list = array_key_exists('list',$vars) ? $vars['list'] : 'list';
663         $order = array_key_exists('order',$vars) ? $vars['order'] : '_real:SORT_DESC';
664
665         return array(
666                 'msg' => $_tracker_messages['msg_list'],
667                 'body'=> str_replace('$1',$s_page,$_tracker_messages['msg_back']).
668                         plugin_tracker_getlist($page,$refer,$config,$list,$order)
669         );
670 }
671 function plugin_tracker_getlist($page,$refer,$config_name,$list,$order='',$limit=NULL)
672 {
673         $config = new Config('plugin/tracker/'.$config_name);
674
675         if (!$config->read())
676         {
677                 return "<p>config file '".htmlsc($config_name)."' is not exist.</p>";
678         }
679
680         $config->config_name = $config_name;
681
682         if (!is_page($config->page.'/'.$list))
683         {
684                 return "<p>config file '".make_pagelink($config->page.'/'.$list)."' not found.</p>";
685         }
686
687         $list = new Tracker_list($page,$refer,$config,$list);
688         $list->sort($order);
689         return $list->toString($limit);
690 }
691
692 // 一覧クラス
693 class Tracker_list
694 {
695         var $page;
696         var $config;
697         var $list;
698         var $fields;
699         var $pattern;
700         var $pattern_fields;
701         var $rows;
702         var $order;
703         var $sort_keys;
704
705         function Tracker_list($page,$refer,&$config,$list)
706         {
707                 $this->__construct($page, $refer, $config, $list);
708         }
709         function __construct($page,$refer,&$config,$list)
710         {
711                 $this->page = $page;
712                 $this->config = &$config;
713                 $this->list = $list;
714                 $this->fields = plugin_tracker_get_fields($page,$refer,$config);
715
716                 $pattern = join('',plugin_tracker_get_source($config->page.'/page'));
717                 // ブロックプラグインをフィールドに置換
718                 // #commentなどで前後に文字列の増減があった場合に、[_block_xxx]に吸い込ませるようにする
719                 $pattern = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m','[_block_$1]',$pattern);
720
721                 // パターンを生成
722                 $this->pattern = '';
723                 $this->pattern_fields = array();
724                 $pattern = preg_split('/\\\\\[(\w+)\\\\\]/',preg_quote($pattern,'/'),-1,PREG_SPLIT_DELIM_CAPTURE);
725                 while (count($pattern))
726                 {
727                         $this->pattern .= preg_replace('/\s+/','\\s*','(?>\\s*'.trim(array_shift($pattern)).'\\s*)');
728                         if (count($pattern))
729                         {
730                                 $field = array_shift($pattern);
731                                 $this->pattern_fields[] = $field;
732                                 $this->pattern .= '(.*?)';
733                         }
734                 }
735                 // ページの列挙と取り込み
736                 $this->rows = array();
737                 $pattern = "$page/";
738                 $pattern_len = strlen($pattern);
739                 foreach (get_existpages() as $_page)
740                 {
741                         if (strpos($_page,$pattern) === 0)
742                         {
743                                 $name = substr($_page,$pattern_len);
744                                 if (preg_match(TRACKER_LIST_EXCLUDE_PATTERN,$name))
745                                 {
746                                         continue;
747                                 }
748                                 $this->add($_page,$name);
749                         }
750                 }
751         }
752         function add($page,$name)
753         {
754                 static $moved = array();
755
756                 // 無限ループ防止
757                 if (array_key_exists($name,$this->rows))
758                 {
759                         return;
760                 }
761
762                 $source = plugin_tracker_get_source($page);
763                 if (preg_match('/move\sto\s(.+)/',$source[0],$matches))
764                 {
765                         $page = strip_bracket(trim($matches[1]));
766                         if (array_key_exists($page,$moved) or !is_page($page))
767                         {
768                                 return;
769                         }
770                         $moved[$page] = TRUE;
771                         return $this->add($page,$name);
772                 }
773                 $source = join('',preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/','$1$2',$source));
774
775                 // デフォルト値
776                 $this->rows[$name] = array(
777                         '_page'  => "[[$page]]",
778                         '_refer' => $this->page,
779                         '_real'  => $name,
780                         '_update'=> get_filetime($page),
781                         '_past'  => get_filetime($page)
782                 );
783                 if ($this->rows[$name]['_match'] = preg_match("/{$this->pattern}/s",$source,$matches))
784                 {
785                         array_shift($matches);
786                         foreach ($this->pattern_fields as $key=>$field)
787                         {
788                                 $this->rows[$name][$field] = trim($matches[$key]);
789                         }
790                 }
791         }
792         function compare($a, $b)
793         {
794                 foreach ($this->sort_keys as $sort_key)
795                 {
796                         $field = $sort_key['field'];
797                         $dir = $sort_key['dir'];
798                         $f = $this->fields[$field];
799                         $sort_type = $f->sort_type;
800                         $aVal = isset($a[$field]) ? $f->get_value($a[$field]) : '';
801                         $bVal = isset($b[$field]) ? $f->get_value($b[$field]) : '';
802                         $c = strnatcmp($aVal, $bVal) * ($dir === SORT_ASC ? 1 : -1);
803                         if ($c === 0) continue;
804                         return $c;
805                 }
806                 return 0;
807         }
808         function sort($order)
809         {
810                 if ($order == '')
811                 {
812                         return;
813                 }
814                 $names = array_flip(array_keys($this->fields));
815                 $this->order = array();
816                 foreach (explode(';',$order) as $item)
817                 {
818                         list($key,$dir) = array_pad(explode(':',$item),1,'ASC');
819                         if (!array_key_exists($key,$names))
820                         {
821                                 continue;
822                         }
823                         switch (strtoupper($dir))
824                         {
825                                 case 'SORT_ASC':
826                                 case 'ASC':
827                                 case SORT_ASC:
828                                         $dir = SORT_ASC;
829                                         break;
830                                 case 'SORT_DESC':
831                                 case 'DESC':
832                                 case SORT_DESC:
833                                         $dir = SORT_DESC;
834                                         break;
835                                 default:
836                                         continue;
837                         }
838                         $this->order[$key] = $dir;
839                 }
840                 $sort_keys = array();
841                 foreach ($this->order as $field=>$order)
842                 {
843                         if (!array_key_exists($field,$names))
844                         {
845                                 continue;
846                         }
847                         $sort_keys[] = array('field' => $field, 'dir' => $order);
848                 }
849                 $this->sort_keys = $sort_keys;
850                 usort($this->rows, array($this, 'compare'));
851         }
852         function replace_item($arr)
853         {
854                 $params = explode(',',$arr[1]);
855                 $name = array_shift($params);
856                 if ($name == '')
857                 {
858                         $str = '';
859                 }
860                 else if (array_key_exists($name,$this->items))
861                 {
862                         $str = $this->items[$name];
863                         if (array_key_exists($name,$this->fields))
864                         {
865                                 $str = $this->fields[$name]->format_cell($str);
866                         }
867                 }
868                 else
869                 {
870                         return $this->pipe ? str_replace('|','&#x7c;',$arr[0]) : $arr[0];
871                 }
872                 $style = count($params) ? $params[0] : $name;
873                 if (array_key_exists($style,$this->items)
874                         and array_key_exists($style,$this->fields))
875                 {
876                         $str = sprintf($this->fields[$style]->get_style($this->items[$style]),$str);
877                 }
878                 return $this->pipe ? str_replace('|','&#x7c;',$str) : $str;
879         }
880         function replace_title($arr)
881         {
882                 $script = get_base_uri();
883                 $field = $sort = $arr[1];
884                 if ($sort == '_name' or $sort == '_page')
885                 {
886                         $sort = '_real';
887                 }
888                 if (!array_key_exists($field,$this->fields))
889                 {
890                         return $arr[0];
891                 }
892                 $dir = SORT_ASC;
893                 $arrow = '';
894                 $order = $this->order;
895
896                 if (is_array($order) && isset($order[$sort]))
897                 {
898                         // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
899                         $order_keys = array_keys($order); // with array_shift();
900
901                         $index = array_flip($order_keys);
902                         $pos = 1 + $index[$sort];
903                         $b_end = ($sort == array_shift($order_keys));
904                         $b_order = ($order[$sort] == SORT_ASC);
905                         $dir = ($b_end xor $b_order) ? SORT_ASC : SORT_DESC;
906                         $arrow = '&br;'.($b_order ? '&uarr;' : '&darr;')."($pos)";
907
908                         unset($order[$sort], $order_keys);
909                 }
910                 $title = $this->fields[$field]->title;
911                 $r_page = rawurlencode($this->page);
912                 $r_config = rawurlencode($this->config->config_name);
913                 $r_list = rawurlencode($this->list);
914                 $_order = array("$sort:$dir");
915                 if (is_array($order))
916                         foreach ($order as $key=>$value)
917                                 $_order[] = "$key:$value";
918                 $r_order = rawurlencode(join(';',$_order));
919
920                 return "[[$title$arrow>$script?plugin=tracker_list&refer=$r_page&config=$r_config&list=$r_list&order=$r_order]]";
921         }
922         function toString($limit=NULL)
923         {
924                 global $_tracker_messages;
925
926                 $source = '';
927                 $body = array();
928
929                 if ($limit !== NULL and count($this->rows) > $limit)
930                 {
931                         $source = str_replace(
932                                 array('$1','$2'),
933                                 array(count($this->rows),$limit),
934                                 $_tracker_messages['msg_limit'])."\n";
935                         $this->rows = array_splice($this->rows,0,$limit);
936                 }
937                 if (count($this->rows) == 0)
938                 {
939                         return '';
940                 }
941                 foreach (plugin_tracker_get_source($this->config->page.'/'.$this->list) as $line)
942                 {
943                         if (preg_match('/^\|(.+)\|[hHfFcC]$/',$line))
944                         {
945                                 $source .= preg_replace_callback('/\[([^\[\]]+)\]/',array(&$this,'replace_title'),$line);
946                         }
947                         else
948                         {
949                                 $body[] = $line;
950                         }
951                 }
952                 foreach ($this->rows as $key=>$row)
953                 {
954                         if (!TRACKER_LIST_SHOW_ERROR_PAGE and !$row['_match'])
955                         {
956                                 continue;
957                         }
958                         $this->items = $row;
959                         foreach ($body as $line)
960                         {
961                                 if (trim($line) == '')
962                                 {
963                                         // Ignore empty line
964                                         continue;
965                                 }
966                                 $this->pipe = ($line{0} == '|' or $line{0} == ':');
967                                 $source .= preg_replace_callback('/\[([^\[\]]+)\]/',array(&$this,'replace_item'),$line);
968                         }
969                 }
970                 return convert_html($source);
971         }
972 }
973 function plugin_tracker_get_source($page)
974 {
975         $source = get_source($page);
976         // Delete anchor part of Headings (Example: "*Heading1 [#id] AAA" to "*Heading1 AAA")
977         $s2 = preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m','$1$2',$source);
978         // Delete #freeze
979         $s3 = preg_replace('/^#freeze\s*$/im', '', $s2);
980         // Delete #author line
981         $s4 = preg_replace('/^#author\b[^\r\n]*$/im', '', $s3);
982         return $s4;
983 }