2 // PukiWiki - Yet another WikiWikiWeb clone
3 // $Id: tracker.inc.php,v 1.70 2007/09/23 04:47:06 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;
72 $from = $to = $hidden = array();
73 $fields = plugin_tracker_get_fields($base, $refer, $config);
74 foreach (array_keys($fields) as $field) {
75 $from[] = '[' . $field . ']';
76 $_to = $fields[$field]->get_tag();
77 if (is_a($fields[$field], 'Tracker_field_hidden')) {
83 unset($fields[$field]);
86 $form = $config->page . '/' . $form;
87 $retval = plugin_tracker_get_source($form);
88 if ($retval === FALSE || empty($retval)) {
89 return '#tracker: Form \'' . make_pagelink($form) . '\' not found or seems empty<br />';
92 $script = get_script_uri();
93 $retval = str_replace($from, $to, convert_html($retval));
94 $hidden = implode('<br />' . "\n", $hidden);
96 <form enctype="multipart/form-data" action="$script" method="post">
106 function plugin_tracker_action()
108 global $post, $vars, $now;
110 if (PKWK_READONLY) die_message('PKWK_READONLY prohibits editing');
112 $base = isset($post['_base']) ? $post['_base'] : '';
113 $refer = isset($post['_refer']) ? $post['_refer'] : $base;
114 if (! is_pagename($refer)) {
116 'msg' => 'Cannot write',
117 'body' => 'Page name (' . htmlspecialchars($refer) . ') invalid'
121 // $page name to add will be decided here
123 $name = isset($post['_name']) ? $post['_name'] : '';
124 if (isset($post['_page'])) {
125 $real = $page = $post['_page'];
127 $real = is_pagename($name) ? $name : ++$num;
128 $page = get_fullname('./' . $real, $base);
130 if (! is_pagename($page)) $page = $base;
131 while (is_page($page)) {
133 $page = $base . '/' . $real;
136 // Loading configuration
137 $config_name = isset($post['_config']) ? $post['_config'] : '';
138 $config = new Config('plugin/tracker/' . $config_name);
139 if (! $config->read()) {
140 return '<p>config file \'' . htmlspecialchars($config_name) . '\' not found.</p>';
142 $config->config_name = $config_name;
145 $_post = array_merge($post, $_FILES);
146 $_post['_date'] = $now;
147 $_post['_page'] = $page;
148 $_post['_name'] = $name;
149 $_post['_real'] = $real;
150 // $_post['_refer'] = $_post['refer'];
152 // Creating an empty page, before attaching files
153 pkwk_touch_file(get_filename($page));
155 $from = $to = array();
156 $fields = plugin_tracker_get_fields($page, $refer, $config);
157 foreach (array_keys($fields) as $field) {
158 $from[] = '[' . $field . ']';
159 $to[] = isset($_post[$field]) ? $fields[$field]->format_value($_post[$field]) : '';
160 unset($fields[$field]);
164 $template_page = $config->page . '/page';
165 $template = plugin_tracker_get_source($template_page);
166 if ($template === FALSE || empty($template)) {
168 'msg' => 'Cannot write',
169 'body' => 'Page template (' . htmlspecialchars($template_page) . ') not exists or seems empty'
173 // Repalace every [$field]s to real values in the $template
174 $subject = $subject_e = array();
175 foreach (array_keys($template) as $num) {
176 if (trim($template[$num]) == '') continue;
177 $letter = $template[$num]{0};
178 if ($letter == '|' || $letter == ':') {
179 // Escape for some TextFormattingRules: <table> and <dr>
180 $subject_e[$num] = $template[$num];
182 $subject[$num] = $template[$num];
185 foreach (str_replace($from, $to, $subject ) as $num => $line) {
186 $template[$num] = $line;
188 // Escape for some TextFormattingRules: <table> and <dr>
191 foreach($to as $value) {
192 if (strpos($value, '|') !== FALSE) {
193 // Escape for some TextFormattingRules: <table> and <dr>
194 $to_e[] = str_replace('|', '|', $value);
199 foreach (str_replace($from, $to_e, $subject_e) as $num => $line) {
200 $template[$num] = $line;
204 // Write $template, without touch
205 page_write($page, join('', $template));
208 header('Location: ' . get_script_uri() . '?' . rawurlencode($page));
212 // Construct $fields (an array of Tracker_field objects)
213 function plugin_tracker_get_fields($base, $refer, & $config)
219 foreach ($config->get('fields') as $field) {
220 // $field[0]: Field name
221 // $field[1]: Field name (for display)
222 // $field[2]: Field type
223 // $field[3]: Option ("size", "cols", "rows", etc)
224 // $field[4]: Default value
225 $class = 'Tracker_field_' . $field[2];
226 if (! class_exists($class)) {
229 $class = 'Tracker_field_' . $field[2];
232 $fieldname = $field[0];
233 $fields[$fieldname] = & new $class($field, $base, $refer, $config);
239 '_date' => 'text', // Post date
240 '_update' => 'date', // Last modified date
241 '_past' => 'past', // Elapsed time (passage)
242 '_page' => 'page', // Page name
243 '_name' => 'text', // Page name specified by poster
244 '_real' => 'real', // Page name (Real)
245 '_refer' => 'page', // Page name refer from this (Page who has forms)
247 '_submit' => 'submit'
248 ) as $fieldname => $type)
250 if (isset($fields[$fieldname])) continue;
251 $field = array($fieldname, plugin_tracker_message('btn' . $fieldname), '', '20', '');
252 $class = 'Tracker_field_' . $type;
253 $fields[$fieldname] = & new $class($field, $base, $refer, $config);
270 var $sort_type = SORT_REGULAR;
273 function Tracker_field($field, $base, $refer, & $config)
276 static $id = 0; // Unique id per instance
279 $this->name = $field[0];
280 $this->title = $field[1];
281 $this->values = explode(',', $field[3]);
282 $this->default_value = $field[4];
284 $this->refer = $refer;
285 $this->config = & $config;
286 $this->data = isset($post[$this->name]) ? $post[$this->name] : '';
289 // XHTML part inside a form
300 function format_value($value)
305 function format_cell($str)
310 // Compare key for Tracker_list->sort()
311 function get_value($value)
313 return $value; // Default: $value itself
317 class Tracker_field_text extends Tracker_field
319 var $sort_type = SORT_STRING;
323 return '<input type="text"' .
324 ' name="' . htmlspecialchars($this->name) . '"' .
325 ' size="' . htmlspecialchars($this->values[0]) . '"' .
326 ' value="' . htmlspecialchars($this->default_value) . '" />';
330 class Tracker_field_page extends Tracker_field_text
332 var $sort_type = SORT_STRING;
334 function format_value($value)
336 $value = strip_bracket($value);
337 if (is_pagename($value)) $value = '[[' . $value . ']]';
338 return parent::format_value($value);
342 class Tracker_field_real extends Tracker_field_text
344 var $sort_type = SORT_REGULAR;
347 class Tracker_field_title extends Tracker_field_text
349 var $sort_type = SORT_STRING;
351 function format_cell($str)
358 class Tracker_field_textarea extends Tracker_field
360 var $sort_type = SORT_STRING;
365 ' name="' . htmlspecialchars($this->name) . '"' .
366 ' cols="' . htmlspecialchars($this->values[0]) . '"' .
367 ' rows="' . htmlspecialchars($this->values[1]) . '">' .
368 htmlspecialchars($this->default_value) .
372 function format_cell($str)
374 $str = preg_replace('/[\r\n]+/', '', $str);
375 if (! empty($this->values[2]) && strlen($str) > ($this->values[2] + 3)) {
376 $str = mb_substr($str, 0, $this->values[2]) . '...';
382 class Tracker_field_format extends Tracker_field
384 var $sort_type = SORT_STRING;
385 var $styles = array();
386 var $formats = array();
388 function Tracker_field_format($field, $base, $refer, & $config)
390 parent::Tracker_field($field, $base, $refer, $config);
392 foreach ($this->config->get($this->name) as $option) {
393 list($key, $style, $format) =
394 array_pad(array_map(create_function('$a', 'return trim($a);'), $option), 3, '');
395 if ($style != '') $this->styles[$key] = $style;
396 if ($format != '') $this->formats[$key] = $format;
402 return '<input type="text"' .
403 ' name="' . htmlspecialchars($this->name) . '"' .
404 ' size="' . htmlspecialchars($this->values[0]) . '" />';
407 function get_key($str)
409 return ($str == '') ? 'IS NULL' : 'IS NOT NULL';
412 function format_value($str)
414 if (is_array($str)) {
415 return join(', ', array_map(array($this, 'format_value'), $str));
418 $key = $this->get_key($str);
419 return isset($this->formats[$key]) ? str_replace('%s', $str, $this->formats[$key]) : $str;
422 function get_style($str)
424 $key = $this->get_key($str);
425 return isset($this->styles[$key]) ? $this->styles[$key] : '%s';
429 class Tracker_field_file extends Tracker_field_format
431 var $sort_type = SORT_STRING;
435 return '<input type="file"' .
436 ' name="' . htmlspecialchars($this->name) . '"' .
437 ' size="' . htmlspecialchars($this->values[0]) . '" />';
440 function format_value()
442 if (isset($_FILES[$this->name])) {
444 require_once(PLUGIN_DIR . 'attach.inc.php');
446 $result = attach_upload($_FILES[$this->name], $this->base);
447 if (isset($result['result']) && $result['result']) {
449 return parent::format_value($this->base . '/' . $_FILES[$this->name]['name']);
453 // Filename not specified, or Fail to upload
454 return parent::format_value('');
458 class Tracker_field_radio extends Tracker_field_format
460 var $sort_type = SORT_NUMERIC;
461 var $_options = array();
468 $s_name = htmlspecialchars($this->name);
469 foreach ($this->config->get($this->name) as $option) {
471 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
472 $s_option = htmlspecialchars($option[0]);
473 $checked = trim($option[0]) == trim($this->default_value) ? ' checked="checked"' : '';
475 $retval .= '<input type="radio"' .
476 ' name="' . $s_name . '"' .
477 ' id="' . $s_id . '"' .
478 ' value="' . $s_option . '"' .
480 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
486 function get_key($str)
491 function get_value($value)
493 $options = & $this->_options;
496 if (! isset($options[$name])) {
498 create_function('$array', 'return $array[0];'),
499 $this->config->get($name)
501 $options[$name] = array_flip($values); // array('value0' => 0, 'value1' => 1, ...)
504 return isset($options[$name][$value]) ? $options[$name][$value] : $value;
508 class Tracker_field_select extends Tracker_field_radio
510 var $sort_type = SORT_NUMERIC;
512 function get_tag($empty = FALSE)
514 $s_name = htmlspecialchars($this->name);
515 $s_size = (isset($this->values[0]) && is_numeric($this->values[0])) ?
516 ' size="' . htmlspecialchars($this->values[0]) . '"' :
518 $s_multiple = (isset($this->values[1]) && strtolower($this->values[1]) == 'multiple') ?
519 ' multiple="multiple"' :
522 $retval = '<select name="' . $s_name . '[]"' . $s_size . $s_multiple . '>' . "\n";
523 if ($empty) $retval .= ' <option value=""></option>' . "\n";
524 $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
525 foreach ($this->config->get($this->name) as $option) {
526 $s_option = htmlspecialchars($option[0]);
527 $selected = isset($defaults[trim($option[0])]) ? ' selected="selected"' : '';
528 $retval .= ' <option value="' . $s_option . '"' . $selected . '>' . $s_option . '</option>' . "\n";
530 $retval .= '</select>';
536 class Tracker_field_checkbox extends Tracker_field_radio
538 var $sort_type = SORT_NUMERIC;
545 $s_name = htmlspecialchars($this->name);
546 $defaults = array_flip(preg_split('/\s*,\s*/', $this->default_value, -1, PREG_SPLIT_NO_EMPTY));
547 foreach ($this->config->get($this->name) as $option)
550 $s_id = '_p_tracker_' . $s_name . '_' . $this->id . '_' . $id;
551 $s_option = htmlspecialchars($option[0]);
552 $checked = isset($defaults[trim($option[0])]) ? ' checked="checked"' : '';
554 $retval .= '<input type="checkbox"' .
555 ' name="' . $s_name . '[]"' .
556 ' id="' . $s_id . '"' .
557 ' value="' . $s_option . '"' .
559 '<label for="' . $s_id . '">' . $s_option . '</label>' . "\n";
566 class Tracker_field_hidden extends Tracker_field_radio
568 var $sort_type = SORT_NUMERIC;
572 return '<input type="hidden"' .
573 ' name="' . htmlspecialchars($this->name) . '"' .
574 ' value="' . htmlspecialchars($this->default_value) . '" />' . "\n";
578 class Tracker_field_submit extends Tracker_field
582 $s_title = htmlspecialchars($this->title);
583 $s_base = htmlspecialchars($this->base);
584 $s_refer = htmlspecialchars($this->refer);
585 $s_config = htmlspecialchars($this->config->config_name);
588 <input type="submit" value="$s_title" />
589 <input type="hidden" name="plugin" value="tracker" />
590 <input type="hidden" name="_refer" value="$s_refer" />
591 <input type="hidden" name="_base" value="$s_base" />
592 <input type="hidden" name="_config" value="$s_config" />
597 class Tracker_field_date extends Tracker_field
599 var $sort_type = SORT_NUMERIC;
601 function format_cell($timestamp)
603 return format_date($timestamp);
607 class Tracker_field_past extends Tracker_field
609 var $sort_type = SORT_NUMERIC;
611 function format_cell($timestamp)
613 return get_passage($timestamp, FALSE);
616 function get_value($value)
618 return UTIME - $value;
622 ///////////////////////////////////////////////////////////////////////////
623 // tracker_list plugin
625 function plugin_tracker_list_convert()
629 $base = $refer = isset($vars['page']) ? $vars['page'] : '';
630 $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
631 $list = PLUGIN_TRACKER_DEFAULT_LIST;
632 $limit = PLUGIN_TRACKER_DEFAULT_LIMIT;
633 $order = PLUGIN_TRACKER_DEFAULT_ORDER;
635 $args = func_get_args();
636 $argc = count($args);
638 return PLUGIN_TRACKER_LIST_USAGE . '<br />';
641 case 4: $limit = $args[3]; /*FALLTHROUGH*/
642 case 3: $order = $args[2]; /*FALLTHROUGH*/
644 $arg = get_fullname($args[1], $base);
645 if (is_pagename($arg)) $base = $arg;
649 if ($args[0] != '') {
650 $arg = explode('/', $args[0], 2);
651 if ($arg[0] != '' ) $config_name = $arg[0];
652 if (isset($arg[1])) $list = $arg[1];
655 unset($args, $argc, $arg);
657 return plugin_tracker_list_render($base, $refer, $config_name, $list, $order, $limit);
660 function plugin_tracker_list_action()
664 $base = isset($get['base']) ? $get['base'] : ''; // Base directory to load
665 $refer = isset($get['refer']) ? $get['refer'] : $base; // Where to #tracker_list
666 if ($base == '') $base = $refer; // Compat before 1.4.8
668 $config = isset($get['config']) ? $get['config'] : '';
669 $list = isset($get['list']) ? $get['list'] : 'list';
670 $order = isset($vars['order']) ? $vars['order'] : PLUGIN_TRACKER_DEFAULT_ORDER;
671 $limit = isset($vars['limit']) ? $vars['limit'] : 0;
673 $s_refer = make_pagelink(trim($refer));
675 'msg' => plugin_tracker_message('msg_list'),
676 'body'=> str_replace('$1', $s_refer, plugin_tracker_message('msg_back')) .
677 plugin_tracker_list_render($base, $refer, $config, $list, $order, $limit)
681 function plugin_tracker_list_render($base, $refer, $config_name, $list, $order_commands = '', $limit = 0)
684 if ($base == '') return '#tracker_list: Base not specified' . '<br />';
687 $refer = trim($refer);
688 if (! is_page($refer)) {
689 return '#tracker_list: Refer page not found: ' . htmlspecialchars($refer) . '<br />';
692 $config_name = trim($config_name);
693 if ($config_name == '') $config_name = PLUGIN_TRACKER_DEFAULT_CONFIG;
696 if (! is_numeric($limit)) return PLUGIN_TRACKER_LIST_USAGE . '<br />';
697 $limit = intval($limit);
699 $config = new Config('plugin/tracker/' . $config_name);
700 if (! $config->read()) {
701 return '#tracker_list: Config not found: ' . htmlspecialchars($config_name) . '<br />';
703 $config->config_name = $config_name;
704 if (! is_page($config->page . '/' . $list)) {
705 return '#tracker_list: List not found: ' . make_pagelink($config->page . '/' . $list) . '<br />';
708 $list = & new Tracker_list($base, $refer, $config, $list);
709 if ($list->setOrder($order_commands) === FALSE) {
710 return '#tracker_list: ' . htmlspecialchars($list->error) . '<br />';
712 $result = $list->toString($limit);
713 if ($result === FALSE) {
714 return '#tracker_list: ' . htmlspecialchars($list->error) . '<br />';
718 return convert_html($result);
733 var $orders = array();
734 var $error = ''; // Error message
737 var $_added = array();
741 var $_the_first_character_of_the_line;
743 function Tracker_list($base, $refer, & $config, $list)
746 $this->refer = $refer;
747 $this->config = & $config;
749 $this->fields = plugin_tracker_get_fields($base, $refer, $config);
755 $pattern = $this->base . '/';
756 $pattern_len = strlen($pattern);
758 foreach (get_existpages() as $_page) {
759 if (strpos($_page, $pattern) === 0) {
760 $name = substr($_page, $pattern_len);
761 if (preg_match(PLUGIN_TRACKER_LIST_EXCLUDE_PATTERN, $name)) continue;
763 // Adding $this->rows
764 if ($this->add($_page, $name) === FALSE) return FALSE;
767 if (empty($this->rows)) {
768 $this->error = 'Pages not found under: ' . $pattern;
775 // add(): Generate regexes
776 function _generate_regex()
778 $config_page = $this->config->page . '/page';
779 $fields = $this->fields;
782 $pattern_fields = array();
784 $source = plugin_tracker_get_source($config_page, TRUE);
785 if ($source === FALSE || empty($source)) {
786 $this->error = 'Page not found or seems empty: ' . $config_page;
790 // Block-plugins to pseudo fields (#convert => [_block_convert])
791 $source = preg_replace('/^\#([^\(\s]+)(?:\((.*)\))?\s*$/m', '[_block_$1]', $source);
793 // Now, $source = array('*someting*', 'fieldname', '*someting*', 'fieldname', ...)
794 $source = preg_split('/\\\\\[(\w+)\\\\\]/', preg_quote($source, '/'), -1, PREG_SPLIT_DELIM_CAPTURE);
796 // NOTE: if the page has garbages between fields, it will fail to be load
797 while (! empty($source)) {
798 // Just ignore these _fixed_ data
799 $pattern[] = preg_replace('/\s+/', '\\s*', '(?>\\s*' . trim(array_shift($source)) . '\\s*)');
800 if (empty($source)) continue;
802 $fieldname = array_shift($source);
803 if (isset($fields[$fieldname])) {
804 $pattern[] = '(.*?)'; // Just capture it
805 $pattern_fields[] = $fieldname; // Capture it as this $filedname
807 $pattern[] = '.*?'; // Just ignore pseudo fields etc
810 $this->pattern = '/' . implode('', $pattern) . '/sS';
811 $this->pattern_fields = $pattern_fields;
816 function add($page, $name, $rescan = FALSE)
818 if (isset($this->_added[$page])) return TRUE;
819 $this->_added[$page] = TRUE;
821 $source = plugin_tracker_get_source($page, TRUE);
822 if ($source === FALSE) $source = '';
824 // Compat: 'move to [[page]]' (bugtrack plugin)
826 if (! $rescan && ! empty($source) && preg_match('/move\sto\s(.+)/', $source, $matches)) {
827 $to_page = strip_bracket(trim($matches[1]));
828 if (is_page($to_page)) {
829 unset($source, $matches); // Release
830 return $this->add($to_page, $name, TRUE); // Recurse(Rescan) once
835 $filetime = get_filetime($page);
837 // column => default data of the cell
838 '_page' => '[[' . $page . ']]',
840 '_update' => $filetime,
841 '_past' => $filetime,
845 // Load / Redefine cell
847 $row['_match'] = preg_match($this->pattern, $source, $matches);
849 if ($row['_match']) {
850 array_shift($matches); // $matches[0] = all of the captured string
851 foreach ($this->pattern_fields as $key => $fieldname) {
852 $row[$fieldname] = trim($matches[$key]);
853 unset($matches[$key]);
857 $this->rows[$name] = $row;
862 function _order_commands2orders($order_commands = '')
864 $order_commands = trim($order_commands);
865 if ($order_commands == '') return array();
868 $fields = $this->fields;
871 foreach (explode(';', $order_commands) as $command) {
872 $command = trim($command);
873 if ($command == '') continue;
874 $arg = explode(':', $command, 2);
875 $fieldname = isset($arg[0]) ? trim($arg[0]) : '';
876 $order = isset($arg[1]) ? trim($arg[1]) : '';
878 if (! isset($fields[$fieldname])) {
879 $this->error = 'No such field: ' . $fieldname;
883 $_order = $this->_sortkey_string2define($order);
884 if ($_order === FALSE) {
885 $this->error = 'Invalid sortkey: ' . $order;
887 } else if (isset($orders[$fieldname])) {
888 $this->error = 'Sortkey already set: ' . $fieldname;
892 if (PLUGIN_TRACKER_LIST_SORT_LIMIT <= $i) continue; // Ignore
895 $orders[$fieldname] = $_order;
901 // Set commands for sort()
902 function setOrder($order_commands = '')
904 $orders = $this->_order_commands2orders($order_commands);
905 if ($orders === FALSE) {
906 $this->orders = array();
909 $this->orders = $orders;
913 // Sort $this->rows by $this->orders
916 $orders = $this->orders;
917 $fields = $this->fields;
919 $params = array(); // Arguments for array_multisort()
920 foreach ($orders as $fieldname => $order) {
921 // One column set (one-dimensional array(), sort type, and order-by)
923 if ($order == PLUGIN_TRACKER_LIST_SORT_ASC) {
925 } else if ($order == PLUGIN_TRACKER_LIST_SORT_DESC) {
928 $this->error = 'Invalid sort order for array_multisort()';
933 foreach ($this->rows as $row) {
934 $array[] = isset($row[$fieldname]) ?
935 $fields[$fieldname]->get_value($row[$fieldname]) :
939 $params[] = $fields[$fieldname]->sort_type;
942 $params[] = & $this->rows;
944 call_user_func_array('array_multisort', $params);
949 // toString(): Sort key: Define to string (internal var => string)
950 function _sortkey_define2string($sortkey)
953 case PLUGIN_TRACKER_LIST_SORT_ASC: $sortkey = 'SORT_ASC'; break;
954 case PLUGIN_TRACKER_LIST_SORT_DESC: $sortkey = 'SORT_DESC'; break;
956 $this->error = 'No such define: ' . $sortkey;
962 // toString(): Sort key: String to define (string => internal var)
963 function _sortkey_string2define($sortkey)
965 switch (strtoupper(trim($sortkey))) {
966 case '': $sortkey = PLUGIN_TRACKER_LIST_SORT_DEFAULT; break;
968 case SORT_ASC: /*FALLTHROUGH*/ // Compat, will be removed at 1.4.9 or later
969 case 'SORT_ASC': /*FALLTHROUGH*/
970 case 'ASC': $sortkey = PLUGIN_TRACKER_LIST_SORT_ASC; break;
972 case SORT_DESC: /*FALLTHROUGH*/ // Compat, will be removed at 1.4.9 or later
973 case 'SORT_DESC': /*FALLTHROUGH*/
974 case 'DESC': $sortkey = PLUGIN_TRACKER_LIST_SORT_DESC; break;
977 $this->error = 'Invalid sort key: ' . $sortkey;
983 // toString(): Escape special characters not to break Wiki syntax
984 function _escape($syntax_hint = '|', $string)
986 $from = array("\n", "\r" );
987 $to = array('&br;', '&br;');
988 if ($syntax_hint == '|' || $syntax_hint == ':') {
989 // <table> or <dl> Wiki syntax: Excape '|'
992 } else if ($syntax_hint == ',') {
997 return str_replace($from, $to, $string);
1000 // toString(): Called within preg_replace_callback()
1001 function _replace_title($matches = array())
1005 $fields = $this->fields;
1006 $orders = $this->orders;
1007 $base = $this->base;
1008 $refer = $this->refer;
1009 $config_name = $this->config->config_name;
1010 $list = $this->list;
1012 $fieldname = isset($matches[1]) ? $matches[1] : '';
1013 if (! isset($fields[$fieldname])) {
1014 // Invalid $fieldname or user's own string or something. Nothing to do
1015 return isset($matches[0]) ? $matches[0] : '';
1017 if ($fieldname == '_name' || $fieldname == '_page') $fieldname = '_real';
1020 if (isset($orders[$fieldname])) {
1022 $order_keys = array_keys($orders);
1025 $b_end = ($fieldname == (isset($order_keys[0]) ? $order_keys[0] : ''));
1026 $b_order = ($orders[$fieldname] === PLUGIN_TRACKER_LIST_SORT_ASC);
1027 $order = ($b_end xor $b_order)
1028 ? PLUGIN_TRACKER_LIST_SORT_ASC
1029 : PLUGIN_TRACKER_LIST_SORT_DESC;
1032 $index = array_flip($order_keys);
1033 $pos = 1 + $index[$fieldname];
1034 $arrow = '&br;' . ($b_order ? '↑' : '↓') . '(' . $pos . ')';
1036 unset($order_keys, $index);
1037 unset($orders[$fieldname]); // $fieldname will be added to the first
1039 // Not sorted yet, but
1040 $order = PLUGIN_TRACKER_LIST_SORT_ASC; // Default
1043 // $fieldname become the first, if you click this link
1044 $_order = array($fieldname . ':' . $this->_sortkey_define2string($order));
1045 foreach ($orders as $key => $value) {
1046 $_order[] = $key . ':' . $this->_sortkey_define2string($value);
1049 if (! isset($script)) $script = get_script_uri();
1050 $r_refer = ($refer != $base) ?
1051 '&refer=' . rawurlencode($refer) : '';
1052 $r_config = ($config_name != PLUGIN_TRACKER_DEFAULT_CONFIG) ?
1053 '&config=' . rawurlencode($config_name) : '';
1054 $r_list = ($list != PLUGIN_TRACKER_DEFAULT_LIST) ?
1055 '&list=' . rawurlencode($list) : '';
1056 $r_order = ! empty($_order) ?
1057 '&order=' . rawurlencode(join(';', $_order)) : '';
1061 $fields[$fieldname]->title . $arrow .
1063 $script . '?plugin=tracker_list' .
1064 '&base=' . rawurlencode($base) .
1065 $r_refer . $r_config . $r_list . $r_order .
1069 // toString(): Called within preg_replace_callback()
1070 function _replace_item($matches = array())
1072 $fields = $this->fields;
1073 $items = $this->_items;
1074 $tfc = $this->_the_first_character_of_the_line ;
1076 $params = isset($matches[1]) ? explode(',', $matches[1]) : array();
1077 $fieldname = isset($params[0]) ? $params[0] : '';
1078 $stylename = isset($params[1]) ? $params[1] : $fieldname;
1080 if ($fieldname == '') return ''; // Invalid
1082 if (! isset($items[$fieldname])) {
1083 // Maybe load miss of the page
1084 if (isset($fields[$fieldname])) {
1085 $str = '[page_err]'; // Exactlly
1087 $str = isset($matches[0]) ? $matches[0] : ''; // Nothing to do
1090 $str = $items[$fieldname];
1091 if (isset($fields[$fieldname])) {
1092 $str = $fields[$fieldname]->format_cell($str);
1094 if (isset($fields[$stylename]) && isset($items[$stylename])) {
1095 $_style = $fields[$stylename]->get_style($items[$stylename]);
1096 $str = sprintf($_style, $str);
1100 return $this->_escape($tfc, $str);
1103 // Output a part of Wiki text
1104 function toString($limit = 0)
1107 $list = $this->config->page . '/' . $this->list;
1108 $regex = '/\[([^\[\]]+)\]/';
1110 // Loading template: Roughly checking listed fields
1112 $used_fieldname = array('_real' => TRUE);
1113 $template = plugin_tracker_get_source($list, TRUE);
1114 if ($template === FALSE || empty($template)) {
1115 $this->error = 'Page not found or seems empty: ' . $template;
1118 preg_match_all($regex, $template, $matches);
1120 foreach ($matches[1] as $match) {
1121 $params = explode(',', $match);
1122 if (isset($params[0]) && ! isset($used_fieldname[$params[0]])) {
1123 $used_fieldname[$params[0]] = TRUE;
1127 foreach (array_keys($this->orders) as $fieldname) {
1128 if (! isset($used_fieldname[$fieldname])) {
1129 $used_fieldname[$fieldname] = TRUE;
1133 // Remove unused $this->fields
1134 $fields = $this->fields;
1135 $new_filds = array();
1136 foreach (array_keys($fields) as $fieldname) {
1137 if (isset($used_fieldname[$fieldname])) {
1138 $new_filds[$fieldname] = & $fields[$fieldname];
1141 $this->fields = $new_filds;
1143 // Generate regex for $this->fields
1144 if ($this->_generate_regex() === FALSE) return FALSE;
1147 if ($this->_load() === FALSE) return FALSE;
1150 if ($this->_sort() === FALSE) return FALSE;
1151 $rows = $this->rows;
1154 $count = count($this->rows);
1155 $limit = intval($limit);
1156 if ($limit != 0) $limit = max(1, $limit);
1157 if ($limit != 0 && $count > $limit) {
1158 $source[] = str_replace(
1160 array($count, $limit),
1161 plugin_tracker_message('msg_limit')
1163 $rows = array_slice($this->rows, 0, $limit);
1167 $header = $body = array();
1168 foreach (plugin_tracker_get_source($list) as $line) {
1169 if (preg_match('/^\|(.+)\|[hfc]$/i', $line)) {
1170 // TODO: Why c and f here
1171 $header[] = $line; // Table header, footer, and decoration
1173 $body[] = $line; // The others
1177 foreach($header as $line) {
1178 $source[] = preg_replace_callback($regex, array(& $this, '_replace_title'), $line);
1180 foreach ($rows as $row) {
1181 if (! PLUGIN_TRACKER_LIST_SHOW_ERROR_PAGE && ! $row['_match']) continue;
1182 $this->_items = $row;
1183 foreach ($body as $line) {
1184 if (ltrim($line) != '') {
1185 $this->_the_first_character_of_the_line = $line[0];
1186 $line = preg_replace_callback($regex, array(& $this, '_replace_item'), $line);
1192 return implode('', $source);
1196 function plugin_tracker_get_source($page, $join = FALSE)
1198 $source = get_source($page, TRUE, $join);
1199 if ($source === FALSE) return FALSE;
1201 // Remove fixed-heading anchors
1202 $source = preg_replace('/^(\*{1,3}.*)\[#[A-Za-z][\w-]+\](.*)$/m', '$1$2', $source);
1204 // Remove #freeze-es
1205 return preg_replace('/^#freeze\s*$/im', '', $source);
1208 function plugin_tracker_message($key)
1210 global $_tracker_messages;
1211 return isset($_tracker_messages[$key]) ? $_tracker_messages[$key] : 'NOMESSAGE';