2 // PukiWiki - Yet another WikiWikiWeb clone
3 // $Id: tracker.inc.php,v 1.63 2007/09/22 08:24:41 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[/list]][[,base][,field:sort[;field:sort ...][,limit]]])');
12 define('PLUGIN_TRACKER_DEFAULT_CONFIG', 'default');
13 define('PLUGIN_TRACKER_DEFAULT_FORM', 'form');
14 define('PLUGIN_TRACKER_DEFAULT_LIST', 'list');
15 define('PLUGIN_TRACKER_DEFAULT_LIMIT', 0 ); // 0 = Unlimited
16 define('PLUGIN_TRACKER_DEFAULT_ORDER', ''); // Example: '_real'
18 // Sort N columns at a time
19 define('PLUGIN_TRACKER_LIST_SORT_LIMIT', 3);
22 define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#^SubMenu$|/#'); // 'SubMenu' and using '/'
23 //define('PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN','#(?!)#'); // Nothing excluded
25 // Show error rows (can't capture columns properly)
26 define('PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE', 1);
31 define('PLUGIN_TRACKER_LIST_SORT_DESC', 3);
32 define('PLUGIN_TRACKER_LIST_SORT_ASC', 4);
33 define('PLUGIN_TRACKER_LIST_SORT_DEFAULT', PLUGIN_TRACKER_LIST_SORT_ASC);
36 function plugin_tracker_convert()
40 if (PKWK_READONLY) return ''; // Show nothing
42 $base = $refer = isset($vars['page']) ? $vars['page'] : '';
43 $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
44 $form = PLUGIN_TRACKER_DEFAULT_FORM;
46 $args = func_get_args();
49 return PLUGIN_TRACKER_USAGE . '<br />';
53 $arg = get_fullname($args[1], $base);
54 if (is_pagename($arg)) $base = $arg;
59 $arg = explode('/', $args[0], 2);
60 if ($arg[0] != '' ) $config_name = $arg[0];
61 if (isset($arg[1])) $form = $arg[1];
64 unset($args, $argc, $arg);
66 $config = new Config('plugin/tracker/' . $config_name);
67 if (! $config->read()) {
68 return '#tracker: Config \'' . htmlspecialchars($config_name) . '\' not found<br />';
70 $config->config_name = $config_name;
71 $form = $config->page . '/' . $form;
72 if (! is_page($form)) {
73 return '#tracker: Form \'' . make_pagelink($form) . '\' not found<br />';
76 $from = $to = $hidden = array();
77 $fields = plugin_tracker_get_fields($base, $refer, $config);
78 foreach (array_keys($fields) as $field) {
79 $from[] = '[' . $field . ']';
80 $_to = $fields[$field]->get_tag();
81 if (is_a($fields[$field], 'Tracker_field_hidden')) {
87 unset($fields[$field]);
90 $script = get_script_uri();
91 $retval = str_replace($from, $to, convert_html(plugin_tracker_get_source($form)));
92 $hidden = implode('<br />' . "\n", $hidden);
94 <form enctype="multipart/form-data" action="$script" method="post">
104 function plugin_tracker_action()
106 global $post, $vars, $now;
108 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
110 $base = isset($post['_base']) ? $post['_base'] : '';
111 $refer = isset($post['_refer']) ? $post['_refer'] : $base;
112 if (! is_pagename($refer)) {
114 'msg' => 'Cannot write',
115 'body' => 'Page name (' . htmlspecialchars($refer) . ') invalid'
119 // $page name to add will be decided here
121 $name = isset($post['_name']) ? $post['_name'] : '';
122 if (isset($post['_page'])) {
123 $real = $page = $post['_page'];
125 $real = is_pagename($name) ? $name : ++$num;
126 $page = get_fullname('./' . $real, $base);
128 if (! is_pagename($page)) $page = $base;
129 while (is_page($page)) {
131 $page = $base . '/' . $real;
134 // Loading configuration
135 $config_name = isset($post['_config']) ? $post['_config'] : '';
136 $config = new Config('plugin/tracker/' . $config_name);
137 if (! $config->read()) {
138 return '<p>config file \'' . htmlspecialchars($config_name) . '\' not found.</p>';
140 $config->config_name = $config_name;
141 $template_page = $config->page . '/page';
142 if (! is_page($template_page)) {
144 'msg' => 'Cannot write',
145 'body' => 'Page template (' . htmlspecialchars($template_page) . ') not exists'
150 $_post = array_merge($post, $_FILES);
151 $_post['_date'] = $now;
152 $_post['_page'] = $page;
153 $_post['_name'] = $name;
154 $_post['_real'] = $real;
155 // $_post['_refer'] = $_post['refer'];
157 // Creating an empty page, before attaching files
158 pkwk_touch_file(get_filename($page));
160 $from = $to = array();
161 $fields = plugin_tracker_get_fields($page, $refer, $config);
162 foreach (array_keys($fields) as $field) {
163 $from[] = '[' . $field . ']';
164 $to[] = isset($_post[$field]) ? $fields[$field]->format_value($_post[$field]) : '';
165 unset($fields[$field]);
169 $template = plugin_tracker_get_source($template_page);
171 // Repalace every [$field]s to real values in the $template
172 $subject = $subject_e = array();
173 foreach (array_keys($template) as $num) {
174 if (trim($template[$num]) == '') continue;
175 $letter = $template[$num]{0};
176 if ($letter == '|' || $letter == ':') {
177 // Escape for some TextFormattingRules: <table> and <dr>
178 $subject_e[$num] = $template[$num];
180 $subject[$num] = $template[$num];
183 foreach (str_replace($from, $to, $subject ) as $num => $line) {
184 $template[$num] = $line;
186 // Escape for some TextFormattingRules: <table> and <dr>
189 foreach($to as $value) {
190 if (strpos($value, '|') !== FALSE) {
191 // Escape for some TextFormattingRules: <table> and <dr>
192 $to_e[] = str_replace('|', '|', $value);
197 foreach (str_replace($from, $to_e, $subject_e) as $num => $line) {
198 $template[$num] = $line;
202 // Write $template, without touch
203 page_write($page, join('', $template));
206 header('Location: ' . get_script_uri() . '?' . rawurlencode($page));
210 // Construct $fields (an array of Tracker_field objects)
211 function plugin_tracker_get_fields($base, $refer, & $config)
217 foreach ($config->get('fields') as $field) {
218 // $field[0]: Field name
219 // $field[1]: Field name (for display)
220 // $field[2]: Field type
221 // $field[3]: Option ("size", "cols", "rows", etc)
222 // $field[4]: Default value
223 $class = 'Tracker_field_' . $field[2];
224 if (! class_exists($class)) {
227 $class = 'Tracker_field_' . $field[2];
230 $fieldname = $field[0];
231 $fields[$fieldname] = & new $class($field, $base, $refer, $config);
237 '_date' => 'text', // Post date
238 '_update' => 'date', // Last modified date
239 '_past' => 'past', // Elapsed time (passage)
240 '_page' => 'page', // Page name
241 '_name' => 'text', // Page name specified by poster
242 '_real' => 'real', // Page name (Real)
243 '_refer' => 'page', // Page name refer from this (Page who has forms)
245 '_submit' => 'submit'
246 ) as $fieldname => $type)
248 if (isset($fields[$fieldname])) continue;
249 $field = array($fieldname, plugin_tracker_message('btn' . $fieldname), '', '20', '');
250 $class = 'Tracker_field_' . $type;
251 $fields[$fieldname] = & new $class($field, $base, $refer, $config);
268 var $sort_type = SORT_REGULAR;
271 function Tracker_field($field, $base, $refer, & $config)
274 static $id = 0; // Unique id per instance
277 $this->name = $field[0];
278 $this->title = $field[1];
279 $this->values = explode(',', $field[3]);
280 $this->default_value = $field[4];
282 $this->refer = $refer;
283 $this->config = & $config;
284 $this->data = isset($post[$this->name]) ? $post[$this->name] : '';
287 // XHTML part inside a form
298 function format_value($value)
303 function format_cell($str)
308 // Compare key for Tracker_list->sort()
309 function get_value($value)
311 return $value; // Default: $value itself
315 class Tracker_field_text extends Tracker_field
317 var $sort_type = SORT_STRING;
321 return '<input type="text"' .
322 ' name="' . htmlspecialchars($this->name) . '"' .
323 ' size="' . htmlspecialchars($this->values[0]) . '"' .
324 ' value="' . htmlspecialchars($this->default_value) . '" />';
328 class Tracker_field_page extends Tracker_field_text
330 var $sort_type = SORT_STRING;
332 function format_value($value)
334 $value = strip_bracket($value);
335 if (is_pagename($value)) $value = '[[' . $value . ']]';
336 return parent::format_value($value);
340 class Tracker_field_real extends Tracker_field_text
342 var $sort_type = SORT_REGULAR;
345 class Tracker_field_title extends Tracker_field_text
347 var $sort_type = SORT_STRING;
349 function format_cell($str)
356 class Tracker_field_textarea extends Tracker_field
358 var $sort_type = SORT_STRING;
363 ' name="' . htmlspecialchars($this->name) . '"' .
364 ' cols="' . htmlspecialchars($this->values[0]) . '"' .
365 ' rows="' . htmlspecialchars($this->values[1]) . '">' .
366 htmlspecialchars($this->default_value) .
370 function format_cell($str)
372 $str = preg_replace('/[\r\n]+/', '', $str);
373 if (! empty($this->values[2]) && strlen($str) > ($this->values[2] + 3)) {
374 $str = mb_substr($str, 0, $this->values[2]) . '...';
380 class Tracker_field_format extends Tracker_field
382 var $sort_type = SORT_STRING;
383 var $styles = array();
384 var $formats = array();
386 function Tracker_field_format($field, $base, $refer, & $config)
388 parent::Tracker_field($field, $base, $refer, $config);
390 foreach ($this->config->get($this->name) as $option) {
391 list($key, $style, $format) =
392 array_pad(array_map(create_function('$a', 'return trim($a);'), $option), 3, '');
393 if ($style != '') $this->styles[$key] = $style;
394 if ($format != '') $this->formats[$key] = $format;
400 return '<input type="text"' .
401 ' name="' . htmlspecialchars($this->name) . '"' .
402 ' size="' . htmlspecialchars($this->values[0]) . '" />';
405 function get_key($str)
407 return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
410 function format_value($str)
412 if (is_array($str)) {
413 return join(', ', array_map(array($this, 'format_value'), $str));
416 $key = $this->get_key($str);
417 return isset($this->formats[$key]) ? str_replace('%s', $str, $this->formats[$key]) : $str;
420 function get_style($str)
422 $key = $this->get_key($str);
423 return isset($this->styles[$key]) ? $this->styles[$key] : '%s';
427 class Tracker_field_file extends Tracker_field_format
429 var $sort_type = SORT_STRING;
433 return '<input type="file"' .
434 ' name="' . htmlspecialchars($this->name) . '"' .
435 ' size="' . htmlspecialchars($this->values[0]) . '" />';
438 function format_value()
440 if (isset($_FILES[$this->name])) {
442 require_once(PLUGIN_DIR . 'attach.inc.php');
444 $result = attach_upload($_FILES[$this->name], $this->base);
445 if (isset($result['result']) && $result['result']) {
447 return parent::format_value($this->base . '/' . $_FILES[$this->name]['name']);
451 // Filename not specified, or Fail to upload
452 return parent::format_value('');
456 class Tracker_field_radio extends Tracker_field_format
458 var $sort_type = SORT_NUMERIC;
459 var $_options = array();
466 $s_name = htmlspecialchars($this->name);
467 foreach ($this->config->get($this->name) as $option) {
469 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
470 $s_option = htmlspecialchars($option[0]);
471 $checked = trim($option[0]) == trim($this->default_value) ? ' checked="checked"' : '';
473 $retval .= '<input type="radio"' .
474 ' name="' . $s_name . '"' .
475 ' id="' . $s_id . '"' .
476 ' value="' . $s_option . '"' .
478 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
484 function get_key($str)
489 function get_value($value)
491 $options = & $this->_options;
494 if (! isset($options[$name])) {
496 create_function('$array', 'return $array[0];'),
497 $this->config->get($name)
499 $options[$name] = array_flip($values); // array('value0' => 0, 'value1' => 1, ...)
502 return isset($options[$name][$value]) ? $options[$name][$value] : $value;
506 class Tracker_field_select extends Tracker_field_radio
508 var $sort_type = SORT_NUMERIC;
510 function get_tag($empty = FALSE)
512 $s_name = htmlspecialchars($this->name);
513 $s_size = (isset($this->values[0]) && is_numeric($this->values[0])) ?
514 ' size="' . htmlspecialchars($this->values[0]) . '"' :
516 $s_multiple = (isset($this->values[1]) && strtolower($this->values[1]) == 'multiple') ?
517 ' multiple="multiple"' :
520 $retval = '<select name="' . $s_name . '[]"' . $s_size . $s_multiple . '>' . "\n";
521 if ($empty) $retval .= ' <option value=""></option>' . "\n";
522 $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
523 foreach ($this->config->get($this->name) as $option) {
524 $s_option = htmlspecialchars($option[0]);
525 $selected = isset($defaults[trim($option[0])]) ? ' selected="selected"' : '';
526 $retval .= ' <option value="' . $s_option . '"' . $selected . '>' . $s_option . '</option>' . "\n";
528 $retval .= '</select>';
534 class Tracker_field_checkbox extends Tracker_field_radio
536 var $sort_type = SORT_NUMERIC;
543 $s_name = htmlspecialchars($this->name);
544 $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
545 foreach ($this->config->get($this->name) as $option)
548 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
549 $s_option = htmlspecialchars($option[0]);
550 $checked = isset($defaults[trim($option[0])]) ? ' checked="checked"' : '';
552 $retval .= '<input type="checkbox"' .
553 ' name="' . $s_name . '[]"' .
554 ' id="' . $s_id . '"' .
555 ' value="' . $s_option . '"' .
557 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
564 class Tracker_field_hidden extends Tracker_field_radio
566 var $sort_type = SORT_NUMERIC;
570 return '<input type="hidden"' .
571 ' name="' . htmlspecialchars($this->name) . '"' .
572 ' value="' . htmlspecialchars($this->default_value) . '" />' . "\n";
576 class Tracker_field_submit extends Tracker_field
580 $s_title = htmlspecialchars($this->title);
581 $s_base = htmlspecialchars($this->base);
582 $s_refer = htmlspecialchars($this->refer);
583 $s_config = htmlspecialchars($this->config->config_name);
586 <input type="submit" value="$s_title" />
587 <input type="hidden" name="plugin" value="tracker" />
588 <input type="hidden" name="_refer" value="$s_refer" />
589 <input type="hidden" name="_base" value="$s_base" />
590 <input type="hidden" name="_config" value="$s_config" />
595 class Tracker_field_date extends Tracker_field
597 var $sort_type = SORT_NUMERIC;
599 function format_cell($timestamp)
601 return format_date($timestamp);
605 class Tracker_field_past extends Tracker_field
607 var $sort_type = SORT_NUMERIC;
609 function format_cell($timestamp)
611 return get_passage($timestamp, FALSE);
614 function get_value($value)
616 return UTIME - $value;
620 ///////////////////////////////////////////////////////////////////////////
621 // tracker_list plugin
623 function plugin_tracker_list_convert()
627 $base = $refer = isset($vars['page']) ? $vars['page'] : '';
628 $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
629 $list = PLUGIN_TRACKER_DEFAULT_LIST;
630 $limit = PLUGIN_TRACKER_DEFAULT_LIMIT;
631 $order = PLUGIN_TRACKER_DEFAULT_ORDER;
633 $args = func_get_args();
634 $argc = count($args);
636 return PLUGIN_TRACKER_LIST_USAGE . '<br />';
639 case 4: $limit = $args[3]; /*FALLTHROUGH*/
640 case 3: $order = $args[2]; /*FALLTHROUGH*/
642 $arg = get_fullname($args[1], $base);
643 if (is_pagename($arg)) $base = $arg;
647 if ($args[0] != '') {
648 $arg = explode('/', $args[0], 2);
649 if ($arg[0] != '' ) $config_name = $arg[0];
650 if (isset($arg[1])) $list = $arg[1];
653 unset($args, $argc, $arg);
655 return plugin_tracker_list_render($base, $refer, $config_name, $list, $order, $limit);
658 function plugin_tracker_list_action()
662 $base = isset($get['base']) ? $get['base'] : '';
663 $config = isset($get['config']) ? $get['config'] : '';
664 $list = isset($get['list']) ? $get['list'] : 'list';
666 $order = isset($vars['order']) ? $vars['order'] : PLUGIN_TRACKER_DEFAULT_ORDER;
667 $limit = isset($vars['limit']) ? $vars['limit'] : 0;
669 // Compat before 1.4.8
670 if ($base == '') $base = isset($get['refer']) ? $get['refer'] : '';
672 $s_base = make_pagelink(trim($base));
674 'msg' => plugin_tracker_message('msg_list'),
675 'body'=> str_replace('$1', $s_base, plugin_tracker_message('msg_back')) .
676 plugin_tracker_list_render($base, $base, $config, $list, $order, $limit)
680 function plugin_tracker_list_render($base, $refer, $config_name, $list, $order_commands = '', $limit = 0)
683 if ($base == '') return '#tracker_list: Base not specified' . '<br />';
685 $refer = trim($refer);
687 $config_name = trim($config_name);
688 if ($config_name == '') $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
691 if (! is_numeric($limit)) return PLUGIN_TRACKER_LIST_USAGE . '<br />';
692 $limit = intval($limit);
695 $config = new Config('plugin/tracker/' . $config_name);
696 if (! $config->read()) {
697 return '#tracker_list: Config not found: ' . htmlspecialchars($config_name) . '<br />';
699 $config->config_name = $config_name;
700 if (! is_page($config->page . '/' . $list)) {
701 return '#tracker_list: List not found: ' . make_pagelink($config->page . '/' . $list) . '<br />';
704 $list = & new Tracker_list($base, $refer, $config, $list);
705 if ($list->setOrder($order_commands) === FALSE) {
706 return '#tracker_list: ' . htmlspecialchars($list->error) . '<br />';
708 $result = $list->toString($limit);
709 if ($result === FALSE) {
710 return '#tracker_list: ' . htmlspecialchars($list->error) . '<br />';
714 return convert_html($result);
729 var $orders = array();
730 var $_added = array();
732 var $error = ''; // Error message
734 // Used by toString() only
736 var $_the_first_character_of_the_line;
738 function Tracker_list($base, $refer, & $config, $list)
741 $this->refer = $refer;
742 $this->config = & $config;
744 $this->fields = plugin_tracker_get_fields($base, $refer, $config);
748 function _generate_regex()
750 $config_page = $this->config->page;
751 $fields = $this->fields;
754 $pattern_fields = array();
756 // TODO: if (is FALSE) OR file_exists()
757 $source = plugin_tracker_get_source($config_page . '/page', TRUE);
758 // Block-plugins to pseudo fields (#convert => [_block_convert])
759 $source = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m', '[_block_$1]', $source);
761 // Now, $source = array('*someting*', 'fieldname', '*someting*', 'fieldname', ...)
762 $source = preg_split('/\\\\\[(\w+)\\\\\]/', preg_quote($source, '/'), -1, PREG_SPLIT_DELIM_CAPTURE);
764 // NOTE: if the page has garbages between fields, it will fail to be load
765 while (! empty($source)) {
766 // Just ignore these _fixed_ data
767 $pattern[] = preg_replace('/\s+/', '\\s*', '(?>\\s*' . trim(array_shift($source)) . '\\s*)');
768 if (empty($source)) continue;
770 $fieldname = array_shift($source);
771 if (isset($fields[$fieldname])) {
772 $pattern[] = '(.*)'; // Just capture it
773 $pattern_fields[] = $fieldname; // Capture it as this $filedname
775 $pattern[] = '.*'; // Just ignore pseudo fields etc
778 $this->pattern = '/' . implode('', $pattern) . '/s';
779 $this->pattern_fields = $pattern_fields;
787 $pattern = $base . '/';
788 $pattern_len = strlen($pattern);
789 foreach (get_existpages() as $_page) {
790 if (strpos($_page, $pattern) === 0) {
791 $name = substr($_page, $pattern_len);
792 if (preg_match(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN, $name)) continue;
793 $this->add($_page, $name);
798 function add($page, $name)
800 if (isset($this->_added[$page])) return;
801 $this->_added[$page] = TRUE;
803 $source = plugin_tracker_get_source($page);
805 // Compat: 'move to [[page]]' (bugtrack plugin)
807 if (! empty($source) && preg_match('/move\sto\s(.+)/', $source[0], $matches)) {
808 $to_page = strip_bracket(trim($matches[1]));
809 if (is_page($to_page)) {
810 unset($source); // Release
811 $this->add($to_page, $name); // Recurse(Rescan)
819 $filetime = get_filetime($page);
821 // column => default data of the cell
822 '_page' => '[[' . $page . ']]',
824 '_update' => $filetime,
825 '_past' => $filetime,
829 // Load / Redefine cell
831 $row['_match'] = preg_match($this->pattern, implode('', $source), $matches);
833 if ($row['_match']) {
834 array_shift($matches); // $matches[0] = all of the captured string
835 foreach ($this->pattern_fields as $key => $fieldname) {
836 $row[$fieldname] = trim($matches[$key]);
837 unset($matches[$key]);
841 $this->rows[$name] = $row;
845 function _order_commands2orders($order_commands = '')
847 $order_commands = trim($order_commands);
848 if ($order_commands == '') return array();
851 $fields = $this->fields;
854 foreach (explode(';', $order_commands) as $command) {
855 $command = trim($command);
856 if ($command == '') continue;
857 $arg = explode(':', $command, 2);
858 $fieldname = isset($arg[0]) ? trim($arg[0]) : '';
859 $order = isset($arg[1]) ? trim($arg[1]) : '';
861 if (! isset($fields[$fieldname])) {
862 $this->error = 'No such field: ' . $fieldname;
865 $_order = $this->_sortkey_string2define($order);
866 if ($_order === FALSE) {
867 $this->error = 'Invalid sortkey: ' . $order;
869 } else if (isset($orders[$fieldname])) {
870 $this->error = 'Sortkey already set: ' . $fieldname;
874 if (PLUGIN_TRACKER_LIST_SORT_LIMIT <= $i) continue; // Ignore
876 $orders[$fieldname] = $_order;
883 function setOrder($order_commands = '')
885 $orders = $this->_order_commands2orders($order_commands);
886 if ($orders === FALSE) {
887 $this->orders = array();
891 $this->orders = $orders;
895 // Sort $this->rows by $this->orders
898 $orders = $this->orders;
899 $fields = $this->fields;
901 $params = array(); // Arguments for array_multisort()
902 foreach ($orders as $fieldname => $order) {
903 // One column set (one-dimensional array(), sort type, and order-by)
905 if ($order = PLUGIN_TRACKER_LIST_SORT_ASC) {
907 } else if ($order = PLUGIN_TRACKER_LIST_SORT_DESC) {
910 $this->error = 'Invalid sort order for array_multisort()';
915 foreach ($this->rows as $row) {
916 $array[] = isset($row[$fieldname]) ?
917 $fields[$fieldname]->get_value($row[$fieldname]) :
921 $params[] = $fields[$fieldname]->sort_type;
924 $params[] = & $this->rows;
926 call_user_func_array('array_multisort', $params);
931 // toString(): Sort key: Define to string (internal var => string)
932 function _sortkey_define2string($sortkey)
935 case PLUGIN_TRACKER_LIST_SORT_ASC: $sortkey = 'SORT_ASC'; break;
936 case PLUGIN_TRACKER_LIST_SORT_DESC: $sortkey = 'SORT_DESC'; break;
938 $this->error = 'No such define: ' . $sortkey;
944 // toString(): Sort key: String to define (string => internal var)
945 function _sortkey_string2define($sortkey)
947 switch (strtoupper(trim($sortkey))) {
948 case '': $sortkey = PLUGIN_TRACKER_LIST_SORT_DEFAULT; break;
950 case SORT_ASC: /*FALLTHROUGH*/ // Compat, will be removed at 1.4.9 or later
951 case 'SORT_ASC': /*FALLTHROUGH*/
952 case 'ASC': $sortkey = PLUGIN_TRACKER_LIST_SORT_ASC; break;
954 case SORT_DESC: /*FALLTHROUGH*/ // Compat, will be removed at 1.4.9 or later
955 case 'SORT_DESC': /*FALLTHROUGH*/
956 case 'DESC': $sortkey = PLUGIN_TRACKER_LIST_SORT_DESC; break;
959 $this->error = 'Invalid sort key: ' . $sortkey;
965 // toString(): Escape special characters not to break Wiki syntax
966 function _escape($syntax_hint = '|', $string)
968 $from = array("\n", "\r" );
969 $to = array('&br;', '&br;');
970 if ($syntax_hint == '|' || $syntax_hint == ':') {
971 // <table> or <dl> Wiki syntax: Excape '|'
974 } else if ($syntax_hint == ',') {
979 return str_replace($from, $to, $string);
982 // toString(): Called within preg_replace_callback()
983 function _replace_title($matches = array())
985 $fields = $this->fields;
986 $orders = $this->orders;
988 $config_name = $this->config->config_name;
990 $fieldname = isset($matches[1]) ? $matches[1] : '';
991 if (! isset($fields[$fieldname])) {
992 // Invalid $fieldname or user's own string or something. Nothing to do
993 return isset($matches[0]) ? $matches[0] : '';
995 if ($fieldname == '_name' || $fieldname == '_page') $fieldname = '_real';
998 if (isset($orders[$fieldname])) {
1000 $order_keys = array_keys($orders);
1003 $b_end = ($fieldname == (isset($order_keys[0]) ? $order_keys[0] : ''));
1004 $b_order = ($orders[$fieldname] === PLUGIN_TRACKER_LIST_SORT_ASC);
1005 $order = ($b_end xor $b_order)
1006 ? PLUGIN_TRACKER_LIST_SORT_ASC
1007 : PLUGIN_TRACKER_LIST_SORT_DESC;
1010 $index = array_flip($order_keys);
1011 $pos = 1 + $index[$fieldname];
1012 $arrow = '&br;' . ($b_order ? '↑' : '↓') . '(' . $pos . ')';
1014 unset($order_keys, $index);
1015 unset($orders[$fieldname]); // $fieldname will be added to the first
1017 // Not sorted yet, but
1018 $order = PLUGIN_TRACKER_LIST_SORT_ASC; // Default
1021 // $fieldname become the first, if you click this link
1022 $_order = array($fieldname . ':' . $this->_sortkey_define2string($order));
1023 foreach ($orders as $key => $value) {
1024 $_order[] = $key . ':' . $this->_sortkey_define2string($value);
1027 $r_config = ($config_name != PLUGIN_TRACKER_DEFAULT_CONFIG) ?
1028 '&config=' . rawurlencode($config_name) : '';
1029 $r_list = ($list != PLUGIN_TRACKER_DEFAULT_LIST) ?
1030 '&list=' . rawurlencode($list) : '';
1034 $fields[$fieldname]->title . $arrow .
1037 '?plugin=tracker_list' .
1038 '&base=' . rawurlencode($this->base) .
1041 '&order=' . rawurlencode(join(';', $_order)) .
1045 // toString(): Called within preg_replace_callback()
1046 function _replace_item($matches = array())
1048 $fields = $this->fields;
1049 $items = $this->_items;
1050 $tfc = $this->_the_first_character_of_the_line ;
1052 $params = isset($matches[1]) ? explode(',', $matches[1]) : array();
1053 $fieldname = isset($params[0]) ? $params[0] : '';
1054 $stylename = isset($params[1]) ? $params[1] : $fieldname;
1056 if ($fieldname == '') return ''; // Invalid
1058 if (! isset($items[$fieldname])) {
1059 // Maybe load miss of the page
1060 if (isset($fields[$fieldname])) {
1061 $str = '[page_err]'; // Exactlly
1063 $str = isset($matches[0]) ? $matches[0] : ''; // Nothing to do
1066 $str = $items[$fieldname];
1067 if (isset($fields[$fieldname])) {
1068 $str = $fields[$fieldname]->format_cell($str);
1070 if (isset($fields[$stylename]) && isset($items[$stylename])) {
1071 $_style = $fields[$stylename]->get_style($items[$stylename]);
1072 $str = sprintf($_style, $str);
1076 return $this->_escape($tfc, $str);
1079 // Output a part of Wiki text
1080 function toString($limit = 0)
1083 $list = $this->config->page . '/' . $this->list;
1084 $regex = '/\[([^\[\]]+)\]/';
1086 // Loading template: Roughly checking listed fields
1088 $used_fieldname = array('_real' => TRUE);
1089 preg_match_all($regex, plugin_tracker_get_source($list, TRUE), $matches);
1091 foreach ($matches[1] as $match) {
1092 $params = explode(',', $match);
1093 if (isset($params[0]) && ! isset($used_fieldname[$params[0]])) {
1094 $used_fieldname[$params[0]] = TRUE;
1098 foreach (array_keys($this->orders) as $fieldname) {
1099 if (! isset($used_fieldname[$fieldname])) {
1100 $used_fieldname[$fieldname] = TRUE;
1104 // Remove unused $this->fields
1105 $fields = $this->fields;
1106 $new_filds = array();
1107 foreach (array_keys($fields) as $fieldname) {
1108 if (isset($used_fieldname[$fieldname])) {
1109 $new_filds[$fieldname] = & $fields[$fieldname];
1112 $this->fields = $new_filds;
1114 // Generate regex for $this->fields
1115 $this->_generate_regex();
1119 if (empty($this->rows)) {
1120 $this->error = 'Pages not found under: ' . $this->base . '/';
1126 $rows = $this->rows;
1128 $count = count($this->rows);
1129 $limit = intval($limit);
1130 if ($limit != 0) $limit = max(1, $limit);
1131 if ($limit != 0 && $count > $limit) {
1132 $source[] = str_replace(
1134 array($count, $limit),
1135 plugin_tracker_message('msg_limit')
1137 $rows = array_slice($this->rows, 0, $limit);
1141 $header = $body = array();
1142 foreach (plugin_tracker_get_source($list) as $line) {
1143 if (preg_match('/^\|(.+)\|[hfc]$/i', $line)) {
1144 // TODO: Why c and f here
1145 $header[] = $line; // Table header, footer, and decoration
1147 $body[] = $line; // The others
1151 foreach($header as $line) {
1152 $source[] = preg_replace_callback($regex, array(& $this, '_replace_title'), $line);
1154 foreach ($rows as $row) {
1155 if (! PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE && ! $row['_match']) continue;
1156 $this->_items = $row;
1157 foreach ($body as $line) {
1158 if (ltrim($line) != '') {
1159 $this->_the_first_character_of_the_line = $line[0];
1160 $line = preg_replace_callback($regex, array(& $this, '_replace_item'), $line);
1166 return implode('', $source);
1170 function plugin_tracker_get_source($page, $join = FALSE)
1172 $source = get_source($page, TRUE, $join);
1174 // Remove fixed-heading anchors
1175 $source = preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m', '$1$2', $source);
1177 // Remove #freeze-es
1178 return preg_replace('/^#freeze\s*$/im', '', $source);
1181 function plugin_tracker_message($key)
1183 global $_tracker_messages;
1184 return isset($_tracker_messages[$key]) ? $_tracker_messages[$key] : 'NOMESSAGE';