2 // PukiWiki - Yet another WikiWikiWeb clone
4 // Copyright 2003-2018 PukiWiki Development Team
5 // License: GPL v2 or (at your option) any later version
7 // Issue tracker plugin (See Also bugtrack plugin)
9 // tracker_listで表示しないページ名(正規表現で)
10 // 'SubMenu'ページ および '/'を含むページを除外する
11 define('TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#');
13 //define('TRACKER_LIST_EXCLUDE_PATTERN','#(?!)#');
15 // 項目の取り出しに失敗したページを一覧に表示する
16 define('TRACKER_LIST_SHOW_ERROR_PAGE',TRUE);
19 define('TRACKER_LIST_USE_CACHE', TRUE);
21 function plugin_tracker_convert()
25 $script = get_base_uri();
26 if (PKWK_READONLY) return ''; // Show nothing
28 $base = $refer = $vars['page'];
30 $config_name = 'default';
35 $args = func_get_args();
39 $options = array_splice($args,2);
41 $args[1] = get_fullname($args[1],$base);
42 $base = is_pagename($args[1]) ? $args[1] : $base;
44 $config_name = ($args[0] != '') ? $args[0] : $config_name;
45 list($config_name,$form) = array_pad(explode('/',$config_name,2),2,$form);
49 $config = new Config('plugin/tracker/'.$config_name);
53 return "<p>config file '".htmlsc($config_name)."' not found.</p>";
56 $config->config_name = $config_name;
58 $fields = plugin_tracker_get_fields($base,$refer,$config);
60 $form = $config->page.'/'.$form;
63 return "<p>config file '".make_pagelink($form)."' not found.</p>";
65 $retval = convert_html(plugin_tracker_get_source($form));
68 foreach (array_keys($fields) as $name)
70 $replace = $fields[$name]->get_tag();
71 if (is_a($fields[$name],'Tracker_field_hidden'))
76 $retval = str_replace("[$name]",$replace,$retval);
79 <form enctype="multipart/form-data" action="$script" method="post">
87 function plugin_tracker_action()
89 global $post, $vars, $now;
91 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
93 $config_name = array_key_exists('_config',$post) ? $post['_config'] : '';
95 $config = new Config('plugin/tracker/'.$config_name);
98 return "<p>config file '".htmlsc($config_name)."' not found.</p>";
100 $config->config_name = $config_name;
101 $source = $config->page.'/page';
103 $refer = array_key_exists('_refer',$post) ? $post['_refer'] : $post['_base'];
105 if (!is_pagename($refer))
108 'msg'=>'cannot write',
109 'body'=>'page name ('.htmlsc($refer).') is not valid.'
112 if (!is_page($source))
115 'msg'=>'cannot write',
116 'body'=>'page template ('.htmlsc($source).') is not exist.'
120 $base = $post['_base'];
121 if (!is_pagename($base))
124 'msg'=>'cannot write',
125 'body'=>'page name ('.htmlsc($base).') is not valid.'
128 $name = (array_key_exists('_name',$post)) ? $post['_name'] : '';
129 $_page = (array_key_exists('_page',$post)) ? $post['_page'] : '';
130 if (is_pagename($_page)) {
131 // Create _page page if _page is in parameters
132 $page = $real = $_page;
133 } else if (is_pagename($name)) {
134 // Create "$base/$name" page if _name is in parameters
136 $page = get_fullname('./' . $name, $base);
140 if (!is_pagename($page) || is_page($page)) {
141 // Need new page name => Get last article number + 1
142 $page_list = plugin_tracker_get_page_list($base, false);
143 usort($page_list, '_plugin_tracker_list_paganame_compare');
144 if (count($page_list) === 0) {
147 $latest_page = $page_list[count($page_list) - 1]['name'];
148 $num = intval(substr($latest_page, strlen($base) + 1)) + 1;
151 $page = $base . '/' . $num;
154 $postdata = plugin_tracker_get_source($source);
157 $_post = array_merge($post,$_FILES);
158 $_post['_date'] = $now;
159 $_post['_page'] = $page;
160 $_post['_name'] = $name;
161 $_post['_real'] = $real;
162 // $_post['_refer'] = $_post['refer'];
164 $fields = plugin_tracker_get_fields($page,$refer,$config);
166 check_editable($page, true, true);
167 // Creating an empty page, before attaching files
168 touch(get_filename($page));
170 foreach (array_keys($fields) as $key)
172 $value = array_key_exists($key,$_post) ?
173 $fields[$key]->format_value($_post[$key]) : '';
175 foreach (array_keys($postdata) as $num)
177 if (trim($postdata[$num]) == '')
181 $postdata[$num] = str_replace(
183 ($postdata[$num]{0} == '|' or $postdata[$num]{0} == ':') ?
184 str_replace('|','|',$value) : $value,
190 // Writing page data, without touch
191 page_write($page, join('', $postdata));
193 header('Location: ' . get_page_uri($page, PKWK_URI_ROOT));
198 * Page_list comparator
200 function _plugin_tracker_list_paganame_compare($a, $b)
202 return strnatcmp($a['name'], $b['name']);
206 * Get page list for "$page/"
208 function plugin_tracker_get_page_list($page, $needs_filetime) {
209 $page_list = array();
210 $pattern = $page . '/';
211 $pattern_len = strlen($pattern);
212 foreach (get_existpages() as $p) {
213 if (strncmp($p, $pattern, $pattern_len) === 0 && pkwk_ctype_digit(substr($p, $pattern_len))) {
214 if ($needs_filetime) {
215 $page_list[] = array('name'=>$p,'filetime'=>get_filetime($p));
217 $page_list[] = array('name'=>$p);
226 function plugin_tracker_inline()
230 if (PKWK_READONLY) return ''; // Show nothing
232 $args = func_get_args();
233 if (count($args) < 3)
237 $body = array_pop($args);
238 list($config_name,$field) = $args;
240 $config = new Config('plugin/tracker/'.$config_name);
242 if (!$config->read())
244 return "config file '".htmlsc($config_name)."' not found.";
247 $config->config_name = $config_name;
249 $fields = plugin_tracker_get_fields($vars['page'],$vars['page'],$config);
250 $fields[$field]->default_value = $body;
251 return $fields[$field]->get_tag();
255 function plugin_tracker_get_fields($base,$refer,&$config)
257 global $now,$_tracker_messages;
262 '_date'=>'text', // 投稿日時
263 '_update'=>'date', // 最終更新
264 '_past'=>'past', // 経過(passage)
265 '_page'=>'page', // ページ名
266 '_name'=>'text', // 指定されたページ名
267 '_real'=>'real', // 実際のページ名
268 '_refer'=>'page', // 参照元(フォームのあるページ)
269 '_base'=>'page', // 基準ページ
270 '_submit'=>'submit' // 追加ボタン
273 $class = 'Tracker_field_'.$class;
274 $fields[$field] = new $class(array($field,$_tracker_messages["btn$field"],'','20',''),$base,$refer,$config);
277 foreach ($config->get('fields') as $field)
279 // 0=>項目名 1=>見出し 2=>形式 3=>オプション 4=>デフォルト値
280 $class = 'Tracker_field_'.$field[2];
281 if (!class_exists($class))
283 $class = 'Tracker_field_text';
287 $fields[$field[0]] = new $class($field,$base,$refer,$config);
302 var $sort_type = SORT_REGULAR;
305 function Tracker_field($field,$page,$refer,&$config)
307 $this->__construct($field, $page, $refer, $config);
309 function __construct($field,$page,$refer,&$config)
315 $this->name = $field[0];
316 $this->title = $field[1];
317 $this->values = explode(',',$field[3]);
318 $this->default_value = $field[4];
320 $this->refer = $refer;
321 $this->config = &$config;
322 $this->data = array_key_exists($this->name,$post) ? $post[$this->name] : '';
327 function get_style($str)
331 function format_value($value)
335 function format_cell($str)
339 function get_value($value)
344 class Tracker_field_text extends Tracker_field
346 var $sort_type = SORT_STRING;
350 $s_name = htmlsc($this->name);
351 $s_size = htmlsc($this->values[0]);
352 $s_value = htmlsc($this->default_value);
353 return "<input type=\"text\" name=\"$s_name\" size=\"$s_size\" value=\"$s_value\" />";
356 class Tracker_field_page extends Tracker_field_text
358 var $sort_type = SORT_STRING;
360 function format_value($value)
364 $value = strip_bracket($value);
365 if (is_pagename($value))
367 $value = "[[$value]]";
369 return parent::format_value($value);
372 class Tracker_field_real extends Tracker_field_text
374 var $sort_type = SORT_REGULAR;
376 class Tracker_field_title extends Tracker_field_text
378 var $sort_type = SORT_STRING;
380 function format_cell($str)
386 class Tracker_field_textarea extends Tracker_field
388 var $sort_type = SORT_STRING;
392 $s_name = htmlsc($this->name);
393 $s_cols = htmlsc($this->values[0]);
394 $s_rows = htmlsc($this->values[1]);
395 $s_value = htmlsc($this->default_value);
396 return "<textarea name=\"$s_name\" cols=\"$s_cols\" rows=\"$s_rows\">$s_value</textarea>";
398 function format_cell($str)
400 $str = preg_replace('/[\r\n]+/','',$str);
401 if (!empty($this->values[2]) and strlen($str) > ($this->values[2] + 3))
403 $str = mb_substr($str,0,$this->values[2]).'...';
409 class Tracker_field_format extends Tracker_field
411 var $sort_type = SORT_STRING;
413 var $styles = array();
414 var $formats = array();
416 function Tracker_field_format($field,$page,$refer,&$config)
418 $this->__construct($field, $page, $refer, $config);
420 function __construct($field,$page,$refer,&$config)
422 parent::__construct($field,$page,$refer,$config);
424 foreach ($this->config->get($this->name) as $option)
426 list($key,$style,$format) = array_pad(array_map('trim',$option),3,'');
429 $this->styles[$key] = $style;
433 $this->formats[$key] = $format;
439 $s_name = htmlsc($this->name);
440 $s_size = htmlsc($this->values[0]);
441 return "<input type=\"text\" name=\"$s_name\" size=\"$s_size\" />";
443 function get_key($str)
445 return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
447 function format_value($str)
451 return join(', ',array_map(array($this,'format_value'),$str));
453 $key = $this->get_key($str);
454 return array_key_exists($key,$this->formats) ? str_replace('%s',$str,$this->formats[$key]) : $str;
456 function get_style($str)
458 $key = $this->get_key($str);
459 return array_key_exists($key,$this->styles) ? $this->styles[$key] : '%s';
462 class Tracker_field_file extends Tracker_field_format
464 var $sort_type = SORT_STRING;
468 $s_name = htmlsc($this->name);
469 $s_size = htmlsc($this->values[0]);
470 return "<input type=\"file\" name=\"$s_name\" size=\"$s_size\" />";
472 function format_value($str)
474 if (array_key_exists($this->name,$_FILES))
476 require_once(PLUGIN_DIR.'attach.inc.php');
477 $result = attach_upload($_FILES[$this->name],$this->page);
478 if ($result['result']) // アップロード成功
480 return parent::format_value($this->page.'/'.$_FILES[$this->name]['name']);
483 // ファイルが指定されていないか、アップロードに失敗
484 return parent::format_value('');
487 class Tracker_field_radio extends Tracker_field_format
489 var $sort_type = SORT_NUMERIC;
493 $s_name = htmlsc($this->name);
496 foreach ($this->config->get($this->name) as $option)
498 $s_option = htmlsc($option[0]);
499 $checked = trim($option[0]) == trim($this->default_value) ? ' checked="checked"' : '';
501 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
502 $retval .= '<input type="radio" name="' . $s_name . '" id="' . $s_id .
503 '" value="' . $s_option . '"' . $checked . ' />' .
504 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
509 function get_key($str)
513 function get_value($value)
515 static $options = array();
516 if (!array_key_exists($this->name,$options))
518 // 'reset' means function($arr) { return $arr[0]; }
519 $options[$this->name] = array_flip(array_map('reset',$this->config->get($this->name)));
521 return array_key_exists($value,$options[$this->name]) ? $options[$this->name][$value] : $value;
524 class Tracker_field_select extends Tracker_field_radio
526 var $sort_type = SORT_NUMERIC;
528 function get_tag($empty=FALSE)
530 $s_name = htmlsc($this->name);
531 $s_size = (array_key_exists(0,$this->values) and is_numeric($this->values[0])) ?
532 ' size="'.htmlsc($this->values[0]).'"' : '';
533 $s_multiple = (array_key_exists(1,$this->values) and strtolower($this->values[1]) == 'multiple') ?
534 ' multiple="multiple"' : '';
535 $retval = "<select name=\"{$s_name}[]\"$s_size$s_multiple>\n";
538 $retval .= " <option value=\"\"></option>\n";
540 $defaults = array_flip(preg_split('/\s*,\s*/',$this->default_value,-1,PREG_SPLIT_NO_EMPTY));
541 foreach ($this->config->get($this->name) as $option)
543 $s_option = htmlsc($option[0]);
544 $selected = array_key_exists(trim($option[0]),$defaults) ? ' selected="selected"' : '';
545 $retval .= " <option value=\"$s_option\"$selected>$s_option</option>\n";
547 $retval .= "</select>";
552 class Tracker_field_checkbox extends Tracker_field_radio
554 var $sort_type = SORT_NUMERIC;
556 function get_tag($empty=FALSE)
558 $s_name = htmlsc($this->name);
559 $defaults = array_flip(preg_split('/\s*,\s*/',$this->default_value,-1,PREG_SPLIT_NO_EMPTY));
562 foreach ($this->config->get($this->name) as $option)
564 $s_option = htmlsc($option[0]);
565 $checked = array_key_exists(trim($option[0]),$defaults) ?
566 ' checked="checked"' : '';
568 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
569 $retval .= '<input type="checkbox" name="' . $s_name .
570 '[]" id="' . $s_id . '" value="' . $s_option . '"' . $checked . ' />' .
571 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
577 class Tracker_field_hidden extends Tracker_field_radio
579 var $sort_type = SORT_NUMERIC;
581 function get_tag($empty=FALSE)
583 $s_name = htmlsc($this->name);
584 $s_default = htmlsc($this->default_value);
585 $retval = "<input type=\"hidden\" name=\"$s_name\" value=\"$s_default\" />\n";
590 class Tracker_field_submit extends Tracker_field
594 $s_title = htmlsc($this->title);
595 $s_page = htmlsc($this->page);
596 $s_refer = htmlsc($this->refer);
597 $s_config = htmlsc($this->config->config_name);
600 <input type="submit" value="$s_title" />
601 <input type="hidden" name="plugin" value="tracker" />
602 <input type="hidden" name="_refer" value="$s_refer" />
603 <input type="hidden" name="_base" value="$s_page" />
604 <input type="hidden" name="_config" value="$s_config" />
608 class Tracker_field_date extends Tracker_field
610 var $sort_type = SORT_NUMERIC;
612 function format_cell($timestamp)
614 return format_date($timestamp);
617 class Tracker_field_past extends Tracker_field
619 var $sort_type = SORT_NUMERIC;
621 function format_cell($timestamp)
623 return '&passage("' . get_date_atom($timestamp + LOCALZONE) . '");';
625 function get_value($value)
627 return UTIME - $value;
630 ///////////////////////////////////////////////////////////////////////////
632 function plugin_tracker_list_convert()
634 global $vars, $_title_cannotread;
637 $page = $refer = $vars['page'];
644 $args = func_get_args();
645 switch (count($args))
648 $limit = is_numeric($args[3]) ? $args[3] : $limit;
652 $args[1] = get_fullname($args[1],$page);
653 $page = is_pagename($args[1]) ? $args[1] : $page;
655 $config = ($args[0] != '') ? $args[0] : $config;
656 list($config,$list) = array_pad(explode('/',$config,2),2,$list);
659 if (!is_page_readable($page)) {
660 $body = str_replace('$1', htmlsc($page), $_title_cannotread);
663 return plugin_tracker_getlist($page,$refer,$config,$list,$order,$limit);
665 function plugin_tracker_list_action()
667 global $vars, $_tracker_messages, $_title_cannotread;
669 $page = $refer = $vars['refer'];
670 $s_page = make_pagelink($page);
671 $config = $vars['config'];
672 $list = array_key_exists('list',$vars) ? $vars['list'] : 'list';
673 $order = array_key_exists('order',$vars) ? $vars['order'] : '_real:SORT_DESC';
675 if (!is_page_readable($page)) {
676 $body = str_replace('$1', htmlsc($page), $_title_cannotread);
683 'msg' => $_tracker_messages['msg_list'],
684 'body'=> str_replace('$1',$s_page,$_tracker_messages['msg_back']).
685 plugin_tracker_getlist($page,$refer,$config,$list,$order)
688 function plugin_tracker_getlist($page,$refer,$config_name,$list,$order='',$limit=NULL)
690 global $whatsdeleted;
692 $config = new Config('plugin/tracker/'.$config_name);
693 if (!$config->read())
695 return "<p>config file '".htmlsc($config_name)."' is not exist.</p>";
697 $config->config_name = $config_name;
699 if (!is_page($config->page.'/'.$list))
701 return "<p>config file '".make_pagelink($config->page.'/'.$list)."' not found.</p>";
704 $cache_enabled = defined('TRACKER_LIST_USE_CACHE') && TRACKER_LIST_USE_CACHE &&
705 defined('JSON_UNESCAPED_UNICODE') && defined('PKWK_UTF8_ENABLE');
706 $cache_filepath = CACHE_DIR . encode($page) . '.tracker';
708 $cache_format_version = 1;
709 if ($cache_enabled) {
710 $config_filetime = get_filetime($config->page);
711 $config_list_filetime = get_filetime($config->page.'/'. $list);
712 if (file_exists($cache_filepath)) {
713 $json_cached = pkwk_file_get_contents($cache_filepath);
715 $wrapdata = json_decode($json_cached, true);
716 if (is_array($wrapdata) && isset($wrapdata['version'],
717 $wrapdata['html'], $wrapdata['refreshed_at'])) {
718 $cache_time_prev = $wrapdata['refreshed_at'];
719 if ($cache_format_version === $wrapdata['version']) {
720 if ($config_filetime === $wrapdata['config_updated_at'] &&
721 $config_list_filetime === $wrapdata['config_list_updated_at']) {
722 $cachedata = $wrapdata;
724 // (Ignore) delete file
725 unlink($cache_filepath);
732 // Check recent.dat timestamp
733 $recent_dat_filemtime = filemtime(CACHE_DIR . PKWK_MAXSHOW_CACHE);
734 // Check RecentDeleted timestamp
735 $recent_deleted_filetime = get_filetime($whatsdeleted);
736 if (is_null($cachedata)) {
737 $cachedata = array();
739 if ($recent_dat_filemtile !== false) {
740 if ($recent_dat_filemtime === $cachedata['recent_dat_filemtime'] &&
741 $recent_deleted_filetime === $cachedata['recent_deleted_filetime'] &&
742 $order === $cachedata['order']) {
743 // recent.dat is unchanged
744 // RecentDeleted is unchanged
745 // order is unchanged
746 return $cachedata['html'];
750 $cache_holder = $cachedata;
751 $tracker_list = new Tracker_list($page,$refer,$config,$list,$cache_holder);
752 if ($order === $cache_holder['order'] &&
753 empty($tracker_list->newly_deleted_pages) &&
754 empty($tracker_list->newly_updated_pages) &&
755 !$tracker_list->link_update_required) {
756 $result = $cache_holder['html'];
758 $tracker_list->sort($order);
759 $result = $tracker_list->toString($limit);
761 if ($cache_enabled) {
762 $refreshed_at = time();
764 'refreshed_at' => $refreshed_at,
765 'rows' => $tracker_list->rows,
768 'config_updated_at' => $config_filetime,
769 'config_list_updated_at' => $config_list_filetime,
770 'recent_dat_filemtime' => $recent_dat_filemtime,
771 'recent_deleted_filetime' => $recent_deleted_filetime,
772 'link_pages' => $tracker_list->link_pages,
773 'version' => $cache_format_version);
774 $cache_body = json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
775 file_put_contents($cache_filepath, $cache_body, LOCK_EX);
792 var $newly_deleted_pages = array();
793 var $newly_updated_pages = array();
795 function Tracker_list($page,$refer,&$config,$list,&$cache_holder)
797 $this->__construct($page, $refer, $config, $list, $cache_holder);
799 function __construct($page,$refer,&$config,$list,&$cache_holder)
801 global $whatsdeleted, $_cached_page_filetime;
803 $this->config = &$config;
805 $this->fields = plugin_tracker_get_fields($page,$refer,$config);
807 $pattern = join('',plugin_tracker_get_source($config->page.'/page'));
808 // ブロックプラグインをフィールドに置換
809 // #commentなどで前後に文字列の増減があった場合に、[_block_xxx]に吸い込ませるようにする
810 $pattern = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m','[_block_$1]',$pattern);
814 $this->pattern_fields = array();
815 $pattern = preg_split('/\\\\\[(\w+)\\\\\]/',preg_quote($pattern,'/'),-1,PREG_SPLIT_DELIM_CAPTURE);
816 while (count($pattern))
818 $this->pattern .= preg_replace('/\s+/','\\s*','(?>\\s*'.trim(array_shift($pattern)).'\\s*)');
821 $field = array_shift($pattern);
822 $this->pattern_fields[] = $field;
823 $this->pattern .= '(.*?)';
826 if (empty($cache_holder)) {
827 // List pages and get contents (non-cache behavior)
828 $this->rows = array();
830 $pattern_len = strlen($pattern);
831 foreach (get_existpages() as $_page)
833 if (substr($_page, 0, $pattern_len) === $pattern)
835 $name = substr($_page,$pattern_len);
836 if (preg_match(TRACKER_LIST_EXCLUDE_PATTERN,$name))
840 $this->add($_page,$name);
843 $this->link_pages = $this->get_filetimes($this->get_all_links());
845 // Cache-available behavior
846 // Check RecentDeleted timestamp
847 $cached_rows = $this->decode_cached_rows($cache_holder['rows']);
848 $updated_linked_pages = array();
849 $newly_deleted_pages = array();
851 $pattern_len = strlen($pattern);
852 $recent_deleted_filetime = get_filetime($whatsdeleted);
853 $deleted_page_list = array();
854 if ($recent_deleted_filetime !== $cache_holder['recent_deleted_filetime']) {
855 foreach (plugin_tracker_get_source($whatsdeleted) as $line) {
857 if (preg_match('#\[\[([^\]]+)\]\]#', $line, $m)) {
859 if (is_pagename($_page)) {
860 $deleted_page_list[] = $m[1];
864 foreach ($deleted_page_list as $_page) {
865 if (substr($_page, 0, $pattern_len) === $pattern) {
866 $name = substr($_page, $pattern_len);
867 if (!is_page($_page) && isset($cached_rows[$name]) &&
868 !preg_match(TRACKER_LIST_EXCLUDE_PATTERN, $name)) {
869 // This page was just deleted
870 array_push($newly_deleted_pages, $_page);
871 unset($cached_rows[$name]);
876 $this->newly_deleted_pages = $newly_deleted_pages;
877 $updated_pages = array();
878 $this->rows = $cached_rows;
879 // Check recent.dat timestamp
880 $recent_dat_filemtime = filemtime(CACHE_DIR . PKWK_MAXSHOW_CACHE);
881 $updated_page_list = array();
882 if ($recent_dat_filemtime !== $cache_holder['recent_dat_filemtime']) {
883 // recent.dat was updated. Search which page was updated.
884 $target_pages = array();
885 // Active page file time (1 hour before timestamp of recent.dat)
886 $target_filetime = $cache_holder['recent_dat_filemtime'] - LOCALZONE - 60 * 60;
887 foreach (get_recent_files() as $_page=>$time) {
888 if ($time <= $target_filetime) {
889 // Older updated pages
892 $updated_page_list[$_page] = $time;
893 $name = substr($_page, $pattern_len);
894 if (substr($_page, 0, $pattern_len) === $pattern) {
895 $name = substr($_page, $pattern_len);
896 if (preg_match(TRACKER_LIST_EXCLUDE_PATTERN, $name)) {
899 // Tracker target page
900 if (isset($this->rows[$name])) {
902 $row = $this->rows[$name];
903 if ($row['_update'] === get_filetime($_page)) {
907 // Found updated page
908 $updated_pages[] = $_page;
909 unset($this->rows[$name]);
910 $this->add($_page, $name);
914 $updated_pages[] = $_page;
915 $this->add($_page, $name);
920 $this->newly_updated_pages = $updated_pages;
921 $new_link_names = $this->get_all_links();
922 $old_link_map = array();
923 foreach ($cache_holder['link_pages'] as $link_page) {
924 $old_link_map[$link_page['page']] = $link_page['filetime'];
926 $new_link_map = $old_link_map;
927 $link_update_required = false;
928 foreach ($deleted_page_list as $_page) {
929 if (in_array($_page, $new_link_names)) {
930 if (isset($old_link_map[$_page])) {
931 // This link keeps existing
932 if (!is_page($_page)) {
933 // OK. Confirmed the page doesn't exist
934 if ($old_link_map[$_page] === 0) {
935 // Do nothing (From no-page to no-page)
937 // This page was just deleted
938 $new_link_map[$_page] = get_filetime($_page);
939 $link_update_required = true;
943 // This link was just added
944 $new_link_map[$_page] = get_filetime($_page);
945 $link_update_required = true;
949 foreach ($updated_page_list as $_page=>$time) {
950 if (in_array($_page, $new_link_names)) {
951 if (isset($old_link_map[$_page])) {
952 // This link keeps existing
953 if (is_page($_page)) {
954 // OK. Confirmed the page now exists
955 if ($old_link_map[$_page] === 0) {
956 // This page was just added
957 $new_link_map[$_page] = get_filetime($_page);
958 $link_update_required = true;
960 // Do nothing (existing-page to existing-page)
964 // This link was just added
965 $new_link_map[$_page] = get_filetime($_page);
966 $link_update_required = true;
970 $new_link_pages = array();
971 foreach ($new_link_map as $_page => $time) {
972 $new_link_pages[] = array(
977 $this->link_pages = $new_link_pages;
978 $this->link_update_required = $link_update_required;
979 $time_map_for_cache = $new_link_map;
980 foreach ($this->rows as $row) {
981 $time_map_for_cache[$this->page . '/' . $row['_real']] = $row['_update'];
983 $_cached_page_filetime = $time_map_for_cache;
986 function decode_cached_rows($decoded_rows)
989 foreach ($decoded_rows as $row) {
990 $ar[$row['_real']] = $row;
994 function get_all_links() {
996 foreach ($this->rows as $row) {
997 foreach ($row['_links'] as $link) {
1001 return array_keys($ar);
1003 function get_filetimes($pages) {
1004 $filetimes = array();
1005 foreach ($pages as $page) {
1006 $filetimes[] = array(
1008 'filetime' => get_filetime($page),
1013 function add($page,$name)
1015 static $moved = array();
1018 if (array_key_exists($name,$this->rows))
1023 $source = plugin_tracker_get_source($page);
1024 if (preg_match('/move\sto\s(.+)/',$source[0],$matches))
1026 $page = strip_bracket(trim($matches[1]));
1027 if (array_key_exists($page,$moved) or !is_page($page))
1031 $moved[$page] = TRUE;
1032 return $this->add($page,$name);
1034 $source = join('',preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/','$1$2',$source));
1037 $page_filetime = get_filetime($page);
1039 '_page' => "[[$page]]",
1040 '_refer' => $this->page,
1042 '_update'=> $page_filetime,
1043 '_past' => $page_filetime,
1046 if ($row['_match'] = preg_match("/{$this->pattern}/s",$source,$matches))
1048 array_shift($matches);
1049 foreach ($this->pattern_fields as $key=>$field)
1051 $row[$field] = trim($matches[$key]);
1052 if ($field === '_refer') {
1056 if (preg_match('/\[\[([^\]\]]+)\]/', $row[$field], $lmatch)) {
1058 if (is_pagename($link) && $link !== $this->page && $link !== $page) {
1059 if (!in_array($link, $links)) {
1066 $row['_links'] = $links;
1067 $this->rows[$name] = $row;
1069 function compare($a, $b)
1071 foreach ($this->sort_keys as $sort_key)
1073 $field = $sort_key['field'];
1074 $dir = $sort_key['dir'];
1075 $f = $this->fields[$field];
1076 $sort_type = $f->sort_type;
1077 $aVal = isset($a[$field]) ? $f->get_value($a[$field]) : '';
1078 $bVal = isset($b[$field]) ? $f->get_value($b[$field]) : '';
1079 $c = strnatcmp($aVal, $bVal) * ($dir === SORT_ASC ? 1 : -1);
1080 if ($c === 0) continue;
1085 function sort($order)
1091 $names = array_flip(array_keys($this->fields));
1092 $this->order = array();
1093 foreach (explode(';',$order) as $item)
1095 list($key,$dir) = array_pad(explode(':',$item),1,'ASC');
1096 if (!array_key_exists($key,$names))
1100 switch (strtoupper($dir))
1115 $this->order[$key] = $dir;
1117 $sort_keys = array();
1118 foreach ($this->order as $field=>$order)
1120 if (!array_key_exists($field,$names))
1124 $sort_keys[] = array('field' => $field, 'dir' => $order);
1126 $this->sort_keys = $sort_keys;
1127 usort($this->rows, array($this, 'compare'));
1129 function replace_item($arr)
1131 $params = explode(',',$arr[1]);
1132 $name = array_shift($params);
1137 else if (array_key_exists($name,$this->items))
1139 $str = $this->items[$name];
1140 if (array_key_exists($name,$this->fields))
1142 $str = $this->fields[$name]->format_cell($str);
1147 return $this->pipe ? str_replace('|','|',$arr[0]) : $arr[0];
1149 $style = count($params) ? $params[0] : $name;
1150 if (array_key_exists($style,$this->items)
1151 and array_key_exists($style,$this->fields))
1153 $str = sprintf($this->fields[$style]->get_style($this->items[$style]),$str);
1155 return $this->pipe ? str_replace('|','|',$str) : $str;
1157 function replace_title($arr)
1159 $field = $sort = $arr[1];
1160 if ($sort == '_name' or $sort == '_page')
1164 if (!array_key_exists($field,$this->fields))
1170 $order = $this->order;
1172 if (is_array($order) && isset($order[$sort]))
1174 // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
1175 $order_keys = array_keys($order); // with array_shift();
1177 $index = array_flip($order_keys);
1178 $pos = 1 + $index[$sort];
1179 $b_end = ($sort == array_shift($order_keys));
1180 $b_order = ($order[$sort] == SORT_ASC);
1181 $dir = ($b_end xor $b_order) ? SORT_ASC : SORT_DESC;
1182 $arrow = '&br;'.($b_order ? '↑' : '↓')."($pos)";
1184 unset($order[$sort], $order_keys);
1186 $title = $this->fields[$field]->title;
1187 $r_page = rawurlencode($this->page);
1188 $r_config = rawurlencode($this->config->config_name);
1189 $r_list = rawurlencode($this->list);
1190 $_order = array("$sort:$dir");
1191 if (is_array($order))
1192 foreach ($order as $key=>$value)
1193 $_order[] = "$key:$value";
1194 $r_order = rawurlencode(join(';',$_order));
1196 $script = get_base_uri(PKWK_URI_ABSOLUTE);
1197 return "[[$title$arrow>$script?plugin=tracker_list&refer=$r_page&config=$r_config&list=$r_list&order=$r_order]]";
1199 function toString($limit=NULL)
1201 global $_tracker_messages;
1206 if ($limit !== NULL and count($this->rows) > $limit)
1208 $source = str_replace(
1210 array(count($this->rows),$limit),
1211 $_tracker_messages['msg_limit'])."\n";
1212 $this->rows = array_splice($this->rows,0,$limit);
1214 if (count($this->rows) == 0)
1218 foreach (plugin_tracker_get_source($this->config->page.'/'.$this->list) as $line)
1220 if (preg_match('/^\|(.+)\|[hHfFcC]$/',$line))
1222 $source .= preg_replace_callback('/\[([^\[\]]+)\]/',array(&$this,'replace_title'),$line);
1229 foreach ($this->rows as $key=>$row)
1231 if (!TRACKER_LIST_SHOW_ERROR_PAGE and !$row['_match'])
1235 $this->items = $row;
1236 foreach ($body as $line)
1238 if (trim($line) == '')
1240 // Ignore empty line
1243 $this->pipe = ($line{0} == '|' or $line{0} == ':');
1244 $source .= preg_replace_callback('/\[([^\[\]]+)\]/',array(&$this,'replace_item'),$line);
1247 return convert_html($source);
1250 function plugin_tracker_get_source($page)
1252 $source = get_source($page);
1253 // Delete anchor part of Headings (Example: "*Heading1 [#id] AAA" to "*Heading1 AAA")
1254 $s2 = preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m','$1$2',$source);
1256 $s3 = preg_replace('/^#freeze\s*$/im', '', $s2);
1257 // Delete #author line
1258 $s4 = preg_replace('/^#author\b[^\r\n]*$/im', '', $s3);