2 // PukiWiki - Yet another WikiWikiWeb clone
3 // $Id: tracker.inc.php,v 1.46 2007/09/08 16:37:16 henoheno Exp $
4 // Copyright (C) 2003-2005, 2007 PukiWiki Developers Team
5 // License: GPL v2 or (at your option) any later version
7 // Issue tracker plugin (See Also bugtrack plugin)
9 define('PLUGIN_TRACKER_USAGE', '#tracker([config[/form][,basepage]])');
10 define('PLUGIN_TRACKER_LIST_USAGE', '#tracker_list([config][[,base][,field:sort[;field:sort ...][,limit]]])');
12 define('PLUGIN_TRACKER_DEFAULT_CONFIG', 'default');
13 define('PLUGIN_TRACKER_DEFAULT_FORM', 'form');
15 // #tracker_list: Excluding pattern
16 define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#'); // 'SubMenu' and using '/'
17 //define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#(?!)#'); // Nothing excluded
19 // #tracker_list: Show error rows (can't capture columns properly)
20 define('PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE', 1);
24 function plugin_tracker_convert()
28 if (PKWK_READONLY) return ''; // Show nothing
30 $base = $refer = isset($vars['page']) ? $vars['page'] : '';
31 $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
32 $form = PLUGIN_TRACKER_DEFAULT_FORM;
34 $args = func_get_args();
37 return PLUGIN_TRACKER_USAGE . '<br />';
41 $arg = get_fullname($args[1], $base);
42 if (is_pagename($arg)) $base = $arg;
44 if ($argc > 0 && $args[0] != '') {
45 // Configuration name AND form name
46 $arg = explode('/', $args[0], 2);
47 if ($arg[0] != '' ) $config_name = $arg[0];
48 if (isset($arg[1])) $form = $arg[1];
52 $config = new Config('plugin/tracker/' . $config_name);
53 if (! $config->read()) {
54 return '#tracker: Config \'' . htmlspecialchars($config_name) . '\' not found<br />';
56 $config->config_name = $config_name;
57 $form = $config->page . '/' . $form;
58 if (! is_page($form)) {
59 return '#tracker: Form \'' . make_pagelink($form) . '\' not found<br />';
62 $from = $to = $hidden = array();
63 $fields = plugin_tracker_get_fields($base, $refer, $config);
64 foreach (array_keys($fields) as $field) {
65 $from[] = '[' . $field . ']';
66 $_to = $fields[$field]->get_tag();
67 if (is_a($fields[$field], 'Tracker_field_hidden')) {
73 $fields[$field]->dispose();
74 unset($fields[$field]);
77 $script = get_script_uri();
78 $retval = str_replace($from, $to, convert_html(plugin_tracker_get_source($form)));
79 $hidden = implode('<br />' . "\n", $hidden);
81 <form enctype="multipart/form-data" action="$script" method="post">
91 function plugin_tracker_action()
93 global $post, $vars, $now;
95 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
97 $base = isset($post['_base']) ? $post['_base'] : '';
98 $refer = isset($post['_refer']) ? $post['_refer'] : $base;
99 if (! is_pagename($refer)) {
101 'msg' => 'Cannot write',
102 'body' => 'Page name (' . htmlspecialchars($refer) . ') invalid'
106 // $page name to add will be decided here
108 $name = isset($post['_name']) ? $post['_name'] : '';
109 if (isset($post['_page'])) {
110 $real = $page = $post['_page'];
112 $real = is_pagename($name) ? $name : ++$num;
113 $page = get_fullname('./' . $real, $base);
115 if (! is_pagename($page)) $page = $base;
116 while (is_page($page)) {
118 $page = $base . '/' . $real;
121 // Loading configuration
122 $config_name = isset($post['_config']) ? $post['_config'] : '';
123 $config = new Config('plugin/tracker/' . $config_name);
124 if (! $config->read()) {
125 return '<p>config file \'' . htmlspecialchars($config_name) . '\' not found.</p>';
127 $config->config_name = $config_name;
128 $template_page = $config->page . '/page';
129 if (! is_page($template_page)) {
131 'msg' => 'Cannot write',
132 'body' => 'Page template (' . htmlspecialchars($template_page) . ') not exists'
137 $_post = array_merge($post, $_FILES);
138 $_post['_date'] = $now;
139 $_post['_page'] = $page;
140 $_post['_name'] = $name;
141 $_post['_real'] = $real;
142 // $_post['_refer'] = $_post['refer'];
144 // Creating an empty page, before attaching files
145 pkwk_touch_file(get_filename($page));
147 $from = $to = array();
148 $fields = plugin_tracker_get_fields($page, $refer, $config);
149 foreach (array_keys($fields) as $field) {
150 $from[] = '[' . $field . ']';
151 $to[] = isset($_post[$field]) ? $fields[$field]->format_value($_post[$field]) : '';
152 $fields[$field]->dispose();
153 unset($fields[$field]);
157 $template = plugin_tracker_get_source($template_page);
159 // Repalace every [$field]s to real values in the $template
160 $subject = $subject_e = array();
161 foreach (array_keys($template) as $num) {
162 if (trim($template[$num]) == '') continue;
163 $letter = $template[$num]{0};
164 if ($letter == '|' || $letter == ':') {
165 // Escape for some TextFormattingRules: <table> and <dr>
166 $subject_e[$num] = $template[$num];
168 $subject[$num] = $template[$num];
171 foreach (str_replace($from, $to, $subject ) as $num => $line) {
172 $template[$num] = $line;
174 // Escape for some TextFormattingRules: <table> and <dr>
177 foreach($to as $value) {
178 if (strpos($value, '|') !== FALSE) {
179 // Escape for some TextFormattingRules: <table> and <dr>
180 $to_e[] = str_replace('|', '|', $value);
185 foreach (str_replace($from, $to_e, $subject_e) as $num => $line) {
186 $template[$num] = $line;
190 // Write $template, without touch
191 page_write($page, join('', $template));
194 header('Location: ' . get_script_uri() . '?' . rawurlencode($page));
198 // Construct $fields (an array of Tracker_field objects)
199 function plugin_tracker_get_fields($base, $refer, & $config)
205 foreach ($config->get('fields') as $field) {
206 // $field[0]: Field name
207 // $field[1]: Field name (for display)
208 // $field[2]: Field type
209 // $field[3]: Option ("size", "cols", "rows", etc)
210 // $field[4]: Default value
211 $class = 'Tracker_field_' . $field[2];
212 if (! class_exists($class)) {
215 $class = 'Tracker_field_' . $field[2];
218 $fieldname = $field[0];
219 $fields[$fieldname] = & new $class($field, $base, $refer, $config);
225 '_date' => 'text', // Post date
226 '_update' => 'date', // Last modified date
227 '_past' => 'past', // Elapsed time (passage)
228 '_page' => 'page', // Page name
229 '_name' => 'text', // Page name specified by poster
230 '_real' => 'real', // Page name (Real)
231 '_refer' => 'page', // Page name refer from this (Page who has forms)
233 '_submit' => 'submit'
234 ) as $fieldname => $type)
236 if (isset($fields[$fieldname])) continue;
237 $field = array($fieldname, plugin_tracker_message('btn' . $fieldname), '', '20', '');
238 $class = 'Tracker_field_' . $type;
239 $fields[$fieldname] = & new $class($field, $base, $refer, $config);
246 // TODO: Not to use static variables (except $id)
257 var $sort_type = SORT_REGULAR;
260 function Tracker_field($field, $page, $refer, & $config)
263 static $id = 0; // Unique id per instance
266 $this->name = $field[0];
267 $this->title = $field[1];
268 $this->values = explode(',', $field[3]);
269 $this->default_value = $field[4];
271 $this->refer = $refer;
272 $this->config = & $config;
273 $this->data = isset($post[$this->name]) ? $post[$this->name] : '';
276 // XHTML part inside a form
287 function format_value($value)
292 function format_cell($str)
297 // Compare key for Tracker_list->sort()
298 function get_value($value)
300 return $value; // Default: $value itself
303 // Release the resources
309 class Tracker_field_text extends Tracker_field
311 var $sort_type = SORT_STRING;
315 return '<input type="text"' .
316 ' name="' . htmlspecialchars($this->name) . '"' .
317 ' size="' . htmlspecialchars($this->values[0]) . '"' .
318 ' value="' . htmlspecialchars($this->default_value) . '" />';
322 class Tracker_field_page extends Tracker_field_text
324 var $sort_type = SORT_STRING;
326 function format_value($value)
328 $value = strip_bracket($value);
329 if (is_pagename($value)) $value = '[[' . $value . ']]';
330 return parent::format_value($value);
334 class Tracker_field_real extends Tracker_field_text
336 var $sort_type = SORT_REGULAR;
339 class Tracker_field_title extends Tracker_field_text
341 var $sort_type = SORT_STRING;
343 function format_cell($str)
350 class Tracker_field_textarea extends Tracker_field
352 var $sort_type = SORT_STRING;
357 ' name="' . htmlspecialchars($this->name) . '"' .
358 ' cols="' . htmlspecialchars($this->values[0]) . '"' .
359 ' rows="' . htmlspecialchars($this->values[1]) . '">' .
360 htmlspecialchars($this->default_value) .
364 function format_cell($str)
366 $str = preg_replace('/[\r\n]+/', '', $str);
367 if (! empty($this->values[2]) && strlen($str) > ($this->values[2] + 3)) {
368 $str = mb_substr($str, 0, $this->values[2]) . '...';
374 class Tracker_field_format extends Tracker_field
376 var $sort_type = SORT_STRING;
377 var $styles = array();
378 var $formats = array();
380 function Tracker_field_format($field, $page, $refer, & $config)
382 parent::Tracker_field($field, $page, $refer, $config);
384 foreach ($this->config->get($this->name) as $option) {
385 list($key, $style, $format) =
386 array_pad(array_map(create_function('$a', 'return trim($a);'), $option), 3, '');
387 if ($style != '') $this->styles[$key] = $style;
388 if ($format != '') $this->formats[$key] = $format;
394 return '<input type="text"' .
395 ' name="' . htmlspecialchars($this->name) . '"' .
396 ' size="' . htmlspecialchars($this->values[0]) . '" />';
399 function get_key($str)
401 return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
404 function format_value($str)
406 if (is_array($str)) {
407 return join(', ', array_map(array($this, 'format_value'), $str));
410 $key = $this->get_key($str);
411 return isset($this->formats[$key]) ? str_replace('%s', $str, $this->formats[$key]) : $str;
414 function get_style($str)
416 $key = $this->get_key($str);
417 return isset($this->styles[$key]) ? $this->styles[$key] : '%s';
421 class Tracker_field_file extends Tracker_field_format
423 var $sort_type = SORT_STRING;
427 return '<input type="file"' .
428 ' name="' . htmlspecialchars($this->name) . '"' .
429 ' size="' . htmlspecialchars($this->values[0]) . '" />';
432 function format_value()
434 if (isset($_FILES[$this->name])) {
436 require_once(PLUGIN_DIR . 'attach.inc.php');
438 $result = attach_upload($_FILES[$this->name], $this->page);
439 if ($result['result']) {
441 return parent::format_value($this->page . '/' . $_FILES[$this->name]['name']);
445 // Filename not specified, or Fail to upload
446 return parent::format_value('');
450 class Tracker_field_radio extends Tracker_field_format
452 var $sort_type = SORT_NUMERIC;
459 $s_name = htmlspecialchars($this->name);
460 foreach ($this->config->get($this->name) as $option) {
462 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
463 $s_option = htmlspecialchars($option[0]);
464 $checked = trim($option[0]) == trim($this->default_value) ? ' checked="checked"' : '';
466 $retval .= '<input type="radio"' .
467 ' name="' . $s_name . '"' .
468 ' id="' . $s_id . '"' .
469 ' value="' . $s_option . '"' .
471 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
477 function get_key($str)
482 function get_value($value)
484 static $options = array();
486 if ($value === NULL) {
491 if (! isset($options[$this->name])) {
492 $options[$this->name] = array_flip(
494 create_function('$arr', 'return $arr[0];'),
495 $this->config->get($this->name)
501 return isset($options[$this->name][$value]) ? $options[$this->name][$value] : $value;
506 $this->get_value(NULL);
510 class Tracker_field_select extends Tracker_field_radio
512 var $sort_type = SORT_NUMERIC;
514 function get_tag($empty = FALSE)
516 $s_name = htmlspecialchars($this->name);
517 $s_size = (isset($this->values[0]) && is_numeric($this->values[0])) ?
518 ' size="' . htmlspecialchars($this->values[0]) . '"' :
520 $s_multiple = (isset($this->values[1]) && strtolower($this->values[1]) == 'multiple') ?
521 ' multiple="multiple"' :
524 $retval = '<select name="' . $s_name . '[]"' . $s_size . $s_multiple . '>' . "\n";
525 if ($empty) $retval .= ' <option value=""></option>' . "\n";
526 $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
527 foreach ($this->config->get($this->name) as $option) {
528 $s_option = htmlspecialchars($option[0]);
529 $selected = isset($defaults[trim($option[0])]) ? ' selected="selected"' : '';
530 $retval .= ' <option value="' . $s_option . '"' . $selected . '>' . $s_option . '</option>' . "\n";
532 $retval .= '</select>';
538 class Tracker_field_checkbox extends Tracker_field_radio
540 var $sort_type = SORT_NUMERIC;
547 $s_name = htmlspecialchars($this->name);
548 $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
549 foreach ($this->config->get($this->name) as $option)
552 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
553 $s_option = htmlspecialchars($option[0]);
554 $checked = isset($defaults[trim($option[0])]) ? ' checked="checked"' : '';
556 $retval .= '<input type="checkbox"' .
557 ' name="' . $s_name . '[]"' .
558 ' id="' . $s_id . '"' .
559 ' value="' . $s_option . '"' .
561 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
568 class Tracker_field_hidden extends Tracker_field_radio
570 var $sort_type = SORT_NUMERIC;
574 return '<input type="hidden"' .
575 ' name="' . htmlspecialchars($this->name) . '"' .
576 ' value="' . htmlspecialchars($this->default_value) . '" />' . "\n";
580 class Tracker_field_submit extends Tracker_field
584 $s_title = htmlspecialchars($this->title);
585 $s_page = htmlspecialchars($this->page);
586 $s_refer = htmlspecialchars($this->refer);
587 $s_config = htmlspecialchars($this->config->config_name);
590 <input type="submit" value="$s_title" />
591 <input type="hidden" name="plugin" value="tracker" />
592 <input type="hidden" name="_refer" value="$s_refer" />
593 <input type="hidden" name="_base" value="$s_page" />
594 <input type="hidden" name="_config" value="$s_config" />
599 class Tracker_field_date extends Tracker_field
601 var $sort_type = SORT_NUMERIC;
603 function format_cell($timestamp)
605 return format_date($timestamp);
609 class Tracker_field_past extends Tracker_field
611 var $sort_type = SORT_NUMERIC;
613 function format_cell($timestamp)
615 return get_passage($timestamp, FALSE);
618 function get_value($value)
620 return UTIME - $value;
624 ///////////////////////////////////////////////////////////////////////////
625 // tracker_list plugin
627 function plugin_tracker_list_convert()
631 $config = PLUGIN_TRACKER_DEFAULT_CONFIG;
632 $page = $refer = isset($vars['page']) ? $vars['page'] : '';
637 // TODO: SHOW USAGE OR ERROR CLEARLY
638 if (func_num_args()) {
639 $args = func_get_args();
640 switch (count($args)) {
642 if (! is_numeric($args[3])) return PLUGIN_TRACKER_LIST_USAGE . '<br />';
643 $limit = intval($args[3]);
647 $arg = get_fullname($args[1], $page);
648 if (is_pagename($arg)) $page = $arg;
650 if ($args[0] != '') $config = $args[0];
651 list($config, $list) = array_pad(explode('/', $config, 2), 2, $list);
654 return plugin_tracker_list_render($page, $refer, $config, $list, $order, $limit);
657 function plugin_tracker_list_action()
661 $page = $refer = isset($vars['refer']) ? $vars['refer'] : '';
662 $config = isset($vars['config']) ? $vars['config'] : '';
663 $list = isset($vars['list']) ? $vars['list'] : 'list';
664 $order = isset($vars['order']) ? $vars['order'] : '_real:SORT_DESC';
666 $s_page = make_pagelink($page);
668 'msg' => plugin_tracker_message('msg_list'),
669 'body'=> str_replace('$1', $s_page, plugin_tracker_message('msg_back')) .
670 plugin_tracker_list_render($page, $refer, $config, $list, $order)
674 function plugin_tracker_list_render($page, $refer, $config_name, $list, $order_commands = '', $limit = NULL)
676 $config = new Config('plugin/tracker/' . $config_name);
677 if (! $config->read()) {
678 return '#tracker_list: Config \'' . htmlspecialchars($config_name) . '\' not found<br />';
680 $config->config_name = $config_name;
681 if (! is_page($config->page . '/' . $list)) {
682 return '#tracker_list: List \'' . make_pagelink($config->page . '/' . $list) . '\' not found<br />';
685 $list = & new Tracker_list($page, $refer, $config, $list);
686 $list->sort($order_commands);
687 $result = $list->toString($limit);
688 if ($result == FALSE) {
689 $result = '#tracker_list: Pages under \'' . htmlspecialchars($page) . '/\' not found' . '<br />';
697 // TODO: Not to use static variable
709 function Tracker_list($page, $refer, & $config, $list)
712 $this->config = & $config;
714 $this->fields = plugin_tracker_get_fields($page, $refer, $config);
716 $this->pattern_fields = array();
717 $this->rows = array();
718 $this->order = array();
720 $pattern = plugin_tracker_get_source($config->page . '/page', TRUE);
721 // TODO: if (is FALSE) OR file_exists()
723 // Convert block-plugins to fields
724 // Incleasing and decreasing around #comment etc, will be covererd with [_block_xxx]
725 $pattern = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m', '[_block_$1]', $pattern);
728 $pattern = preg_split('/\\\\\[(\w+)\\\\\]/', preg_quote($pattern, '/'), -1, PREG_SPLIT_DELIM_CAPTURE);
729 while (! empty($pattern)) {
730 $this->pattern .= preg_replace('/\s+/', '\\s*', '(?>\\s*' . trim(array_shift($pattern)) . '\\s*)');
731 if (! empty($pattern)) {
732 $field = array_shift($pattern);
733 $this->pattern_fields[] = $field;
734 $this->pattern .= '(.*)';
739 $pattern = $page . '/';
740 $pattern_len = strlen($pattern);
741 foreach (get_existpages() as $_page) {
742 if (strpos($_page, $pattern) === 0) {
743 $name = substr($_page, $pattern_len);
744 if (preg_match(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN, $name)) continue;
745 $this->add($_page, $name);
750 function add($page, $name)
752 static $done = array();
754 if (isset($done[$page])) return;
756 if ($page === NULL) {
763 $source = plugin_tracker_get_source($page);
765 // Compat: 'move to [[page]]' (bugtrack plugin)
767 if (! empty($source) && preg_match('/move\sto\s(.+)/', $source[0], $matches)) {
768 $to_page = strip_bracket(trim($matches[1]));
769 if (is_page($to_page)) {
770 unset($source); // Release
771 $this->add($to_page, $name); // Recurse(Rescan)
779 $filetime = get_filetime($page);
781 '_page' => '[[' . $page . ']]',
782 '_refer' => $this->page,
784 '_update' => $filetime,
785 '_past' => $filetime,
791 $row['_match'] = preg_match('/' . $this->pattern . '/s', implode('', $source), $matches);
793 if ($row['_match']) {
794 array_shift($matches);
795 foreach ($this->pattern_fields as $key => $field) {
796 $row[$field] = trim($matches[$key]);
800 $this->rows[$name] = $row;
803 // Sort $this->rows with $order_commands
804 function sort($order_commands = '')
806 if ($order_commands == '') {
807 $this->order = array();
812 $params = array(); // Arguments for array_multisort()
813 $names = array_flip(array_keys($this->fields));
815 foreach (explode(';', $order_commands) as $command) {
817 list($fieldname, $order) = array_pad(explode(':', $command), 1, 'SORT_ASC');
818 $fieldname = trim($fieldname);
820 if (! isset($names[$fieldname])) {
821 // TODO: SHOW INVALID FIELDNAME CLEARLY
825 // TODO: SHOULD NOT TO USE DEFINES AT THIS string WORLD
826 switch (strtoupper(trim($order))) {
841 $orders[$fieldname] = $order;
843 // TODO: LIMIT (count($orders) < N < count(fields)) TO LIMIT array_multisort()
845 foreach ($orders as $fieldname => $order) {
846 // One column set (one-dimensional array(), sort type, and order-by)
848 foreach ($this->rows as $row) {
849 $array[] = isset($row[$fieldname]) ?
850 $this->fields[$fieldname]->get_value($row[$fieldname]) :
854 $params[] = $this->fields[$fieldname]->sort_type;
857 $params[] = & $this->rows;
859 call_user_func_array('array_multisort', $params);
860 $this->order = $orders;
865 // Used with preg_replace_callback() at toString()
866 function replace_item($arr)
868 $params = explode(',', $arr[1]);
869 $name = array_shift($params);
872 } else if (isset($this->items[$name])) {
873 $str = $this->items[$name];
874 if (isset($this->fields[$name])) {
875 $str = $this->fields[$name]->format_cell($str);
878 return $this->pipe ? str_replace('|', '|', $arr[0]) : $arr[0];
881 $style = empty($params) ? $name : $params[0];
882 if (isset($this->items[$style]) && isset($this->fields[$style])) {
883 $str = sprintf($this->fields[$style]->get_style($this->items[$style]), $str);
886 return $this->pipe ? str_replace('|', '|', $str) : $str;
889 // Used with preg_replace_callback() at toString()
890 function replace_title($arr)
892 $field = $sort = $arr[1];
893 if (! isset($this->fields[$field])) return $arr[0];
895 if ($sort == '_name' || $sort == '_page') $sort = '_real';
899 $order = $this->order;
900 if (is_array($order) && isset($order[$sort])) {
901 // BugTrack2/106: Only variables can be passed by reference from PHP 5.0.5
902 $order_keys = array_keys($order); // with array_shift();
904 $index = array_flip($order_keys);
905 $pos = 1 + $index[$sort];
906 $b_end = ($sort == array_shift($order_keys));
907 $b_order = ($order[$sort] == SORT_ASC);
908 $dir = ($b_end xor $b_order) ? SORT_ASC : SORT_DESC;
909 $arrow = '&br;' . ($b_order ? '↑' : '↓') . '(' . $pos . ')';
911 unset($order[$sort], $order_keys);
913 $title = $this->fields[$field]->title;
914 $r_page = rawurlencode($this->page);
915 $r_config = rawurlencode($this->config->config_name);
916 $r_list = rawurlencode($this->list);
917 $_order = array($sort . ':' . $dir);
918 if (is_array($order)) {
919 foreach ($order as $key => $value) {
920 $_order[] = $key . ':' . $value;
923 $r_order = rawurlencode(join(';', $_order));
925 $script = get_script_uri();
926 return '[[' . $title . $arrow . '>' .
927 $script . '?plugin=tracker_list&refer=' . $r_page .
928 '&config=' . $r_config .
929 '&list=' . $r_list . '&order=' . $r_order . ']]';
932 function toString($limit = 10)
934 if (empty($this->rows)) return FALSE;
936 $limit = max(1, intval($limit));
938 $count = $_count = count($this->rows);
939 if ($limit != 0 && $count > $limit) {
940 $rows = array_slice($this->rows, 0, $limit);
941 $_count = count($rows);
948 if ($count > $_count) {
950 $source[] = str_replace(
952 array($count, $_count),
953 plugin_tracker_message('msg_limit')
958 foreach (plugin_tracker_get_source($this->config->page . '/' . $this->list) as $line) {
959 if (preg_match('/^\|(.+)\|[hfc]$/i', $line)) {
961 $source[] = preg_replace_callback('/\[([^\[\]]+)\]/', array(& $this, 'replace_title'), $line);
966 foreach ($rows as $row) {
967 if (! PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE && ! $row['_match']) continue;
970 foreach ($body as $line) {
971 if (ltrim($line) == '') {
974 $this->pipe = ($line{0} == '|' || $line{0} == ':');
975 $source[] = preg_replace_callback('/\[([^\[\]]+)\]/', array(& $this, 'replace_item'), $line);
980 return convert_html(implode('', $source));
985 $this->add(NULL, NULL);
990 function plugin_tracker_get_source($page, $join = FALSE)
992 $source = get_source($page, TRUE, $join);
994 // Remove fixed-heading anchors
995 $source = preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m', '$1$2', $source);
998 return preg_replace('/^#freeze\s*$/im', '', $source);
1001 function plugin_tracker_message($key)
1003 global $_tracker_messages;
1004 return isset($_tracker_messages[$key]) ? $_tracker_messages[$key] : 'NOMESSAGE';