OSDN Git Service

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